图像处理:直方图——模板匹配 OpenCV v4.8.0

小提醒:英语中的mask在计算机领域有遮罩、掩码、蒙板等意思,需要自行辨析。

上一个教程反向投影

下一个教程在图像中寻找轮廓

原作者Ana Huamán
兼容性OpenCV >= 3.0

目标

在本教程中,您将学习如何

  • 使用 OpenCV 函数 matchTemplate() 搜索图像补丁和输入图像之间的匹配项
  • 使用 OpenCV 函数 minMaxLoc() 查找给定数组中的最大值和最小值(以及它们的位置)。

理论

什么是模板匹配?

模板匹配是一种查找图像中与模板图像(补丁)匹配(相似)区域的技术。

虽然补丁必须是一个矩形,但可能并非矩形的所有区域都与之相关。在这种情况下,可以使用遮罩来隔离补丁中用于查找匹配的部分。

它是如何工作的?

  • 我们需要两个主要组件:
  1. 源图像 (I): 我们希望找到与模板图像匹配的图像
  2. 模板图像 (T): 将与源图像进行比较的补丁图像

我们的目标是检测匹配度最高的区域:

在这里插入图片描述

  • 要识别匹配区域,我们必须通过滑动模板图像与源图像进行比较:

在这里插入图片描述

  • 所谓滑动,就是每次移动一个像素(从左到右,从上到下)。在每个位置上,我们都会计算出一个度量值,它代表了该位置上匹配的 "好 "或 "坏 "程度(或补丁与源图像中该特定区域的相似程度)。
  • 对于 T 在 I 上的每个位置,都要将度量值存储在结果矩阵 R 中。R 中的每个位置 (x,y) 都包含匹配度量:

在这里插入图片描述

  • 上图是以 TM_CCORR_NORMED 为匹配度量滑动补丁后的结果 R。最亮的位置表示匹配度最高。正如您所看到的,红圈标记的位置可能是值最高的位置,因此该位置(以该点为角、宽和高与补丁图像相等的矩形)被认为是匹配位置。

  • 在实际操作中,我们使用函数 minMaxLoc() 在 R 矩阵中找到最高值(或更低值,取决于匹配方法的类型)。

遮罩是如何工作的?

  • 如果需要遮罩匹配,则需要三个组件:
  1. 源图像 (I): 我们希望找到与模板图像匹配的图像
  2. 模板图像 (T): 将与源图像进行比较的补丁图像
  3. 遮罩图像 (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,yT(x,y)2x,yI(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,yT(x,y)2x,yI(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/(wh)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/(wh)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,yT(x,y)2x,yI(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)

结果

  1. 使用输入图像测试我们的程序,例如

在这里插入图片描述

和模板图片进行测试:

在这里插入图片描述

  1. 生成以下结果矩阵(第一行是标准方法 SQDIFF、CCORR 和 CCOEFF,第二行是相同方法的规范化版本)。在第一列中,颜色越深匹配度越高,在其他两列中,颜色越亮匹配度越高。

在这里插入图片描述

结果_0

在这里插入图片描述

结果_1

在这里插入图片描述

结果_2

在这里插入图片描述

结果_3

在这里插入图片描述

结果_4

在这里插入图片描述

结果_5
  1. 正确的匹配结果如下所示(右侧人脸周围的黑色矩形)。请注意,CCORR 和 CCDEFF 错误地给出了最佳匹配结果,但它们的归一化版本却正确地给出了最佳匹配结果,这可能是因为我们只考虑了 “最高匹配度”,而没有考虑其他可能的高匹配度。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值