OpenCV如何模板匹配

 返回:OpenCV系列文章目录(持续更新中......)

上一篇:OpenCV如何实现背投
下一篇 :OpenCV在图像中寻找轮廓

目标

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

  • 使用 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情况下,掩码值被视为二进制值,即零和非零。CV_32F情况下,这些值应落在 [0..1] 范围内,模板像素将乘以相应的蒙版像素值。由于示例中的输入图像具有CV_8UC3类型,因此掩码也被读取为彩色图像。

OpenCV 中可用的匹配方法有哪些?

问得好。OpenCV 在函数 matchTemplate()中实现模板匹配。可用的方法有 6 种:

1、方法=TM_SQDIFF

 

2、方法=TM_SQDIFF_NORMED

3、方法=TM_CCORR

4、方法=TM_CCORR_NORMED

′)2

5、方法=TM_CCOEFF

哪里

6、方法=TM_CCOEFF_NORMED

C++代码:

  • 这个程序是做什么的?
    • 加载输入图像、图像补丁(模板)和可选的蒙版
    • 通过将 OpenCV 函数 matchTemplate() 与前面描述的 6 种匹配方法中的任何一种结合使用来执行模板匹配过程。用户可以通过在跟踪栏中输入其选择来选择方法。如果提供了掩码,则该掩码将仅用于支持掩码的方法
    • 规范化匹配过程的输出
    • 以更高的匹配概率定位位置
    • 在与最高匹配项对应的区域周围绘制一个矩形
  • 可下载代码: 点击这里
  • 代码一览:
#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;
}

解释

声明一些全局变量,例如图像、模板和结果矩阵,以及匹配方法和窗口名称:

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;

加载源图像、模板,以及可选的掩码(如果匹配方法支持):

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;}

创建跟踪栏以输入要使用的匹配方法的种类。检测到更改时,将调用回调函数。

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

让我们来看看回调函数。首先,它复制源图像:

 Mat img_display;img.copyTo( img_display );

执行模板匹配操作。参数自然是输入图像 I、模板 T、结果 R 和 match_method(由 Trackbar 给出),以及可选的蒙版图像 M

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

我们使用 minMaxLoc() 对结果矩阵 R 中的最小值和最大值进行本地化。

 double minVal; double maxVal; Point minLoc; Point maxLoc;Point matchLoc;minMaxLoc( result, &minVal, &maxVal, &minLoc, &maxLoc, Mat() );

对于前两种方法(TM_SQDIFF 和 MT_SQDIFF_NORMED),最佳匹配是最低值。对于所有其他值,较高的值表示更好的匹配。因此,我们将相应的值保存在 matchLoc 变量中:

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

结果

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

和模板图像:

生成以下结果矩阵(第一行是标准方法 SQDIFF、CCORR 和 CCOEFF,第二行是其规范化版本中的相同方法)。在第一列中,最暗的匹配度越好,对于其他两列,位置越亮,匹配度越高。

  1. 右边的匹配项如下所示(右边那个人的脸周围的黑色矩形)。请注意,CCORR 和 CCDEFF 给出了错误的最佳匹配,但是它们的规范化版本是正确的,这可能是因为我们只考虑“最高匹配”,而不是其他可能的高匹配。

参考文献:

1、《Template Matching》 -------Ana Huamán

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/4667.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何下载AndroidStudio旧版本

文章目录 1. Android官方网站2. 往下滑找到历史版本归档3. 同意软件下载条款协议4. 下载旧版本Androidstudio1. Android官方网站 点击 Android官网AS下载页面 https://developer.android.google.cn/studio 进入AndroidStuido最新版下载页面,如下图: 2. 往下滑找到历史版本归…

一本书了解AI的下一个风口:AI Agent

在数字化浪潮中&#xff0c;人工智能&#xff08;AI&#xff09;已成为推动现代社会前进的强劲引擎。 从智能手机的智能助手到自动驾驶汽车的精准导航&#xff0c;AI技术的应用已经渗透到生活的方方面面。 随着技术的飞速发展&#xff0c;我们正站在一个新的转折点上&#xff…

构建本地大语言模型知识库问答系统

MaxKB 2024 年 4 月 12 日&#xff0c;1Panel 开源项目组正式对外介绍了其官方出品的开源子项目 ——MaxKB&#xff08;github.com/1Panel-dev/MaxKB&#xff09;。MaxKB 是一款基于 LLM&#xff08;Large Language Model&#xff09;大语言模型的知识库问答系统。MaxKB 的产品…

[论文笔记]GAUSSIAN ERROR LINEAR UNITS (GELUS)

引言 今天来看一下GELU的原始论文。 作者提出了GELU(Gaussian Error Linear Unit,高斯误差线性单元)非线性激活函数&#xff1a; GELU x Φ ( x ) \text{GELU} x\Phi(x) GELUxΦ(x)&#xff0c;其中 Φ ( x ) \Phi(x) Φ(x)​是标准高斯累积分布函数。与ReLU激活函数通过输入…

网盘—上传文件

本文主要讲解网盘里面关于文件操作部分的上传文件&#xff0c;具体步骤如下 目录 1、实施步骤&#xff1a; 2、代码实现 2.1、添加上传文件协议 2.2、添加上传文件槽函数 2.3、添加槽函数定义 2.4、关联上传槽函数 2.5、服务器端 2.6、在服务器端添加上传文件请求的ca…

算法学习(5)-图的遍历

目录 什么是深度和广度优先 图的深度优先遍历-城市地图 图的广度优先遍历-最少转机 什么是深度和广度优先 使用深度优先搜索来遍历这个图的过程具体是&#xff1a; 首先从一个未走到过的顶点作为起始顶点&#xff0c; 比如以1号顶点作为起点。沿1号顶点的边去尝试访问其它未…

提升编码技能:学习如何使用 C# 和 Fizzler 获取特价机票

引言 五一假期作为中国的传统节日&#xff0c;也是旅游热门的时段之一&#xff0c;特价机票往往成为人们关注的焦点。在这个数字化时代&#xff0c;利用爬虫技术获取特价机票信息已成为一种常见的策略。通过结合C#和Fizzler库&#xff0c;我们可以更加高效地实现这一目标&…

2024年---蓝桥杯网络安全赛道部分WP

一、题目名称&#xff1a;packet 1、下载附件是一个流量包 2、用wireshark分析&#xff0c;看到了一个cat flag的字样 3、追踪http数据流&#xff0c;在下面一行看到了base64编码。 4、解码之后得到flag 二、题目名称&#xff1a;cc 1、下载附件&#xff0c;打开是一个html …

Docker构建LNMP部署WordPress

前言 使用容器化技术如 Docker 可以极大地简化应用程序的部署和管理过程&#xff0c;本文将介绍如何利用 Docker 构建 LNMP 环境&#xff0c;并通过部署 WordPress 来展示这一过程。 目录 一、环境准备 1. 项目需求 2. 安装包下载 3. 服务器环境 4. 规划工作目录 5. 创…

CAPS Wizard for Mac:打字输入辅助应用

CAPS Wizard for Mac是一款专为Mac用户设计的打字输入辅助应用&#xff0c;以其简洁、高效的功能&#xff0c;为用户带来了全新的打字体验。 CAPS Wizard for Mac v5.3激活版下载 该软件能够智能预测用户的输入内容&#xff0c;实现快速切换和自动大写锁定&#xff0c;从而大大…

OmniReader Pro for Mac:强大且全面的阅读工具

OmniReader Pro for Mac是一款专为Mac用户设计的强大且全面的阅读工具&#xff0c;它集阅读、编辑、管理等多种功能于一身&#xff0c;为用户提供了卓越的阅读体验。 OmniReader Pro for Mac v2.9.5激活版下载 该软件支持多种文件格式的阅读&#xff0c;包括PDF、Word、Excel、…

pycharm配置wsl开发环境(conda)

背景 在研究qanything项目的过程中&#xff0c;为了进行二次开发&#xff0c;需要在本地搭建开发环境。然后根据文档说明发现该项目并不能直接运行在windows开发环境&#xff0c;但可以运行在wsl环境中。于是我需要先创建wsl环境并配置pycharm。 wsl环境创建 WSL是“Windows Su…

新时代写作与互动:《一本书讲透 Elasticsearch》读者群的创新之路

1、《一本书讲透 Elasticsearch》销售最近进展汇报 给大家同步一下《一本书讲透 Elasticsearch》图书的进展情况&#xff0c;本周五&#xff08;2024年4月26日&#xff09;&#xff0c;出版社编辑老师反馈图书相关销量进展&#xff1a; 预计全网销量 1000 册&#xff0c;发货量…

OpenHarmony语言基础类库【@ohos.xml (xml解析与生成)】

将XML文本转换为JavaScript对象、以及XML文件生成和解析的一系列接口。 说明&#xff1a; 本模块首批接口从API version 8开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 导入模块 import xml from ohos.xml; XmlSerializer XmlSerializer接口…

FPGA实现图像处理之【直方图均衡-寄存器版】

FPGA实现直方图统计 一、图像直方图统计原理 直方图的全称为灰度直方图&#xff0c;是对图像每一灰度间隔内像素个数的统计。即对一张图片中每隔二灰度值的像素数量做统计&#xff0c;然后以直方图的形式展现出来。图下的亮暗分布在直方图中就可以一目了然&#xff0c;直方图…

Spark核心名词解释与编程

Spark核心概念 名词解释 1)ClusterManager&#xff1a;在Standalone(上述安装的模式&#xff0c;也就是依托于spark集群本身)模式中即为Master&#xff08;主节点&#xff09;&#xff0c;控制整个集群&#xff0c;监控Worker。在YARN模式中为资源管理器ResourceManager(国内…

paddlehub的简单应用

1、下载安装 pip install paddlehub -i https://pypi.tuna.tsinghua.edu.cn/simple 报错&#xff1a; Collecting onnx<1.9.0 (from paddle2onnx>0.5.1->paddlehub)Using cached https://pypi.tuna.tsinghua.edu.cn/packages/73/e9/5b953497c0e36df589fc60cc6c6b35…

Redux数据流架构

Redux的难点是理解它对于数据修改的规则, 下图动态展示了在整个数据的修改中&#xff0c;数据的流向 Redux代码被分为三个核心的概念&#xff0c;三个概念分别是: state: 一个对象 存放着我们管理的数据action: 一个对象 用来描述你想怎么改数据reducer: 一个函数 根据action的…

万兆以太网MAC设计(11)完整UDP协议栈仿真

文章目录 前言一、模块接口二、IP模块与ARP模块之间的联系三、整体协议栈仿真总结&#xff1a; 前言 目前除了巨帧处理逻辑之外&#xff0c;所有的准备工作都已经结束了&#xff0c;先进行整体的功能验证。 一、模块接口 所有模块接口皆采用AXIS数据流的形式&#xff0c;其中…

用Jenkins实现cherry-pick多个未入库的gerrit编译Android固件

背景: 在做Android固件开发的时候,通常我们可以利用gerrit-trigger插件,开发者提交一笔的时候自动触发jenkins编译,如果提交的这一笔的编译依赖其他gerrit才能编译过,我们可以在commit message中加入特殊字段,让jenkins在编译此笔patch的时候同时抓取依赖的gerrit代码下…