您好,
会员登录 快速注册
退出 ( 条未读消息)
关于本站 意见反馈 首页

公告:小宅博客网可以开发票了,需要发票的,去群里找群主哈!!
全部文章分类
  • 人工智能 >

  • 编程语言 >

  • WPF系列 >

  • ASP.NET系列 >

  • Linux >

  • 数据库 >

  • 嵌入式 >

  • WEB技术 >

  • PLC系列 >

  • 微服务与框架 >

  • 小宅DIY >

  • 学习资料 >

OpenCv基础 ANN车牌识别 yolov5车牌识别 指针式仪表识别 ROS系列 YOLO Halcon Detectron2 昇腾AI ChatGPT在线体验 英伟达JETSON ChatGLM ChatTTS FunASR 地平线 ByteTrack 魔搭社区 LangChain
C C# C++ Python Java Go
WPF
ASP.NET小功能 GPS定位系统-MVC GPS定位系统-VUE ASP.NET WebRTC
Linux Linux内核 Shell MakeFile
MySql SqlServer Oracle
STM8 STM32 51单片机
VUE入门 HTML JavaScript CSS layui镜像网站 ElementUi中文官网 element-plus 图标
三菱 欧姆龙 西门子 施耐德 松下 台达
IOTSharp IOTGateway ABP FRAMEWORK Docker
亚克力音响 编程仙途:智驭万法
面试题与技巧 Python入门技能树 微软C#教程
首页 编程之美 工具下载 全国就业 流量地图 文心一言
OpenCv基础
内容介绍与资料分享 C# OpenCv环境搭建 C++ OpenCv环境搭建 Python OpenCv环境搭建 Java OpenCv环境搭建 OpenCv组件结构解析 OpenCv命名规范 OpenCv基本专业术语与方法 OpenCV 常用函数与构造体详细说明 创建画布 打开一张图片 利用imwrite生成透明png图像 图像打开、混合显示和输出 图像腐蚀 blur图像模糊(均值滤波) sobel边缘检测 canny边缘检测 Scharr滤波器 程序性能检测及优化 视频播放 摄像头录像与播放 双摄像头操作与图像相似度检测 颜色空间转换与物体追踪 彩色目标追踪 光流法运动目标检测 OpenCV中的稠密光流 背景减除 点追踪 人脸识别 支持向量机之SVM引导 支持向量机之处理线性不可分数据 ROI矩形截取 鼠标绘制矩形 用OpenCV进行基本绘图 绘图函数(python中文显示) 把鼠标当画笔 用滑动条做调色板 图像的基础操作 图像上的算术运算 多通道图像混合 图像的亮度、对比度调整 XML和YAML文件的写入 XML和YAML文件的读取 卷积操作 三种线性滤波 两种非线性滤波 7种图像处理形态学(1) 7种图像处理形态学(2) 漫水填充 图像缩放与图像金字塔 二值化基本阈值操作 图像阈值 Laplacian图像变换(拉普拉斯算子) 霍夫变换HoughLines边缘检测与线性矢量 霍夫变换HoughLinesP边缘检测与线性矢量 霍夫变换HoughCircles边缘检测与线性矢量 LSD快速直线检测 几何变换 remap重映射 remap实现多种重映射 仿射变换综合示例 直方图均衡化 CLAHE有限对比适应性直方图均衡化 draw最大的轮廓 轮廓的性质 点到多边形的最短距离 形状匹配 椭圆拟合与直线拟合 基础轮廓查找 查找并绘制轮廓综合示例 凸缺陷/凸包检测 凸包检测 创建包围轮廓的矩形边界 创建包围轮廓的圆形边 创建包围轮廓的矩形和圆形边界框 查找和绘制图片轮廓矩 分水岭算法 图像修补 H-S二维直方图的绘制/2D直方图 一维直方图的绘制 RGB三色直方图的绘制 直方图对比 使用掩膜绘制直方图 直方图反向投影 模板匹配 多对象模板匹配 cornerHarris角点检测 cornerHarris角点检测综合示例 Shi-Tomasi角点检测 亚像素级角点检测 角点检测的FAST算法(FAST特征检测器) 颜色识别 warpPerspective透视变换 SURF/SIFT特征点检测 SURF/SIFT特征描述 使用FLANN进行特征点匹配 FLANN结合SURF/SIFT进行关键点的描述和匹配 寻找已知物体(一) 寻找已知物体(二) 目标查找与跟踪 - Meanshift与CamShift BRIEF描述符 ORB ORB算法描述与匹配 LUT 图像灰度调整 离散傅里叶变换 双目摄像头与图像拼接 环境亮度检测 stitching 全景拼接 Maze-Solver迷宫解密 使用Haar分类器之面部检测 使用Haar分类器之行人检测 OpenCv Haar/LBP/HOG分类器-人脸识别 斑点检测 使用GrabCut算法进行交互式前景提取 对极几何 摄像机标定 姿势估计 立体图像中的深度地图 OpenCv中的KNN KNN手写数字识别 ​SVM手写数字识别(使用SVM进行手写数据OCR) 英文字母的OCR 预测手写数字(预测不准) K值聚类(一) K值聚类(二) 计算摄影学-图像去噪 高动态范围成像(HDRI或HDR) MSER区域检测 二维码、条形码识别 删除图像中的水印 OpenCv深度学习dnn Kinect-深度相机 OpenCv常用数学算法 360度旋转直线绘制 向量延长线上的像素扫描 Tools工具包-窗体分配
直方图反向投影
多对象模板匹配
激萌の小宅 小宅博客网 OpenCv基础

文章作者:激萌の小宅

促销:¥0

价格:¥0

配送方式: 购买后立即生效(如购买异常,请联系站长)
付款之后一定要等待自动跳转结束,否则购买可能会失败
  • 0 天

    有效期

  • 0

    总销量

  • 3

    累计评价

模板匹配

视频讲解如下:


在本章节中给大家演示一下模板匹配,模板匹配用到的主要函数是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


首先呢,我们需要准备两张测试图片。(不想下载工程的童鞋,可直接复制下面的图片和代码)

原始图片如下:

1.jpg

匹配图片如下:

2.jpg

我们匹配的目标是这个人头。


关于匹配度:

        当模板匹配采用CV_TM_SQDIFF(g_nMatchMethod = 0)模式时,minValue值越小,说明匹配度越高。


适用场景:

        模板匹配只适用于当目标距离和角度固定或者变化不大时,用于目标查找,如果目标距离和角度存在较大变化时,非要使用模板匹配的话,可以尝试将模板图像进行循环缩放和旋转,以便实现目标查找。还有一种场景是使用模板匹配进行定位操作。

        具体案例如下:https://blog.csdn.net/gs1069405343/article/details/98968998

        在该案例中,博主对棋盘格标签进行了模板匹配,实现目标查找,棋盘格如下:

QQ截图20220528163310.jpg

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

3.png

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

QQ截图20220528163609.jpg

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

1.gif


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

测试原图:

messi5.jpg


messi_face.jpg


匹配效果:

QQ截图20231030165844.jpg

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()



直方图反向投影
多对象模板匹配

友情链接: CSDN激萌の小宅 95知识库 自考题库 罗分明个人网络博客 精益编程leanboot

小宅博客  www.bilibili996.com All Rights Reserved. 备案号: 闽ICP备2024034575号

网站经营许可证  福建省福州市 Copyright©2021-2025 版权所有

小宅博客
首页 智能家居 地图定位
公告:小宅博客网可以开发票了,需要发票的,去群里找群主哈!!

文章作者:激萌の小宅

促销:¥0

价格:¥0

配送方式: 购买后立即生效(如购买异常,请联系站长)
付款之后一定要等待自动跳转结束,否则购买可能会失败
  • 0 天

    有效期

  • 0

    总销量

  • 3

    累计评价

模板匹配

视频讲解如下:


在本章节中给大家演示一下模板匹配,模板匹配用到的主要函数是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


首先呢,我们需要准备两张测试图片。(不想下载工程的童鞋,可直接复制下面的图片和代码)

原始图片如下:

1.jpg

匹配图片如下:

2.jpg

我们匹配的目标是这个人头。


关于匹配度:

        当模板匹配采用CV_TM_SQDIFF(g_nMatchMethod = 0)模式时,minValue值越小,说明匹配度越高。


适用场景:

        模板匹配只适用于当目标距离和角度固定或者变化不大时,用于目标查找,如果目标距离和角度存在较大变化时,非要使用模板匹配的话,可以尝试将模板图像进行循环缩放和旋转,以便实现目标查找。还有一种场景是使用模板匹配进行定位操作。

        具体案例如下:https://blog.csdn.net/gs1069405343/article/details/98968998

        在该案例中,博主对棋盘格标签进行了模板匹配,实现目标查找,棋盘格如下:

QQ截图20220528163310.jpg

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

3.png

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

QQ截图20220528163609.jpg

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

1.gif


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

测试原图:

messi5.jpg


messi_face.jpg


匹配效果:

QQ截图20231030165844.jpg

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()