模板匹配就是在整个图像区域发现与给定图像最相似的小块区域,所以模板匹配首先需要一个模板图像,另外需要一个待检测图像:
在待检测图像上,从左到右,从上到下,计算模板图像与重叠子图像的匹配度(相似度),匹配度(相似度)越大,两者相同的可能性越大。
对于每一个位置将计算的相似结果保存在矩阵 R 中。如果输入图像的大小为 WxH 且模板图像的大小为 wxh,则输出矩阵 R 的大小为 (W-w+1)x(H-h+1) 。
获得 R 后,从 R 中找出匹配度最高的位置,那么该位置对应的区域就是最匹配的,区域为以该点为顶点,长宽和模板图像一样大小的矩阵。)
OpenCV函数
matchTemplate
将模板与重叠的图像区域进行比较。
使用指定的方法将大小为 w×h 的重叠矩阵与 templ 进行比较,并将比较结果存储在 result 中。
函数完成比较后,可以使用 minMaxLoc 函数找到最佳匹配项,作为全局最小值(使用TM_SQDIFF时)或最大值(使用TM_CCORR或TM_CCOEFF时)。 在彩色图像的情况下,分子的模板求和和分母中的每个和在所有通道上进行,每个通道使用单独的平均值。 即,该函数可以获取彩色模板和彩色图像。 结果仍然是单通道图像,更易于分析。
enum cv::TemplateMatchModes {
cv::TM_SQDIFF = 0,
cv::TM_SQDIFF_NORMED = 1,
cv::TM_CCORR = 2,
cv::TM_CCORR_NORMED = 3,
cv::TM_CCOEFF = 4,
cv::TM_CCOEFF_NORMED = 5
}
void cv::matchTemplate( InputArray image,
InputArray templ,
OutputArray result,
int method,
InputArray mask = noArray()
)
//Python:
result = cv.matchTemplate(image, templ, method[, result[, mask]])
参数解释
参数 | 解释 |
---|---|
image | 源图像,要搜索的图像,8位或32位浮点。 |
templ | 搜索的模板,不大于源图像并且具有相同的数据类型。 |
result | 比较结果图,单通道32位浮点。 如果 image 为 W×H 并且 templ 为 w×h ,则结果为 (W-w + 1)×(H-h + 1)。 |
method | 比较的方法 |
mask | 搜索模板的掩码。 必须与 templ 具有相同的数据类型和大小。默认情况下未设置。 当前,仅支持TM_SQDIFF 和 TM_CCORR_NORMED 方法。 |
比较方法:
TM_SQDIFF:平方差匹配法,该方法采用平方差进行匹配,最好的匹配值为0;匹配越差,匹配值越大
TM_SQDIFF_NORMED:归一化平方差匹配法
TM_CCORR:相关匹配法,该方法采用乘法操作,数值越大表示匹配程度越好
TM_CCORR_NORMED:归一化相关匹配法
TM_CCOEFF:相关系数匹配法,1表示完美的匹配,-1表示最差的匹配
TM_CCOEFF_NORMED:归一化相关系数匹配法
在 OpenCV 的实现中采用了傅里叶变换,速度会快很多。OpenCV 源码路径:modules/imgproc/src/templmatch.cpp
。
简单说一下 OpenCV 源码的方法:
对于 TM_CCORR 方法,
R ( x , y ) = ∑ x ′ , y ′ ( T ( x ′ , y ′ ) ⋅ I ( x + x ′ , y + y ′ ) ) R(x, y) = \sum_{x',y'}(T(x', y') \cdot I(x+x', y+y')) R(x,y)=x′,y′∑(T(x′,y′)⋅I(x+x′,y+y′))
对于 TM_SQDIFF:
R ( x , y ) = ∑ x ′ , y ′ ( T ( x ′ , y ′ ) − I ( x + x ′ , y + y ′ ) ) 2 = ∑ x ′ , y ′ ( T ( x ′ , y ′ ) 2 − 2 ⋅ T ( x ′ , y ′ ) ⋅ I ( x + x ′ , y + y ′ ) + I ( x + x ′ , y + y ′ ) 2 ) \begin{aligned} R(x, y) & = \sum_{x',y'}(T(x', y') - I(x+x', y+y'))^2 \\ & = \sum_{x',y'}(T(x', y')^2 - 2\cdot T(x', y') \cdot I(x+x', y+y') + I(x+x', y+y')^2) \end{aligned} R(x,y)=x′,y′∑(T(x′,y′)−I(x+x′,y+y′))2=x′,y′∑(T(x′,y′)2−2⋅T(x′,y′)⋅I(x+x′,y+y′)+I(x+x′,y+y′)2)
对于 TM_CCOEFF:
R ( x , y ) = ∑ x ′ , y ′ ( T ′ ( x ′ , y ′ ) ⋅ I ′ ( x + x ′ , y + y ′ ) ) = ∑ x ′ , y ′ ( T ( x ′ , y ′ ) ⋅ I ( x + x ′ , y + y ′ ) ) − s u m ( I ) ⋅ m e a n ( T ) \begin{aligned} R(x, y) & = \sum_{x',y'}(T'(x', y') \cdot I'(x+x', y+y')) \\ & = \sum_{x',y'}(T(x', y') \cdot I(x+x', y+y')) - sum(I)\cdot mean(T) \end{aligned} R(x,y)=x′,y′∑(T′(x′,y′)⋅I′(x+x′,y+y′))=x′,y′∑(T(x′,y′)⋅I(x+x′,y+y′))−sum(I)⋅mean(T)
其中 s u m ( I ) sum(I) sum(I) 为源图像中模板图像对应区域的和, m e a n ( T ) mean(T) mean(T) 是模板图像的均值。
而对于以上三个公式中的 ∑ x ′ , y ′ ( T ( x ′ , y ′ ) ⋅ I ( x + x ′ , y + y ′ ) ) \sum_{x',y'}(T(x', y') \cdot I(x+x', y+y')) ∑x′,y′(T(x′,y′)⋅I(x+x′,y+y′)) 是在频率域上进行计算的(可以将模板图像看成卷积核在源图像上滑动求卷积,可以在频率域计算,从而加快速度)
对于 I ( x + x ′ , y + y ′ ) 2 , s u m ( I ) I(x+x', y+y')^2, sum(I) I(x+x′,y+y′)2,sum(I) 可以通过图像的(平方)积分实现。
对于 TM_SQDIFF_NORMED,TM_CCORR_NORMED 的分母部分都可以通过图像的(平方)积分来实现。
对于 TM_CCOEFF_NORMED 的分母:
∑ x ′ , y ′ T ′ ( x ′ , y ′ ) 2 = N M ⋅ σ 2 \sum_{x',y'}T'(x', y')^2 = NM \cdot \sigma^2 x′,y′∑T′(x′,y′)2=NM⋅σ2
∑ x ′ , y ′ I ′ ( x + x ′ , y + y ′ ) 2 = ∑ x ′ , y ′ I ( x + x ′ , y + y ′ ) 2 − s u m ( I ) 2 N M \sum_{x',y'}I'(x+x', y+y')^2 = \sum_{x',y'}I(x+x', y+y')^2 - \frac{sum(I)^2}{NM} x′,y′∑I′(x+x′,y+y′)2=x′,y′∑I(x+x′,y+y′)2−NMsum(I)2
其中 σ 2 \sigma^2 σ2 为模板图像的方差, N , M N,M N,M 分别是模板图像的宽和高
通过以上的分析,使用不同的方法计算,可以加快模板匹配的速度。
C++示例
string srcWindow = "src";
string temWindow = "tem";
string matchWindow = "match";
string resultWindow = "result";
int matchMethod = 5;
int main()
{
string outDir = "./";
Mat src = imread("src.jpg");
Mat tem = imread("tem.jpg");
Mat result;
Mat matchResult;
// 模板匹配
matchTemplate(src, tem, result, matchMethod);
// 获取最大值,最小值,以及对应的点
Point minLoc, maxLoc, temLoc;
double minVal, maxVal;
minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc);
if(matchMethod == 0 || matchMethod == 1)
{
temLoc = minLoc;
}
else
{
temLoc = maxLoc;
}
// 获取匹配到的图像
matchResult = src(Range(temLoc.y, temLoc.y+tem.rows), Range(temLoc.x, temLoc.x+tem.cols));
// 在原图画矩形
rectangle(src, Rect(temLoc.x, temLoc.y, tem.cols, tem.rows), Scalar(0, 0, 255), 2);
imshow(resultWindow, result);
imshow(srcWindow, src);
imshow(matchWindow, matchResult);
imshow(temWindow, tem);
imwrite(outDir+"src.jpg", src);
imwrite(outDir+"result.jpg", matchResult);
waitKey(0);
return 0;
}
效果
https://www.bilibili.com/video/BV18v41117un