opencv模板匹配
模板匹配在图像处理中经常使用,该算法主要用于寻找图像中与模板图像相同的区域。此外,也用于图像定位,通过模板匹配找到指定的位置,然后进行后续的处理。
在进行模板匹配的时候,需要先制作模板,模板图像一般是从原始图像中取出一块图像区域作为模板。模板图像一定要小于待匹配的图像。在opencv中,提供了6种模板匹配的方式,即平方差匹配法(TM_SQDIFF)、 归一化平方差匹配法(TM_SQDIFF_NORMED)、相关匹配法(TM_CCORR)、归一化相关匹配法(TM_CCORR_NORMED)、系数匹配法(TM_CCOEFF)、归一化相关系数匹配法(TM_CCOEFF_NORMED)。其中,前面两种匹配方式得到的匹配系数越小则模板与待匹配图像越匹配。后面两种的匹配系数越大越匹配。所以,在进行模板匹配的时候,要根据选择的匹配方式来确定最匹配的位置。不管哪种匹配方式,其实方法都很简单,就是让模板图像在待匹配图像上滑动,计算模板图像覆盖在待匹配图像上的一些参数值,如平方差,相关性等。记录下每次滑动后的这些值,然后寻找最小值或最大值,得到该值在待匹配图像上的位置就是匹配的位置。
图1 模板匹配示意图
模板匹配是一种比较简单的算法。但是,该算法有一定的缺陷,首先,如果基于灰度图进行模板匹配,匹配的准确性不太好,因为实时采集的图像受到光照条件的变化,灰度值会发生变化。其次,该算法不具有旋转不变性,当图像有一定旋转角度的时候,与模板的匹配效果不好,这种匹配只适用于没有旋转或旋转角度很小的时候。最后,该算法的计算速度较慢,不太适合实时应用。
刚开始做图像处理的时候,几乎都会接触到模板匹配算法,但是。用过几次之后,就发现该算法好像不能得到想要的结果。这就是因为模板匹配算法有上面所提到的不足。但是,其实稍微修改一下,就可以解决很多问题。对于基于灰度值匹配不准确的问题,其实,一般不采用基于灰度值的匹配,而可以改为基于边界的匹配,这样的匹配准确率会好很多。对于计算速度慢的问题,一般不要直接图像和模板图像进行匹配,而是将图像和模板图像先进行高斯金字塔向下采样,将图像缩小,然后再进行匹配,然后将匹配结果再进行高斯金字塔向上采样,这时再得到匹配的位置,这样的计算速度会提高很多。对于图像旋转的问题,这个算法不能够很好的处理,一种变通的处理方法是先制作不同角度的模板,但是这种方法由于需要匹配的模板太多,导致计算速度变慢。因此,一般在实时处理的地方不推荐采用这种方式,而应该采用具有旋转匹配功能的方式,在这里暂时不说,以后有机会再说。
下面以opencv的代码具体实现模板匹配。代码在opencv4.0和vs2015的c++实现,如果已经配置好了,只要找到一张图,稍微修改一下模板图像位置就可以直接运行。代码中关键位置有注释。
#include"iostream"
#include"opencv2\opencv.hpp"
usingnamespace std;
usingnamespace cv;
int main(intargc, char** argv)
{
//读取一张图像
Mat src = imread("E:/img1.bmp", 0);
Mat gaussImg;
pyrDown(src, gaussImg);
namedWindow("原图",0);
imshow("原图", src);
//从原图中取出一块区域作为而模板图像
Mat templateImg = src(Range(200, 400),Range(300, 600));
namedWindow("模板图", 0);
imshow("模板图", templateImg);
//匹配结果矩阵的大小
int result_cols =src.cols - templateImg.cols + 1;
int result_rows =src.rows - templateImg.rows + 1;
Mat result = cv::Mat(result_cols,result_rows, CV_32FC1);
//进行模板匹配,选择的是TM_CCOEFF_NORMED方式
matchTemplate(src, templateImg, result, TM_CCOEFF_NORMED);
double minVal, maxVal;
Point minLoc, maxLoc,matchLoc;
//在匹配结果矩阵中查找匹配的最大最小值以及最大最小值对应的位置
minMaxLoc(result, &minVal, &maxVal, &minLoc,&maxLoc, Mat());
matchLoc = maxLoc;
//将匹配结果矩形在原图上绘制出来
Mat srcRGB;
cvtColor(src, srcRGB, COLOR_GRAY2RGB);
rectangle(srcRGB, Rect(matchLoc.x, matchLoc.y, templateImg.cols,templateImg.rows), Scalar(255,0, 0),3);
namedWindow("匹配结果", 0);
imshow("匹配结果", srcRGB);
//基于边界的模板匹配,首先对图像进行边缘检测,这里采用canny算子进行边缘检测
Mat srcCannyResult;
Canny(src, srcCannyResult, 75, 170);
namedWindow("原图canny边界", 0);
imshow("原图canny边界", srcCannyResult);
//模板图与匹配图像采用图像的边缘检测算法和同样的参数
Mat tempCannyResult;
Canny(templateImg, tempCannyResult, 75, 170);
namedWindow("模板canny边界", 0);
imshow("模板canny边界", tempCannyResult);
Mat borderResult = cv::Mat(result_cols,result_rows, CV_32FC1);
matchTemplate(srcCannyResult, tempCannyResult, borderResult, TM_CCOEFF_NORMED);
double borderMinVal,borderMaxVal;
Point borderMinLoc,borderMaxLoc, borderMtchLoc;
minMaxLoc(borderResult, &borderMinVal, &borderMaxVal,&borderMinLoc, &borderMaxLoc, Mat());
matchLoc = borderMaxLoc;
//绘制基于边缘的模板匹配结果,直接在边缘检测结果图像绘制矩形,也可以在原图上绘制,这里没有转成彩色图显示
rectangle(srcCannyResult, Rect(matchLoc.x,matchLoc.y, templateImg.cols, templateImg.rows), Scalar(255, 255, 255), 3);
namedWindow("边缘匹配结果", 0);
imshow("边缘匹配结果", srcCannyResult);
waitKey(0);
}
图2 基于灰度值的模板匹配
图3 基于边界的模板匹配
在这里没有实现高斯金字塔采样后的匹配,高斯金字塔采样很简单,就两个函数:pyrDown和pyrUp函数。有兴趣的可以自己去实现。模板匹配算法还是很有用的,只是对于图像有旋转的情况下适应性不好,对于大部分没有旋转的时候,或者旋转角度很小的时候,利用该算法实现特定目标的定位是没有问题的。