OpenCV 直方图比较实战:用 compareHist 与六种度量衡量的图像匹配度
OpenCV 直方图比较实战用 compareHist 与六种度量衡量的图像匹配度【免费下载链接】opencvOpen Source Computer Vision Library项目地址: https://gitcode.com/GitHub_Trending/opencv31/opencv本文以 OpenCV 官方直方图比较教程 histogram_comparison.markdown 为主体讲解如何通过cv::compareHist/cv.compareHist计算两张图片直方图的匹配数值并系统对比 OpenCV 提供的 6 种比较度量Correlation、Chi-Square、Intersection、Bhattacharyya、Alternative Chi-Square、Kullback-Leibler divergence。文中将给出可在当前仓库直接编译运行的 C 与 Python 示例并结合 imgproc 模块的底层实现源码modules/imgproc/src/histogram.cpp说明各度量真实的计算细节与适用前提帮助读者理解直方图距离/相似度在光照变化下的表现并直接用于图像检索、模板匹配、内容相似度排序等实战任务。直方图比较目标与核心思路许多视觉任务例如基于内容的图像检索、视频镜头切换检测、同一场景不同曝光下的配准都需要回答一个问题两张图片有多像直方图比较提供了一条简单而稳健的路线先用颜色直方图把图像的内容压缩为统计分布再计算两个分布之间的距离或相似度。本教程的目标非常聚焦使用 OpenCV 函数cv::compareHist得到一个数值参数表达两张图像直方图互相匹配的程度学会使用不同的度量metric来比较直方图并理解各度量的方向性越大越像还是越小越像。要比较两幅直方图 (H_1) 和 (H_2)首先要选择一个度量 (d(H_1, H_2)) 来表达二者的匹配程度。OpenCV 在compareHist中实现了6 种不同的度量常量定义于 modules/imgproc/include/opencv2/imgproc.hpp 的枚举HistCompMethods见下表编号枚举常量中文名值方向0cv::HISTCMP_CORREL相关性Correlation越大越匹配1cv::HISTCMP_CHISQR卡方Chi-Square越小越匹配2cv::HISTCMP_INTERSECT交集Intersection越大越匹配3cv::HISTCMP_BHATTACHARYYABhattacharyya 距离实为 Hellinger 距离越小越匹配4cv::HISTCMP_CHISQR_ALT备选卡方Alternative Chi-Square越小越匹配5cv::HISTCMP_KL_DIVKL 散度Kullback-Leibler divergence越小越匹配注意从源码注释可知imgproc.hppHISTCMP_BHATTACHARYYA实际上计算的是与 Bhattacharyya 系数相关的Hellinger 距离OpenCV 同时提供别名HISTCMP_HELLINGER HISTCMP_BHATTACHARYYA。HISTCMP_CHISQR_ALT则常被用于纹理比较源码引用自文献 Puzicha1997。六种度量的数学定义以下公式均取自官方头文件注释imgproc.hpp其中 (I) 遍历所有直方图 bin(N) 为 bin 总数。1) 相关性Correlation,HISTCMP_CORREL[ d(H_1,H_2)\frac{\sum_I (H_1(I)-\bar{H_1})(H_2(I)-\bar{H_2})}{\sqrt{\sum_I(H_1(I)-\bar{H_1})^2\sum_I(H_2(I)-\bar{H_2})^2}} ]其中 (\bar{H_k}\frac{1}{N}\sum_J H_k(J))(N) 为直方图 bin 总数。这是把两个直方图当作两个向量计算Pearson 相关系数完美相同的直方图结果为 1完全不相关的为 0。2) 卡方Chi-Square,HISTCMP_CHISQR[ d(H_1,H_2)\sum_I \frac{\left(H_1(I)-H_2(I)\right)^2}{H_1(I)} ]注意该式以 (H_1) 为分母基准不对称取值越小匹配越好完全相同时为 0。3) 交集Intersection,HISTCMP_INTERSECT[ d(H_1,H_2)\sum_I \min\left(H_1(I),H_2(I)\right) ]即逐 bin 取最小值后求和。对归一化直方图完全相同时结果等于 1即归一化后的总面积。4) Bhattacharyya 距离HISTCMP_BHATTACHARYYA[ d(H_1,H_2)\sqrt{1-\frac{1}{\sqrt{\bar{H_1}\bar{H_2}N^2}}\sum_I\sqrt{H_1(I)\cdot H_2(I)}} ]取值范围通常在 ([0,1])0 表示完全匹配越接近 1 越不相似。5) 备选卡方Alternative Chi-Square,HISTCMP_CHISQR_ALT[ d(H_1,H_2)2\cdot\sum_I \frac{\left(H_1(I)-H_2(I)\right)^2}{H_1(I)H_2(I)} ]该公式以 (H_1H_2) 为分母是对称的且对纹理比较更为稳健故单独作为第 4 种度量提供。6) KL 散度Kullback-Leibler divergence,HISTCMP_KL_DIV[ d(H_1,H_2)\sum_I H_1(I)\log\left(\frac{H_1(I)}{H_2(I)}\right) ]KL 散度衡量用 (H_2) 近似 (H_1) 的信息损失同样不对称并且要求两个直方图处处不为 0否则 (\log) 无定义OpenCV 在源码中做了特殊处理下文会展开。演示程序它做什么仓库中的完整示例代码有两份Csamples/cpp/tutorial_code/Histograms_Matching/compareHist_Demo.cppPythonsamples/python/tutorial_code/Histograms_Matching/histogram_comparison/compareHist_Demo.py程序的工作流程如下载入 1 张base 图像基准图和 2 张test 图像待比较图从 base 图像裁剪出下半部分作为第 3 张待比较图像把全部图像从 BGR 转换到 HSV 颜色空间对所有图像计算 H-S色相-饱和度二维直方图并做归一化以便比较将 base 直方图分别与 base 自身、base 下半部分、test1、test2 的直方图做 6 种方法的两两比较打印所有数值匹配结果。设计意图很清晰通过与自身比较得到完美匹配的参考值与自身下半部分比较验证同源高匹配与两张光照条件截然不同的 test 图比较观察失配程度。运行方式C 版三个输入均可省略有内置默认值图片通过 OpenCV 数据搜索机制自动定位# 默认使用内置示例图 ./compareHist_Demo # 或显式指定三张输入图 ./compareHist_Demo path/to/base.jpg path/to/test1.jpg path/to/test2.jpg命令行参数在 compareHist_Demo.cpp 中用CommandLineParser定义input1/input2/input3默认指向Histogram_Comparison_Source_0/1/2.jpg并且第 28 行调用samples::addSamplesDataSearchSubDirectory(doc/tutorials/imgproc/histograms/histogram_comparison/images)注册了教程图片目录。Python 版三张输入图均必填python compareHist_Demo.py \ --input1 doc/tutorials/imgproc/histograms/histogram_comparison/images/Histogram_Comparison_Source_0.jpg \ --input2 doc/tutorials/imgproc/histograms/histogram_comparison/images/Histogram_Comparison_Source_1.jpg \ --input3 doc/tutorials/imgproc/histograms/histogram_comparison/images/Histogram_Comparison_Source_2.jpg输入图像下面是教程自带的 3 张输入图存放于 doc/tutorials/imgproc/histograms/histogram_comparison/images第一张为base基准图后两张为光照差异明显的test图。代码逐步拆解下面结合仓库内 C 与 Python 两份示例源码逐步解释各环节的做法与原因。步骤一载入三张图像C 中使用samples::findFileCommandLineParser读取并检查是否为空CommandLineParser parser( argc, argv, keys ); samples::addSamplesDataSearchSubDirectory( doc/tutorials/imgproc/histograms/histogram_comparison/images ); Mat src_base imread(samples::findFile( parser.getString( input1 ) ) ); Mat src_test1 imread(samples::findFile( parser.getString( input2 ) ) ); Mat src_test2 imread(samples::findFile( parser.getString( input3 ) ) ); if( src_base.empty() || src_test1.empty() || src_test2.empty() ) { cout Could not open or find the images!\n endl; parser.printMessage(); return -1; }对应的 Python 版本用argparse接收--input1/2/3并用cv.imread读入读取失败返回None时直接退出。步骤二转换到 HSV 颜色空间Mat hsv_base, hsv_test1, hsv_test2; cvtColor( src_base, hsv_base, COLOR_BGR2HSV ); cvtColor( src_test1, hsv_test1, COLOR_BGR2HSV ); cvtColor( src_test2, hsv_test2, COLOR_BGR2HSV );hsv_base cv.cvtColor(src_base, cv.COLOR_BGR2HSV) hsv_test1 cv.cvtColor(src_test1, cv.COLOR_BGR2HSV) hsv_test2 cv.cvtColor(src_test2, cv.COLOR_BGR2HSV)为什么要用 HSV因为示例想比较的是色彩分布而非亮度。HSV 把亮度放到 V 通道色相 H 与饱和度 S 相对独立于光照强度这样在光照变化明显本组图片正是如此的场景下H-S 二维直方图能突出颜色内容的相似性这正是本教程刻意选择的实验环境。步骤三生成 base 图的下半部分Mat hsv_half_down hsv_base( Range( hsv_base.rows/2, hsv_base.rows ), Range( 0, hsv_base.cols ) );hsv_half_down hsv_base[hsv_base.shape[0]//2:,:]使用 ROI感兴趣区域从 base 图的纵向中点一直裁到图像底部得到同源但内容不完全相同的第 3 张待比较图用来验证来自同一场景的图像应得到较高的匹配分。步骤四设定直方图参数bins、ranges、channelsint h_bins 50, s_bins 60; int histSize[] { h_bins, s_bins }; // hue varies from 0 to 179, saturation from 0 to 255 float h_ranges[] { 0, 180 }; float s_ranges[] { 0, 256 }; const float* ranges[] { h_ranges, s_ranges }; // Use the 0-th and 1-st channels int channels[] { 0, 1 };h_bins 50 s_bins 60 histSize [h_bins, s_bins] # hue varies from 0 to 179, saturation from 0 to 255 h_ranges [0, 180] s_ranges [0, 256] ranges h_ranges s_ranges # concat lists channels [0, 1]要点通道取 0 号H色相与 1 号S饱和度通道不含 V 通道从而弱化光照影响bin 数H 用 50 个 binS 用 60 个 bin得到一个 50×60 3000 个 bin 的二维直方图。bin 越多分辨率越高、但统计越稀疏、对噪声越敏感50×60 是精度与稳健性的折中取值范围8 位 HSV 中 H 范围是 0–179S 范围 0–255因此ranges采用左闭右开的区间边界[0,180)、[0,256)保证每个像素值都落在有效区间内。这与 imgproc 中calcHist的通用约定一致——各维区间必须覆盖对应通道的实际取值范围。步骤五计算并归一化 H-S 直方图calcHist( hsv_base, 1, channels, Mat(), hist_base, 2, histSize, ranges, true, false ); normalize( hist_base, hist_base, 1, 0, NORM_L1, -1, Mat() ); // 对 hsv_half_down / hsv_test1 / hsv_test2 重复相同操作hist_base cv.calcHist([hsv_base], channels, None, histSize, ranges, accumulateFalse) cv.normalize(hist_base, hist_base, alpha1, beta0, norm_typecv.NORM_L1) # 对 hist_half_down / hist_test1 / hist_test2 重复相同操作关键点在于归一化方式这里用的是NORM_L1L1 范数即令全部 bin 之和等于 1使直方图变为概率分布从而消除图像尺寸差异的影响。对使用 (H_1) 作为分母HISTCMP_CHISQR、涉及 (\sqrt{H_1 H_2})Bhattacharyya以及对数比KL 散度的度量来说把两个直方图都规整到同一尺度是得到有意义比较结果的必要条件。若直方图未经归一化像素总数不同的两张图在几乎所有度量下都会产生偏大的失配分数。步骤六执行 6 种方法的两两比较for( int compare_method 0; compare_method 6; compare_method ) { double base_base compareHist( hist_base, hist_base, compare_method ); double base_half compareHist( hist_base, hist_half_down, compare_method ); double base_test1 compareHist( hist_base, hist_test1, compare_method ); double base_test2 compareHist( hist_base, hist_test2, compare_method ); cout Method compare_method Perfect, Base-Half, Base-Test(1), Base-Test(2) : base_base / base_half / base_test1 / base_test2 endl; }for compare_method in range(6): base_base cv.compareHist(hist_base, hist_base, compare_method) base_half cv.compareHist(hist_base, hist_half_down, compare_method) base_test1 cv.compareHist(hist_base, hist_test1, compare_method) base_test2 cv.compareHist(hist_base, hist_test2, compare_method) print(Method:, compare_method, Perfect, Base-Half, Base-Test(1), Base-Test(2) :,\ base_base, /, base_half, /, base_test1, /, base_test2)compareHist的第三个参数直接用整数编号 0–5 对应前表的 6 种度量HISTCMP_CORREL0HISTCMP_CHISQR1……见 imgproc.hpp因此用for循环即可一次跑完所有度量。C 与 Python 的参数顺序完全一致compareHist(H1, H2, method)。结果解读数值说明了什么官方教程在 OpenCV 4.12.0 下得到的数值如下本仓库提供相同的输入图运行示例可获得同数量级的结果方法Base - BaseBase - HalfBase - Test 1Base - Test 2Correlation1.0000000.8804380.2045700.065752Chi-square0.0000000.328307181.67400080.149400Intersection1.0000000.7500500.3150610.090802Bhattacharyya0.0000000.2378660.6798250.873709Chi-Square alt.0.0000000.3950462.3157203.410240KL divergence0.0000000.3210642.6616009.554120解读要点与文档结论一致方向性要先分清Correlation与Intersection是越大越匹配其余四种Chi-Square、Bhattacharyya、备选卡方、KL 散度是越小越匹配**Base–Base自身比较**在所有 6 种度量下都是理论最优值相关性/交集为 1.0其余为 0.0这是所有系统的标定基准**Base–Half同源下半图**在所有度量中都是第二好相关性 0.88、卡方 0.33、Bhattacharyya 0.24 等说明内容相近的判定成立Base–Test1 / Base–Test2两张测试图与 base 图光照条件差异很大Test1 偏亮偏暖、Test2 严重欠曝因此各度量都给出了明显的失配信号。其中 Correlation 从 1.0 一路掉到 0.066Bhattacharyya 升到 0.87直观展示了直方图比较对光照/曝光变化相当敏感这一特性——这正是选择 HSV 并只取 H-S 通道能部分缓解、却无法完全消除的问题。需要注意不同度量的数值域和量纲完全不同卡方可以到 181相关性只落在 [-1,1]因此在应用中不要跨度量比较绝对值也不要用统一的阈值衡量不同度量。源码级原理compareHist 内部如何计算compareHist的密集计算实现在 modules/imgproc/src/histogram.cpp掌握其细节有助于理解结果与陷阱输入约束与通用流程。函数先断言H1、H2类型相同且深度必须为CV_32F即直方图必须是 32 位浮点calcHist的输出与normalize的输入默认满足此条件再通过NAryMatIterator把可能的密集 Mat 拆成连续平面逐块求和。也就是说如果用户直接传入未归一化的整型直方图或类型不匹配的矩阵compareHist会直接抛异常而不是悄悄给出错误结果。CHISQR 与 CHISQR_ALT 的共用路径与除零保护。两者都计算(h1-h2)^2 / b的累加和区别仅在分母b h1CHISQR还是b h1h2CHISQR_ALT见 histogram.cpp。为防御空 bin 除零源码要求fabs(b) DBL_EPSILON才累加即分母为 0 的 bin 直接跳过。对 CHISQR 而言这等于放弃基准直方图此处为 0的项所以强烈建议先用 NORM_L1 归一化并避免全零直方图。CHISQR_ALT 的最终结果还会整体再乘以 2histogram.cpp与公式中的系数 2 对应。CORREL 的两遍式 Pearson 相关系数。代码用一次循环累计s1Σh1、s2Σh2、s11Σh1²、s12Σh1·h2、s22Σh2²结束时以总 bin 数total H1.total()计算num s12 - s1*s2/totaldenom2 (s11 - s1²/total)(s22 - s2²/total)最终result num / sqrt(denom2)当denom2过小方差趋近 0如两张完全平坦的直方图时返回 1histogram.cpp。这一退化保护在实际数据如纯色图中会触发理解它可避免对异常值的困惑。BHATTACHARYYA 的数值稳定处理。内层循环累加sqrt(h1·h2)与两个直方图的总和s1、s2最后通过1/sqrt(s1·s2)归一化后输出sqrt(max(1 - result·scale, 0))histogram.cpp。外层再套sqrt并夹到非负因此对 NORM_L1 归一化直方图其结果落在 ([0,1])是图像检索中较常用的距离型度量。KL_DIV 的奇点处理。代码对每个 bin若|p| ≤ DBL_EPSILON基准为 0则直接跳过该 bin若|q| ≤ DBL_EPSILON近似目标为 0则把q替换为1e-10再计算p·log(p/q)histogram.cpp。这意味着 KL 散度对目标直方图的稀疏性做了隐式平滑实际结果对目标图包含基准图没有的 bin不会爆炸到无穷但数值仍可能偏大——结果表中的 9.554 即属此类。此外compareHist还提供了接受SparseMat的重载histogram.cpp 起用于稀疏直方图比较并且为了效率在非 CHISQR/CHISQR_ALT/KL_DIV 时会交换两个稀疏直方图以遍历非零元素更少的一方histogram.cpp。普通Mat与稀疏矩阵两个入口共享相同的度量化逻辑业务行为一致。六种度量的选型建议综合公式性质、源码实现与教程实验结果可以给出如下参考属经验性建议非官方结论Correlation0有界[-1,1]、对称、对线性缩放不敏感是最直观的相似度。适合快速排序哪张图更像基准图。Chi-Square1对局部差异更敏感但因不对称以 H1 为分母需要固定基准图在 H1 位置的调用约定。Intersection2物理含义清晰重叠面积对归一化直方图上限为 1计算最简单适合对速度敏感的场景。Bhattacharyya3输出接近概率意义下的分布距离Hellinger 距离有界且在图像检索文献中广受认可适合作为默认首选的距离度量。Chi-Square Alternative4对称形式、分母含两图之和对稀疏直方图更稳健源码注释指出其常用于纹理比较。KL Divergence5来自信息论不对称、无上界因 (\log) 奇点对零 bin 敏感源码虽做了平滑仍可能放大差异更适合严格的意义下用 H2 编码 H1 的代价这类分析型任务。关键调用链与延伸阅读函数声明与度量枚举modules/imgproc/include/opencv2/imgproc.hppcompareHist属于imgproc_hist组密集与稀疏直方图比较实现modules/imgproc/src/histogram.cppC 可运行示例samples/cpp/tutorial_code/Histograms_Matching/compareHist_Demo.cppPython 可运行示例samples/python/tutorial_code/Histograms_Matching/histogram_comparison/compareHist_Demo.py输入图像目录doc/tutorials/imgproc/histograms/histogram_comparison/images直方图比较是直方图主题系列的一环上游使用calcHist构建直方图参见同目录教程 直方图计算 中对应的示例下游则常把比较结果用于**反向投影Back Projection**定位目标区域——即官方文档中的下一节 Back Projection。在实现诸如按颜色分布搜索最相似图像检测内容突变的镜头切换对同一场景不同曝光帧进行内容对齐等系统时compareHist的 6 种度量配合 HSV 直方图与NORM_L1归一化即可构成一套完整、可量化、可复现的相似度判定管线。【免费下载链接】opencvOpen Source Computer Vision Library项目地址: https://gitcode.com/GitHub_Trending/opencv31/opencv创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

最新新闻

日新闻

周新闻

月新闻