小提醒:英语中的mask在计算机领域有遮罩、掩码、蒙板等意思,需要自行辨析。
上一个教程 : 反向投影
下一个教程 : 在图像中寻找轮廓
原作者 | Ana Huamán |
---|---|
兼容性 | OpenCV >= 3.0 |
目标
在本教程中,您将学习如何
- 使用 OpenCV 函数 matchTemplate() 搜索图像补丁和输入图像之间的匹配项
- 使用 OpenCV 函数 minMaxLoc() 查找给定数组中的最大值和最小值(以及它们的位置)。
理论
什么是模板匹配?
模板匹配是一种查找图像中与模板图像(补丁)匹配(相似)区域的技术。
虽然补丁必须是一个矩形,但可能并非矩形的所有区域都与之相关。在这种情况下,可以使用遮罩来隔离补丁中用于查找匹配的部分。
它是如何工作的?
- 我们需要两个主要组件:
- 源图像 (I): 我们希望找到与模板图像匹配的图像
- 模板图像 (T): 将与源图像进行比较的补丁图像
我们的目标是检测匹配度最高的区域:
- 要识别匹配区域,我们必须通过滑动模板图像与源图像进行比较:
- 所谓滑动,就是每次移动一个像素(从左到右,从上到下)。在每个位置上,我们都会计算出一个度量值,它代表了该位置上匹配的 "好 "或 "坏 "程度(或补丁与源图像中该特定区域的相似程度)。
- 对于 T 在 I 上的每个位置,都要将度量值存储在结果矩阵 R 中。R 中的每个位置 (x,y) 都包含匹配度量:
-
上图是以 TM_CCORR_NORMED 为匹配度量滑动补丁后的结果 R。最亮的位置表示匹配度最高。正如您所看到的,红圈标记的位置可能是值最高的位置,因此该位置(以该点为角、宽和高与补丁图像相等的矩形)被认为是匹配位置。
-
在实际操作中,我们使用函数 minMaxLoc() 在 R 矩阵中找到最高值(或更低值,取决于匹配方法的类型)。
遮罩是如何工作的?
- 如果需要遮罩匹配,则需要三个组件:
- 源图像 (I): 我们希望找到与模板图像匹配的图像
- 模板图像 (T): 将与源图像进行比较的补丁图像
- 遮罩图像 (M): 遮罩(掩码),掩盖模板的灰度图像
- 目前只有两种匹配方法接受遮罩: TM_SQDIFF 和 TM_CCORR_NORMED(有关 opencv 中所有可用匹配方法的解释,请参阅下文)。
- 遮罩的尺寸必须与模板相同
- 遮罩的深度应为 CV_8U 或 CV_32F,通道数与模板图像相同。在 CV_8U 情况下,遮罩被视为二值图像,即 0 和非 0。在 CV_32F 情况下,数值应在 [0…1] 范围内,模板像素将乘以相应的掩码像素值。由于样本中的输入图像为 CV_8UC3 类型,因此掩码也作为彩色图像读取。
OpenCV 中有哪些匹配方法?
问得好。OpenCV 在函数 matchTemplate() 中实现了模板匹配。可用的方法有 6 种:
1. method=TM_SQDIFF
R ( x , y ) = ∑ x ′ , y ′ ( T ( x ′ , y ′ ) − I ( x + x ′ , y + y ′ ) ) 2 R\left( x,y \right) =\sum_{x',y'}^{}{\left( T\left( x',y' \right) -I\left( x+x',y+y' \right) \right) ^2} R(x,y)=x′,y′∑(T(x′,y′)−I(x+x′,y+y′))2
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\left( x,y \right) =\frac{\sum_{x',y'}^{}{\left( T\left( x',y' \right) -I\left( x+x',y+y' \right) \right) ^2}}{\sqrt{\sum_{x',y'}^{}{T\left( x',y' \right) ^2}\sum_{x',y'}^{}{I\left( x+x',y+y' \right) ^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
3.方法=TM_CCORR
R ( x , y ) = ∑ x ′ , y ′ ( T ( x ′ , y ′ ) ⋅ I ( x + x ′ , y + y ′ ) ) R\left( x,y \right) =\sum_{x',y'}^{}{\left( T\left( x',y' \right) \cdot I\left( x+x',y+y' \right) \right)} R(x,y)=x′,y′∑(T(x′,y′)⋅I(x+x′,y+y′))
4. 方法=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\left( x,y \right) =\frac{\sum_{x',y'}^{}{\left( T\left( x',y' \right) \cdot I\left( x+x',y+y' \right) \right)}}{\sqrt{\sum_{x',y'}^{}{T\left( x',y' \right) ^2}\sum_{x',y'}^{}{I\left( x+x',y+y' \right) ^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′))
5. 方法=TM_CCOEFF
R ( x , y ) = ∑ x ′ , y ′ ( T ′ ( x ′ , y ′ ) ⋅ I ′ ( x + x ′ , y + y ′ ) ) R\left( x,y \right) =\sum_{x',y'}^{}{\left( T'\left( x',y' \right) \cdot I'\left( x+x',y+y' \right) \right)} R(x,y)=x′,y′∑(T′(x′,y′)⋅I′(x+x′,y+y′))
其中
T
′
(
x
′
,
y
′
)
=
T
(
x
′
,
y
′
)
−
1
/
(
w
⋅
h
)
⋅
∑
x
′
′
,
y
′
′
T
(
x
′
′
,
y
′
′
)
T'\left( x',y' \right) =T\left( x',y' \right) -1/\left( w\cdot h \right) \cdot \sum_{x'',y''}^{}{T\left( x'',y'' \right)}
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
′
′
)
I'\left( x+x',y+y' \right) =I\left( x+x',y+y' \right) -1/\left( w\cdot h \right) \cdot \sum_{x'',y''}^{}{I\left( x+x'',y+y'' \right)}
I′(x+x′,y+y′)=I(x+x′,y+y′)−1/(w⋅h)⋅x′′,y′′∑I(x+x′′,y+y′′)
6. method=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\left( x,y \right) =\frac{\sum_{x',y'}^{}{\left( T'\left( x',y' \right) \cdot I'\left( x+x',y+y' \right) \right)}}{\sqrt{\sum_{x',y'}^{}{T'\left( x',y' \right) ^2}\sum_{x',y'}^{}{I'\left( x+x',y+y' \right) ^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′))
代码
- 这个程序要做什么?
- 加载输入图像、图像补丁(模板)和可选的掩码
- 使用 OpenCV 函数 matchTemplate() 与之前描述的 6 种匹配方法中的任何一种进行模板匹配。用户可以通过在 Trackbar 中输入选择项来选择匹配方法。如果提供了掩码,它将只用于支持掩码的方法。
- 将匹配程序的输出归一化
- 定位匹配概率较高的位置
- 在匹配度最高的区域周围画一个矩形
C++
可下载代码: 点击此处
代码一览
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
using namespace std;
using namespace cv;
bool use_mask;
Mat img; Mat templ; Mat mask; Mat result;
const char* image_window = "Source Image";
const char* result_window = "Result window";
int match_method;
int max_Trackbar = 5;
void MatchingMethod( int, void* );
const char* keys =
"{ help h| | Print help message. }"
"{ @input1 | Template_Matching_Original_Image.jpg | image_name }"
"{ @input2 | Template_Matching_Template_Image.jpg | template_name }"
"{ @input3 | | mask_name }";
int main( int argc, char** argv )
{
CommandLineParser parser( argc, argv, keys );
samples::addSamplesDataSearchSubDirectory( "doc/tutorials/imgproc/histograms/template_matching/images" );
img = imread( samples::findFile( parser.get<String>("@input1") ) );
templ = imread( samples::findFile( parser.get<String>("@input2") ), IMREAD_COLOR );
if(argc > 3) {
use_mask = true;
mask = imread(samples::findFile( parser.get<String>("@input3") ), IMREAD_COLOR );
}
if(img.empty() || templ.empty() || (use_mask && mask.empty()))
{
cout << "Can't read one of the images" << endl;
return EXIT_FAILURE;
}
namedWindow( image_window, WINDOW_AUTOSIZE );
namedWindow( result_window, WINDOW_AUTOSIZE );
const char* trackbar_label = "Method: \n 0: SQDIFF \n 1: SQDIFF NORMED \n 2: TM CCORR \n 3: TM CCORR NORMED \n 4: TM COEFF \n 5: TM COEFF NORMED";
createTrackbar( trackbar_label, image_window, &match_method, max_Trackbar, MatchingMethod );
MatchingMethod( 0, 0 );
waitKey(0);
return EXIT_SUCCESS;
}
void MatchingMethod( int, void* )
{
Mat img_display;
img.copyTo( img_display );
int result_cols = img.cols - templ.cols + 1;
int result_rows = img.rows - templ.rows + 1;
result.create( result_rows, result_cols, CV_32FC1 );
bool method_accepts_mask = (TM_SQDIFF == match_method || match_method == TM_CCORR_NORMED);
if (use_mask && method_accepts_mask)
{ matchTemplate( img, templ, result, match_method, mask); }
else
{ matchTemplate( img, templ, result, match_method); }
normalize( result, result, 0, 1, NORM_MINMAX, -1, Mat() );
double minVal; double maxVal; Point minLoc; Point maxLoc;
Point matchLoc;
minMaxLoc( result, &minVal, &maxVal, &minLoc, &maxLoc, Mat() );
if( match_method == TM_SQDIFF || match_method == TM_SQDIFF_NORMED )
{ matchLoc = minLoc; }
else
{ matchLoc = maxLoc; }
rectangle( img_display, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar::all(0), 2, 8, 0 );
rectangle( result, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar::all(0), 2, 8, 0 );
imshow( image_window, img_display );
imshow( result_window, result );
return;
}
Java
可下载代码: 点击此处
代码一览
import java.awt.GridLayout;
import java.awt.Image;
import java.util.Hashtable;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
class MatchTemplateDemoRun implements ChangeListener {
Boolean use_mask = false;
Mat img = new Mat(), templ = new Mat();
Mat mask = new Mat();
int match_method;
JLabel imgDisplay = new JLabel(), resultDisplay = new JLabel();
public void run(String[] args) {
if (args.length < 2) {
System.out.println("Not enough parameters");
System.out.println("Program arguments:\n<image_name> <template_name> [<mask_name>]");
System.exit(-1);
}
img = Imgcodecs.imread(args[0], Imgcodecs.IMREAD_COLOR);
templ = Imgcodecs.imread(args[1], Imgcodecs.IMREAD_COLOR);
if (args.length > 2) {
use_mask = true;
mask = Imgcodecs.imread(args[2], Imgcodecs.IMREAD_COLOR);
}
if (img.empty() || templ.empty() || (use_mask && mask.empty())) {
System.out.println("Can't read one of the images");
System.exit(-1);
}
matchingMethod();
createJFrame();
}
private void matchingMethod() {
Mat result = new Mat();
Mat img_display = new Mat();
img.copyTo(img_display);
int result_cols = img.cols() - templ.cols() + 1;
int result_rows = img.rows() - templ.rows() + 1;
result.create(result_rows, result_cols, CvType.CV_32FC1);
Boolean method_accepts_mask = (Imgproc.TM_SQDIFF == match_method || match_method == Imgproc.TM_CCORR_NORMED);
if (use_mask && method_accepts_mask) {
Imgproc.matchTemplate(img, templ, result, match_method, mask);
} else {
Imgproc.matchTemplate(img, templ, result, match_method);
}
Core.normalize(result, result, 0, 1, Core.NORM_MINMAX, -1, new Mat());
Point matchLoc;
Core.MinMaxLocResult mmr = Core.minMaxLoc(result);
if (match_method == Imgproc.TM_SQDIFF || match_method == Imgproc.TM_SQDIFF_NORMED) {
matchLoc = mmr.minLoc;
} else {
matchLoc = mmr.maxLoc;
}
Imgproc.rectangle(img_display, matchLoc, new Point(matchLoc.x + templ.cols(), matchLoc.y + templ.rows()),
new Scalar(0, 0, 0), 2, 8, 0);
Imgproc.rectangle(result, matchLoc, new Point(matchLoc.x + templ.cols(), matchLoc.y + templ.rows()),
new Scalar(0, 0, 0), 2, 8, 0);
Image tmpImg = HighGui.toBufferedImage(img_display);
ImageIcon icon = new ImageIcon(tmpImg);
imgDisplay.setIcon(icon);
result.convertTo(result, CvType.CV_8UC1, 255.0);
tmpImg = HighGui.toBufferedImage(result);
icon = new ImageIcon(tmpImg);
resultDisplay.setIcon(icon);
}
@Override
public void stateChanged(ChangeEvent e) {
JSlider source = (JSlider) e.getSource();
if (!source.getValueIsAdjusting()) {
match_method = source.getValue();
matchingMethod();
}
}
private void createJFrame() {
String title = "Source image; Control; Result image";
JFrame frame = new JFrame(title);
frame.setLayout(new GridLayout(2, 2));
frame.add(imgDisplay);
int min = 0, max = 5;
JSlider slider = new JSlider(JSlider.VERTICAL, min, max, match_method);
slider.setPaintTicks(true);
slider.setPaintLabels(true);
// 设置小刻度线的间距
slider.setMinorTickSpacing(1);
// 自定义标签
Hashtable<Integer, JLabel> labelTable = new Hashtable<>();
labelTable.put(new Integer(0), new JLabel("0 - SQDIFF"));
labelTable.put(new Integer(1), new JLabel("1 - SQDIFF NORMED"));
labelTable.put(new Integer(2), new JLabel("2 - TM CCORR"));
labelTable.put(new Integer(3), new JLabel("3 - TM CCORR NORMED"));
labelTable.put(new Integer(4), new JLabel("4 - TM COEFF"));
labelTable.put(new Integer(5), new JLabel("5 - TM COEFF NORMED : (Method)"));
slider.setLabelTable(labelTable);
slider.addChangeListener(this);
frame.add(slider);
frame.add(resultDisplay);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
}
public class MatchTemplateDemo {
public static void main(String[] args) {
// 加载本地 OpenCV 库
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
// 运行代码
new MatchTemplateDemoRun().run(args);
}
}
Python
可下载代码: 点击此处
代码一览
from __future__ import print_function
import sys
import cv2 as cv
use_mask = False
img = None
templ = None
mask = None
image_window = "Source Image"
result_window = "Result window"
match_method = 0
max_Trackbar = 5
def main(argv):
if (len(sys.argv) < 3):
print('Not enough parameters')
print('Usage:\nmatch_template_demo.py <image_name> <template_name> [<mask_name>]')
return -1
global img
global templ
img = cv.imread(sys.argv[1], cv.IMREAD_COLOR)
templ = cv.imread(sys.argv[2], cv.IMREAD_COLOR)
if (len(sys.argv) > 3):
global use_mask
use_mask = True
global mask
mask = cv.imread( sys.argv[3], cv.IMREAD_COLOR )
if ((img is None) or (templ is None) or (use_mask and (mask is None))):
print('Can\'t read one of the images')
return -1
cv.namedWindow( image_window, cv.WINDOW_AUTOSIZE )
cv.namedWindow( result_window, cv.WINDOW_AUTOSIZE )
trackbar_label = 'Method: \n 0: SQDIFF \n 1: SQDIFF NORMED \n 2: TM CCORR \n 3: TM CCORR NORMED \n 4: TM COEFF \n 5: TM COEFF NORMED'
cv.createTrackbar( trackbar_label, image_window, match_method, max_Trackbar, MatchingMethod )
MatchingMethod(match_method)
cv.waitKey(0)
return 0
def MatchingMethod(param):
global match_method
match_method = param
img_display = img.copy()
method_accepts_mask = (cv.TM_SQDIFF == match_method or match_method == cv.TM_CCORR_NORMED)
if (use_mask and method_accepts_mask):
result = cv.matchTemplate(img, templ, match_method, None, mask)
else:
result = cv.matchTemplate(img, templ, match_method)
cv.normalize( result, result, 0, 1, cv.NORM_MINMAX, -1 )
_minVal, _maxVal, minLoc, maxLoc = cv.minMaxLoc(result, None)
if (match_method == cv.TM_SQDIFF or match_method == cv.TM_SQDIFF_NORMED):
matchLoc = minLoc
else:
matchLoc = maxLoc
cv.rectangle(img_display, matchLoc, (matchLoc[0] + templ.shape[0], matchLoc[1] + templ.shape[1]), (0,0,0), 2, 8, 0 )
cv.rectangle(result, matchLoc, (matchLoc[0] + templ.shape[0], matchLoc[1] + templ.shape[1]), (0,0,0), 2, 8, 0 )
cv.imshow(image_window, img_display)
cv.imshow(result_window, result)
pass
if __name__ == "__main__":
main(sys.argv[1:])
说明
- 声明一些全局变量,如图像、模板和结果矩阵,以及匹配方法和窗口名称:
C++
bool use_mask;
Mat img; Mat templ; Mat mask; Mat result;
const char* image_window = "Source Image";
const char* result_window = "Result window";
int match_method;
int max_Trackbar = 5;
Java
Boolean use_mask = false;
Mat img = new Mat(), templ = new Mat();
Mat mask = new Mat();
int match_method;
JLabel imgDisplay = new JLabel(), resultDisplay = new JLabel();
Python
use_mask = False
img = None
templ = None
mask = None
image_window = "Source Image"
result_window = "Result window"
match_method = 0
max_Trackbar = 5
- 加载源图像、模板和可选的掩码(如果匹配方法支持):
C++
img = imread( samples::findFile( parser.get<String>("@input1") ) );
templ = imread( samples::findFile( parser.get<String>("@input2") ), IMREAD_COLOR );
if(argc > 3) {
use_mask = true;
mask = imread(samples::findFile( parser.get<String>("@input3") ), IMREAD_COLOR );
}
if(img.empty() || templ.empty() || (use_mask && mask.empty()))
{
cout << "Can't read one of the images" << endl;
return EXIT_FAILURE;
}
Java
img = Imgcodecs.imread(args[0], Imgcodecs.IMREAD_COLOR);
templ = Imgcodecs.imread(args[1], Imgcodecs.IMREAD_COLOR);
Python
global img
global templ
img = cv.imread(sys.argv[1], cv.IMREAD_COLOR)
templ = cv.imread(sys.argv[2], cv.IMREAD_COLOR)
if (len(sys.argv) > 3):
global use_mask
use_mask = True
global mask
mask = cv.imread( sys.argv[3], cv.IMREAD_COLOR )
if ((img is None) or (templ is None) or (use_mask and (mask is None))):
print('Can\'t read one of the images')
return -1
- 创建 Trackbar,输入要使用的匹配方法。检测到变化时,将调用回调函数。
C++
const char* trackbar_label = "Method: \n 0: SQDIFF \n 1: SQDIFF NORMED \n 2: TM CCORR \n 3: TM CCORR NORMED \n 4: TM COEFF \n 5: TM COEFF NORMED";
createTrackbar( trackbar_label, image_window, &match_method, max_Trackbar, MatchingMethod );
Java
int min = 0, max = 5;
JSlider slider = new JSlider(JSlider.VERTICAL, min, max, match_method);
Python
trackbar_label = 'Method: \n 0: SQDIFF \n 1: SQDIFF NORMED \n 2: TM CCORR \n 3: TM CCORR NORMED \n 4: TM COEFF \n 5: TM COEFF NORMED'
cv.createTrackbar( trackbar_label, image_window, match_method, max_Trackbar, MatchingMethod )
- 让我们来看看回调函数。首先,它会复制源图像:
C++
Mat img_display;
img.copyTo( img_display );
Java
Mat img_display = new Mat();
img.copyTo(img_display);
Python
img_display = img.copy()
- 执行模板匹配操作。参数自然是输入图像 I、模板 T、结果 R 和 match_method(由 Trackbar 给出),以及可选的遮罩图像 M。
C++
bool method_accepts_mask = (TM_SQDIFF == match_method || match_method == TM_CCORR_NORMED);
if (use_mask && method_accepts_mask)
{ matchTemplate( img, templ, result, match_method, mask); }
else
{ matchTemplate( img, templ, result, match_method); }
Java
Boolean method_accepts_mask = (Imgproc.TM_SQDIFF == match_method || match_method == Imgproc.TM_CCORR_NORMED);
if (use_mask && method_accepts_mask) {
Imgproc.matchTemplate(img, templ, result, match_method, mask);
} else {
Imgproc.matchTemplate(img, templ, result, match_method);
}
Python
method_accepts_mask = (cv.TM_SQDIFF == match_method or match_method == cv.TM_CCORR_NORMED)
if (use_mask and method_accepts_mask):
result = cv.matchTemplate(img, templ, match_method, None, mask)
else:
result = cv.matchTemplate(img, templ, match_method)
- 我们对结果进行归一化处理:
C++
normalize( result, result, 0, 1, NORM_MINMAX, -1, Mat() );
Java
Core.normalize(result, result, 0, 1, Core.NORM_MINMAX, -1, new Mat());
Python
cv.normalize( result, result, 0, 1, cv.NORM_MINMAX, -1 )
- 使用 minMaxLoc() 在结果矩阵 R 中定位最小值和最大值。
C++
double minVal; double maxVal; Point minLoc; Point maxLoc;
Point matchLoc;
minMaxLoc( result, &minVal, &maxVal, &minLoc, &maxLoc, Mat() );
Java
Point matchLoc;
Core.MinMaxLocResult mmr = Core.minMaxLoc(result);
Python
_minVal, _maxVal, minLoc, maxLoc = cv.minMaxLoc(result, None)
- 对于前两种方法(TM_SQDIFF 和 MT_SQDIFF_NORMED),最佳匹配值是最低值。对于所有其他方法,较高的值代表较好的匹配。因此,我们将相应的值保存在 matchLoc 变量中:
C++
if( match_method == TM_SQDIFF || match_method == TM_SQDIFF_NORMED )
{ matchLoc = minLoc; }
else
{ matchLoc = maxLoc; }
Java
if (match_method == Imgproc.TM_SQDIFF || match_method == Imgproc.TM_SQDIFF_NORMED) {
matchLoc = mmr.minLoc;
} else {
matchLoc = mmr.maxLoc;
}
Python
if (match_method == cv.TM_SQDIFF or match_method == cv.TM_SQDIFF_NORMED):
matchLoc = minLoc
else:
matchLoc = maxLoc
- 显示源图像和结果矩阵。在匹配度最高的区域周围画一个矩形:
C++
rectangle( img_display, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar::all(0), 2, 8, 0 );
rectangle( result, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar::all(0), 2, 8, 0 );
imshow( image_window, img_display );
imshow( result_window, result );
Java
Imgproc.rectangle(img_display, matchLoc, new Point(matchLoc.x + templ.cols(), matchLoc.y + templ.rows()),
new Scalar(0, 0, 0), 2, 8, 0);
Imgproc.rectangle(result, matchLoc, new Point(matchLoc.x + templ.cols(), matchLoc.y + templ.rows()),
new Scalar(0, 0, 0), 2, 8, 0);
Image tmpImg = HighGui.toBufferedImage(img_display);
ImageIcon icon = new ImageIcon(tmpImg);
imgDisplay.setIcon(icon);
result.convertTo(result, CvType.CV_8UC1, 255.0);
tmpImg = HighGui.toBufferedImage(result);
icon = new ImageIcon(tmpImg);
resultDisplay.setIcon(icon);
Python
cv.rectangle(img_display, matchLoc, (matchLoc[0] + templ.shape[0], matchLoc[1] + templ.shape[1]), (0,0,0), 2, 8, 0 )
cv.rectangle(result, matchLoc, (matchLoc[0] + templ.shape[0], matchLoc[1] + templ.shape[1]), (0,0,0), 2, 8, 0 )
cv.imshow(image_window, img_display)
cv.imshow(result_window, result)
结果
- 使用输入图像测试我们的程序,例如
和模板图片进行测试:
- 生成以下结果矩阵(第一行是标准方法 SQDIFF、CCORR 和 CCOEFF,第二行是相同方法的规范化版本)。在第一列中,颜色越深匹配度越高,在其他两列中,颜色越亮匹配度越高。
- 正确的匹配结果如下所示(右侧人脸周围的黑色矩形)。请注意,CCORR 和 CCDEFF 错误地给出了最佳匹配结果,但它们的归一化版本却正确地给出了最佳匹配结果,这可能是因为我们只考虑了 “最高匹配度”,而没有考虑其他可能的高匹配度。