【opencv】第9章 直方图与匹配
第9章 直方图与匹配
9.1 图像直方图概述
直方图广泛运用于很多计算机视觉运用当中,通过标记帧与帧之间显著的边 缘和颜色的统计变化,来检测视频中场景的变化。在每个兴趣点设置一个有相近 特征的直方图所构成“标签”,用以确定图像中的兴趣点。边缘、色彩、角度等直 方图构成了可以被传递给目标识别分类器的一个通用特征类型。色彩和边缘的直 方图序列还可以用来识别网络视频是否被复制。如图9. 1所示。

其实,简单点说,直方图就是对数据进行统计的一种方法,并且将统计值组 织到一系列事先定义好的bin 当 中 。 其 中 ,bin 为直方图中经常用到的一个概念, 可翻译为“直条”或"组距",其数值是从数据中计算出的特征统计量,这些数据 可以是诸如梯度、方向、色彩或任何其他特征。且无论如何,直方图获得的是数 据分布的统计图。通常直方图的维数要低于原始数据。总而言之,直方图是计算 机视觉中最经典的工具之一。
在统计学中,直方图(Histogram) 是一种对数据分布情况的图形表示,是一 种二维统计图表,它的两个坐标分别是统计样本和该样本对应的某个属性的度量。
我们在图像变换的那一章中讲过直方图的均衡化,它是通过拉伸像素强度分 布范围来增强图像对比度的一种方法。大家在自己的心目中应该已经对直方图有 一定的理解和认知。下面就来看一看对图像直方图比较书面化的解释。
图像直方图(Image Histogram) 是用以表示数字图像中亮度分布的直方图, 标绘了图像中每个亮度值的像素数。可以借助观察该直方图了解需要如何调整亮 度分布。这种直方图中,横坐标的左侧为纯黑、较暗的区域,而右侧为较亮、纯 白的区域。因此, 一张较暗图片的图像直方图中的数据多集中于左侧和中间部分, 而整体明亮、只有少量阴影的图像则相反。计算机视觉领域常借助图像直方图来 实现图像的二值化。
直方图的意义如下。
- 直方图是图像中像素强度分布的图形表达方式。
- 它统计了每一个强度值所具有的像素个数。
上面已经讲到,直方图是对数据的统计集合,并将统计结果分布于一系列预定义的bins 中。这里的数据不仅仅指的是灰度值,且统计数据可能是任何能有效 描述图像的特征。下面看一个例子,假设有一个矩阵包含一张图像的信息(灰度 值0-255),让我们按照某种方式来统计这些数字。既然已知数字的范围包含256 个值,于是可以将这个范围分割成子区域(也就是上面讲到bins), 如 :
[ 0 , 255 ] = [ 0 , 15 ] ∪ [ 16 , 31 ] ∪ … ∪ [ 240 , 255 ] r a n g e = b i n i ∪ b i n 2 ∪ … ∪ b i n n = 15 [0,255]=[0,15] \cup [16,31] \cup…\cup [240,255] \\ range=bin_i \cup bin_2 \cup…\cup bin_{n=15} [0,255]=[0,15]∪[16,31]∪…∪[240,255]range=bini∪bin2∪…∪binn=15
然后再统计每一个bin; 的像素数目。采用这一方法来统计上面的数字矩阵, 可以得到图9.2(其中x 轴表示bin,y 轴表示各个bin 中的像素个数)。

以上就是一个说明直方图的用途的简单示例。其实,直方图并不局限于统计 颜色灰度,而是可以统计任何图像特征,如梯度、方向等。
让我们具体讲讲直方图的一些术语和细节。
- dims:需要统计的特征的数目。在上例中,dims =1因为我们仅仅统计了 灰度值(灰度图像)。
- bins:每个特征空间子区段的数目,可翻译为“直条”或“组距”。在上例 中 ,bins=16。
- range: 每个特征空间的取值范围。在上例中,range=[0,255]。
9.2 直方图的计算与绘制
直方图的计算在OpenCV 中可以使用calcHist() 函数,而计算完成之后,可以 采用OpenCV 中的绘图函数,如绘制矩形的rectangle() 函数,绘制线段的line() 来 完成。
9.2.1 计 算 直 方 图 :calcHist() 函 数
在OpenCV 中 ,calcHist(函数用于计算一个或者多个阵列的直方图。原型如 下
void calcHist(const Mat *images, int nimages, const int *channels,InputArray mask, OutputArray hist, int dims, const int *histSize, const float **ranges, bool uniform = true, bool accumulate = false)
- 第一个参数,const Mat*类型的images, 输入的数组(或数组集),它们需为相同的深度(CV_8U 或 CV_32F) 和相同的尺寸。
- 第二个参数,int 类 型 的nimages, 输入数组的个数,也就是第一个参数中 存放了多少张“图像”,有几个原数组。
- 第三个参数,const int*类型的channels, 需要统计的通道(dim) 索引。第 一 个 数 组 通 道 从 0 到 images[0].channels)-1, 而 第 二 个 数 组 通 道 从 images[0].channels() 计算到images[0].channels()+images[1].channels()-1。
- 第四个参数,InputArray 类型的mask, 可选的操作掩码。如果此掩码不为 空,那么它必须为8位,并且与images[i] 有同样大小的尺寸。这里的非零 掩码元素用于标记出统计直方图的数组元素数据。
- 第五个参数,OutputArray类 型 的hist,输出的目标直方图, 一个二维数组。
- 第六个参数,int类 型 的dims, 需要计算的直方图的维度,必须是正数,且 不大于CV_MAX_DIMS (在当前版本的OpenCV 中等于32)。
- 第七个参数,const int*类型的histSize,存放每个维度的直方图尺寸的数组。
- 第八个参数,const float**类型的ranges, 表示每一个维度数组(第六个参 数dims) 的每一维的边界阵列,可以理解为每一维数值的取值范围。
- 第九个参数,bool 类 型 的uniform,指示直方图是否均匀的标识符,有默认 值 true。
- 第十个参数,bool类型的accumulate, 累计标识符,有默认值false。若 其 为 true, 直方图在配置阶段不会被清零。此功能主要是允许从多个阵列中 计算单个直方图,或者用于在特定的时间更新直方图。
9.2.2 找寻最值:minMaxLoc() 函数
minMaxLoc(函数的作用是在数组中找到全局最小值和最大值。它有两个版 本的原型,在此介绍常用的那一个版本。
void minMaxLoc(InputArray src, double *minVal, double *maxVal = 0, Point *minLoc = 0, Point *maxLoc = 0, InputArray mask = noArrayO)
- 第 一 个参数,InputArray类 型 的src, 输入的单通道阵列。
- 第二个参数,double* 类型的minVal, 返回最小值的指针。若无须返回,此 值置为NULL。\
- 第三个参数,double*类型的maxVal, 返回的最大值的指针。若无须返回, 此值置为NULL。
- 第四个参数,Point*类型的minLoc, 返回最小位置的指针(二维情况下)。 若无须返回,此值置为NULL。
- 第五个参数,Point*类型的maxLoc, 返回最大位置的指针(二维情况下)。 若无须返回,此值置为NULL。
- 第六个参数,InputArray类型的mask, 用于选择子阵列的可选掩膜。
9.2.3 示例程序:绘制H—S 直方图
下面的示例说明如何计算彩色图像的色调,饱和度二维直方图。
注意:色调(Hue), 饱和度(Saturation)。所 以“H-S 直方图”就是“色调— 饱和度直方图”。
void Test59() {Mat srcImage, hsvImage;srcImage = imread("image.jpg");cvtColor(srcImage, hsvImage, COLOR_BGR2HSV); //转换为HSV图像//将色调量化为30个等级,将饱和度量化为32个等级int hueBinNum = 30, saturationBinNum = 32;int histSize[] = { hueBinNum ,saturationBinNum };//定义色调变化范围为0到179float hueRanges[] = { 0,180 };//定义饱和度的范围为0(黑/白/灰到255(纯光谱颜色))float saturationRange[] = { 0,256 };const float* ranges[] = { hueRanges,saturationRange };MatND dstHist;//calcHist函数将计算第0通道和第1通道的直方图int channels[] = { 0,1 };//调用calcHist,进行直方图计算calcHist(&hsvImage, 1, channels, Mat(), dstHist, 2, histSize, ranges, true, false);//为绘制直方图准备参数double maxValue = 0;minMaxLoc(dstHist, 0, &maxValue, 0, 0);int scale = 10;Mat histImage = Mat::zeros(saturationBinNum * scale, hueBinNum * 10, CV_8UC3);for (int hue = 0; hue < hueBinNum; ++hue) {for (int saturation = 0; saturation < saturationBinNum; ++saturation) {float binValue = dstHist.at<float>(hue, saturation);int intensity = cvRound(binValue * 255 / maxValue);//绘制直方图rectangle(histImage, Point(hue * scale, saturation * scale),Point((hue + 1) * scale - 1, (saturation + 1) * scale - 1),Scalar::all(intensity), FILLED);}}imshow("srcImage", srcImage);imshow("histImage", histImage);waitKey(0);}


9.2.4 示例程序:计算并绘制图像一维直方图
上 文 中 已 经 讲 解 了calcHist ( 函 数 的 用 法 , 并 绘 制 出 了 图 像 的H-S 二 维 直 方 图 。 而本节我们会通过一个示例 , 来学习图像一维直方图的计算和绘制过程 。
**void Test60() {Mat srcImage = imread("image.jpg", 0); //灰度图imshow("srcImage", srcImage);MatND dstHist;int dims = 1;float hranges[] = { 0,255 };const float* ranges[] = { hranges };int size = 256, channels = 0;int scale = 1;//计算图像的直方图calcHist(&srcImage, 1, &channels, Mat(), dstHist, dims, &size, ranges);//获取最大值和最小值double maxValue = 0, minValue = 0;minMaxLoc(dstHist, &minValue, &maxValue, 0, 0);//输出直方图Mat dstImage(size * scale, size, CV_8U, Scalar(0));int hpt = saturate_cast<int>(0.9 * size);for (int i = 0; i < 256; ++i) {float binValue = dstHist.at<float>(i);int realValue = saturate_cast<int>(binValue * hpt / maxValue);rectangle(dstImage, Point(i * scale, size - 1), Point((i + 1) * scale - 1, size - realValue), Scalar(255));}imshow("dstImage", dstImage);waitKey(0);
}


9.2.5 示 例 程 序 : 绘 制RGB 三色直方图
上文我们讲解的是单个分量的一维直方图的绘制,接下来看看如何分别绘制 图像的RGB 三色直方图。详细注释的代码如下。
void Test61() {Mat srcImage = imread("image.jpg");imshow("srcImage", srcImage);int bins = 256;int hist_size[] = { bins };float range[] = { 0, 256 };const float* ranges[] = { range };MatND redHist, greenHist, blueHist;// 红色直方图计算int channels_r[] = { 0 };calcHist(&srcImage, 1, channels_r, Mat(), redHist, 1, hist_size, ranges, true, false);// 绿色直方图计算int channels_g[] = { 1 };calcHist(&srcImage, 1, channels_g, Mat(), greenHist, 1, hist_size, ranges, true, false);// 蓝色直方图计算int channels_b[] = { 2 };calcHist(&srcImage, 1, channels_b, Mat(), blueHist, 1, hist_size, ranges, true, false);// 获取最大值,确保最大值不为零double maxValue_red, maxValue_green, maxValue_blue;minMaxLoc(redHist, 0, &maxValue_red, 0, 0);minMaxLoc(greenHist, 0, &maxValue_green, 0, 0);minMaxLoc(blueHist, 0, &maxValue_blue, 0, 0);// 确保最大值至少为1,以避免除以零的错误maxValue_red = max(maxValue_red, 1.0);maxValue_green = max(maxValue_green, 1.0);maxValue_blue = max(maxValue_blue, 1.0);int scale = 2; int histHeight = 256; // 创建空白图像用于绘制直方图Mat histImage = Mat::zeros(histHeight, bins * 3 * scale, CV_8UC3);for (int i = 0; i < bins; ++i) {// 获取每个通道的直方图数据float binValue_red = redHist.at<float>(i);float binValue_green = greenHist.at<float>(i);float binValue_blue = blueHist.at<float>(i);// 计算对应的高度int intensity_red = cvRound(binValue_red * histHeight / maxValue_red);int intensity_green = cvRound(binValue_green * histHeight / maxValue_green);int intensity_blue = cvRound(binValue_blue * histHeight / maxValue_blue);// 绘制红色分量的直方图rectangle(histImage, Point(i * scale, histHeight - 1),Point((i + 1) * scale - 1, histHeight - intensity_red), Scalar(0, 0, 255));// 绘制绿色分量的直方图rectangle(histImage, Point(i * scale, histHeight - 1),Point((i + 1) * scale - 1, histHeight - intensity_green), Scalar(0, 255, 0));// 绘制蓝色分量的直方图rectangle(histImage, Point(i * scale, histHeight - 1),Point((i + 1) * scale - 1, histHeight - intensity_blue), Scalar(255, 0, 0));}imshow("histImage", histImage);waitKey(0);}


9.3 直方图对比
对于直方图来说, 一个不可或缺的工具便是用某些具体的标准来比较两个 直方图的相似度。要对两个直方图(比如说H₁ 和 H₂) 进行比较,首先必须选 择一个衡量直方图相似度的对比标准(d(H₁,H₂))。 在OpenCV 2.X中,我们用 compareHist()函数来对比两个直方图的相似度,而此函数的返回值就是
d(H,H₂)。
9.3.1 对 比 直 方 图 :compareHist ( 函 数
compareHist(函数用于对两幅直方图进行比较。有两个版本的C++ 原型,如 下。
double compareHist(InputArray H1, InputArray H2, int method) C++ : double compareHist(const SparseMat &H1, const SparseMat &H2, int method)
它们的前两个参数是要比较的大小相同的直方图,第三个变量是所选择的距离 标准。可采用如下4种方法,比较两个直方图 (H1 表示第一个,H2 表示第二个):
-
相 关 ,Correlation(method=CV_COMP_CORREL)

-
卡 方 ,Chi-Square (method=CV_COMP_CHISQR)

-
直方图相交,Intersection(method=CV_COMP_INTERSECT)

-
Bhattacharyya 距 离(method=CV_COMP_BHATTACHARYYA)
这 里 的 Bhattacharyya 距 离 和 Hellinger 距 离 相 关 , 也 可 以 写 作 method=CV_COMP_HELLINGER

此处的宏定义在当前版本的OpenCV3 中 依 然 沿 用“CV_” 前缀,在未 来版本中应该会有更改。如需使用,可以分别用int 类型的1、2、3、4替 代 CV_COMP_CORREL 、CV_COMP_CHISQR、CV_COMP_INTERSECT、 CV_COMP_BHATTACHARYYA 这四个宏。或者“#include<cv.h>” 加 入cv.h 头文件。
9.3.2 示 例 程 序 : 直 方 图 对 比
此次的示例程序为大家演示了如何用compareHist() 函数进行直方图对比。代 码中的MatND 类是用于存储直方图的一种数据结构,用法简单,在这里就不多做 讲解,大家看到详细注释的示例程序就会明白。
void Test62() {Mat srcImage_base, hsvImage_base;Mat srcImage_test1, hsvImage_test1;Mat srcImage_test2, hsvImage_test2;Mat hsvImage_halfDown;srcImage_base = imread("image.jpg");srcImage_test1 = imread("image2.jpg");srcImage_test2 = imread("image3.jpg");imshow("image1", srcImage_base);imshow("image2", srcImage_test1);imshow("image3", srcImage_test2);//转换到HSV色彩空间cvtColor(srcImage_base, hsvImage_base, COLOR_BGR2HSV);cvtColor(srcImage_test1, hsvImage_test1, COLOR_BGR2HSV);cvtColor(srcImage_test2, hsvImage_test2, COLOR_BGR2HSV);//创建包含基准图像下半部的半身图像hsvImage_halfDown = hsvImage_base(Range(hsvImage_base.rows / 2, hsvImage_base.rows - 1),Range(0, hsvImage_base.cols - 1));//初始化计算直方图需要的实参int h_bins = 50, s_bins = 60;int histSize[] = { h_bins,s_bins };float h_ranges[] = { 0,256 };float s_ranges[] = { 0,180 };const float* ranges[] = { h_ranges ,s_ranges };int channels [] = {0,1};MatND baseHist, halfDownHist, testHist1, testHist2;//计算基准图像,两张测试图像,半身基准图像的HSV直方图calcHist(&hsvImage_base, 1, channels, Mat(), baseHist, 2, histSize, ranges, true, false);normalize(baseHist, baseHist, 0, 1, NORM_MINMAX, -1, Mat());calcHist(&hsvImage_halfDown, 1, channels, Mat(), halfDownHist, 2, histSize, ranges, true, false);normalize(halfDownHist, halfDownHist, 0, 1, NORM_MINMAX, -1, Mat());calcHist(&hsvImage_test1, 1, channels, Mat(), testHist1, 2, histSize, ranges, true, false);normalize(testHist1, testHist1, 0, 1, NORM_MINMAX, -1, Mat());calcHist(&hsvImage_test2, 1, channels, Mat(), testHist2, 2, histSize, ranges, true, false);normalize(testHist2, testHist2, 0, 1, NORM_MINMAX, -1, Mat());//进行直方图对比for (int i = 0; i < 4; ++i) {int compare_method = i;double base_base = compareHist(baseHist, baseHist, compare_method);double base_half = compareHist(baseHist, halfDownHist, compare_method);double base_test1 = compareHist(baseHist, testHist1, compare_method);double base_test2 = compareHist(baseHist, testHist2, compare_method);printf("----------------------\n");printf("methon [%d] result \n\n base-base:%f,base-halfbase:%f,base-test1:%f,base-test2:%f\n", i, base_base, base_test1, base_test2);}printf("finished\n");waitKey(0);}




需要注意的是,在上述代码中还会将基准图像与它自身及其半身图像进行对 比。而我们知道,当将基准图像直方图及其自身进行对比时,会产生完美的匹配; 当与来源于同一样的背景环境的半身图对比时,应该会有比较高的相似度;当与 来自不同亮度光照条件的其余两张测试图像对比时,匹配度应该不是很好。输出 的匹配结果如图9.12所示。
其中的方法0至3,分别表示之前讲过的Correlation 、Chi-square 、Intersection 、 Bhattacharyya 对比标准。其中,对于Correlation ( 方 法 0 ) 和Intersection ( 方 法 2 ) 标准,值越大表示相似度越高。可以发现,【基准图一基准图】的匹配数值结果相 对于其他几种匹配方式是最大的,符合实际情况。【基准图—半身图】的匹配结果 次大,正如我们预料。而【基准—测试图1】和【基准图—测试图2】的匹配结果却不尽人意,同样和之前的预料吻合。
9.4 反 向 投 影
9.4.1 引 言
如果一幅图像的区域中显示的是一种结构纹理或者一个独特的物体,那么这 个区域的直方图可以看作一个概率函数,其表现形式是某个像素属于该纹理或物 体的概率。
而反向投影(back projection) 就是一种记录给定图像中的像素点如何适应直 方图模型像素分布方式的一种方法。
简单的讲,所谓反向投影就是首先计算某一特征的直方图模型,然后使用模 型去寻找图像中存在的该特征的方法。
9.4.2 反向投影的工作原理
下面,我们将使用H-S 肤色直方图为例来解释反向投影的工作原理。
首先通过之前讲过的求H-S 直方图的示例程序,得到如图9.13~9.16所示的 H-S 肤色直方图。




而我们要做的,就是使用模型直方图(代表手掌的皮肤色调)来检测测试图 像中的皮肤区域。以下是检测步骤。
(1)对测试图像中的每个像素(p(ij)), 获取色调数据并找到该色调(hi,sj) 在直方图中的bin 的位置。
(2)查询模型直方图中对应bin 的数值。
(3)将此数值储存在新的反射投影图像中。也可以先归一化直方图数值到 0-255范围,这样可以直接显示反射投影图像(单通道图像)。
(4)通过对测试图像中的每个像素采用以上步骤,可以得到最终的反射投影 图像。如图9.17所示。

(5)使用统计学的语言进行分析。反向投影中储存的数值代表了测试图像中 该像素属于皮肤区域的概率。比如以图9.17为例,亮起的区域是皮肤区域的概 率更大,而更暗的区域则表示是皮肤的概率更低。另外,可以注意到,手掌内部 和边缘的阴影影响了检测的精度。
9.4.3 反向投影的作用
反向投影用于在输入图像(通常较大)中查找与特定图像(通常较小或者仅 1个像素,以下将其称为模板图像)最匹配的点或者区域,也就是定位模板图像 出现在输入图像的位置。
9.4.4 反向投影的结果
反向投影的结果包含了以每个输入图像像素点为起点的直方图对比结果。可 以把它看成是一个二维的浮点型数组、二维矩阵,或者单通道的浮点型图像。
9.4.5 计算反向投影:calcBackProject() 函 数
calcBackProject()函数用于计算直方图的反向投影。
void calcBackProject(const Mat *images,int nimages,const int *channels,InputArray hist,OutputArray backProject, const float **ranges,double scale = 1,bool uniform = true)
- 第一个参数,const Mat*类型的images,输入的数组(或数组集),它们须为 相同的深度(CV_8U 或 CV_32F) 和相同的尺寸,而通道数则可以任意。
- 第二个参数,int 类型的nimages,输入数组的个数,也就是第一个参数中 存放了多少张“图像”,有几个原数组。
- 第三个参数,const int*类型的channels,需要统计的通道(dim)索引。第一 个 数 组 通 道 从 0 到 images[0].channels()-1, 而 第 二 个 数 组 通 道 从 images[0].channelsO计算到images[0].channels(+images[1].channels()-1。
- 第 四 个 参 数 ,InputArray 类 型 的hist, 输入的直方图。
- 第五个参数,OutputArray类型的backProject,目标反向投影阵列,其须为 单通道,并且和image[0] 有相同的大小和深度。
- 第六个参数,const float**类型的ranges,表示每一个维度数组(第六个参 数dims) 的每一维的边界阵列,可以理解为每一维数值的取值范围。
- 第七个参数,double scale,有默认值1,输出的方向投影可选的缩放因子, 默 认 值 为 1 。
- 第八个参数,bool类型的uniform,指示直方图是否均匀的标识符,有默认 值true。
9.4.6 通道复制: mixChannelsO 函 数
此函数由输入参数复制某通道到输出参数特定的通道中。有两个版本的C++ 原型,采用函数注释方式分别介绍如下。
void mixChannels(const Mat *src, // 输入的数组,所有的矩阵必须有相同的尺寸和深度size_t nsrcs, // 第一个参数src 输入的矩阵数Mat *dst, // 输出的数组,所有矩阵必须被初始化,且大小和深度必须与src[0] 相同size_t ndsts, // 第三个参数dst 输入的矩阵数const int *fromTo, // 对指定的通道进行复制的数组索引size_t npairs) // 第五个参数fromTo 的索引数
void mixChannels(const vector<Mat> &src, // 输入的矩阵向量,所有的矩阵必须有相同的尺寸和深度 vector<Mat>&dst,// 输出的矩阵向量,所有矩阵须被初始化,且大小和深度须与src[0] 相同const int *fromTo, // 对指定的通道进行复制的数组索引size_t npairs) // 第三个参数fromTo 的索引数
此函数为重排图像通道提供了比较先进的机制。其实,之前我们接触到的 split( 和merge), 以 及cvtColor(的某些形式,都只是mixChannels(的一部分。
下面给出一个示例,将一个4通道的RGBA 图像转化为3通道BGR(R 通道和B 通道交换)和一个单独的Alpha 通道的图像。
Mat rgba(100, 100, CV_8UC4, Scalar(1, 2, 3, 4));
Mat bgr(rgba.rows, rgba.cols, CV_8UC3);
Mat alpha(rgba.rows, rgba.cols, CV_8UC1);
// 组成矩阵数组来进行操作
Mat out[] = {bgr, alpha};
// 说明:将rgba[0]->bgr[2],rgba[1]->bgr[1],
// 说明:将rgba[2]->bgr[0],rgba[3]->alpha[0]
int from_to[] = {0, 2, 1, 1, 2, 0, 3, 3};
mixChannels(&rgba, 1, out, 2, from_to, 4);
9.4.7 综合程序:反向投影
下面将给大家展示一个浓缩了本节内容的讲解内容经过详细注释的反向投影 示例程序 。
namespace test63 {Mat g_srcImage, g_hsvImage, g_hueImage;int g_bins = 30;void on_BinChange(int, void*) {MatND hist;int histSize = MAX(g_bins, 2);float hue_range[] = { 0,180 };const float* ranges = { hue_range };//计算直方图并归一化calcHist(&g_hueImage, 1, 0, Mat(), hist, 1, &histSize, &ranges, true,false);normalize(hist, hist, 0, 255, NORM_MINMAX, -1, Mat());MatND backproj;calcBackProject(&g_hueImage, 1, 0, hist, backproj, &ranges, 1, true);//显示反向投影imshow("backproj", backproj);//绘制直方图int w = 400, h = 400;int bin_w = cvRound(w * 1.0 / histSize);Mat histImage = Mat::zeros(w, h, CV_8UC3);//绘制直方图for (int i = 0; i < g_bins; ++i) {rectangle(histImage, Point(i * bin_w, h), Point((i + 1) * bin_w, h - cvRound(hist.at<float>(i) * h / 255.0)), Scalar(100, 123, 255), -1);}imshow("histImage2", histImage);}void Test() {g_srcImage = imread("hand.jpg");cvtColor(g_srcImage, g_hsvImage, COLOR_BGR2HSV);if (g_srcImage.empty()) {return;}//分离Hue色调通道g_hueImage.create(g_hsvImage.size(), g_hsvImage.depth());int ch[] = { 0,0 };mixChannels(&g_hsvImage,1, &g_hueImage,1,ch,1);//创建滑动条namedWindow("srcImage");createTrackbar("value", "srcImage", &g_bins, 180, on_BinChange);imshow("srcImage", g_srcImage);waitKey(0);}}void Test63() {test63::Test();
}



9.5 模 板 匹 配
9.5.1 模板匹配的概念与原理
模板匹配是一项在一幅图像中寻找与另一幅模板图像最匹配(相似)部分的 技术。在OpenCV2 和 OpenCV3 中,模板匹配由MatchTemplate() 函数完成。需要 注意,模板匹配不是基于直方图的,而是通过在输入图像上滑动图像块,对实际 的图像块和输入图像进行匹配的一种匹配方法。
如图9.23所示,通过一个人脸“图像模板”,在整幅输入图像上移动这张“脸”, 来寻找和这张脸相似的最优匹配。

9.5.2 实现模板匹配:matchTemplate() 函数
matchTemplate() 用于匹配出和模板重叠的图像区域。
void matchTemplate(InputArray image, InputArray templ, OutputArray result, int method)
- 第一个参数,InputArray 类 型 的image, 待搜索的图像,且需为8位或32 位浮点型图像。
- 第二个参数,InputArray类型的 templ, 搜索模板,需和源图片有一样的数 据类型,且尺寸不能大于源图像。
- 第三个参数,OutputArray 类型的 result,比较结果的映射图像。其必须为 单通道、32位浮点型图像.如果图像尺寸是W×H 而 templ 尺 寸 是w×h,则 此 参 数result 一 定是(W-w+1)×(H-h+1).
- 第四个参数,int 类 型 的method, 指定的匹配方法,OpenCV 为我们提供了 如下6种图像匹配方法可供使用。
-
平方差匹配法 method=TM_SQDIFF
这类方法利用平方差来进行匹配,最好匹配为0。而若匹配越差,匹配值则
越大。

-
归一化平方差匹配法 method=TM_SQDIFF_NORMED

-
相关匹配法 method=TM_CCORR
这类方法采用模板和图像间的乘法操作,所以较大的数表示匹配程度较高,0标识最坏的匹配效果。

-
归一化相关匹配法 method=TM_CCORR_NORMED

-
系数匹配法method=TM_CCOEFF
这类方法将模版对其均值的相对值与图像对其均值的相关值进行匹配,1表 示完美匹配,-1表示糟糕的匹配,而0表示没有任何相关性(随机序列)。

其中:
T ′ ( x ′ , y ′ ) = T ( x ′ , y ′ ) − 1 / ( w . h ) ⋅ ∑ x ′ ′ , y ′ ′ T ( x ′ ′ , y ′ ′ ) I ′ ( x + x ′ , y + y ′ ) = I ( x + x ′ , y + y ′ ) − 1 / ( w . h ) ⋅ ∑ x ′ ′ , y ′ ′ I ( x + x ′ ′ , y + y ′ ′ ) T'(x',y')=T(x',y')-1/(w.h)·\sum_{x'',y''} T(x'',y'') \\ I'(x+x',y+y')=I(x+x',y+y')-1/(w.h)·\sum_{x'',y''}I(x+x'',y+y'') T′(x′,y′)=T(x′,y′)−1/(w.h)⋅x′′,y′′∑T(x′′,y′′)I′(x+x′,y+y′)=I(x+x′,y+y′)−1/(w.h)⋅x′′,y′′∑I(x+x′′,y+y′′)
- 化相关系数匹配法 method=TM_CCOEFF_NORMED

上述的6个宏,在OpenCV2 依然是可以加上“CV_” 前缀,分别写作: CV_TM_SQDIFF 、CV_TM_SQDIFF_NORMED 、CV_TM_CCORR、CV_ TM_CCORR_NORMED 、CV_TM_CCOEFF 、CV_TM_CCOEFF_
NORMED。
通常,随着从简单的测量(平方差)到更复杂的测量(相关系数),我们可获得越来越准确的匹配。然而,这同时也会以越来越大的计算量为代价。比较科学 的办法是对所有这些方法多次测试实验,以便为自己的应用选择同时兼顾速度和 精度的最佳方案 。
9.5.3 综合示例:模板匹配
讲解完成基本概念和函数用法,下面依然是放出一个经过详细注释的示例程 序源代码,演示了如何种不同的模板匹配方法对人脸进行检测。
namespace test64 {Mat g_srcImage, g_templateImage, g_resultImage;int g_nMatchMethod;int g_nMaxTrackbarNum = 5;void on_Matching(int, void*) {Mat srcImage;g_srcImage.copyTo(srcImage);int resultImage_cols = g_srcImage.cols - g_templateImage.cols + 1;int resultImage_rows = g_srcImage.rows - g_templateImage.rows + 1;g_resultImage.create(resultImage_cols, resultImage_rows, CV_32FC1);//进行匹配和标准化matchTemplate(g_srcImage, g_templateImage, g_resultImage, g_nMatchMethod);normalize(g_resultImage, g_resultImage, 0, 1, NORM_MINMAX, -1, Mat());//通过MinMaxLoc单位最匹配的位置double minValue, maxValue;Point minLocation, maxLocation, matchLocation;minMaxLoc(g_resultImage, &minValue, &maxValue, &minLocation, &maxLocation, Mat());if (g_nMatchMethod == TM_SQDIFF || g_nMatchMethod == TM_SQDIFF_NORMED) {matchLocation = minLocation;}else {matchLocation = maxLocation;}//绘制出矩形显示最后的结果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("srcImage1", srcImage);imshow("srcImage2", g_resultImage);}void Test() {g_srcImage = imread("image.jpg");g_templateImage = imread("template.jpg");namedWindow("srcImage1");namedWindow("srcImage2");createTrackbar("method:", "srcImage1", &g_nMatchMethod, g_nMaxTrackbarNum, on_Matching);on_Matching(0, nullptr);waitKey(0);}}void Test64() {test64::Test();
}










可以发现,除了相关匹配法 (TM_CCORR) 得到了错误的匹配结果之外,其 他的5种匹配方式结果都匹配较为准确。
9.6 本章小结
本章我们学习了广泛运用于很多计算机视觉运用当中的直方图,而简单点说, 直方图就是对数据进行统计的一种方法。然后还讲到了反向投影和模板匹配。所 谓反向投影就是首先计算某一特征的直方图模型,最后使用模型去寻找图像中存 在的该特征的方法。而模板匹配是一项在一幅图像中寻找与另一幅模板图像最匹 配(相似)部分的技术。
本章核心函数清单
| 函数名称 | 说明 | 对应讲解章节 |
|---|---|---|
| calcHist | 计算一个或者多个阵列的直方图 | 9.2.1 |
| minMaxLoc | 在数组中找到全局最小值和最大值 | 9.2.2 |
| compareHist | 对两幅直方图进行比较 | 9.3.1 |
| calcBackProject | 计算直方图的反向投影 | 9.4.5 |
| mixChannels | 由输入参数复制某通道到输出参数特定的通道中 | 9.4.6 |
| matchTemplate | 匹配出和模板重叠的图像区域 | 9.5.2 |
本章示例程序清单
| 示例程序序号 | 程序说明 | 对应章节 |
|---|---|---|
| 79 | H-S二维直方图的绘制 | 9.2.3 |
| 80 | 一维直方图的绘制 | 9.2.4 |
| 81 | RGB三色直方图的绘制 | 9.2.5 |
| 82 | 直方图对比 | 9.3.2 |
| 83 | 反向投影 | 9.4.7 |
| 84 | 模板匹配 | 9.5.3 |
相关文章:
【opencv】第9章 直方图与匹配
第9章 直方图与匹配 9.1 图像直方图概述 直方图广泛运用于很多计算机视觉运用当中,通过标记帧与帧之间显著的边 缘和颜色的统计变化,来检测视频中场景的变化。在每个兴趣点设置一个有相近 特征的直方图所构成“标签”,用以确定图像中的兴趣点。边缘、色…...
HTML5 Web Worker 的使用与实践
引言 在现代 Web 开发中,用户体验是至关重要的。如果页面在执行复杂计算或处理大量数据时变得卡顿或无响应,用户很可能会流失。HTML5 引入了 Web Worker,它允许我们在后台运行 JavaScript 代码,从而避免阻塞主线程,保…...
MVCC底层原理实现
MVCC的实现原理 了解实现原理之前,先理解下面几个组件的内容 1、 当前读和快照读 先普及一下什么是当前读和快照读。 当前读:读取数据的最新版本,并对数据进行加锁。 例如:insert、update、delete、select for update、 sele…...
基于ESP32-IDF驱动GPIO输出控制LED
基于ESP32-IDF驱动GPIO输出控制LED 文章目录 基于ESP32-IDF驱动GPIO输出控制LED一、点亮LED3.1 LED电路3.2 配置GPIO函数gpio_config()原型和头文件3.3 设置GPIO引脚电平状态函数gpio_set_level()原型和头文件3.4 代码实现并编译烧录 一、点亮LED 3.1 LED电路 可以看到&#x…...
【优选算法】9----长度最小的子数组
----------------------------------------begin-------------------------------------- 铁子们,前面的双指针算法篇就算告一段落啦~ 接下来是我们的滑动窗口篇,不过有一说一,算法题就跟数学题一样,只要掌握方法,多做…...
LabVIEW太阳能照明监控系统
在公共照明领域,传统的电力照明系统存在高能耗和维护不便等问题。利用LabVIEW开发太阳能照明监控系统,通过智能控制和实时监测,提高能源利用效率,降低维护成本,实现照明系统的可持续发展。 项目背景 随着能源危机…...
MongoDB中单对象大小超16M的存储方案
在 MongoDB 中,单个文档的大小限制为 16MB。如果某个对象(文档)的大小超过 16MB,可以通过以下几种方案解决: 1. 使用 GridFS 适用场景:需要存储大文件(如图像、视频、文档等)。 原…...
三维激光扫描-用智能检测系统提升效率
当下,企业对生产效率和质量控制的要求越来越高。传统的检测方法往往难以满足高精度、快速响应的需求。三维激光扫描技术结合智能检测系统,为工业检测带来了革命性的变革。 传统检测方法的局限性 传统检测方法主要依赖于人工测量和机械检测工具…...
css遇到的一些问题
1.vw单位,在PC端vw单位是包含右侧滚轮的宽度,而在移动端不会包含滚轮的长度,在PC端运用vw单位进行居中对齐,会比实际偏左盒子偏右一点,因为内容区域并不包含滚轮。 2.运用媒体查询进行响应式布局式,媒体查询…...
【langgraph】ubuntu安装:langgraph:未找到命令
langgraph 在ubuntu24.04 参考:langgraph运行:报错: (05_ep_dev) root@k8s-master-pfsrv:/home/zhangbin/perfwork/01_ai/05_ep_dev/expert# langgraph dev langgraph:未找到命令查看langraph的安装情况 pip show langgraph...
mysql 学习2 MYSQL数据模型,mysql内部可以创建多个数据库,一个数据库中有多个表;表是真正放数据的地方,关系型数据库 。
在第一章中安装 ,启动mysql80 服务后,连接上了mysql,那么就要 使用 SQL语句来 操作mysql数据库了。那么在学习 SQL语言操作 mysql 数据库 之前,要对于 mysql数据模型有一个了解。 MYSQL数据模型 在下图中 客户端 将 SQL语言&…...
小识JVM堆内存管理的优化机制TLAB
JVM(Java虚拟机)在堆内存分配空间时,TLAB(Thread Local Allocation Buffer,线程本地分配缓存区)是一种重要的内存管理优化技术。以下是对TLAB的详细解释: 一、TLAB的定义 TLAB是JVM堆内存管理…...
ToDesk云电脑、顺网云、网易云、易腾云、极云普惠云横测对比:探寻电竞最佳拍档
一、云电脑:电竞新宠崛起 在电竞游戏不断发展的今天,硬件性能成为了决定游戏体验的关键因素。为了追求极致的游戏画面与流畅度,玩家们往往需要投入大量资金购置高性能电脑。然而,云电脑技术的出现,为玩家们提供了一种…...
学习ASP.NET Core的身份认证(基于JwtBearer的身份认证10)
基于Cookie传递token的主要思路是通过用户身份验证后,将生成的token保存到Response.Cookies返回客户端,后续客户端访问服务接口时会自动携带Cookie到服务端以便验证身份。之前一直搞不清楚的是服务端程序如何从Cookie读取token进行认证(一般都…...
vscode环境中用仓颉语言开发时调出覆盖率的方法
在vscode中仓颉语言想得到在idea中利用junit和jacoco的覆盖率,需要如下几个步骤: 1.在vscode中搭建仓颉语言开发环境; 2.在源代码中右键运行[cangjie]coverage. 思路1:编写了测试代码的情况(包管理工具) …...
OLED--软件I2C驱动__标准库和HAL库
一、标准库---版本一 OLED.c--标准库 #include "stm32f10x.h" #include "OLED_Font.h"/*引脚配置*/ #define OLED_W_SCL(x) GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x)) #define OLED_W_SDA(x) GPIO_WriteBit(GPIOB, GPIO_Pin_9, (BitAction)(x…...
【设计模式-行为型】观察者模式
一、什么是观察者模式 说起观察者模式,不得不说一位观察者模式的高级应用者,朱元璋。不知道大家有没有看过胡军演的电视剧《朱元璋》。这部剧背景是元朝末年,天下大乱,朱元璋自幼父母双亡,沦为乞丐,后遁入空…...
从理论到实践:Django 业务日志配置与优化指南
在现代 Web 开发中,日志记录是确保系统可维护性和可观测性的重要手段。通过合理的日志配置,我们可以快速定位问题、分析系统性能,并进行安全审计。本文将围绕 Django 框架,详细介绍如何配置和优化业务日志,确保开发环境和生产环境都能高效地记录和管理日志。 © ivwdc…...
Linux下php8安装phpredis扩展的方法
Linux下php8安装phpredis扩展的方法 下载redis扩展执行安装编辑php.ini文件重启php-fpmphpinfo 查看 下载redis扩展 前提是已经安装好redis服务了 php-redis下载地址 https://github.com/phpredis/phpredis 执行命令 git clone https://github.com/phpredis/phpredis.git执行…...
Flink运行时架构
一、系统架构 1)作业管理器(JobManager) JobManager是一个Flink集群中任务管理和调度的核心,是控制应用执行的主进程。也就是说,每个应用都应该被唯一的JobManager所控制执行。 JobManger又包含3个不同的组件。 &am…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...
【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...
CentOS下的分布式内存计算Spark环境部署
一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架,相比 MapReduce 具有以下核心优势: 内存计算:数据可常驻内存,迭代计算性能提升 10-100 倍(文档段落:3-79…...
基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...
EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...
让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
算法:模拟
1.替换所有的问号 1576. 替换所有的问号 - 力扣(LeetCode) 遍历字符串:通过外层循环逐一检查每个字符。遇到 ? 时处理: 内层循环遍历小写字母(a 到 z)。对每个字母检查是否满足: 与…...
