LECTURE 21 画像のグレースケール/ネガティブ/エッジ検出

○ はじめに

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 についての説明は終わりです。結構面白いので、色々な画像でぜひ試してみてください。