模板匹配就是在整个图像区域发现与给定图像最相似的小块区域,所以模板匹配首先需要一个模板图像,另外需要一个待检测图像:
-
在待检测图像上,从左到右,从上到下,计算模板图像与重叠子图像的匹配度(相似度),匹配度(相似度)越大,两者相同的可能性越大。
-
对于每一个位置将计算的相似结果保存在矩阵 R 中。如果输入图像的大小为 WxH 且模板图像的大小为 wxh,则输出矩阵 R 的大小为 (W-w+1)x(H-h+1) 。
-
获得 R 后,从 R 中找出匹配度最高的位置,那么该位置对应的区域就是最匹配的,区域为以该点为顶点,长宽和模板图像一样大小的矩阵。)
OpenCV函数
将模板与重叠的图像区域进行比较。
使用指定的方法将大小为 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;
}
效果
参考: