模板匹配
视频讲解如下:
在本章节中给大家演示一下模板匹配,模板匹配用到的主要函数是MatchTemplate,当前代码同样是在毛星云的代码基础上进行扩展优化的。
这里要额外说明一下,CSDN的源码C#工程采用的是OpenCvSharp3-AnyCPU版本,博客下面的源码采用的是OpenCvSharp4版本,因为g_resultImage图像在OpenCvSharp3-AnyCPU版本版本下显示会报错,但是在OpenCvSharp4中就能正常显示。
当前系列所有demo下载地址:
https://github.com/GaoRenBao/OpenCv4-Demo
不同编程语言对应的OpenCv版本以及开发环境信息如下:
语言 | OpenCv版本 | IDE |
C# | OpenCvSharp4.4.8.0.20230708 | Visual Studio 2022 |
C++ | OpenCv-4.5.5-vc14_vc15 | Visual Studio 2022 |
Python | OpenCv-Python (4.6.0.66) | PyCharm Community Edition 2022.1.3 |
首先呢,我们需要准备两张测试图片。(不想下载工程的童鞋,可直接复制下面的图片和代码)
原始图片如下:

匹配图片如下:

我们匹配的目标是这个人头。
关于匹配度:
当模板匹配采用CV_TM_SQDIFF(g_nMatchMethod = 0)模式时,minValue值越小,说明匹配度越高。
适用场景:
模板匹配只适用于当目标距离和角度固定或者变化不大时,用于目标查找,如果目标距离和角度存在较大变化时,非要使用模板匹配的话,可以尝试将模板图像进行循环缩放和旋转,以便实现目标查找。还有一种场景是使用模板匹配进行定位操作。
具体案例如下:https://blog.csdn.net/gs1069405343/article/details/98968998
在该案例中,博主对棋盘格标签进行了模板匹配,实现目标查找,棋盘格如下:

而采用的模板,我们可以如下定义:

这样,无论目标距离如何,都可以实现目标定位,当然,这里不包含角度偏差,最终就实现了如下的定位效果。

好了,关于模板匹配的介绍,就到这里,下面我们来看下我们的工程演示效果,这里以C++版本的效果为例子,C#版本的“匹配窗口”图像由于格式问题,显示不出来。

C#版本代码如下:
C#版本需要安装“OpenCvSharp4”、“OpenCvSharp4.runtime.win”两个库才行。不然会报错。
如果需要使用“ BitmapConverter.ToBitmap”操作,则需要追加安装“OpenCvSharp4.Extensions”库。
using OpenCvSharp;
using System;
namespace demo
{
internal class Program
{
static string WINDOW_NAME1 = "【原始图片】";
static string WINDOW_NAME2 = "【匹配窗口】";
static Mat g_srcImage = new Mat();
static Mat g_templateImage = new Mat();
static Mat g_resultImage = new Mat();
static int g_nMatchMethod = 0;
static int g_nMaxTrackbarNum = 5;
static void Main(string[] args)
{
//【1】载入原图像和模板块
g_srcImage = Cv2.ImRead("../../../images/girl6.jpg");
g_templateImage = Cv2.ImRead("../../../images/girl6_roi.jpg");
//【2】创建窗口
Cv2.NamedWindow(WINDOW_NAME1, WindowFlags.AutoSize);
Cv2.NamedWindow(WINDOW_NAME2, WindowFlags.AutoSize);
//【3】创建滑动条并进行一次初始化
int v = Cv2.CreateTrackbar("方法:", WINDOW_NAME1, ref g_nMatchMethod, g_nMaxTrackbarNum, on_Matching);
on_Matching(0, IntPtr.Zero);
Cv2.WaitKey(0);
}
public static void on_Matching(int pos, IntPtr userData)
{
//【1】给局部变量初始化
Mat srcImage = new Mat();
g_srcImage.CopyTo(srcImage);
//【2】初始化用于结果输出的矩阵
int resultImage_rows = g_srcImage.Rows - g_templateImage.Rows + 1;
int resultImage_cols = g_srcImage.Cols - g_templateImage.Cols + 1;
g_resultImage = new Mat(new Size(resultImage_rows, resultImage_cols), MatType.CV_32FC1);
g_resultImage.SetIdentity(new Scalar(0));
//【3】进行匹配和标准化
Cv2.MatchTemplate(g_srcImage, g_templateImage, g_resultImage, (TemplateMatchModes)g_nMatchMethod);
Cv2.Normalize(g_resultImage, g_resultImage, 0, 1, NormTypes.MinMax, -1);
//【4】通过函数 minMaxLoc 定位最匹配的位置
double minValue = 0;
double maxValue = 0;
Point minLocation = new Point();
Point maxLocation = new Point();
Point matchLocation = new Point();
Cv2.MinMaxLoc(g_resultImage, out minValue, out maxValue, out minLocation, out maxLocation);
//【5】对于方法 SQDIFF 和 SQDIFF_NORMED, 越小的数值有着更高的匹配结果. 而其余的方法, 数值越大匹配效果越好
double matched = 0; // 匹配度
if ((TemplateMatchModes)g_nMatchMethod == TemplateMatchModes.SqDiff ||
(TemplateMatchModes)g_nMatchMethod == TemplateMatchModes.SqDiffNormed)
{
matchLocation = minLocation;
matched = minValue;
}
else
{
matchLocation = maxLocation;
matched = maxValue;
}
string[] txts = { "SqDiff", "SqDiffNormed", "CCorr", "CCorrNormed", "CCoeff", "CCoeffNormed" };
Cv2.PutText(srcImage, $"{txts[g_nMatchMethod]} {matched}", new Point(10, 50), HersheyFonts.HersheyComplex, 1, new Scalar(0, 0, 255), 2);
//【6】绘制出矩形,并显示最终结果
Cv2.Rectangle(srcImage, matchLocation,
new Point(matchLocation.X + g_templateImage.Cols, matchLocation.Y + g_templateImage.Rows),
new Scalar(0, 0, 255), 2, LineTypes.Link8, 0);
Cv2.Rectangle(g_resultImage, matchLocation,
new Point(matchLocation.X + g_templateImage.Cols, matchLocation.Y + g_templateImage.Rows),
new Scalar(0, 0, 255), 2, LineTypes.Link8, 0);
Cv2.ImShow(WINDOW_NAME1, srcImage);
Cv2.ImShow(WINDOW_NAME2, g_resultImage);
}
}
}
C++版本代码如下:
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
#include <sstream>
#include <string>
#include <iomanip>
using namespace cv;
using namespace std;
#define WINDOW_NAME1 "【原始图片】" //为窗口标题定义的宏
#define WINDOW_NAME2 "【匹配窗口】" //为窗口标题定义的宏
Mat g_srcImage; Mat g_templateImage; Mat g_resultImage;
int g_nMatchMethod;
int g_nMaxTrackbarNum = 5;
static void ShowHelpText()
{
printf("\n\n 当前使用的OpenCV版本为:" CV_VERSION);
printf("\n\n ----------------------------------------------------------------------------\n");
//输出一些帮助信息
printf("\t欢迎来到【模板匹配】示例程序~\n");
printf("\n\n\t请调整滑动条观察图像效果\n\n");
printf("\n\t滑动条对应的方法数值说明: \n\n"
"\t\t方法【0】- 平方差匹配法(SQDIFF)\n"
"\t\t方法【1】- 归一化平方差匹配法(SQDIFF NORMED)\n"
"\t\t方法【2】- 相关匹配法(TM CCORR)\n"
"\t\t方法【3】- 归一化相关匹配法(TM CCORR NORMED)\n"
"\t\t方法【4】- 相关系数匹配法(TM COEFF)\n"
"\t\t方法【5】- 归一化相关系数匹配法(TM COEFF NORMED)\n");
}
void on_Matching(int, void*)
{
//【1】给局部变量初始化
Mat srcImage;
g_srcImage.copyTo(srcImage);
//【2】初始化用于结果输出的矩阵
int resultImage_rows = g_srcImage.rows - g_templateImage.rows + 1;
int resultImage_cols = g_srcImage.cols - g_templateImage.cols + 1;
g_resultImage.create(resultImage_rows, resultImage_cols, CV_32FC1);
//【3】进行匹配和标准化
// 当模板匹配采用CV_TM_SQDIFF(g_nMatchMethod = 0)模式时,minValue值越小,说明匹配度越高
matchTemplate(g_srcImage, g_templateImage, g_resultImage, g_nMatchMethod);
normalize(g_resultImage, g_resultImage, 0, 1, NORM_MINMAX, -1, Mat());
//【4】通过函数 minMaxLoc 定位最匹配的位置
double minValue;
double maxValue;
Point minLocation;
Point maxLocation;
Point matchLocation;
minMaxLoc(g_resultImage, &minValue, &maxValue, &minLocation, &maxLocation, Mat());
//【5】对于方法 SQDIFF 和 SQDIFF_NORMED, 越小的数值有着更高的匹配结果. 而其余的方法, 数值越大匹配效果越好
double matched = 0; // 匹配度
if (g_nMatchMethod == TM_SQDIFF || g_nMatchMethod == TM_SQDIFF_NORMED)
{
matchLocation = minLocation;
matched = minValue;
}
else
{
matchLocation = maxLocation;
matched = maxValue;
}
std::string txts[] = { "SqDiff","SqDiffNormed","CCorr","CCorrNormed","CCoeff","CCoeffNormed" };
std::ostringstream oss;
// digits10 返回 double 类型能表示的有效数字的十进制位数,加 1 是为了安全起见(尽管通常不需要)
oss << std::fixed << std::setprecision(std::numeric_limits<double>::digits10 + 1);
oss << matched;
putText(srcImage, txts[g_nMatchMethod], Point(10, 30), FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255), 2, LINE_AA);
putText(srcImage, oss.str(), Point(10, 65), FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255), 2, LINE_AA);
//【6】绘制出矩形,并显示最终结果
rectangle(srcImage, matchLocation, Point(matchLocation.x + g_templateImage.cols, matchLocation.y + g_templateImage.rows), Scalar(0, 0, 255), 2, 8, 0);
rectangle(g_resultImage, matchLocation, Point(matchLocation.x + g_templateImage.cols, matchLocation.y + g_templateImage.rows), Scalar(0, 0, 255), 2, 8, 0);
imshow(WINDOW_NAME1, srcImage);
imshow(WINDOW_NAME2, g_resultImage);
}
int main()
{
//【0】改变console字体颜色
system("color 1F");
//【0】显示帮助文字
ShowHelpText();
//【1】载入原图像和模板块
g_srcImage = imread("../images/girl6.jpg", 1);
g_templateImage = imread("../images/girl6_roi.jpg", 1);
//【2】创建窗口
namedWindow(WINDOW_NAME1, WINDOW_AUTOSIZE);
namedWindow(WINDOW_NAME2, WINDOW_AUTOSIZE);
//【3】创建滑动条并进行一次初始化
createTrackbar("方法", WINDOW_NAME1, &g_nMatchMethod, g_nMaxTrackbarNum, on_Matching);
on_Matching(0, 0);
waitKey(0);
return 0;
}
Python版本代码如下:
import cv2
import numpy as np
# 请调整滑动条观察图像效果
# 滑动条对应的方法数值说明
# 方法【0】- 平方差匹配法(SQDIFF)
# 方法【1】- 归一化平方差匹配法(SQDIFF NORMED)
# 方法【2】- 相关匹配法(TM CCORR)
# 方法【3】- 归一化相关匹配法(TM CCORR NORMED)
# 方法【4】- 相关系数匹配法(TM COEFF)
# 方法【5】- 归一化相关系数匹配法(TM COEFF NORMED)
def on_Matching(x):
global g_srcImage, g_templateImage, g_resultImage
# 获取滑动条的值
g_nMatchMethod = cv2.getTrackbarPos('g_nMatchMethod', 'WINDOW_NAME1')
# 给局部变量初始化
srcImage = np.copy(g_srcImage)
# 初始化用于结果输出的矩阵
resultImage_rows = g_srcImage.shape[1] - g_templateImage.shape[1] + 1
resultImage_cols = g_srcImage.shape[0] - g_templateImage.shape[0] + 1
g_resultImage = np.ones((resultImage_cols, resultImage_rows, 3), np.uint8)
# 进行匹配和标准化
g_resultImage = cv2.matchTemplate(g_srcImage, g_templateImage, g_nMatchMethod)
cv2.normalize(g_resultImage, g_resultImage, 0, 1, cv2.NORM_MINMAX)
# 通过函数 minMaxLoc 定位最匹配的位置
minValue, maxValue, minLocation, maxLocation = cv2.minMaxLoc(g_resultImage)
# 对于方法 SQDIFF 和 SQDIFF_NORMED, 越小的数值有着更高的匹配结果. 而其余的方法, 数值越大匹配效果越好
# SqDiff = 0
# SqDiffNormed = 1
# CCorr = 2
# CCorrNormed = 3
# CCoeff = 4
# CCoeffNormed = 5
matched = 0 # 匹配度
if g_nMatchMethod == 0 or g_nMatchMethod == 1:
matchLocation = minLocation
matched = minValue
else:
matchLocation = maxLocation
matched = maxValue
txts = ["SqDiff", "SqDiffNormed", "CCorr", "CCorrNormed", "CCoeff", "CCoeffNormed"]
font = cv2.FONT_HERSHEY_COMPLEX # 设置字体
cv2.putText(srcImage, txts[g_nMatchMethod], (10, 30), font, 1, (0, 0, 255), 2)
cv2.putText(srcImage, f"{matched}", (10, 65), font, 1, (0, 0, 255), 2)
# 绘制出矩形,并显示最终结果
cv2.rectangle(srcImage, matchLocation,
(matchLocation[0] + g_templateImage.shape[0], matchLocation[1] + g_templateImage.shape[1]),
(0, 0, 255), 2, 8, 0)
cv2.rectangle(g_resultImage, matchLocation,
(matchLocation[0] + g_templateImage.shape[0], matchLocation[1] + g_templateImage.shape[1]),
(0, 0, 255), 2, 8, 0)
cv2.imshow("WINDOW_NAME1", srcImage)
cv2.imshow("WINDOW_NAME2", g_resultImage)
# 【1】载入原图像和模板块
g_srcImage = cv2.imread("../images/girl6.jpg")
g_templateImage = cv2.imread("../images/girl6_roi.jpg")
# 创建窗口
cv2.namedWindow('WINDOW_NAME1')
cv2.namedWindow('WINDOW_NAME2')
cv2.createTrackbar('g_nMatchMethod', 'WINDOW_NAME1', 0, 5, on_Matching)
cv2.setTrackbarPos('g_nMatchMethod', 'WINDOW_NAME1', 0)
# 等待用户按键
cv2.imshow("WINDOW_NAME1", g_srcImage)
cv2.waitKey(0)
cv2.destroyAllWindows()
在下面文档中,提供了一种比较精简的匹配代码,分享一下。
OpenCV-Python-Tutorial-中文版.pdf
测试原图:


匹配效果:

import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('messi5.jpg', 0)
img2 = img.copy()
template = cv2.imread('messi_face.jpg', 0)
w, h = template.shape[::-1]
# All the 6 methods for comparison in a list
methods = ['cv2.TM_CCOEFF', 'cv2.TM_CCOEFF_NORMED', 'cv2.TM_CCORR',
'cv2.TM_CCORR_NORMED', 'cv2.TM_SQDIFF', 'cv2.TM_SQDIFF_NORMED']
for meth in methods:
img = img2.copy()
# exec 语句用来执行储存在字符串或文件中的 Python 语句。
# 例如,我们可以在运行时生成一个包含 Python 代码的字符串,
# 然后使用 exec 语句执行这些语句。
# eval 语句用来计算存储在字符串中的有效 Python 表达式
method = eval(meth)
# Apply template Matching
res = cv2.matchTemplate(img, template, method)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
# 使用不同的比较方法,对结果的解释不同
# If the method is TM_SQDIFF or TM_SQDIFF_NORMED, take minimum
if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:
top_left = min_loc
else:
top_left = max_loc
bottom_right = (top_left[0] + w, top_left[1] + h)
cv2.rectangle(img, top_left, bottom_right, 255, 2)
plt.subplot(121), plt.imshow(res, cmap='gray')
plt.title('Matching Result'), plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(img, cmap='gray')
plt.title('Detected Point'), plt.xticks([]), plt.yticks([])
plt.suptitle('method: ' + meth)
plt.show()