マスク画像を使えるように、テンプレートマッチの関数を自作します。
templmatch.cpp 内の元ネタを使っても良かったのですが、相加平均を計算するところが、1/w*h で固定値になっているので、マスク画像を渡すと大幅に書き換えになってしまう…というのと、行列全体をフーリエ演算対象にしてしまっているので、そもそもマスク画像を渡すことができない。ってのが自作の理由です。
相関係数を計算を自作する(途中) | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/2509
前回は、RGB チャンネルを平坦に加算していたので誤検出多くなっていましたが、元の関数と同じようにチャンネル毎に相加平均を計算するという方式に変えました。すると、cv::matchTemplate と同じ結果が得られています(厳密にチェックした訳ではないので、違いはでてくるかも)。
このあたり、CV_TM_CCOEFF_NORMED を指定したときの検証用の関数を作る必要がありますね…先は長そう。
// 実画像の位置(dx,dy)に対して、相関係数を計算する double crossCorr( const cv::Mat& img, const cv::Mat& templ, const cv::Mat& mask, int dx, int dy ) { int cnt = 0; // 対象ドット数を取得する for ( int y=0; y<templ.rows; ++y ) { for ( int x=0; x<templ.cols; ++x ) { if ( mask.at<char>(y,x) == 0 ) continue; cnt++; } } // 分子(numerator) double numerator = 0.0; // 分母(denominator) double denominator_templ = 0.0; double denominator_img = 0.0; // チャンネル毎(RGB)に計算する for ( int k=0; k<3; ++k ) { double templMean = 0.0; // テンプレートの相加平均 double imgMean = 0.0; // 実画像の相加平均 for ( int y=0; y<templ.rows; ++y ) { for ( int x=0; x<templ.cols; ++x ) { if ( mask.at<char>(y,x) == 0 ) continue; cv::Vec3b v1 = templ.at<cv::Vec3b>(y,x); cv::Vec3b v2 = img.at<cv::Vec3b>(y+dy,x+dx); templMean += v1[k]; imgMean += v2[k]; } } templMean /= (double)cnt; imgMean /= (double)cnt; for ( int y=0; y<templ.rows; ++y ) { for ( int x=0; x<templ.cols; ++x ) { if ( mask.at<char>(y,x) == 0 ) continue; cv::Vec3b v1 = templ.at<cv::Vec3b>(y,x); cv::Vec3b v2 = img.at<cv::Vec3b>(y+dy,x+dx); numerator += (v1[k] - templMean)*(v2[k] - imgMean); denominator_templ += (v1[k] - templMean)*(v1[k] - templMean); denominator_img += (v2[k] - imgMean)*(v2[k] - imgMean); } } } // 分母を計算する double denominator = sqrt(denominator_templ)*sqrt(denominator_img); // 相関係数を計算する double corr = numerator/denominator; return corr; } // 実画像のすべてに対して相関係数を計算する void MatchTemplate( const cv::Mat& img, const cv::Mat& templ, cv::Mat& result, const cv::Mat& mask ) { cv::Size corrsize(img.cols-templ.cols, img.rows-templ.rows); result.create(corrsize, CV_64F); for ( int y=0; y<corrsize.height; ++y ) { for ( int x=0; x<corrsize.width; ++x ) { double corr = crossCorr( img, templ, mask,x,y ); result.at<double>(y,x) = corr ; } } }
テンプレートマッチング法を使うときに、OpenCV の cv::matchTemplate では矩形に縛られるので、マッチングしたい画像の背景にあるものに影響を受けてしまう…と考えるわけで、教師画像を
のように、背景を抜くためのマスク画像を作ればよいのではないか?という想定です。
で、実際に実験をしてみると、
マスク画像を使わない場合は、いちごの駒を誤検出していますが、マスク画像を使った場合はみかんの駒だけを検出している…というか、左下の白い領域をぼろぼろと誤検出していますね orz 。
おそらく、マッチングの評価関数に相関係数を使っているので、この白い領域の色ムラがちょうどみかんの教師画像の色ムラとあっているのではないかとと想像できます(相加平均からの差になるので、実画像の色のムラがたまたまマッチングしていると、こういう具合になってしまうかと)。これは、RGB 値の相関係数を使うときの欠点ではなかと。
あと、興味深いマッチングの仕方として、
のように、上記のような教師画像の色がうまく背景の画像にマッチングしてしまっている例があります。みかんの色がオレンジなので、まんなかにオレンジがある領域をさがしてしまうためです。これはマスク画像を使ったときの欠点ですね。
同じ画像でマスクを使わないと次の図になります。
右上のみかんの駒だけが検出されます。これは背景画像をマッチングさせているからです。
マスク画像を使いたいのは、次のように背景が切り替わった場合にも対応させたいためです。
背景差分を取って背景画像のほうを切り取るのもひとつの方法なのですが、検索対象となる教師画像のほうの背景画像を切り取ってしまうほうがより有効ではないかなと。
まあ、この図の場合、みかんだけでなくキウイ(かな?)もマッチングしてしまっている訳で、これも調節に必要ありですが。
はじめまして。
私もOpenCVのTM_CCOEFF_NORMEDの検証用に自前で実装してみたのですが、openCV の結果で0.5なのに0になったりして、かなり悩んでます。
もし差分の検証されたようでしたらぜひ教えていただきたいのですが・・・
あとdenominator は0になることがあると思いますが、どのように回避してらっしゃいますでしょうか。私はというと強制的に return 0;だったりします。
それとcrossCorr()の先頭の cntの2重for部分は countNonZero()で行けるのではないでしょうか。
本家 OpenCV の TM_CCOEFF_NORMED との比較はざっとしか調べていないのですが、これで ok だったと思います(以前のことだったので)。いくつか丸め誤差はあったと思うのですが、同じ式を使っているので。
denominator が 0 のところは考慮していません。0 になるのは、1色と1色の比較になるので、あまり意味はないかなと。
> それとcrossCorr()の先頭の cntの2重for部分は countNonZero()で行けるのではないでしょうか。
なるほど、そういう関数があるんですね。これを作ったときは OpenCV 初心者だったもので、とにかく作れればいいやって感じで作っておりました。まあ、最近は停滞中なんですが ^^;
ありがとうございます。
denominator の0割は、なるほどです。今度からきちんと考えるようにします。