Archive for 12月, 2010

cvMatchTemplateの実装を解読してみた

木曜日, 12月 30th, 2010
このエントリーを含むはてなブックマークはてなブックマーク - cvMatchTemplateの実装を解読してみた

OpenCVでテンプレートマッチングを行う場合、cvMatchTemplateメソッドを使用するのですが、一体どういうアルゴリズムでこの関数が実装されているのか気になったので、ソースを解読してみた。


 
案の定、cvMatchTemplateの実装に関するサイトがあったので、先にそちらを紹介します。

OpenCVのcvMatchTemplate実装について調べてみた

こちらのサイトでは、cvMatchTemplateの頭から、エラー処理までの部分を非常に詳しく説明されていました。
 
cvMatchTemplate調査メモ

また、こちらのサイトではテンプレートマッチングのコアアルゴリズムであるDFTの計算について触れられていました。

これらの情報を元に、cvMatchTemplate関数では何が行われているのかを解読してみました。解読に当たって使用したオリジナルのソースコードと、CV_TM_CCOEFFに限定して、ソースコードをバッサバッサ削ったバージョンのファイルを下に置いておきます。以下ではCV_TM_CCOEFFアルゴリズムに限定して説明を進めたいと思います。

オリジナルソースコード
CV_TM_CCOEFF用ソースコード

 

まずは、cvMatchTemplateのコアとなる部分を掲載します。分かりやすいように、数カ所にコメントを書き加えています
 
cvMatchTemplate関数

	// 元画像とテンプレ画像の相関を求める
    CV_CALL( icvCrossCorr( img, templ, result, cvPoint(0,0) ));

    CV_CALL( sum = cvCreateMat( img->rows + 1, img->cols + 1,
							   CV_MAKETYPE( CV_64F, 1 )));

	// 相関係数を求めるための準備
    CV_CALL( cvIntegral( img, sum, 0, 0 ));
    CV_CALL( templ_mean = cvAvg( templ ));
	p0 = (double*)sum->data.ptr;
    p1 = p0 + templ->cols;
    p2 = (double*)(sum->data.ptr + templ->rows*sum->step);
    p3 = p2 + templ->cols;
	
    sum_step = sum ? sum->step / sizeof(double) : 0;
    sqsum_step = sqsum ? sqsum->step / sizeof(double) : 0;

    for( i = 0; i < result->rows; i++ )
    {
        float* rrow = (float*)(result->data.ptr + i*result->step);
        idx = i * sum_step;
 	
        for( j = 0; j < result->cols; j++, idx++)
        {
			// rrow[j]に座標(i, j)の相関が入っている
            double num = rrow[j], t;

			// 相関係数を求める
			t = p0[idx] - p1[idx] - p2[idx] + p3[idx];
			num -= t*templ_mean.val[0];
			
            rrow[j] = (float)num;
        }
    }

cvMatchTemplateの本質は、icvCrossCorr関数に集約されており、残りの部分は全てオマケ程度な感じです・・・。

そのicvCrossCorr関数では、元画像とテンプレート画像の相関を計算しています。この関数に引数として元画像(img)とテンプレート画像(templ)を渡せば、結果をresultに詰めて返してくれます。icvCrossCorrの詳しい処理は以下で説明するので、その前に2行目移行の処理を見ていきましょう。

CV_TM_CCOEFFアルゴリズムを使用した場合、相関係数は以下の式で得られます。

cvmatchtemplatee1
ここで

cvmatchtemplatee2

相関値自体はicvCrossCorrで計算するのですが、相関係数を求めるためには、T’とI’のそれぞれ2項目に当たる部分も計算しておかなければいけません。それを行っているのが上記プログラムの2行目以下になります。

T’の2項目はテンプレート画像の明度の平均値なので、cvAvg関数を用いて計算しています。また、I’の2項目は任意座標の明度平均値なので、cvIntegral関数を使って積分画像を作っています。

ここで、積分画像とは(インテグラルイメージとは)下図のように左上の明度から特定の座標までの明度を積分した画像のことをいいます。例えば、2行2列目の積分画像の値は3+4+3+1=11になっています。

この積分画像が計算できると何が嬉しいかというと、任意矩形で囲まれる明度の積分値を定数時間で求めることが出来るようになります。例えば青枠で囲った部分(左図)の明度の和が欲しい場合には、積分画像(右図)から下に書いたような加減算のみで、答えを得ることが出来ます。なぜ?という方はぜひ手計算してみてください!

cvmatchtemplate3

プログラムでは28行目で積分画像の計算を行っています。32行目では、この計算で得られた元画像の平均値とテンプレート画像の平均値、icvCrossCorrで得られた相関値を利用することで相関係数を算出し、結果画像に保存しています。

cvmatchtemplate4

最後に、テンプレートマッチングのコアとなるicvCrossCorr関数の説明をします。さっき書いたように、この関数では元画像とテンプレート画像の相関係数を計算しています。

通常、この演算は元画像とテンプレート画像の畳み込み演算で行うのですが、畳み込み演算では計算量が膨大になるという問題があります。そこでOpenCVの実装では、元画像とテンプレート画像を離散フーリエ変換(Discreate Fourie Transformation, DFT)し、フーリエ空間で積算してから元空間に戻す、という方法をとっています。

このあたりの話は、下記の2冊に詳しく説明されていましたので参考にしてみて下さい。

これなら分かる応用数学教室―最小二乗法からウェーブレットまで
金谷 健一
共立出版
売り上げランキング: 8161

 

今日から使えるフーリエ変換 (今日から使えるシリーズ)
三谷 政昭
講談社
売り上げランキング: 240150

簡単に説明すると、計算量の必要な畳み込み演算をするよりも、(フーリエ変換・逆変換は必要ですが)乗算だけで済む下側のパスをとろうというのが基本アイデアです。

cvtemplatematch5

このアイデア自体は難しい話ではなく、例えば画像にぼかしフィルタをかける場合、画像空間でフィルタをかけると、畳み込み演算が必要になるのに対して、フーリエ変換した画像に対してフィルタをかける場合には、マスク処理(要するにマスクとの乗算)だけで処理が完了するという話と同じ内容です。

さて、この処理を行なっている部分のプログラムを載せておきます。こちらも分かりやすいように、少しコメントを足しています。

icvCrossCorr関数

			// 元画像に対してDFTを実行
			cvDFT( _dft_img, _dft_img, CV_DXT_FORWARD, isz.height );
			cvGetSubRect( dft_templ, dst,
						 cvRect(0,0,dftsize.width,dftsize.height) );
			
			// フーリエ空間にて、元画像とテンプレ画像の畳み込みを行う
			cvMulSpectrums( _dft_img, dst, _dft_img, CV_DXT_MUL_CONJ );

			// 座標空間に戻す
			cvDFT( _dft_img, _dft_img, CV_DXT_INVERSE, csz.height );
			

 
 
cvDFTで元画像とテンプレート画像をそれぞれ離散フーリエ変換し、その結果をcvMulSpectrums関数でかけ合わせています。この関数の引数にCV_DXT_MUL_CONJを指定することで、相関係数を算出しています。最後にその結果を逆DFTすることで画像空間上に再マッピングしています。

 
cvmatchtemplate6 
以上がcvMatchTemplate実装の解読結果になります。もう一度処理の流れをまとめると

  • まず元画像とテンプレート画像をフーリエ変換し、
  • フーリエ空間で両者を乗算したのち、逆フーリエ変換すると
  • 元画像とテンプレート画像の各座標の相関値が得られるので
  • その結果を用いて、相関係数を計算し
  • 相関係数が最大に部分が最も一致度が高い部分になる

    こんな感じになります。

  • TwitPicへ投稿するiPhoneアプリが出来るまで

    木曜日, 12月 23rd, 2010
    このエントリーを含むはてなブックマークはてなブックマーク - TwitPicへ投稿するiPhoneアプリが出来るまで

    twitpic1

    Twitterは2010年の8月末でBasic認証を廃止したため、それ以降はログインのために、OAuth(またはその簡易版のxAuth)を使わなければならなくなりました。

    このxAuthですが、得られるメリットはたくさんあるのですが、如何せん実装がめんどくさい&難しいわけです。そこで、今回はxAuthを使ってTwitPicへ投稿するアプリをつくってみたいと思います。

    [Step 1] TwitterにxAuthの使用を申請する
     
    まずは、Twitterの「アプリケーション登録申請」の画面からアプリケーションの登録を行います。この際、アプリケーションの種類は「クライアントアプリケーション」を、標準のアクセスタイプは「Read & Write」を選択しておいて下さい。

    登録後に表示される Consumer key と Consumer secret はアプリ作成の際に必要なので、メモっておいてください。登録情報は後ほどTwitterアプリケーションからも見ることができます。

    この登録ではOAuthの申請しかできないため、xAuthを使うためにはTwitterな人にメールで申請する必要が必要です。僕は次の次のようなメールをapi@twitter.comに送りました。

    Dear Brian

    My account is @apphokuson.

    I would like to use xAuth on my application named “色影”. My application ‘色影’ is a image processing software for iPhone. I would like to upload text and image from my application. Here is a web site of my app (in Japanese)

    http://iphone.moo.jp/colorshadow.html

    I also attached screen shot of my app.

    Best regards
    Kazuma

    Twitterの人からのメールによると、アプリの機能、使用目的、スクリーンショットは書いておく必要があるそうです。2・3日でTwitterな人から「xAuthに対応したよ」という返信があれば、準備完了です。

    twitpic2

    [Step 2] TwitPicからAPI keyを取得する
     
    こちらの「Register an Application」のページからアプリの登録を行い、API keyを取得します。この情報も後ほど、アプリを作成するのに必要ですので、控えておいてください。

    [Step 3] XAuthTwitterEngineを取得する
     
    xAuthの認証をライブラリ化したものが、すでに存在するのでそれを利用します。今回は、XAuthTwitterEngineを使用しました。まずは、こちらからサンプルプロジェクトをダウンロードして下さい。

    まずは、サンプルプロジェクトをそのまま実行してみましょう。そのためには、まずXAuthTwitterEngineDemoViewController.hのkOAuthConsumerKeyとkOAuthConsumerSecretに、それぞれStep 1で取得したonsumer key と Consumer secret をセットします。

    この作業後コンパイル&実行すれば、アプリが起動するので、accountとpasswordを設定し、「Get xAuth Access Token」ボタンをタップ後、「Send test tweet」ボタンをタップします。全てが上手く行けば、入力したアカウントにテストメッセージが投稿されます。

    twitpic3

    ここで注意することは、

    Twitterは二重投稿を禁止している

    ということです。はい、はまりました。投稿直後にもう一度「Send test tweet」ボタンをタップすると、エラーコード403で投稿が失敗します。対策としては、投稿コメントが直前のコメントと異なれば二重投稿とはみなされないため、投稿メッセージに現在時刻などを追加するなどのトリックが必要です。

    現在時刻の文字列はこんな感じで作れます

    NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease];
    [formatter setDateFormat:@"YYYY/MM/dd HH:mm:ss"]; 
    NSString *now = [formatter stringFromDate:[NSDate date];
    

    ここまでで、Twitterへの投稿準備は整いました。次からのステップで、TwitPicへの画像投稿のプログラムを作成していきます。

    [Step 4] 非同期通信の準備を行う
     
    TwitPicへの写真投稿は非同期通信を用いて行います。そのため、非同期通信用ライブラリのASIHTTPRequestをこちらからダウンロードして、そのなかのASIフォルダをStep 3で使用したプロジェクトにフォルダごと追加してください。(xmlフォルダは今回は使用しません)

    次に、フレームワーク関連の追加を行ないます。Xcode左カラムのFrameworksフォルダを右クリック →「追加」→「既存のフレームワーク」の順に選択し、下記のフレームワーク&ダイナミックライブラリ

  • CFNetwork.framework
  • MobileCoreServices.framework
  • SystemConfiguration.framework,
  • libxml2.dylib,
  • libz.1.2.3.dylib

    を追加します。

    twitpic2

    [Step 5] TwitPic投稿用のプログラムを作成する
     
    さて、いよいよ最後に投稿用のメソッドを作成します。下記に載せたのが追加する2つのメソッドになります。まずは、ASIFormDataRequest.hをimportしてから、uploadToTwitterByTwitPic:image中のsetPostValueの引数にStep 2で用意したTwitPicのAPI keyをセットしてください。

    - (ASIFormDataRequest*)createOAuthEchoRequest:(NSString*)url format:(NSString*)format 
    {
    	NSString *authorizeUrl = [NSString stringWithFormat:@"https://api.twitter.com/1/account/verify_credentials.%@", format];
    	OAMutableURLRequest *oauthRequest = [[[OAMutableURLRequest alloc] initWithURL:[NSURL URLWithString:authorizeUrl]
    																		 consumer:self.twitterEngine.consumer
    																			token:self.twitterEngine.accessToken   
    																			realm:@"http://api.twitter.com/"
    																signatureProvider:nil] autorelease];
    	
    	NSString *oauthHeader = [oauthRequest valueForHTTPHeaderField:@"Authorization"];
    	if (!oauthHeader) {
    		[oauthRequest prepare];
    		oauthHeader = [oauthRequest valueForHTTPHeaderField:@"Authorization"];
    	}
    	
    	NSLog(@"OAuth header : %@\n\n", oauthHeader);
    	
    	ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:url]];
    	request.requestMethod = @"POST";
    	request.shouldAttemptPersistentConnection = NO;	
    	
    	[request addRequestHeader:@"X-Auth-Service-Provider" value:authorizeUrl]; 
    	[request addRequestHeader:@"X-Verify-Credentials-Authorization" value:oauthHeader];
    	
    	return request;
    }
    
    - (void)uploadToTwitterByTwitPic:(NSString*)tweet image:(UIImage*)image {
    	NSString *url = @"http://api.twitpic.com/2/upload.json";
    	ASIFormDataRequest *request = [self createOAuthEchoRequest:url format:@"json"];
    	
    	NSData *imageRepresentation = UIImageJPEGRepresentation(image, 1.0);
    	[request setData:imageRepresentation forKey:@"media"];
    	[request setPostValue:tweet  forKey:@"message"];
    	[request setPostValue:@"" forKey:@"key"];
    	
    	[request setDelegate:self];
    	[request setDidFinishSelector:@selector(twitPicRequestFinished:)];
    	[request setDidFailSelector:@selector(requestFailed:)];	
    	[request startAsynchronous];
    }
    

    createOAuthEchoRequestメソッドでは、TwitPicへ投稿するためのリクエストを作成しています。また、uploadToTwitterByTwitPic:imageメソッドの中で、実際にTwitPicへの投稿を行っています。

    そこで、あとはsendTestTweetButtonTouchUpInsideメソッドなどの中で、uploadToTwitterByTwitPic:imageをコールすると、ボタンがタップされた時に、引数で渡した画像をTwitPicに投稿できるようになります。下に、ここまでのサンプルプロジェクトを置いておきます。是非参考にしてみてください

    TwitPic投稿テストプロジェクト
     
    twitpic4
     
    [Reference]
     
    今回の記事を書くにあたって、下記のサイトを参考にさせていただきました。この場を借りて御礼申し上げます

  • TwitPicのOAuthEcho対応
  • TwitterにポストするiPhoneアプリをBasic認証からxAuthに変更してみました
  • OpenCVでウェーブレット変換プログラム

    火曜日, 12月 21st, 2010
    このエントリーを含むはてなブックマークはてなブックマーク - OpenCVでウェーブレット変換プログラム

    OpenCVには2次元ウェーブレット変換(wavelet transform)のメソッドが実装されていなかったので作ってみた。先日作成した画像修復プログラムのテンプレートマッチングが遅い〜ってぼやいてたところ、@fukushima1981様から、ウェーブレット変換して多重解像度でのマッチをすればいいのでは?とのコメントを頂いたのが始まりです。

    ウェーブレット変換の意味などは、こちらの参考書に素晴らしく分かりやすい説明が書いてあるので、参考にしてみてください。うちの研究室でもゼミ資料として使用しています。

    これなら分かる応用数学教室―最小二乗法からウェーブレットまで
    金谷 健一
    共立出版
    売り上げランキング: 51079

    すごく簡単に(しかも1次元ウェーブレットで。笑)説明すると下図のような感じ。フーリエ変換の場合、変換結果は周波数ごとのパワー値で出てきます。(フーリエ変換の簡単な説明)それに対して、ウェーブレット変換の場合には、各周波数ごとに、パワー値の時間変化が変換結果として得られる。

    wavelet2

    要するに、フーリエ変換で得られる結果は、横軸が周波数、縦軸がパワーのグラフなのに対して、ウェーブレット変換で得られる結果は、横軸が時間、縦軸がパワーのグラフが周波数ごとに得られる感じです。(要されていない気もしますが・・・笑)

    [ウェーブレット変換アルゴリズム]

    Haar基底を使ったウェーブレット変換プログラムのアルゴリズムを説明します。非常に単純なアルゴリズムですので、容易に理解できるかと思います。

    step1
    まずは、画像中の行ごとに処理を行っていきます。隣り合うピクセルの平均値を計算し、画像の左半分に配置します。同様に隣り合うピクセルの差分値を計算し、画像の右半分に配置します。これを画像の上から下へ、全ての行に対して行います。

    wavelet3

    step2
    次に、step1で作成した画像に対して、次は列ごとに処理を行っていきます。同様に隣り合うピクセルの平均値と差分値を計算し、それぞれ画像の上半分と下半分に配置します。これを画像の左から右へ、全ての列に対して行います。

    wavelet4

    step3
    ここまでの処理で、左上にはピクセルを1/2に間引いた縮小画像、右上にはx方向エッジ検出画像、左下にはy方向エッジ検出画像、右下にはxy方向のエッジ検出画像が結果として格納されています。階層的な多重解像度の画像を作る場合は左上の画像に対して、再帰的にstep1からstep2を実行します。

    [ウェーブレット変換プログラム]
    ウェーブレット変換プログラムの本体メソッドがこちら。プログラムでは行列計算を用いることで、step1とstep2を同時に処理しています。ドライバプログラムと合わせたプログラムは下に置いておくのでDLしてお使いください。

    void waveletTransform(IplImage *src, IplImage *dst, int level)
    {
    	int w, h;	
    	float a, b, c, d;
    	int tx, ty, hx, hy;
    	
    	w = src->width;
    	h = src->height;
    	
    	while( --level ){
    		for(int x = 0; x < w; x+=2){	
    			for(int y = 0; y < h; y+=2){	
    				tx = x >> 1;
    				ty = y >> 1;	
    				hx = w >> 1;
    				hy = h >> 1;
    				a = PIXVALF(src, x, y);
    				b = PIXVALF(src, x+1, y);
    				c = PIXVALF(src, x, y+1);
    				d = PIXVALF(src, x+1, y+1);		
    				PIXVALF(dst, tx, ty)		= (a+b+c+d)/4.0;
    				PIXVALF(dst, hx+tx, ty)		= (a-b+c-d)/2.0;
    				PIXVALF(dst, tx, hy+ty)		= (a+b-c-d)/2.0;
    				PIXVALF(dst, hx+tx, hy+ty)	= (a-b-c+d);
    			}
    		}
    		cvCopy(dst, src, NULL);
    		w >>= 1;
    		h >>= 1;
    	}
    }
    

    こちらが、OpenCVで3段階に解像度を変えてウェーブレットした実行結果です。各解像度において、高周波成分と低周波成分が分離されていく様子が分かるかと思います。

    wavelet1

    ドライバも含めたウェーブレット変換プログラムをこちらに置いておきます。参考にしてみてください。
    wavelet.zip

    ところで、ウェーブレット空間でマッチングするのって、ただ単に縮小画像でマッチングするのとどう違うんだろ・・・^^;;

    画像修復プログラムを作った

    金曜日, 12月 17th, 2010
    このエントリーを含むはてなブックマークはてなブックマーク - 画像修復プログラムを作った

    OpenCVを使って、欠損領域を含む画像に対して画像修復を行うプログラムを作成しました。参考にした論文はこちらです。かなりGreedyで時間のかかるアルゴリズムですが、結果画像のクオリティは高いです。

    Y. Wexler, E. Shechtman and M. Irani, Space-Time Video Completion. IEEE Conference on Computer Vision and Pattern Recognition (CVPR), Washington, June 2004.

    入力画像がこちら。
    赤色正方形の部分を欠損領域として指定しています。
    inpainting1

    修復結果がこちら
    inpainting

    かなりのクオリティで修復できているのが分かりますね。アルゴリズムとしては、先日紹介した4種類のうち4番目の「テクスチャの全体最適化による画像修復」に相当します。

    このアルゴリズムの基本的なアイデアは、欠損領域と似たようなテクスチャを持つ領域を画像中から探して、欠損領域を1pxずつ埋めて行こうというものです。では具体的にどのような処理を行っているのかを説明します。

    [Step 1]
    まずは、欠損領域を含む小さな領域をテンプレート画像として指定します。具体的には、まず左上の欠損領域を含むようなテンプレート画像を作成します。
    inapinting3

    [Step 2]
    Step1で作成したテンプレートを元に、テンプレートマッチングを行います。今回はSSD(Sum of Suquared Difference)を使用しました。テンプレートマッチングの際、欠損領域付近はマッチング対象としません。
    inpainting4

    [Step 3]
    次に、マッチした領域の中央のピクセル値を使って、欠損領域左上のピクセルを修復します。コレでようやく欠損領域1pxぶんが修復できたことになります。

    [Step 4]
    あとは、テンプレートの位置を1pxぶん右に移動させ、欠損領域が全て修復されるまでstep1からstep4を繰り返します。論文では、このstep1から4までをオールオーバで許容解までループしているようですが、時間とクオリティのトレードオフで、今回はone pathで終了させています。
    inpainting6

    さて、最後に主要部分のプログラムを掲載しておきます。Step2で欠損領域を除く領域からのテンプレートマッチングが必要なのですが、任意形状のROI(region of interest)を指定する手段がOpenCVにはありませんので、ROIを指定できるテンプレートマッチング関数ROItemplateMatchは自作しました。下にサンプルプロジェクトを置いておきますので、そちらも合わせて参考にしてみてください。

    
    

    iint main(int argc, char* argv[])
    {
    cvNamedWindow (“src”, CV_WINDOW_AUTOSIZE);
    cvNamedWindow (“result”, CV_WINDOW_AUTOSIZE);

    IplImage* src = cvLoadImage(“small.jpg”, CV_LOAD_IMAGE_ANYCOLOR);
    IplImage* mask = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
    IplImage* maskForInpaint = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);

    cvSet(mask, cvScalarAll(255));
    cvZero(maskForInpaint);

    // 欠損領域およびROIマスクの作成
    CvPoint pos = cvPoint(330, 120);
    CvSize sz = cvSize(25, 25);
    cvRectangle(src, pos, cvPoint(pos.x+sz.width, pos.y+sz.height), CV_RGB(255,0,0), CV_FILLED, 8, 0);
    cvRectangle(maskForInpaint, cvPoint(pos.x, pos.y), cvPoint(pos.x+sz.width, pos.y+sz.height), CV_RGB(255,255,255), CV_FILLED, 8, 0);
    cvRectangle(mask, cvPoint(pos.x-TMPSZ, pos.y-TMPSZ), cvPoint(pos.x+sz.width+TMPSZ, pos.y+sz.height+TMPSZ), CV_RGB(0,0,0), CV_FILLED, 8, 0);

    cvShowImage (“src”, src);

    // 初期値の決定
    cvInpaint(src, maskForInpaint, src, 10.0, CV_INPAINT_NS);
    cvReleaseImage(&maskForInpaint);

    // 画像修復本体
    IplImage* dst = cvCloneImage(src);
    int tmpr[TMPSZ*TMPSZ];
    int tmpg[TMPSZ*TMPSZ];
    int tmpb[TMPSZ*TMPSZ];

    // 欠損領域内探索
    for(int i = 0; i < 7; ++i){ for(int x = pos.x; x <= pos.x + sz.width; x++){ for(int y = pos.y; y <= pos.y + sz.height; y++){ // テンプレート配列を作成する int c = 0; for(int tx = x; tx < x + TMPSZ; ++tx){ for(int ty = y; ty < y + TMPSZ; ++ty){ tmpr[c] = PIXVALR(src, tx, ty); tmpg[c] = PIXVALG(src, tx, ty); tmpb[c] = PIXVALB(src, tx, ty); c++; } } CvPoint rpos = ROItemplateMatch(src, mask, tmpr, tmpg, tmpb); PIXVALR(dst, x, y) = PIXVALR(src, rpos.x, rpos.y); PIXVALG(dst, x, y) = PIXVALG(src, rpos.x, rpos.y); PIXVALB(dst, x, y) = PIXVALB(src, rpos.x, rpos.y); } } cvCopy(dst, src, NULL); } cvShowImage ("result", dst); cvWaitKey (0); return 0; } [/cpp] 実行時間はMacbook Pro, Core2Duo, 4GB上で、コンパイラ最適化した状態で、大体5秒程度です。ただ、テンプレートマッチングをサボっているので、まともにやると2分程度かかります。さてさて、やはりこのアルゴリズムじゃぁiPhoneでは動作しませんねぇ。どうしたものか・・・(笑) 最後に、サンプルプロジェクトをココにいおいておきます。 画像修復サンプルプログラム

    OpenCVで画像修復

    木曜日, 12月 16th, 2010
    このエントリーを含むはてなブックマークはてなブックマーク - OpenCVで画像修復

    OpenCVにはデフォルトで画像修復を行ってくれる関数cvInpaintという関数が実装されているので、それを使用してみました。

    元画像
    cvinpaint1

    マスク
    cvinpaint2

    上記画像をcvInpaint関数に渡すことで画像修復を行いました。プログラムは以下のとおりになります。

    int main(int argc, char* argv[])
    {
    	IplImage* src  = cvLoadImage("test.jpg", CV_LOAD_IMAGE_ANYCOLOR); 
    	IplImage* mask = cvLoadImage("mask.jpg", CV_LOAD_IMAGE_GRAYSCALE); 	
    	IplImage* dst  = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 3);
    
    	cvInpaint(src, mask, dst, 10.0, CV_INPAINT_NS);
    	
    	cvNamedWindow ("result", CV_WINDOW_AUTOSIZE);
    	cvShowImage ("result", dst);
    	cvWaitKey (0);
    
    	return 0;
    }
    

    こちらが結果
    cvinpaint3

    結果からも分かるように、柵などのテクスチャが復元されておらず、あまりクオリティは高くありません。先日紹介したアルゴリズムのうち、「1. 輝度値の連続性を考慮した画像修復」を使用しているみたいですね。CV_INPAINT_NSのNSがNavier-Stokes法の略のようです。実行速度はそれなりに速いですがやはり、修復箇所が目立ってしまいます。

    背景が、空など平坦な場所では使えそうな関数ですが、それ以外の複雑なテクスチャの復元は難しそうですね。