テンプレートマッチ修正版
作者:王先栄http://www.cnblogs.com/xrwang/archive/2010/02/05/MatchTemplate.html
修正者:MadTurtle------王さんのバージョンを修正しました.どこか間違っているからです.
前言テンプレートマッチングは、画像内でターゲットを探す方法の1つです.Come On, Boy.テンプレートマッチングがどうなっているのか見てみましょう.
テンプレートマッチングの動作テンプレートマッチングの動作はヒストグラムの逆投影と基本的に同じであり、入力画像上で画像ブロックをスライドさせることによって実際の画像ブロックと入力画像をマッチングする大まかな過程である.100 x 100の入力画像があり、10 x 10のテンプレート画像があると仮定すると、(1)入力画像の左上隅(0,0)から(0,0)から(10,10)までの一時画像を切断する.(改者注:実はマッチングのたびにテンプレートの中心点に対応する位置で画素に値を付与する.すなわち、最初の比較はテンプレートの(temp.width/2,temp.height/2)中心点からの1/4面積を入力画像と比較すべきであり、マッチング結果cはテンプレートの中心点がある画素値に保存され、具体的には「Learning OpenCV」を参照する.したがって、最終的にマッチング結果を保持するために使用される画像サイズはsize=(images->width-patch_size.x+1、images->height-patch_size.y+1)であるべきである.
(2)仮画像とテンプレート画像を比較し、比較結果をcとする.(3)比較結果cは、結果画像(0,0)における画素値である.(4)入力画像の(0,1)~(10,11)までの仮画像を切り出し、比較し、結果画像に記録する.(5)(1)〜(4)ステップを入力画像の右下隅まで繰り返す.ヒストグラムの逆投影はヒストグラムであり、テンプレートマッチングは画像の画素値であることがわかります.テンプレートマッチングはヒストグラムの逆投影よりも速度が速いが,個人的にはヒストグラムの逆投影のロバスト性がより良いと思う.
テンプレートマッチングのマッチング方式はOpenCvとEmguCvで以下の6種類のコントラスト方式をサポートする:CV_TM_SQDIFF二乗差マッチング法:この方法は二乗差を用いてマッチングを行う;最良の一致値は0です.一致が悪いほど、一致値が大きくなります. CV_TM_CCORR相関整合法:この方法は乗算操作を採用する.数値が大きいほど、マッチングの程度が良いことを示します. CV_TM_CCOEFF相関係数マッチング法:1は完全なマッチングを表す;1は最悪の一致を表します. CV_TM_SQDIFF_NORMED正規化二乗差整合法CV_TM_CCORR_NORMED正規化相関整合法CV_TM_CCOEFF_NORMED正規化相関係数マッチング法は私のテスト結果から、上記のいくつかのマッチング方式に必要な計算時間が比較的近い(『OpenCvの学習』の本で述べたのとは異なり)ので、シーンに適応できるマッチング方式を選択することができます.
テンプレートマッチングのサンプルコードは、テンプレートマッチングのC#バージョンコードです.
テンプレートマッチング
//
private void btnCalc_Click(object sender, EventArgs e)
{
//
Image<Bgr, Byte> imageInput = new Image<Bgr, byte>((Bitmap)pbInput.Image);
//
Image<Bgr, Byte> imageTemplate = new Image<Bgr, byte>((Bitmap)pbTemplate.Image);
// ,
double scale = 1d;
double.TryParse(txtScale.Text, out scale);
if (scale != 1d)
{
imageInput = imageInput.Resize(scale, INTER.CV_INTER_LINEAR);
imageTemplate = imageTemplate.Resize(scale, INTER.CV_INTER_LINEAR);
}
//
string colorSpace = (string)cmbColorSpace.SelectedItem;
IImage imageInput2, imageTemplate2;
if (colorSpace == "Gray")
{
imageInput2 = imageInput.Convert<Gray, Byte>();
imageTemplate2 = imageTemplate.Convert<Gray, Byte>();
}
else if (colorSpace == "HSV")
{
imageInput2 = imageInput.Convert<Hsv, Byte>();
imageTemplate2 = imageTemplate.Convert<Hsv, Byte>();
}
else
{
imageInput2 = imageInput.Copy();
imageTemplate2 = imageTemplate.Copy();
}
//
TM_TYPE[] tmTypes = new TM_TYPE[] { TM_TYPE.CV_TM_SQDIFF, TM_TYPE.CV_TM_SQDIFF_NORMED, TM_TYPE.CV_TM_CCORR, TM_TYPE.CV_TM_CCORR_NORMED, TM_TYPE.CV_TM_CCOEFF, TM_TYPE.CV_TM_CCOEFF_NORMED };
// ( )
Image<Gray, Single>[] imageResults = new Image<Gray, float>[tmTypes.Length];
// ,
int i = 0;
double totalTime = 0d; //
double time; //
Stopwatch sw = new Stopwatch();
txtResult.Text += string.Format(" ( :{0}, :{1})/r/n", colorSpace, scale);
foreach (TM_TYPE tmType in tmTypes)
{
sw.Start();
// ( : IImage MatchTemplate , )
//Image<Gray, Single> imageResult = imageInput2.MatchTemplate(imageTemplate2, tmType);
Image<Gray, Single> imageResult;
if (colorSpace == "Gray")
imageResult = ((Image<Gray, Byte>)imageInput2).MatchTemplate((Image<Gray, Byte>)imageTemplate2, tmType);
else if (colorSpace == "HSV")
imageResult = ((Image<Hsv, Byte>)imageInput2).MatchTemplate((Image<Hsv, Byte>)imageTemplate2, tmType);
else
imageResult = ((Image<Bgr, Byte>)imageInput2).MatchTemplate((Image<Bgr, Byte>)imageTemplate2, tmType);
sw.Stop();
time = sw.Elapsed.TotalMilliseconds;
totalTime += time;
sw.Reset();
//
CvInvoke.cvNormalize(imageResult.Ptr, imageResult.Ptr, 1d, 0d, NORM_TYPE.CV_MINMAX, IntPtr.Zero);
// ,
double bestValue;
Point bestPoint;
FindBestMatchPointAndValue(imageResult, tmType, out bestValue, out bestPoint);
//
Rectangle rect = new Rectangle(new Point(bestPoint.X - imageTemplate.Size.Width / 2, bestPoint.Y - imageTemplate.Size.Height / 2), imageTemplate.Size);
imageResult.Draw(rect, new Gray(bestValue), 2);
//
imageResults[i] = imageResult;
i++;
//
txtResult.Text += string.Format(" :{0:G}, :{1:F05} , :({2},{3}), :{4}/r/n", tmType, time, bestPoint.X, bestPoint.Y, bestValue);
}
txtResult.Text += string.Format(" , :{0:F05} /r/n", totalTime);
//
pbResultSqdiff.Image = ImageConverter.ImageSingleToBitmap<Gray>(imageResults[0]);
pbResultSqdiffNormalized.Image = ImageConverter.ImageSingleToBitmap<Gray>(imageResults[1]);
pbResultCcorr.Image = ImageConverter.ImageSingleToBitmap<Gray>(imageResults[2]);
pbResultCcorrNormalized.Image = ImageConverter.ImageSingleToBitmap<Gray>(imageResults[3]);
pbResultCcoeff.Image = ImageConverter.ImageSingleToBitmap<Gray>(imageResults[4]);
pbResultCcoeffNormalized.Image = ImageConverter.ImageSingleToBitmap<Gray>(imageResults[5]);
//
imageInput.Dispose();
imageTemplate.Dispose();
imageInput2.Dispose();
imageTemplate2.Dispose();
foreach (Image<Gray, Single> imageResult in imageResults)
imageResult.Dispose();
}
// ,
private void FindBestMatchPointAndValue(Image<Gray, Single> image, TM_TYPE tmType, out double bestValue, out Point bestPoint)
{
bestValue = 0d;
bestPoint = new Point(0, 0);
double[] minValues, maxValues;
Point[] minLocations, maxLocations;
image.MinMax(out minValues, out maxValues, out minLocations, out maxLocations);
// , ; ,
if (tmType == TM_TYPE.CV_TM_SQDIFF || tmType == TM_TYPE.CV_TM_SQDIFF_NORMED)
{
bestValue = minValues[0];
bestPoint = minLocations[0];
}
else
{
bestValue = maxValues[0];
bestPoint = maxLocations[0];
}
}
結果画像テンプレートマッチングとヒストグラム逆投影により生成された結果画像はいずれも32ビット浮動小数点型単一チャネル画像であることを示した.C/C++を使えば、OpenCvのcvShowImage関数で簡単に表示できます.使うならNetは、EmguCvで32ビットの浮動小数点画像を8ビットのビットマップに変換する方法に問題があるので、自分で変換コードを作成してから表示します.
浮動小数点型画像を8ビットbyte画像に変換
/// <summary>
/// Byte ;
/// , 0~255 。
/// </summary>
/// <typeparam name="TColor"> </typeparam>
/// <param name="source"> </param>
/// <returns> Byte </returns>
public static Image<TColor, Byte> ImageSingleToByte<TColor>(Image<TColor, Single> source)
where TColor : struct, IColor
{
Image<TColor, Byte> dest = new Image<TColor, Byte>(source.Size);
//
double[] minVal, maxVal;
Point[] minLoc, maxLoc;
source.MinMax(out minVal, out maxVal, out minLoc, out maxLoc);
double min = minVal[0];
double max = maxVal[0];
for (int i = 1; i < minVal.Length; i++)
{
min = Math.Min(min, minVal[i]);
max = Math.Max(max, maxVal[i]);
}
//
double scale = 1.0, shift = 0.0;
scale = (max == min) ? 0.0 : 255.0 / (max - min);
shift = (scale == 0) ? min : -min * scale;
// , 256
CvInvoke.cvConvertScaleAbs(source.Ptr, dest.Ptr, scale, shift);
return dest;
}
/// <summary>
/// 8 Bitmap;
/// , 0~255 。
/// </summary>
/// <typeparam name="TColor"> </typeparam>
/// <param name="source"> </param>
/// <returns> 8 Bitmap</returns>
public static Bitmap ImageSingleToBitmap<TColor>(Image<TColor, Single> source)
where TColor : struct, IColor
{
Image<TColor, Byte> dest = ImageSingleToByte<TColor>(source);
Bitmap bitmap = dest.Bitmap;
dest.Dispose();
return bitmap;
}
左上は入力画像、左中はテンプレート画像、右側は各種マッチング方式の結果(相関マッチングの結果は明らかに正しくない)
テンプレートマッチングとヒストグラム逆投影の効率は総じて,テンプレートマッチングとヒストグラム逆投影の効率は高くない.私の機械では、1136*852サイズの入力画像に104*132サイズのテンプレート画像(いずれも単一チャネル階調画像)をマッチングし、約700ミリ秒かかります.ヒストグラムの逆投影には約75000ミリ秒(1.25分)かかります.さらに勉強を続け、より良い処理方法を探す必要があるようです.一方,OpenCvのソースコードを検索することで,OpenCvは並列計算をほとんど使用していないことが分かった.勉強が終わってから、まだ時間と情熱があれば、OpenCvの並列計算を最適化してみるつもりです.もし.Net 4.0の正式版が発売され、この面で最適化することもできます.
本文を辛抱強く読んでくれてありがとう.役に立つことを望んでいます.