我的Qt作品(11)使用Qt+OpenCV实现一个带旋转角度的NCC灰度模板匹配演示软件_灰度模板旋转匹配-程序员宅基地

技术标签: qt  OpenCV  Qt作品  灰度  opencv  NCC  模板匹配  

使用Qt+OpenCV自己写了一个带旋转角度的NCC灰度模板匹配算子以及它的演示软件。算子的原理是基于NCC灰度匹配。

一、什么是NCC匹配

1、基于Normalized cross correlation(NCC:归一化互相关)用来比较两幅图像的相似程度已经是一个常见的图像处理手段。在工业生产环节检测、监控领域对对象检测与识别均有应用。NCC算法可以有效降低光照对图像比较结果的影响。而且NCC最终结果在0到1之间,所以特别容易量化比较结果,只要给出一个阈值就可以判断结果的好与坏。

2、在opencv代码中,matchTemplate函数里面有个方法是cv::TM_CCOEFF_NORMED,它实现了NCC算子。CCOEFF的英文全称是:Correlation Coefficient,中文译为相关系数;NORMED是归一化的意思。但是该函数本身是不支持旋转角度和金字塔分级的,所以需要自己实现这些功能。matchTemplate函数保存在源码文件imgproc文件夹下的templmatch.cpp中。 

https://github.com/opencv/opencv/blob/2.2/modules/imgproc/src/templmatch.cpp

https://github.com/opencv/opencv/blob/2.4.13.7/modules/imgproc/src/templmatch.cpp

https://github.com/opencv/opencv/blob/3.4.18/modules/imgproc/src/templmatch.cpp
https://github.com/opencv/opencv/blob/4.5.5/modules/imgproc/src/templmatch.cpp

建议:看v2.x的源码,比较精简。从v3.x开始新增了mask参数。

3、 matchTemplate函数各个方法的计算公式如下:

请重点关注NCC算子的计算方式cv::TM_CCOEFF_NORMED。公式摘录自书籍《学习OpenCV 3》第13章,模板匹配。

此外,OPenCV官方网站的文档也提供了公式说明:

https://docs.opencv.org/3.4.18/df/dfb/group__imgproc__object.html

v4.6.0版本的文档更详细,还新增了参数Mask的说明:

 https://docs.opencv.org/4.6.0/df/dfb/group__imgproc__object.html

那么,公式里的T和I分别是什么意思?

T 代表模板图像(w,h),代表待匹配图像(W,H)。

R 代表输出的结果矩阵,大小为 (W-w+1)x(H-h+1)。

(x,y)代表结果矩阵的每个元素的坐标。

(x',y')代表模板矩阵的每个元素的坐标。

详情见:

https://blog.csdn.net/qq_42791845/article/details/103700503

https://blog.csdn.net/m0_38007695/article/details/114535686

https://blog.csdn.net/weixin_44229257/category_11737954.html

4、从OpenCV源码matchTemplate函数得出,在进行每种方法计算的时候,并不是直接在空间域操作,而是在crossCorr函数中,将输入图像做了一次DFT变换(即傅里叶变换),将空间域的图像转换到频率域中来进行处理,这也解释了OpenCV代码的高效性原因。

void crossCorr( const Mat& img, const Mat& _templ, Mat& corr, Point anchor, double delta, int borderType )

其实,crossCorr函数计算的结果就是TM_CCORR的方法的计算结果,求两个矩阵的卷积。

 TM_CCORR公式

OpenCV源码在执行完成crossCorr()函数之后,会继续调用:

static void common_matchTemplate( Mat& img, Mat& templ, Mat& result, int method, int cn )

所有方法都是基于crossCorr得到的结果矩阵的基础,再继续计算下去的。

 TM_SQDIFF公式可以展开

TM_CCOEFF公式可以展开

 其中 sum(I) 为源图像中模板图像对应区域的和,mean(T) 是模板图像的均值。

TM_CCOEFF_NORMED的分母

 

其中 σ 2为模板图像的方差,N,M 分别是模板图像的宽和高。

详情见:

https://blog.csdn.net/m0_38007695/article/details/114535686

https://blog.csdn.net/weixin_44229257/article/details/125059085

一句话总结:

OpenCV源码没有按照前面提到的公式来计算,而是采用了傅里叶变换,目的是加快运算速度。

其实傅里叶变换仍然不是最快的方法,计算卷积最快的方法是SIMD。

详情见:

https://www.cnblogs.com/Imageshop/p/4126753.html

https://www.cnblogs.com/Imageshop/p/9069650.html

https://www.cnblogs.com/Imageshop/p/8469208.html

5、如果不想使用傅里叶变换(不想在频域计算),而是直接在空间域计算,那么按照CV_TM_SQDIFF公式,我们来计算

在这里插入图片描述

resultImage是结果矩阵R

templateImage是模板矩阵T

srcImage是原图矩阵I

(x,y)代表结果矩阵的每个元素的坐标。

(x',y')代表模板矩阵的每个元素的坐标。

for (int resultImageRow = 0; resultImageRow < resultImage_rows; resultImageRow++)
{
    for (int resultImageCol = 0; resultImageCol < resultImage_cols; resultImageCol++)
	{
		int sum = 0;
		short int temp = 0;
		for (int templateImageRow = 0; templateImageRow < templateImage_rows; templateImageRow++)
		{
			for (int templateImageCol = 0; templateImageCol < templateImage_cols; templateImageCol++)
			{
				temp = templateImage.ptr<uchar>(templateImageRow)[templateImageCol] - srcImage.ptr<uchar>(resultImageRow + templateImageRow)[templateImageCol + resultImageCol];
				sum += temp * temp;
			}
		}
		resultImage.ptr<int>(resultImageRow)[resultImageCol] = sum;
	}
}

  TM_CCORR公式,直接在空间域计算

int* resultImagePtr;
uchar* srcImagePtr;
uchar* templateImagePtr;
for (int resultImageRow = 0; resultImageRow < resultImage_rows; resultImageRow++)
{
resultImagePtr = resultImage.ptr<int>(resultImageRow);
for (int resultImageCol = 0; resultImageCol < resultImage_cols; resultImageCol++)
{
int sum = 0;
short int temp = 0;
for (int templateImageRow = 0; templateImageRow < templateImage_rows; templateImageRow++)
{
srcImagePtr = srcImage.ptr<uchar>(resultImageRow + templateImageRow) + resultImageCol;
templateImagePtr = templateImage.ptr<uchar>(templateImageRow);
for (int templateImageCol = 0; templateImageCol < templateImage_cols; templateImageCol++)
{
temp1 = *(srcImagePtr++);
temp2 = *(templateImagePtr++);
sum += temp1 * temp2;
}
}
*(resultImagePtr++) = sum;
}
}

二、该演示软件实现的主要功能:

未使用商业图像处理库,而是纯粹Qt+OpenCV

1、NCC匹配

2、金字塔

3、最大重叠率

4、旋转角度

5、匹配分数

6、不支持缩放

7、模板文件读写

8、ROI框选功能,人机交互

三、部分头文件

创建模板,对照学习halcon的算子create_ncc_model
查找物体,对照学习halcon的算子find_ncc_model

#ifndef CNCCMATCH_H
#define CNCCMATCH_H

#include "nccmodelid.h"
#include "result.h"

class VISIONCORE_EXPORT CNccMatch
{
public:
    CNccMatch();
    virtual ~CNccMatch();

public:
    void createNccModel(const cv::Mat &imageModel, int numLevels, double angleStart, double angleExtent, double angleStep, CNCCModelID &modelID);
    void findNccModel(const cv::Mat &imageSearch, const CNCCModelID &modelID, double angleStart, double angleExtent, double minScore, int numMatches, double maxOverlap, int numLevels,
                      std::vector<int> &vtRow, std::vector<int> &vtColumn, std::vector<double> &vtAngle, std::vector<double> &vtScore);

private:
    void multipleMaxLoc(const cv::Mat &image, double minScore, int numMatches, std::vector<cv::Point> &vtLocations, std::vector<double> &vtMaxima);
    void imageRotate(cv::Mat &imageSrc, double angle, cv::Mat &imageDst, cv::Mat &mask);
    void clusterAnalyze(const std::vector<SMatchResult> &vtSrc, std::vector<SMatchResult> &vtDst, double disThreshold = 10);
    void nmsMatchesRotatedRect(const std::vector<SMatchResult> &vtSrc, const cv::Size &modelsize, std::vector<SMatchResult> &vtDst, double maxOverlap);
    void nmsMatchesRect(const std::vector<SMatchResult> &vtSrc, std::vector<SMatchResult> &vtDst, double maxOverlap);
};

#endif // CNCCMATCH_H

四、演示软件截图

未使用商业图像处理库,而是纯粹Qt+OpenCV

 

五、下一版的优化改进方向

1、模板积分图像实现预计算

2、CPU指令集的优化提速

优化提速的好文章:

https://www.cnblogs.com/Imageshop/p/14559685.html

六、bilibili视频教程

OpenCV中的模板匹配(二)源码对比_哔哩哔哩_bilibili

OpenCV中的模板匹配(七)相关性系数匹配_哔哩哔哩_bilibili

OpenCV中的模板匹配(八)归一化相关性系数匹配_哔哩哔哩_bilibili

X、引申知识点:NCC匹配与形状匹配的比较

create_ncc_model
find_ncc_model
read_ncc_model
write_ncc_model
clear_ncc_model

create_scaled_shape_model
find_scaled_shape_model
read_shape_model
write_shape_model
clear_shape_model
 

1、NCC匹配优点
纹理
对焦不清
形状轻微变形


2、形状匹配优点
精度高
支持X/Y 方向缩放
支持物体遮挡
支持多模板
支持非线性光照变化

3、他人写的git

GitHub - luosch/stereo-matching: stereo-matching using SSD, NCC and ASW

4、什么是模板匹配?

模板匹配就是在整个图像区域发现与给定图像最相似的小块区域,所以模板匹配首先需要一个模板图像,另外需要一个待检测图像:

在待检测图像上,从左到右,从上到下,计算模板图像与重叠子图像的匹配度(相似度),匹配度(相似度)越大,两者相同的可能性越大。

对于每一个位置将计算的相似结果保存在矩阵 R 中。如果输入图像的大小为 WxH 且模板图像的大小为 wxh,则输出矩阵R 的大小为 (W-w+1)x(H-h+1) 。

获得 R 后,从 R 中找出匹配度最高的位置,那么该位置对应的区域就是最匹配的,区域为以该点为顶点,长宽和模板图像一样大小的矩阵。)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/libaineu2004/article/details/123132530

智能推荐

oracle 12c 集群安装后的检查_12c查看crs状态-程序员宅基地

文章浏览阅读1.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态

解决jupyter notebook无法找到虚拟环境的问题_jupyter没有pytorch环境-程序员宅基地

文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境

国内安装scoop的保姆教程_scoop-cn-程序员宅基地

文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn

Element ui colorpicker在Vue中的使用_vue el-color-picker-程序员宅基地

文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker

迅为iTOP-4412精英版之烧写内核移植后的镜像_exynos 4412 刷机-程序员宅基地

文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机

Linux系统配置jdk_linux配置jdk-程序员宅基地

文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk

随便推点

matlab(4):特殊符号的输入_matlab微米怎么输入-程序员宅基地

文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入

C语言程序设计-文件(打开与关闭、顺序、二进制读写)-程序员宅基地

文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。‍ Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。

Touchdesigner自学笔记之三_touchdesigner怎么让一个模型跟着鼠标移动-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动

【附源码】基于java的校园停车场管理系统的设计与实现61m0e9计算机毕设SSM_基于java技术的停车场管理系统实现与设计-程序员宅基地

文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计

Android系统播放器MediaPlayer源码分析_android多媒体播放源码分析 时序图-程序员宅基地

文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;amp;gt;Jni-&amp;amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图

java 数据结构与算法 ——快速排序法-程序员宅基地

文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法