模板匹配(Match Template)

http://www.cnblogs.com/xrwang/archive/2010/02/05/MatchTemplate.html

作者:王先荣


前言
    模板匹配是在图像中寻找目标的方法之一。Come On, Boy.我们一起来看看模板匹配到底是怎么回事。

 

模板匹配的工作方式
    模板匹配的工作方式跟直方图的反向投影基本一样,大致过程是这样的:通过在输入图像上滑动图像块对实际的图像块和输入图像进行匹配。
    假设我们有一张100x100的输入图像,有一张10x10的模板图像,查找的过程是这样的:
  (1)从输入图像的左上角(0,0)开始,切割一块(0,0)至(10,10)的临时图像;
  (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; }
复制代码

 

 

模板匹配(Match Template)_第1张图片

        左上是输入图像,左中是模板图像,右边是各种匹配方式的结果(相关匹配的结果明显不正确)

 

模板匹配和直方图反向投影的效率
    总的来说,模板匹配和直方图反向投影的效率都不高。在我的机器上,在1136*852大小的输入图像上匹配104*132的大小的模板图像(都是单通道灰度图像),大约需要700毫秒;而直方图反向投影大约需要75000毫秒(1.25分钟)。看来还需要继续学习,寻找更好的处理方法。
    另一方面,通过搜索OpenCv的源代码,发现OpenCv基本上没有使用并行计算。如果学习完之后,还有时间和热情,我准备尝试优化下OpenCv的并行计算;如果.net 4.0正式版推出了,也可以选择在这一方面做点优化。

 

感谢您耐心看完本文,希望对您有所帮助。

你可能感兴趣的:(模板匹配(Match Template))