【NCC】之一:从Pearson相关系数到模板匹配的NCC方法

2023-10-14 06:10

本文主要是介绍【NCC】之一:从Pearson相关系数到模板匹配的NCC方法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • <center> NCC(Normalized Cross Correlation)
    • 1.**Pearson相关系数**
    • 2.**协方差 covariance**
    • 3. **方差 variance**
    • 4.模板匹配中的NCC方法
    • 5.实现过程
    • 6.测试结果
    • 7.部分核心源码
      • NCC.cpp

NCC(Normalized Cross Correlation)

从Pearson相关系数到模板匹配的NCC方法

1.Pearson相关系数

Pearson相关系数是用来衡量两个变量之间的相关性,由下式给出
p = c o v ( X , Y ) σ ( X ) σ ( Y ) p = \frac{cov(X,Y)}{\sigma(X)\sigma(Y)} p=σ(X)σ(Y)cov(X,Y)
p在数值上在[-1,1]之间

  • 当p=0时,说明两个变量不相关;
  • 当p>0时,两个变量正相关,而且p越大正相关性越强;
  • 当p<0时,两个变量负相关,而且p越小负相关性越强;

上式中 c o v ( X , Y ) cov(X,Y) cov(X,Y)表示的是两个变量 X 、 Y X、Y XY的协方差, σ ( X ) \sigma(X) σ(X)表示的是变量X的标准差差, σ ( Y ) \sigma(Y) σ(Y)表示的是变量Y的标准差。

2.协方差 covariance

协方差也是用来衡量两个变量之间的相关性的,当两个变量之间的协方差是0时不相关(两个变量相互独立),大于0时正相关,小于0时负相关。例如身高和体重之间的协方差就是一个正数,因为身高和体重是正相关的。
协方差由下式给出:
c o v ( X , Y ) = E [ ( X − E ( X ) ) ( Y − E ( Y ) ) ] cov(X,Y)=E[(X-E(X))(Y-E(Y))] cov(X,Y)=E[(XE(X))(YE(Y))]
其中 E ( ) E() E()表示变量的数学期望。eg: E ( X ) E(X) E(X)表示变量X的数学期望, E [ X ] = Σ μ i x i , μ i 是 x i E[X]=\Sigma \mu_i x_i,\mu_i 是 x_i E[X]=Σμixi,μixi的权重,如果每个样本的权重都相等的话则写为: E [ X ] = Σ x i n E[X]=\frac{\Sigma x_i}{n} E[X]=nΣxi

此外协方差的等价式

c o v ( X , Y ) = E [ ( X − E ( X ) ) ( Y − E ( Y ) ) ] = E [ X Y − X E ( Y ) − Y E ( X ) + E ( X ) E ( Y ) ] = E ( X Y ) − 2 E ( X ) E ( Y ) + E ( X ) E ( Y ) = E ( X Y ) − E ( X ) E ( Y ) \begin{aligned} cov(X,Y) &= E[(X-E(X))(Y-E(Y))]\\ &=E[XY - XE(Y) - YE(X) + E(X)E(Y)]\\ &=E(XY)-2E(X)E(Y) + E(X)E(Y)\\ &=E(XY)-E(X)E(Y) \end{aligned} cov(X,Y)=E[(XE(X))(YE(Y))]=E[XYXE(Y)YE(X)+E(X)E(Y)]=E(XY)2E(X)E(Y)+E(X)E(Y)=E(XY)E(X)E(Y)

协方差示例
有三个人的身高体重数据,X表示身高,Y表示体重

X身高(cm): 100,150,200
Y体重(kg): 50,100,150

则身高和体重的协方差 c o v ( X , Y ) = E ( X Y ) − E ( X ) E ( Y ) 则身高和体重的协方差cov(X,Y)=E(XY)-E(X)E(Y) 则身高和体重的协方差cov(X,Y)=E(XY)E(X)E(Y)
其中:

E ( X ) = 1 3 ( 100 + 150 + 200 ) = 150 E(X)=\frac{1}{3}(100+150+200)=150 E(X)=31(100+150+200)=150,
E ( Y ) = 100 , E ( X Y ) = 1 3 ( 100 ∗ 50 + 150 ∗ 100 + 200 ∗ 150 ) = 50000 3 E(Y)=100,E(XY)=\frac{1}{3}(100*50+150*100+200*150)=\frac{50000}{3} E(Y)=100,E(XY)=31(10050+150100+200150)=350000
c o v ( X , Y ) = E ( X Y ) − E ( X ) E ( Y ) = 50000 3 − 150 ∗ 100 = 5000 3 . cov(X,Y)=E(XY)-E(X)E(Y)=\frac{50000}{3}-150*100=\frac{5000}{3}. cov(X,Y)=E(XY)E(X)E(Y)=350000150100=35000.

3. 方差 variance

在考察单个变量的分布特征时有方差(variance)的概念,方差是一个大于等于0的实数,方差为0表示变量分布完全集中在一个点上,方差越大变量的分布越分散。方差由下式给出: v a r ( X ) = E [ ( X − E ( X ) ) 2 ] var(X)=E[(X-E(X))^2] var(X)=E[(XE(X))2] 观察可以看到方差是协方差的一个特例,即cov(X,X)=var(X).$

4.模板匹配中的NCC方法

模板匹配就是给定一个目标图和一个搜索图,采用一定的搜索策略去找到一个和目标相近的区域,在刚性模板匹配中,一般都是采用滑窗的方法来搜索,在比较两个图的相似度时有很多种方法,最简单的就是两个图像直接相减,NCC(Normalized Cross Correlation)就是计算两张图的pearson相关性,值越大说明两个图像越相像。

记 T m × n 为目标图 ( t a r g e t ) 记T_{m\times n}为目标图(target) Tm×n为目标图(target), S M × N S_{M\times N} SM×N为源搜索图(source), S x , y S_{x,y} Sx,y为S中以点 ( x , y ) (x,y) (x,y)为左上角的和T大小相同的子图, R ( M − m + 1 ) × ( N − n + 1 ) R_{(M-m+1)\times (N-n+1)} R(Mm+1)×(Nn+1)为匹配的结果图,则 R ( x , y ) = c o v ( S x , y , T ) σ ( S x , y ) σ ( T ) R(x,y)=\frac{cov(S_{x,y},T)}{\sigma(S_{x,y})\sigma(T)} R(x,y)=σ(Sx,y)σ(T)cov(Sx,y,T)

其中 c o v ( S x , y , T ) = E ( S x , y T ) − E ( S x , y ) E ( T ) = Σ i = 1 m Σ j = 1 n S x , y ( i , j ) T ( i , j ) m n − S x , y ˉ T ˉ \begin{aligned} cov(S_{x,y},T) &=E(S_{x,y}T)-E(S_{x,y})E(T)\\ &=\frac{\Sigma_{i=1}^{m}\Sigma_{j=1}^{n}S_{x,y}(i,j)T(i,j)}{mn} - \bar{S_{x,y}}\bar{T} \end{aligned} cov(Sx,y,T)=E(Sx,yT)E(Sx,y)E(T)=mnΣi=1mΣj=1nSx,y(i,j)T(i,j)Sx,yˉTˉ

S x , y ˉ = Σ i = 1 m Σ j = 1 n S x , y ( i , j ) m n \bar{S_{x,y}}=\frac{\Sigma_{i=1}^{m}\Sigma_{j=1}^{n}S_{x,y}(i,j)}{mn} Sx,yˉ=mnΣi=1mΣj=1nSx,y(i,j)

T ˉ = Σ i = 1 m Σ j = 1 n T ( i , j ) m n \bar{T} = \frac{\Sigma_{i=1}^{m}\Sigma_{j=1}^{n}T(i,j)}{mn} Tˉ=mnΣi=1mΣj=1nT(i,j)

σ ( S x , y ) = v a r ( S x , y ) = Σ i = 1 m Σ j = 1 n ( S x , y ( i , j ) − S x , y ˉ ) 2 m n \sigma(S_{x,y})=\sqrt{var(S_{x,y})}=\sqrt{\frac{\Sigma_{i=1}^{m}\Sigma_{j=1}^{n}{(S_{x,y}(i,j)-\bar{S_{x,y}}})^2}{mn}} σ(Sx,y)=var(Sx,y) =mnΣi=1mΣj=1n(Sx,y(i,j)Sx,yˉ)2

σ ( T ) = v a r ( S x , y ) = Σ i = 1 m Σ j = 1 n ( T ( i , j ) − T ˉ ) 2 m n \sigma(T)=\sqrt{var(S_{x,y})}=\sqrt{\frac{\Sigma_{i=1}^{m}\Sigma_{j=1}^{n}{(T(i,j)-\bar{T}})^2}{mn}} σ(T)=var(Sx,y) =mnΣi=1mΣj=1n(T(i,j)Tˉ)2

上面的式子展开看起来感觉很复杂,以往看到的也都是这样完全展开又组合在一起的式子,就像opencv官网的解释,很完整但是让人很费解,具体为什么是这样搞不清楚(也可能是我菜吧),但是看上面R(x,y)的式子意义是很明确的,就是计算两个图之间的Pearson相关系数。按照公式就可以直接开工写代码了。

5.实现过程

观察式子: R ( x , y ) = c o v ( S x , y , T ) σ ( S x , y ) σ ( T ) R(x,y)=\frac{cov(S_{x,y},T)}{\sigma(S_{x,y})\sigma(T)} R(x,y)=σ(Sx,y)σ(T)cov(Sx,y,T)

可以发现 σ ( T ) \sigma(T) σ(T)是固定的,模板给定之后值就确定了,只需要计算一次。 σ ( S x , y )和 c o v ( S x , y , T ) \sigma(S_{x,y})和cov(S_{x,y},T) σ(Sx,y)和cov(Sx,y,T)的计算过程中一直要用到 S x , y ˉ \bar{S_{x,y}} Sx,yˉ,如果直接去计算这个平均值将会有很多计算是浪费掉的,可以用积分图来加速这个过程

几个核心的步骤

  • 计算两个图的协方差
  • 计算图的灰度均值
  • 计算图的标准差

在实现上第一版先只打算跑通整个NCC的计算流程,后续如果有机会的话可以再考虑做几个优化版本。目前很明确想到的有下面这几个:

  • 积分图加速均值的计算
  • 计算协方差时用到卷积用FFT加速
  • 金字塔加速
  • 指令集优化
  • 多线程

6.测试结果

  • source
    在这里插入图片描述

  • target

在这里插入图片描述

  • result

在这里插入图片描述

可以看到在目标处得到最大值,也就是正确匹配到了目标图。

7.部分核心源码

source image size w,h = (1095,680)
target image size w,h = (89,91)
my NCC run 10 times, use 12359.000000 ms       
opencv NCC run 10 times, use 14.000000 ms

opencv的速度是该版本的882.78倍。

NCC.cpp

namespace mycv
{/*** @brief 模板匹配,归一化交叉相关算法。衡量模板和待匹配图像的相似性时* 用(Pearson)相关系数来度量。* r=cov(X,Y)/(sigma(X) * sigma(Y))* 其中cov(X,Y): 表示两个变量的协方差* cov(X,Y) = E[(X-E(x)) * (Y-E(Y))] = E(XY) - E(x)E(Y)* sigma(X): 表示X变量的标准差* sigma(Y): 表示Y变量的标准差* * @param source : 搜索图CV_8UC1格式* @param target :模板图CV_8UC1格式* @param result : 匹配结果的map图* @return int : 程序运行的状态码*/
int NormalizedCrossCorrelation(const cv::Mat &source,const cv::Mat &target,cv::Mat &result){if(source.empty() || target.empty()){MYCV_ERROR(kImageEmpty,"NCC empty input image");return kImageEmpty;}int H = source.rows;int W = source.cols;int t_h = target.rows;int t_w = target.cols;if(t_h > H || t_w > W){MYCV_ERROR(kBadSize,"NCC source image size should larger than targe image");return kBadSize;}//r = cov(X,Y)/(sigma(X) * sigma(Y))//sigma(X) = sqrt(var(X))int r_h = H - t_h + 1; //结果图的高度int r_w = W - t_w + 1;double target_mean = calculateMean(target);double target_var = calculateVariance(target,target_mean);double target_std_var = std::sqrt(target_var);result = cv::Mat::zeros(cv::Size(r_w,r_h),CV_32FC1);for(int row = 0; row < r_h ; row++){float * p = result.ptr<float>(row);for(int col = 0; col < r_w; col++){cv::Rect ROI(col,row,t_w,t_h);//source上和目标图匹配的子图cv::Mat temp = source(ROI);double temp_mean = calculateMean(temp);double cov = calculateCovariance(temp,target,temp_mean,target_mean);double temp_var = calculateVariance(temp,temp_mean);double temp_std_var = std::sqrt(temp_var);p[col] = cov / ((temp_std_var + 0.0000001) * (target_std_var + 0.0000001));}}return kSuccess;}/*** @brief 计算图像上ROI区域内的均值* * @param input  : 输入的图像CV_8UC1* @param ROI  : 输入的ROI区域* @param mean  : 返回的区域均值* @return int */
int calculateRegionMean(const cv::Mat &input,const cv::Rect &ROI,double &mean)
{if(input.empty()){MYCV_ERROR(kImageEmpty,"input empty");return kImageEmpty;}if(1 != input.channels()){MYCV_ERROR(kBadDepth,"Now only sopurt for one channel image");return kBadDepth;}int h = input.rows;int w = input.cols;if((ROI.x+ROI.width > w ) || (ROI.y+ROI.height > h)|| ROI.width <= 0 || ROI.height <= 0 ){MYCV_ERROR(kBadSize,"ROI is too big");return kBadSize;}int tpx = ROI.x;int tpy = ROI.y;int btx = ROI.x + ROI.width;int bty = ROI.y + ROI.height;double sum = 0;for(int row = tpy; row < bty; row++){const uchar *p = input.ptr<uchar>(row);for (int col = tpx ; col < btx ; col++){sum += p[col];}}int pixels_num = ROI.height * ROI.width;mean = sum / pixels_num;return kSuccess;
}/*** @brief 计算两个输入图的协方差,两个输入图的尺寸需要一致,在计算目标图和原图子块的协方差时,* 目标图(模板图)是固定的,均值只需要计算一次,所以如果传入图像均值的话就不在计算均值,均值默认为-1* cov(X,Y): 表示两个变量的协方差* cov(X,Y) = E[ (X-E(x)) * (Y-E(Y)) ] = E(XY) - E(x)E(Y)* * @param A  : 输入图A CV_8UC1* @param B  : 输入图B CV_8UC1* @param mean_a  : A的像素均值* @param mean_b  : B的像素均值* @return double : 两个图像的协方差*/
double calculateCovariance(const cv::Mat &A, const cv::Mat &B,double mean_a,double mean_b)
{if(A.empty() || B.empty()){MYCV_ERROR(kImageEmpty,"input image is empty");return kImageEmpty;}if (A.cols != B.cols || A.rows != B.rows){MYCV_ERROR(kBadSize,"mat A B should be in same size");return kBadSize;}//E(XY)double sum = 0;for (int row = 0; row < A.rows; row++){const uchar *pa = A.ptr<uchar>(row);const uchar *pb = B.ptr<uchar>(row);for (int  col = 0; col < A.cols; col++){sum += (double)pa[col] * (double)pb[col];}}double mean_AB = sum / ((double)A.rows * (double)A.cols);if (-1 == mean_a){mean_a = calculateMean(A);}if (-1 == mean_b){mean_b = calculateMean(B);}//cov(X,Y) = E[ (X-E(x)) * (Y-E(Y)) ] = E(XY) - E(x)E(Y)double cov_AB = mean_AB - (mean_a * mean_b);return cov_AB;
}/*** @brief 计算输入图像的方差,如果已知mean就不再计算mean* * @param image  : 输入图CV_8UC1* @param mean  : 图像的灰度均值,默认值为-1,不输入时会计算mean* @return double :图像的方差*/
double calculateVariance(const cv::Mat &image,double mean)
{if (image.empty())  {MYCV_ERROR(kImageEmpty,"empty image");return -1;//正常的方差不会小于0}if (-1 == mean){mean = calculateMean(image);}double sum = 0 ;for (int  row = 0; row < image.rows; row++){const uchar * p = image.ptr<uchar>(row);for (int col = 0; col < image.cols; col++){sum += (p[col] - mean) * (p[col] - mean);}}double var = sum / ((double)image.cols * (double)image.rows);return var;    
}/*** @brief 计算输入图的灰度均值* * @param image  : 输入图CV_8UC1* @return double : 输入图像的灰度均值*/
double calculateMean(const cv::Mat &image)
{if (image.empty())  {MYCV_ERROR(kImageEmpty,"empty image");return -1;}double sum = 0 ;for (int  row = 0; row < image.rows; row++){const uchar * p = image.ptr<uchar>(row);for (int col = 0; col < image.cols; col++){sum += p[col];}}double mean = sum / ((double)image.cols * (double)image.rows);return mean;
}} //end namespace mycv

这篇关于【NCC】之一:从Pearson相关系数到模板匹配的NCC方法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/208695

相关文章

poj3468(线段树成段更新模板题)

题意:包括两个操作:1、将[a.b]上的数字加上v;2、查询区间[a,b]上的和 下面的介绍是下解题思路: 首先介绍  lazy-tag思想:用一个变量记录每一个线段树节点的变化值,当这部分线段的一致性被破坏我们就将这个变化值传递给子区间,大大增加了线段树的效率。 比如现在需要对[a,b]区间值进行加c操作,那么就从根节点[1,n]开始调用update函数进行操作,如果刚好执行到一个子节点,

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

浅谈主机加固,六种有效的主机加固方法

在数字化时代,数据的价值不言而喻,但随之而来的安全威胁也日益严峻。从勒索病毒到内部泄露,企业的数据安全面临着前所未有的挑战。为了应对这些挑战,一种全新的主机加固解决方案应运而生。 MCK主机加固解决方案,采用先进的安全容器中间件技术,构建起一套内核级的纵深立体防护体系。这一体系突破了传统安全防护的局限,即使在管理员权限被恶意利用的情况下,也能确保服务器的安全稳定运行。 普适主机加固措施:

webm怎么转换成mp4?这几种方法超多人在用!

webm怎么转换成mp4?WebM作为一种新兴的视频编码格式,近年来逐渐进入大众视野,其背后承载着诸多优势,但同时也伴随着不容忽视的局限性,首要挑战在于其兼容性边界,尽管WebM已广泛适应于众多网站与软件平台,但在特定应用环境或老旧设备上,其兼容难题依旧凸显,为用户体验带来不便,再者,WebM格式的非普适性也体现在编辑流程上,由于它并非行业内的通用标准,编辑过程中可能会遭遇格式不兼容的障碍,导致操

poj 1258 Agri-Net(最小生成树模板代码)

感觉用这题来当模板更适合。 题意就是给你邻接矩阵求最小生成树啦。~ prim代码:效率很高。172k...0ms。 #include<stdio.h>#include<algorithm>using namespace std;const int MaxN = 101;const int INF = 0x3f3f3f3f;int g[MaxN][MaxN];int n

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验

uva 1342 欧拉定理(计算几何模板)

题意: 给几个点,把这几个点用直线连起来,求这些直线把平面分成了几个。 解析: 欧拉定理: 顶点数 + 面数 - 边数= 2。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <cmath>#inc

uva 11178 计算集合模板题

题意: 求三角形行三个角三等分点射线交出的内三角形坐标。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <cmath>#include <stack>#include <vector>#include <