模板匹配(Match Template)

  • Post author:
  • Post category:其他


作者:王先荣



前言


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


模板匹配的工作方式


模板匹配的工作方式跟直方图的反向投影基本一样,大致过程是这样的:通过在输入图像上滑动图像块对实际的图像块和输入图像进行匹配。

假设我们有一张100×100的输入图像,有一张10×10的模板图像,查找的过程是这样的:

(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#版本代码:

ContractedBlock.gif
ExpandedBlockStart.gif

模板匹配





//


模板匹配







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位位图的方法有些小问题,我们要自己编写一段转换的代码,然后再显示。

ContractedBlock.gif
ExpandedBlockStart.gif

将浮点型图像转换成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;

}

2010020516591090.jpg

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


模板匹配和直方图反向投影的效率


总的来说,模板匹配和直方图反向投影的效率都不高。在我的机器上,在1136*852大小的输入图像上匹配104*132的大小的模板图像(都是单通道灰度图像),大约需要700毫秒;而直方图反向投影大约需要75000毫秒(1.25分钟)。看来还需要继续学习,寻找更好的处理方法。

另一方面,通过搜索OpenCv的源代码,发现OpenCv基本上没有使用并行计算。如果学习完之后,还有时间和热情,我准备尝试优化下OpenCv的并行计算;如果.net 4.0正式版推出了,也可以选择在这一方面做点优化。


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