什么是模板匹配?
模板匹配是一种用于在较大图像中搜索和查找模板图像位置的方法。OpenCV提供matchTemplate()
方法来实现模板匹配功能。模板匹配结果返回的是灰度图像,其中每个像素表示该像素的邻域与模板匹配程度。假设输入图像的大小(W * H),模板图像的大小为(w * h),则输出图像的大小将为(W - w + 1,H - h + 1)。获得结果后,可以使用minMaxLoc()
方法查找最大/最小值位置,并将其作为矩形的左上角,以(w,h)作为矩形的宽度和高度来确定模板匹配到的区域。
模板匹配原理
在要检测的图像上,从左到右,从上到下遍历这一幅图像,从上到下计算模板与重叠子图像的像素匹配度,如果匹配的程度越大,这说明相同的可能性越大。只是这个匹配度的计算有讲究。
API
public static void matchTemplate(Mat image, Mat templ, Mat result, int method, Mat mask)
-
参数一:image,待匹配图像。必须是8位或者32位浮点图像。
-
参数二:templ,模板图像,类型与输入图像一致,并且大小不能大于源图像。
-
参数三:result,输出结果,必须是单通道32位浮点数,假设源图像
W*H
,模板图像w*h
, 则结果必须为(W-w+1)*(H-h+1)
的大小。 -
参数四:method,匹配方式标志位。若为TM_SQDIFF或者TM_SQDIFF_NORMED,计算值越小,匹配度越高,剩下的几个标志位,计算值越大,匹配度越高。
// C++: enum TemplateMatchModes public static final int TM_SQDIFF = 0, TM_SQDIFF_NORMED = 1, TM_CCORR = 2, TM_CCORR_NORMED = 3, TM_CCOEFF = 4, TM_CCOEFF_NORMED = 5;
-
参数五:mask,可选掩码。必须和templ参数大小相同,要么和templ通道数相同,要么单通道。如果数据类型为#CV_8U,则将掩码解释为二进制掩码,表示仅使用掩码为非零的元素,并且权重与实际掩码值无关(一直等于1)。若数据类型为#CV_32F,掩码值将作为权重参与计算。
标记位
设
R
(
x
,
y
)
为
结
果
矩
阵
,
T
(
x
,
,
y
,
)
为
模
板
矩
阵
,
I
(
x
,
y
)
为
源
图
像
矩
阵
R(x,y)为结果矩阵, T(x^,,y^, )为模板矩阵, I(x,y)为源图像矩阵
R(x,y)为结果矩阵,T(x,,y,)为模板矩阵,I(x,y)为源图像矩阵
TM_SQDIFF
R ( x , y ) = ∑ x ′ , y ′ ( T ( x ′ , y ′ ) − I ( x + x ′ , y + y ′ ) ) 2 R(x,y)= \sum _{x',y'} (T(x',y')-I(x+x',y+y'))^2 R(x,y)=x′,y′∑(T(x′,y′)−I(x+x′,y+y′))2
with mask:
R
(
x
,
y
)
=
∑
x
′
,
y
′
(
(
T
(
x
′
,
y
′
)
−
I
(
x
+
x
′
,
y
+
y
′
)
)
⋅
M
(
x
′
,
y
′
)
)
2
R(x,y)= \sum _{x',y'} \left( (T(x',y')-I(x+x',y+y')) \cdot M(x',y') \right)^2
R(x,y)=x′,y′∑((T(x′,y′)−I(x+x′,y+y′))⋅M(x′,y′))2
TM_SQDIFF_NORMED
R ( x , y ) = ∑ x ′ , y ′ ( T ( x ′ , y ′ ) − I ( x + x ′ , y + y ′ ) ) 2 ∑ x ′ , y ′ T ( x ′ , y ′ ) 2 ⋅ ∑ x ′ , y ′ I ( x + x ′ , y + y ′ ) 2 R(x,y)= \frac{\sum_{x',y'} (T(x',y')-I(x+x',y+y'))^2}{\sqrt{\sum_{ x',y'}T(x',y')^2 \cdot \sum_{x',y'} I(x+x',y+y')^2}} R(x,y)=∑x′,y′T(x′,y′)2⋅∑x′,y′I(x+x′,y+y′)2∑x′,y′(T(x′,y′)−I(x+x′,y+y′))2
with mask:
R
(
x
,
y
)
=
∑
x
′
,
y
′
(
(
T
(
x
′
,
y
′
)
−
I
(
x
+
x
′
,
y
+
y
′
)
)
⋅
M
(
x
′
,
y
′
)
)
2
∑
x
′
,
y
′
(
T
(
x
′
,
y
′
)
⋅
M
(
x
′
,
y
′
)
)
2
⋅
∑
x
′
,
y
′
(
I
(
x
+
x
′
,
y
+
y
′
)
⋅
M
(
x
′
,
y
′
)
)
2
R(x,y)= \frac{\sum _{x',y'} \left( (T(x',y')-I(x+x',y+y')) \cdot M(x',y') \right)^2}{\sqrt{\sum_{x',y'} \left( T(x',y') \cdot M(x',y') \right)^2 \cdot \sum_{x',y'} \left( I(x+x',y+y') \cdot M(x',y') \right)^2}}
R(x,y)=∑x′,y′(T(x′,y′)⋅M(x′,y′))2⋅∑x′,y′(I(x+x′,y+y′)⋅M(x′,y′))2∑x′,y′((T(x′,y′)−I(x+x′,y+y′))⋅M(x′,y′))2
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′))
with mask:
R
(
x
,
y
)
=
∑
x
′
,
y
′
(
T
(
x
′
,
y
′
)
⋅
I
(
x
+
x
′
,
y
+
y
′
)
⋅
M
(
x
′
,
y
′
)
2
)
R(x,y)= \sum _{x',y'} (T(x',y') \cdot I(x+x',y+y') \cdot M(x',y') ^2)
R(x,y)=x′,y′∑(T(x′,y′)⋅I(x+x′,y+y′)⋅M(x′,y′)2)
TM_CCORR_NORMED
R ( x , y ) = ∑ x ′ , y ′ ( T ( x ′ , y ′ ) ⋅ I ( x + x ′ , y + y ′ ) ) ∑ x ′ , y ′ T ( x ′ , y ′ ) 2 ⋅ ∑ x ′ , y ′ I ( x + x ′ , y + y ′ ) 2 R(x,y)= \frac{\sum_{x',y'} (T(x',y') \cdot I(x+x',y+y'))}{\sqrt{ \sum_{x',y'}T(x',y')^2 \cdot \sum_{x',y'} I(x+x',y+y')^2}} R(x,y)=∑x′,y′T(x′,y′)2⋅∑x′,y′I(x+x′,y+y′)2∑x′,y′(T(x′,y′)⋅I(x+x′,y+y′))
with mask:
R
(
x
,
y
)
=
∑
x
′
,
y
′
(
T
(
x
′
,
y
′
)
⋅
I
(
x
+
x
′
,
y
+
y
′
)
⋅
M
(
x
′
,
y
′
)
2
)
∑
x
′
,
y
′
(
T
(
x
′
,
y
′
)
⋅
M
(
x
′
,
y
′
)
)
2
⋅
∑
x
′
,
y
′
(
I
(
x
+
x
′
,
y
+
y
′
)
⋅
M
(
x
′
,
y
′
)
)
2
R(x,y)= \frac{\sum_{x',y'} (T(x',y') \cdot I(x+x',y+y') \cdot M(x',y')^2)}{\sqrt{\sum_{x',y'} \left( T(x',y') \cdot M(x',y') \right)^2 \cdot \sum_{x',y'} \left( I(x+x',y+y') \cdot M(x',y') \right)^2}}
R(x,y)=∑x′,y′(T(x′,y′)⋅M(x′,y′))2⋅∑x′,y′(I(x+x′,y+y′)⋅M(x′,y′))2∑x′,y′(T(x′,y′)⋅I(x+x′,y+y′)⋅M(x′,y′)2)
TM_CCOEFF
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′))
where
T
′
(
x
′
,
y
′
)
=
T
(
x
′
,
y
′
)
−
1
/
(
w
⋅
h
)
⋅
∑
x
′
′
,
y
′
′
T
(
x
′
′
,
y
′
′
)
I
′
(
x
+
x
′
,
y
+
y
′
)
=
I
(
x
+
x
′
,
y
+
y
′
)
−
1
/
(
w
⋅
h
)
⋅
∑
x
′
′
,
y
′
′
I
(
x
+
x
′
′
,
y
+
y
′
′
)
\begin{array}{l} T'(x',y')=T(x',y') - 1/(w \cdot h) \cdot \sum _{ x'',y''} T(x'',y'') \\ I'(x+x',y+y')=I(x+x',y+y') - 1/(w \cdot h) \cdot \sum _{x'',y''} I(x+x'',y+y'') \end{array}
T′(x′,y′)=T(x′,y′)−1/(w⋅h)⋅∑x′′,y′′T(x′′,y′′)I′(x+x′,y+y′)=I(x+x′,y+y′)−1/(w⋅h)⋅∑x′′,y′′I(x+x′′,y+y′′)
with mask:
T
′
(
x
′
,
y
′
)
=
M
(
x
′
,
y
′
)
⋅
(
T
(
x
′
,
y
′
)
−
1
∑
x
′
′
,
y
′
′
M
(
x
′
′
,
y
′
′
)
⋅
∑
x
′
′
,
y
′
′
(
T
(
x
′
′
,
y
′
′
)
⋅
M
(
x
′
′
,
y
′
′
)
)
)
I
′
(
x
+
x
′
,
y
+
y
′
)
=
M
(
x
′
,
y
′
)
⋅
(
I
(
x
+
x
′
,
y
+
y
′
)
−
1
∑
x
′
′
,
y
′
′
M
(
x
′
′
,
y
′
′
)
⋅
∑
x
′
′
,
y
′
′
(
I
(
x
+
x
′
′
,
y
+
y
′
′
)
⋅
M
(
x
′
′
,
y
′
′
)
)
)
\begin{array}{l} T'(x',y')=M(x',y') \cdot \left( T(x',y') - \frac{1}{\sum _{x'',y''} M(x'',y'')} \cdot \sum _{x'',y''} (T(x'',y'') \cdot M(x'',y'')) \right) \\ I'(x+x',y+y')=M(x',y') \cdot \left( I(x+x',y+y') - \frac{1}{\sum _{x'',y''} M(x'',y'')} \cdot \sum _{x'',y''} (I(x+x'',y+y'') \cdot M(x'',y'')) \right) \end{array}
T′(x′,y′)=M(x′,y′)⋅(T(x′,y′)−∑x′′,y′′M(x′′,y′′)1⋅∑x′′,y′′(T(x′′,y′′)⋅M(x′′,y′′)))I′(x+x′,y+y′)=M(x′,y′)⋅(I(x+x′,y+y′)−∑x′′,y′′M(x′′,y′′)1⋅∑x′′,y′′(I(x+x′′,y+y′′)⋅M(x′′,y′′)))
TM_CCOEFF_NORMED
R ( x , y ) = ∑ x ′ , y ′ ( T ′ ( x ′ , y ′ ) ⋅ I ′ ( x + x ′ , y + y ′ ) ) ∑ x ′ , y ′ T ′ ( x ′ , y ′ ) 2 ⋅ ∑ x ′ , y ′ I ′ ( x + x ′ , y + y ′ ) 2 R(x,y)= \frac{ \sum_{x',y'} (T'(x',y') \cdot I'(x+x',y+y')) }{ \sqrt{\sum_{x',y'}T'(x',y')^2 \cdot \sum_{x',y'} I'(x+x',y+y')^2} } R(x,y)=∑x′,y′T′(x′,y′)2⋅∑x′,y′I′(x+x′,y+y′)2∑x′,y′(T′(x′,y′)⋅I′(x+x′,y+y′))
操作
/**
* 模板匹配
* author: yidong
* 2020/10/23
*/
class MatchTemplateActivity : AppCompatActivity() {
private lateinit var mBinding: ActivityMatchTemplateBinding
private lateinit var mRgb: Mat
private lateinit var mTemplate: Mat
private var method = Imgproc.TM_SQDIFF
set(value) {
field = value
doMatch(field)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_match_template)
title = "TM_SQDIFF"
val bgr = Utils.loadResource(this, R.drawable.kobe)
mRgb = Mat()
mTemplate = Mat()
Imgproc.cvtColor(bgr, mRgb, Imgproc.COLOR_BGR2RGB)
val templateBgr = Utils.loadResource(this, R.drawable.kobe_template)
Imgproc.cvtColor(templateBgr, mTemplate, Imgproc.COLOR_BGR2RGB)
mBinding.ivLena.showMat(mTemplate)
doMatch(Imgproc.TM_CCOEFF)
}
private fun doMatch(method: Int) {
val tmp = mRgb.clone()
val result = Mat()
Imgproc.matchTemplate(mRgb, mTemplate, result, method)
val minMaxLoc = Core.minMaxLoc(result)
val topLeft = if (method == Imgproc.TM_SQDIFF || method == Imgproc.TM_SQDIFF_NORMED) {
minMaxLoc.minLoc;
} else {
minMaxLoc.maxLoc;
}
val rect = Rect(topLeft, Size(mTemplate.cols().toDouble(), mTemplate.rows().toDouble()))
Imgproc.rectangle(tmp, rect, Scalar(255.0, 0.0, 0.0), 4, Imgproc.LINE_8)
mBinding.ivResult.showMat(mRgb)
tmp.release()
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_match_template, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.match_tm_sqdiff -> {
method = Imgproc.TM_SQDIFF
title = "TM_SQDIFF"
}
R.id.match_tm_sqdiff_normed -> {
method = Imgproc.TM_SQDIFF_NORMED
title = "TM_SQDIFF_NORMED"
}
R.id.match_tm_ccoeff -> {
method = Imgproc.TM_CCOEFF
title = "TM_CCOEFF"
}
R.id.match_tm_ccoeff_normed -> {
method = Imgproc.TM_CCOEFF_NORMED
title = "TM_CCOEFF_NORMED"
}
R.id.match_tm_ccorr -> {
method = Imgproc.TM_CCORR
title = "TM_CCORR"
}
R.id.match_tm_ccorr_normed -> {
method = Imgproc.TM_CCORR_NORMED
title = "TM_CCORR_NORMED"
}
}
return true
}
override fun onDestroy() {
mTemplate.release()
mRgb.release()
super.onDestroy()
}
}
结果
源码
https://github.com/onlyloveyd/LearningAndroidOpenCV