|
○ はじめに
LECTURE 20 Imlib を使った画像表示 では、Imlib (freshmeat.net の紹介ページ) を使った画像表示について紹介しました。今回は、Imlib で読み込んだ画像データを加工するちょっとした画像処理について書きたいと思います。なお、著者は画像処理をかじった程度で詳しくはありませんので、ご了承ください^^;詳しく知りたい場合は各自でお調べください。
○ Imlib で読み込んだ画像の内部データ形式
Imlib で読み込んだ画像は、内部的には 24-bit ピクセルデータの集合として管理されています。1 ピクセルにつき RGB の順に並び、それがピクセル数分続きます(RGBRGBRGBRGB...)。なお RGB それぞれ 8 bit ずつの値となります。その他にも透明色に関連づいたデータ等ある様ですが、本ページでは画像そのものを表す RGB の配列データを処理の対象としています。
○ グレースケール処理
グレースケールとは、彩度(色の強さとか色らしさ)を持たない画像です。カラー画像からグレースケール画像への変換には、以下の様な方法があるみたいです(他にもいろいろある様ですが、それらについてはここでは取り上げません)。なお、本ページのサンプルプログラムでは、RGB の平均値を用いる方法を使っています。
方法 | 説明 |
---|---|
RGB の平均値を用いる | 変換後の値 = (R + G + B)/3 とする方法。RGB 全ての成分が同じだと彩度 0 となることを利用(しているらしい)。この方法では、緑と青の変換結果が元の色に対する視覚印象と異なる結果となるという問題がある(らしい)。人間の視覚は緑の輝度に敏感に反応し、青の輝度にはあまり反応しないという傾向があるそうです。 |
重み付き平均を用いる | RGB のそれぞれに重み付けを行って平均を求める方法。変換後の値 = (0.299 * R + 0.587 * G + 0.114 * B) とする方法は、日本やアメリカのテレビ放送規格 NTSC で使用されているそうです。 |
○ エッジ検出処理
エッジ検出とは、画像中の物体の境界線を求める処理です。コンピュータで様々な画像処理/画像認識を行う場合、境界線によって物体を認識したりすることが出来るため、エッジ検出は非常に重要な処理と言えます。
画像処理的には、エッジは値が急激に変化する箇所であると言えます。変化量が大きい場所を検出するためには、一般的に微分処理を行います。デジタルデータの場合、微分処理とは二つの値の差分を求めることを意味します。微分処理には 1 次微分と 2 次微分がありますが、ここでは(説明が?)簡単な 1 次微分を取り上げます。
1 次微分処理ではまず、各ピクセルと隣接するピクセルとの差分を x 方向(Δx)と y 方向(Δy)それぞれについて求め、最終的な値を
変換後の値 = sqrt( Δx * Δx + Δy * Δy ); ※sqrt は平方根を表す。
とします。なお、カラー画像ではノイズが多くうまく検出できない場合があるため、二値化(閾値より上なら白、以下なら黒の様な二値のみのデータ)した画像やグレースケール画像を元に検出することが多いです。
隣接するピクセルとの計算を表現するための係数の組を微分オペレータと呼び、
m1 | m2 | m3 |
m4 | m5 | m6 |
m7 | m8 | m9 |
の様な形式で定義されます。m5 の部分が対象ピクセルに対する係数であり、それ以外が隣接するピクセルに対する係数です。(x,y) 位置のピクセルを p(x,y) とする(x は画像の右方向、y は画像の下方向を正とします)と
&Delta = p(x-1,y-1) * m1 + p(x,y-1) * m2 + p(x+1,y-1) * m3 + p(x-1,y) * m4 + p(x,y) * m5 + p(x+1,y) * m6 + p(x-1,y+1) * m7 + p(x,y+1) * m8 + p(x+1,y+1) * m9
として計算できます。なお、隣接ピクセルが画像の境界外である場合には、最も近い境界上のピクセルを代わりに使用します。
1 次微分の際によく使われる有名な微分オペレータに Roberts オペレータ/Sobel オペレータがあります。本ページのサンプルプログラムでは Sobel オペレータを使っています。
- Roberts オペレータ
Δx 方向 Δy 方向 0 0 0 0 0 0 0 1 0 0 0 1 0 0 -1 0 -1 0
- Sobel オペレータ
Δx 方向 Δy 方向 1 0 -1 1 2 1 2 0 -2 0 0 0 1 0 -1 -1 -2 -1
○ サンプルプログラム
画像処理フィルタを実装したサンプルプログラム filter.c です。はじめに のページから全てのサンプルプログラムを固めた tar ボールをダウンロードすることもできます。
前回と同じく、-lImlib オプションを指定してコンパイルしてください。
コンパイルを終えたら、画像ファイル名を引数に与えて実行してみてください。画像が表示されます。右のカーソルキーを押すと、グレースケール処理を施した画像が表示されます。上を押すとネガティブ処理を施した画像が、下を押すとエッジ抽出処理を施した画像が表示されます。左を押すと元画像が表示されます。Esc キーを押すと終了します。
本サンプルプログラムは、起動時に元画像およびグレースケール画像をメモリ上に保存します。そのため、大きな画像等を表示すると結構メモリを消費すると思います。フィルタ処理においては特に高速化とか考えていないため、サイズが大きい程処理に時間がかかりますのでご注意ください。
また、イベントループ内でキーを押すとフィルタ処理を実行して結果画像を表示(元画像とグレースケールはキャッシュをそのまま表示)しますが、結果画像を表示し終えるまでイベント処理を行っていないため、ウィンドウが固まった様になると思います。ご了承ください。GUI として実用的にするためには、フィルタ処理部を別スレッドとして実装する等を考えた方が良いでしょう。
--- 元画像 ---
--- グレースケール処理後 ---
--- ネガティブ処理後 ---
--- エッジ検出処理後 ---
なお、サンプルプログラム内の process_filter() 関数 ( 383 〜 462 行目 ) の 421 〜 461 行目はコメントアウト状態となっていますが、このまま(case1)と 387 〜 419 行目をコメントアウトして 421 〜 461 行目を有効化した場合(case2)について、1280x1024 の JPEG 画像のエッジ抽出にかかった時間を自環境で計測した結果を以下に載せておきます。
PentiumIII 450MHz memory 512MB |
Xeon 3.60GHz memory 1GB |
|
---|---|---|
case1 | 5069.06 ms | 707.413 ms |
case2 | 3650.404 | 409.337 ms |
--- filter.c ---
1 #include <X11/Xlib.h> 2 #include <X11/keysym.h> 3 #include <X11/Xutil.h> /* shape.h の前に必要 */ 4 #include <X11/extensions/shape.h> /* Shape Extension */ 5 #include <Imlib.h> 6 #include <limits.h> 7 #include <math.h> 8 #include <stdio.h> 9 #include <string.h> 10 #include <sys/time.h> 11 #include <unistd.h> 12 13 14 static Display* display; 15 static Window window; 16 static ImlibData* imlib_data; 17 static ImlibImage* imlib_image; 18 static Pixmap pixmap; 19 static Pixmap mask; 20 static unsigned char* original; 21 static unsigned char* grayscale; 22 static int width; 23 static int height; 24 25 26 static int save_original( void ); 27 static void disable_resize( void ); 28 static void render( void ); 29 static void update_image( void ); 30 static void show_original( void ); 31 static void show_grayscale( void ); 32 static void filter_negative( void ); 33 static void filter_edge( void ); 34 35 static void process_filter( int t_row, int t_col, int* mask, 36 int* delta_r, int* delta_g, int* delta_b ); 37 38 39 int 40 main( int argc, char** argv ) 41 { 42 int screen; 43 44 if ( argc <= 1 ) { 45 fprintf( stderr, "画像ファイルを指定してください。\n" ); 46 exit(1); 47 } 48 49 display = XOpenDisplay( NULL ); 50 screen = DefaultScreen( display ); 51 52 if ( ( imlib_data = Imlib_init( display ) ) == NULL ) { 53 fprintf( stderr, "Imlib の初期化に失敗しました。\n" ); 54 XCloseDisplay( display ); 55 exit(1); 56 } 57 58 if ( ( imlib_image = Imlib_load_image( imlib_data, argv[1] ) ) == NULL ) { 59 fprintf( stderr, "画像の読み込みに失敗しました。\n" ); 60 XCloseDisplay( display ); 61 exit(1); 62 } 63 64 width = imlib_image->rgb_width; 65 height = imlib_image->rgb_height; 66 67 if ( save_original() != 0 ) { 68 fprintf( stderr, "元画像の保存に失敗しました。\n" ); 69 Imlib_kill_image( imlib_data, imlib_image ); 70 XCloseDisplay( display ); 71 exit(1); 72 } 73 74 window = XCreateSimpleWindow( 75 display, 76 DefaultRootWindow( display ), 77 width, 78 height, 79 width, 80 height, 81 0, 82 BlackPixel( display, screen ), 83 BlackPixel( display, screen ) 84 ); 85 86 disable_resize(); 87 XSelectInput( display, window, StructureNotifyMask | KeyPressMask ); 88 XMapWindow( display, window ); 89 render(); 90 update_image(); 91 92 while ( 1 ) { 93 int esc_pressed = 0; 94 XEvent event; 95 KeySym key_sym; 96 97 XNextEvent( display, &event ); 98 99 switch ( event.type ) { 100 case KeyPress: 101 XLookupString( &( event.xkey ), 102 NULL, 0, &key_sym, NULL ); 103 104 switch ( key_sym ) { 105 case XK_Left: 106 show_original(); 107 break; 108 109 case XK_Right: 110 show_grayscale(); 111 break; 112 113 case XK_Up: 114 filter_negative(); 115 break; 116 117 case XK_Down: 118 filter_edge(); 119 break; 120 121 case XK_Escape: 122 esc_pressed = 1; 123 break; 124 } 125 126 break; 127 } 128 129 if ( esc_pressed ) { 130 break; 131 } 132 } 133 134 Imlib_free_pixmap( imlib_data, pixmap ); 135 Imlib_kill_image( imlib_data, imlib_image ); 136 free( original ); 137 free( grayscale ); 138 XDestroyWindow( display, window ); 139 XCloseDisplay( display ); 140 return 0; 141 } 142 143 144 static int 145 save_original( void ) 146 { 147 int bufsize = width * height * 3; 148 149 if ( ( original = ( unsigned char* )malloc( bufsize ) ) == NULL ) { 150 return -1; 151 } 152 153 if ( ( grayscale = ( unsigned char* )malloc( bufsize ) ) == NULL ) { 154 free( original ); 155 return -1; 156 } 157 158 memcpy( original, imlib_image->rgb_data, bufsize ); 159 160 /* 161 * 元画像の保存と同時に、(主にエッジ抽出フィルタ用の) 162 * グレースケール画像を作成しておく。 163 */ 164 { 165 int row; 166 int col; 167 int index; 168 int mid; 169 170 unsigned char r; 171 unsigned char g; 172 unsigned char b; 173 174 printf( "Creating grayscale image..." ); 175 fflush( stdout ); 176 177 for ( row = 0; row < height; row ++ ) { 178 for ( col = 0; col < width; col ++ ) { 179 index = ( row * width + col ) * 3; 180 181 r = imlib_image->rgb_data[ index + 0 ]; 182 g = imlib_image->rgb_data[ index + 1 ]; 183 b = imlib_image->rgb_data[ index + 2 ]; 184 185 mid = ( int )( ( r + g + b ) / 3 ); 186 187 grayscale[ index + 0 ] = ( unsigned char )mid; 188 grayscale[ index + 1 ] = ( unsigned char )mid; 189 grayscale[ index + 2 ] = ( unsigned char )mid; 190 } 191 } 192 193 printf( "done.\n" ); 194 fflush( stdout ); 195 } 196 197 return 0; 198 } 199 200 201 static void 202 disable_resize( void ) 203 { 204 XSizeHints hints; 205 206 hints.min_width = width; 207 hints.min_height = height; 208 hints.max_width = width; 209 hints.max_height = height; 210 hints.flags = PMinSize | PMaxSize; 211 212 XSetWMNormalHints( display, window, &hints ); 213 } 214 215 216 static void 217 render( void ) 218 { 219 Imlib_render( imlib_data, imlib_image, width, height ); 220 pixmap = Imlib_move_image( imlib_data, imlib_image ); 221 mask = Imlib_move_mask( imlib_data, imlib_image ); 222 } 223 224 225 static void 226 update_image( void ) 227 { 228 XSetWindowBackgroundPixmap( display, window, pixmap ); 229 230 if ( mask ) { 231 XShapeCombineMask( display, window, 232 ShapeBounding, 0, 0, mask, ShapeSet ); 233 } 234 235 XClearWindow( display, window ); 236 XSync( display, False ); 237 } 238 239 240 static void 241 show_original( void ) 242 { 243 memcpy( imlib_image->rgb_data, original, width * height * 3 ); 244 Imlib_free_pixmap( imlib_data, pixmap ); 245 Imlib_changed_image( imlib_data, imlib_image ); 246 render(); 247 update_image(); 248 } 249 250 251 static void 252 show_grayscale( void ) 253 { 254 memcpy( imlib_image->rgb_data, grayscale, width * height * 3 ); 255 Imlib_free_pixmap( imlib_data, pixmap ); 256 Imlib_changed_image( imlib_data, imlib_image ); 257 render(); 258 update_image(); 259 } 260 261 262 static void 263 filter_negative( void ) 264 { 265 int row; 266 int col; 267 int rgb; 268 int index; 269 270 printf( "Processing negative filter..." ); 271 fflush( stdout ); 272 273 for ( row = 0; row < height; row ++ ) { 274 for ( col = 0; col < width; col ++ ) { 275 for ( rgb = 0; rgb < 3; rgb ++ ) { 276 index = ( row * width + col ) * 3 + rgb; 277 imlib_image->rgb_data[ index ] = 278 UCHAR_MAX - original[ index ]; 279 } 280 } 281 } 282 283 printf( "done.\n" ); 284 fflush( stdout ); 285 286 Imlib_free_pixmap( imlib_data, pixmap ); 287 Imlib_changed_image( imlib_data, imlib_image ); 288 render(); 289 update_image(); 290 } 291 292 293 #define TIMER_DIFF( diff, end, start ) { \ 294 (diff)->tv_usec = (end)->tv_usec - (start)->tv_usec; \ 295 if ( (diff)->tv_usec < 0 ) {\ 296 (diff)->tv_usec += 1000000; \ 297 (diff)->tv_sec = (end)->tv_sec - (start)->tv_sec - 1;\ 298 }\ 299 else {\ 300 (diff)->tv_sec = (end)->tv_sec - (start)->tv_sec;\ 301 }\ 302 if ( (diff)->tv_sec < 0 ) {\ 303 (diff)->tv_sec = (diff)->tv_usec = 0;\ 304 }\ 305 } 306 307 static void 308 filter_edge( void ) 309 { 310 /* Sobel Operator (X) */ 311 int mask_x[] = { 312 1, 0, -1, 313 2, 0, -2, 314 1, 0, -1 315 }; 316 317 /* Sobel Operator (Y) */ 318 int mask_y[] = { 319 1, 2, 1, 320 0, 0, 0, 321 -1, -2, -1 322 }; 323 324 int rx; 325 int gx; 326 int bx; 327 int ry; 328 int gy; 329 int by; 330 331 int r; 332 int g; 333 int b; 334 335 int row; 336 int col; 337 int index; 338 339 struct timeval start; 340 struct timeval end; 341 struct timeval diff; 342 343 printf( "Processing edge filter..." ); 344 fflush( stdout ); 345 gettimeofday( &start, NULL ); 346 347 for ( row = 0; row < height; row ++ ) { 348 for ( col = 0; col < width; col ++ ) { 349 process_filter( row, col, mask_x, &rx, &gx, &bx ); 350 process_filter( row, col, mask_y, &ry, &gy, &by ); 351 352 r = ( int )sqrt( ( double )( rx * rx + ry * ry ) ); 353 g = ( int )sqrt( ( double )( gx * gx + gy * gy ) ); 354 b = ( int )sqrt( ( double )( bx * bx + by * by ) ); 355 356 index = ( row * width + col ) * 3; 357 358 imlib_image->rgb_data[ index + 0 ] = 359 ( r > UCHAR_MAX ) ? UCHAR_MAX : r; 360 361 imlib_image->rgb_data[ index + 1 ] = 362 ( g > UCHAR_MAX ) ? UCHAR_MAX : g; 363 364 imlib_image->rgb_data[ index + 2 ] = 365 ( b > UCHAR_MAX ) ? UCHAR_MAX : b; 366 } 367 } 368 369 gettimeofday( &end, NULL ); 370 TIMER_DIFF( &diff, &end, &start ); 371 printf( "done ( %ld ).\n", diff.tv_sec * 1000000 + diff.tv_usec ); 372 fflush( stdout ); 373 374 Imlib_free_pixmap( imlib_data, pixmap ); 375 Imlib_changed_image( imlib_data, imlib_image ); 376 render(); 377 update_image(); 378 } 379 380 381 #define INDEX( row, col ) ( ( (row) * width + (col) ) * 3 ) 382 383 static void 384 process_filter( int t_row, int t_col, int* mask, 385 int* delta_r, int* delta_g, int* delta_b ) 386 { 387 int s_row; 388 int s_col; 389 int index; 390 int m_index; 391 int i; 392 int j; 393 394 *delta_r = 0; 395 *delta_g = 0; 396 *delta_b = 0; 397 398 for ( i = -1; i <= 1; i ++ ) { 399 s_row = t_row + i; 400 s_row = ( s_row < 0 ) ? 0 : 401 ( ( s_row >= height ) ? height - 1 : s_row ); 402 403 for ( j = -1; j <= 1; j ++ ) { 404 s_col = t_col + j; 405 s_col = ( s_col < 0 ) ? 0 : 406 ( ( s_col >= width ) ? width - 1 : s_col ); 407 408 index = s_row * width + s_col; 409 m_index = ( i + 1 ) * 3 + ( j + 1 ); 410 411 ( *delta_r ) += ( int )( 412 grayscale[ index * 3 + 0 ] * mask[ m_index ] ); 413 414 ( *delta_g ) += ( int )( 415 grayscale[ index * 3 + 1 ] * mask[ m_index ] ); 416 417 ( *delta_b ) += ( int )( 418 grayscale[ index * 3 + 2 ] * mask[ m_index ] ); 419 } 420 } 421 #if 0 422 int r0 = ( t_row - 1 < 0 ) ? 0 : t_row - 1; 423 int r1 = t_row; 424 int r2 = ( t_row + 1 >= height ) ? height - 1 : t_row + 1; 425 int c0 = ( t_col - 1 < 0 ) ? 0 : t_col - 1; 426 int c1 = t_col; 427 int c2 = ( t_col + 1 >= width ) ? width - 1 : t_col + 1; 428 429 *delta_r = 430 grayscale[ INDEX( r0, c0 ) + 0 ] * mask[0] + 431 grayscale[ INDEX( r0, c1 ) + 0 ] * mask[1] + 432 grayscale[ INDEX( r0, c2 ) + 0 ] * mask[2] + 433 grayscale[ INDEX( r1, c0 ) + 0 ] * mask[3] + 434 grayscale[ INDEX( r1, c1 ) + 0 ] * mask[4] + 435 grayscale[ INDEX( r1, c2 ) + 0 ] * mask[5] + 436 grayscale[ INDEX( r2, c0 ) + 0 ] * mask[6] + 437 grayscale[ INDEX( r2, c1 ) + 0 ] * mask[7] + 438 grayscale[ INDEX( r2, c2 ) + 0 ] * mask[8]; 439 440 *delta_g = 441 grayscale[ INDEX( r0, c0 ) + 1 ] * mask[0] + 442 grayscale[ INDEX( r0, c1 ) + 1 ] * mask[1] + 443 grayscale[ INDEX( r0, c2 ) + 1 ] * mask[2] + 444 grayscale[ INDEX( r1, c0 ) + 1 ] * mask[3] + 445 grayscale[ INDEX( r1, c1 ) + 1 ] * mask[4] + 446 grayscale[ INDEX( r1, c2 ) + 1 ] * mask[5] + 447 grayscale[ INDEX( r2, c0 ) + 1 ] * mask[6] + 448 grayscale[ INDEX( r2, c1 ) + 1 ] * mask[7] + 449 grayscale[ INDEX( r2, c2 ) + 1 ] * mask[8]; 450 451 *delta_b = 452 grayscale[ INDEX( r0, c0 ) + 2 ] * mask[0] + 453 grayscale[ INDEX( r0, c1 ) + 2 ] * mask[1] + 454 grayscale[ INDEX( r0, c2 ) + 2 ] * mask[2] + 455 grayscale[ INDEX( r1, c0 ) + 2 ] * mask[3] + 456 grayscale[ INDEX( r1, c1 ) + 2 ] * mask[4] + 457 grayscale[ INDEX( r1, c2 ) + 2 ] * mask[5] + 458 grayscale[ INDEX( r2, c0 ) + 2 ] * mask[6] + 459 grayscale[ INDEX( r2, c1 ) + 2 ] * mask[7] + 460 grayscale[ INDEX( r2, c2 ) + 2 ] * mask[8]; 461 #endif 462 } 463 464 465 /* End of filter.c */
○ サンプルプログラムの説明
では、サンプルプログラムについて説明していきます。Imlib による画像読み込み/表示等については、LECTURE 20 Imlib を使った画像表示 を基本としていますので、そちらを参照してください。
- 20 〜 21 行目
original および grayscale は、それぞれ元画像およびグレースケール処理後の画像データを保持するための領域を保存するための変数です。
- 39 〜 141 行目 ( main() )
filter.c の main 関数です。各処理について以下で説明していきます。
- 67 〜 72 行目
save_original() という自作関数で、Imlib で読み込んだ元画像データの保存およびグレースケール処理後の画像データの保存を行っています。詳しくは後程説明します。
- 86 行目
簡単のため、disable_resize() という自作関数でウィンドウサイズを変更できなくしています。詳しくは後程説明します。
- 105 〜 107 行目
左カーソルキーが押された時の処理です。show_original() という自作関数で元画像の表示を行っています。詳しくは後程説明します。
- 109 〜 111 行目
右カーソルキーが押された時の処理です。show_grayscale() という自作関数でグレースケール処理後の画像を表示しています。詳しくは後程説明します。
- 113 〜 115 行目
上カーソルキーが押された時の処理です。filter_negative() という自作関数でネガティブ処理後の画像を表示しています。詳しくは後程説明します。
- 117 〜 119 行目
下カーソルキーが押された時の処理です。filter_edge() という自作関数でエッジ検出処理後の画像を表示しています。詳しくは後程説明します。
- 134 〜 137 行目
Imlib_free_pixmap() によって Pixmap を解放した後、Imlib_kill_image() を使って ImlibImage 構造体を解放しています。更に、保存しておいた元画像およびグレースケール画像データを解放しています。
- 144 〜 198 行目 ( save_original() )
本関数では、Imlib 内部に格納されている元画像データの保存および、グレースケール処理後の画像データの保存を行います。元画像データは元画像表示用およびネガティブ処理用に、グレースケール画像データはグレースケール画像表示用およびエッジ抽出処理用に保存しています。
149 〜 151 行目で元画像データ保存用のメモリ領域を、153 〜 156 行目でグレースケール画像データ保存用のメモリ領域を確保しています。Imlib 内部では 1 ピクセル 24 ビットで管理されているため、幅 * 高さ * 3 バイトのメモリ領域を確保しています。
158 行目で、Imlib 内部の元画像データを確保したメモリ領域に転送しています。元画像データは
ImlibImage* image; unsigned char* image->rgb_data;
として参照できます。
160 〜 195 行目でグレースケール画像を確保したメモリ領域に作成しています。前述のグレースケール処理の説明通り、各ピクセルについて RGB の平均値 ( 185 行目の mid ) を計算し、その値を grayscale 上で対応するピクセルの RGB 全てに設定しています。
- 201 〜 213 行目 ( disable_resize() )
LECTURE 13 ウィンドウマネージャとのやりとり で説明している方法を使って、ウィンドウサイズの変更を不可としています。
- 240 〜 248 行目 ( show_original() )
元画像の表示を行う関数です。original に保存しておいた元画像データを ImlibImage の rgb_data に転送します。rgb_data を直接加工する場合、Imlib_changed_image() を実行して Imlib に変更を通知する必要があります。
void Imlib_changed_image( ImlibData* id, ImlibImage* image );
あとは Imlib_free_pixmap() によって既存の Pixmap を解放対象とし、render()/update_image() で Pixmap の生成/画像の表示を行います。render() と update_image() については LECTURE 20 Imlib を使った画像表示 を参照してください。
- 251 〜 259 行目 ( show_grayscale() )
グレースケール画像の表示を行う関数です。original の代わりに grayscale を参照している以外は show_original() と同じです。
- 262 〜 290 行目 ( filter_negative() )
ネガティブ処理を行い結果画像を表示する関数です。273 〜 281 行目において、前述のネガティブ処理の説明通り
変換後の値 = 値の最大値 - 変換前の値
を計算して新しい値としています。RGB それぞれ unsigned char として扱うため、各要素の最大値は UCHAR_MAX ( limits.h ) です。
変換終了後の 286 〜 289 行目は show_original()/show_grayscale() と同じです。
- 293 〜 305 行目 ( TIMER_DIFF() )
filter_edge() 内で所用時間を表示したいために用意したマクロです。struct timeval 型変数である start と end 間の経過時間を計算して、同じく struct timeval 型変数の diff に格納しています。
- 307 〜 378 行目 ( filter_edge() )
エッジ検出処理を行う関数です。310 〜 322 行目で xy 方向の Sobel オペレータを定義しています。347 〜 367 行目にて xy 方向の差分を計算し、新しい値を
変換後の値 = sqrt( Δx * Δx + Δy * Δy );
として設定しています。xy 方向の差分計算は実際には process_filter() という自作関数で行っています。
変換後の 374 〜 377 行目は show_original()/show_grayscale()/filter_negative() と同じです。
- 383 〜 462 行目 ( process_fileter() )
xy 方向の差分を実際に計算する関数です。
- 387 〜420 行目
Sobel オペレータの適用をループで処理しています。引数の t_row,t_col が差分計算対象であるピクセルの行および列を表しています。オペレータは 3x3 の行列ですが、i のループでオペレータの行方向、j のループでオペレータの列方向を処理しています。i,j とオペレータの各要素の対応は以下の様になります。
m1(i=-1,j=-1) m2(i=-1,j=0) m3(i=-1,j=1) m4(i=0,j=-1) m5(i=0,j=0) m6(i=0,j=1) m7(i=1,j=-1) m8(i=1,j=0) m9(i=1,j=1)
- 421 〜 461 行目
387 〜 420 行目の処理では自分の環境であまりにも遅かったため、ループではなく直接計算するコードも書いてみました。環境に応じた計算速度の違いについては こちら のサンプルプログラムの直前に掲載してあります。結構違います。ただし Zeon 3.60GHz だと体感的にはあまり差がありませんでしたが…
以上で filter.c についての説明は終わりです。結構面白いので、色々な画像でぜひ試してみてください。