news 2026/9/7 5:48:56

OpenCV 二维直方图实战:用 cv.calcHist 与 np.histogram2d 计算并可视化 HSV 色彩直方图

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
OpenCV 二维直方图实战:用 cv.calcHist 与 np.histogram2d 计算并可视化 HSV 色彩直方图

OpenCV 二维直方图实战:用 cv.calcHist 与 np.histogram2d 计算并可视化 HSV 色彩直方图

【免费下载链接】opencvOpen Source Computer Vision Library项目地址: https://gitcode.com/GitHub_Trending/opencv31/opencv

本文围绕 OpenCV 教程《Histograms - 3 : 2D Histograms》展开,系统讲解如何计算和绘制二维直方图(2D Histogram)。读完本篇,你将掌握三个核心技能:用cv.calcHist()对 HSV 图像同时统计 Hue(色相)与 Saturation(饱和度)两个特征;用np.histogram2d()在纯 NumPy 环境中完成同样的统计;以及三种不同的二维直方图可视化方案——cv.imshow()、Matplotlib 的imshow()和带真实色彩标注的"OpenCV 示例风格"彩色直方图。这些能力是理解后续直方图反向投影(Histogram Back-Projection)等主题的基础。

为什么需要二维直方图

在前一篇教程(一维直方图)中,我们只考虑单一特征——像素的灰度强度值,因此得到一维直方图。而二维直方图同时考虑两个特征,最典型的应用是颜色直方图(color histogram):每个像素有两个特征,即 HSV 颜色空间中的 Hue(色相)与 Saturation(饱和度)值。

一维灰度直方图只能回答"图像中什么灰度等级的像素多",无法回答"图像中是什么颜色占主导"。而色彩信息天然分布在 H 与 S 两个维度上,必须用二维统计才能完整表达。例如在教程使用的示例图像home.jpg中,可以看到 H ≈ 100、S ≈ 200 附近有明显高值峰,对应天空的蓝色;另一处 H ≈ 25、S ≈ 100 附近的峰对应宫殿的黄色。这种"坐标位置 → 实际颜色"的对应关系,正是二维直方图的核心价值,也可以用 GIMP 等图像编辑工具对原图取色加以验证。

用 cv.calcHist 计算二维直方图

OpenCV 中计算二维直方图与一维直方图使用同一个函数cv.calcHist(),区别仅在于参数。对于颜色直方图,需要先把图像从 BGR 转换到 HSV(一维直方图时是从 BGR 转到 Grayscale)。参数修改方式如下:

  • channels = [0, 1]—— 因为需要同时处理 H 平面(HSV 的第 0 通道)和 S 平面(第 1 通道);
  • bins = [180, 256]—— H 平面取 180 个 bin,S 平面取 256 个 bin;
  • range = [0, 180, 0, 256]—— OpenCV 中 8 位图像 HSV 转换后 Hue 的取值范围是 0–180,Saturation 的取值范围是 0–256。

完整代码:

import numpy as np import cv2 as cv img = cv.imread('home.jpg') assert img is not None, "file could not be read, check with os.path.exists()" hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV) hist = cv.calcHist([hsv], [0, 1], None, [180, 256], [0, 180, 0, 256])

几点说明:

  • 第 1 个参数[hsv]是图像列表(列表形式是为了兼容多图像统计);
  • 第 2 个参数[0, 1]指定参与统计的通道索引,0是 H、1是 S;
  • 第 3 个参数None表示不使用掩码,统计全图。传入掩码时只统计掩码非零区域的像素;
  • 第 4 个参数[180, 256]是各维度 bin 数,第 5 个参数[0, 180, 0, 256]是对应各维度的取值范围,按维度顺序依次拼接。

返回结果hist是一个180 × 256 的二维数组hist[h, s]表示色相落在第 h 个 bin、饱和度落在第 s 个 bin 的像素个数。

这一参数模式在仓库官方教程代码中有真实印证。官方反向投影示例 calcBackProject_Demo2.py 中,正是用同样的"双通道 + 双维度 bins + 拼接 range"写法从 HSV 图像提取颜色直方图(该示例使用更细粒度的h_bins=30s_bins=32以减少计算量):

# samples/python/tutorial_code/Histograms_Matching/back_projection/calcBackProject_Demo2.py h_bins = 30 s_bins = 32 histSize = [h_bins, s_bins] h_range = [0, 180] s_range = [0, 256] ranges = h_range + s_range # Concat list channels = [0, 1] # Get the Histogram and normalize it hist = cv.calcHist([hsv], channels, mask, histSize, ranges, accumulate=False) cv.normalize(hist, hist, alpha=0, beta=255, norm_type=cv.NORM_MINMAX) # Get Backprojection backproj = cv.calcBackProject([hsv], channels, hist, ranges, scale=1)

从这段源码可以确认:二维 HSV 直方图是反向投影(cv.calcBackProject)的直接输入——先统计出目标区域的直方图,再用它生成反向投影图做相似区域检索。这正是本文档末尾提到的"对后续主题有用"的具体落点。

用 np.histogram2d 在 NumPy 中计算

NumPy 提供了专用函数np.histogram2d()(对应一维时使用的np.histogram()),可以在不依赖 OpenCV 直方图接口的前提下完成同样的统计:

import numpy as np import cv2 as cv from matplotlib import pyplot as plt img = cv.imread('home.jpg') assert img is not None, "file could not be read, check with os.path.exists()" hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV) hist, xbins, ybins = np.histogram2d(h.ravel(), s.ravel(), [180, 256], [[0, 180], [0, 256]])

参数含义(原文档逐一点明):

  • 第 1 个参数:H 平面数据,h.ravel()把 H 通道展开为一维数组;
  • 第 2 个参数:S 平面数据,s.ravel()
  • 第 3 个参数:每个维度的 bin 数[180, 256]
  • 第 4 个参数:每个维度的取值范围[[0, 180], [0, 256]]

函数返回三个值:hist为统计结果(形状为(len(xbins), len(ybins))的二维数组,注意 NumPy 约定行轴对应第一个输入 x),xbinsybins是各维度的 bin 边界数组。与cv.calcHist相比,NumPy 版本的直方图数值语义一致,但绘制时需要自行处理轴方向与边界;OpenCV 版本则与后续cv.calcBackProjectcv.compareHist等接口天然兼容,工程链路上更顺。

小提示:若用np.split(hsv, 3, axis=2)拆出h, s, v,可避免在示例代码中手写hsv[:,:,0]hsv[:,:,1]这类索引。

二维直方图的三种可视化方法

二维直方图的结果是 180 × 256 的矩阵,"怎么画"直接影响可读性。教程给出了三种方案,按信息量递增排列。

方法一:直接使用 cv.imshow()

结果是一个 180 × 256 的二维数组,可以直接当作灰度图像显示:

cv.imshow('2D Histogram', hist) cv.waitKey(0)

它的局限在于显示的是灰度图,除非你熟悉不同颜色对应的 Hue 数值区间,否则看不出图中峰值代表什么颜色。优点是零依赖、适合在无 GUI 绘图库的环境(如嵌入式、服务器)快速查看直方图形态。

方法二:使用 Matplotlib(推荐)

matplotlib.pyplot.imshow()可以配合不同的 color map 来绘制二维直方图,能更直观地反映像素密度分布。同样地,它无法直接告诉你峰值处是什么颜色(除非你知道各 Hue 值对应的色相),但教程作者明确表示更推荐这种方式——简单且效果好。

import numpy as np import cv2 as cv from matplotlib import pyplot as plt img = cv.imread('home.jpg') assert img is not None, "file could not be read, check with os.path.exists()" hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV) hist = cv.calcHist([hsv], [0, 1], None, [180, 256], [0, 180, 0, 256]) plt.imshow(hist, interpolation='nearest') plt.show()

上面第一张配图(2dhist_matplotlib.jpg)即为该方法的输出。使用时的注意点:插值标志应设为interpolation='nearest',以避免双线性插值模糊 bin 边界,得到更准确的密度呈现。

该图的坐标语义需要记住:X 轴是 S 值(0–255),Y 轴是 Hue(0–179)。图中可见两类显著高密度区:H ≈ 100、S ≈ 200 附近的蓝色(天空),以及 H ≈ 25、S ≈ 100 附近的黄色(宫殿主体)。

方法三:OpenCV 示例风格——输出彩色直方图

教程提到 OpenCV-Python 示例中有一个samples/python/color_histogram.py,运行后可看到直方图中带有真实色彩标注(即"color coded histogram")。需要说明的是:在当前仓库版本中,该文件已不再位于 samples/python/ 目录下(仓库的 Python 示例已重组为 tutorial_code/ 目录结构),但教程对其实现原理的描述依然有参考价值:

作者先创建了一张 HSV 颜色映射图(color map),再将其转换为 BGR;然后把直方图图像与该颜色映射图逐像素相乘。同时使用若干预处理步骤去除孤立的小像素点,最终得到漂亮的直方图。

这种方法的精髓可以概括为三步:

  1. 构造一张与直方图同尺寸(180 × 256)的"色谱图",横坐标位置 s 的像素取饱和度 s、纵坐标位置 h 的像素取色相 h,整体转换到 BGR;
  2. 将归一化后的直方图与该色谱图逐元素相乘,使每个 bin 位置呈现"密度 × 颜色";
  3. 用形态学运算(如开运算)剔除孤立的微小亮点,减少视觉噪声。

教程给出的同一输入图像的输出结果如下,可以清晰地看到蓝色、黄色的存在,以及因棋盘格产生的白色成分:

如果你想自己复现这套效果,核心骨架大致是:

import numpy as np import cv2 as cv img = cv.imread('home.jpg') assert img is not None, "file could not be read, check with os.path.exists()" hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV) hist = cv.calcHist([hsv], [0, 1], None, [180, 256], [0, 180, 0, 256]) cv.normalize(hist, hist, 0, 255, cv.NORM_MINMAX) hist_img = np.uint8(hist) # 构造与直方图同尺寸的 HSV 色谱图:S 随列变化,H 随行变化 color_map = np.zeros((180, 256, 3), dtype=np.uint8) color_map[:, :, 0] = np.arange(256)[np.newaxis, :] # H 固定为 0 示例; color_map[:, :, 1] = 255 # 实际应让 H 随行 h 变化 color_map[:, :, 2] = 0 # 正确的完整做法:H 平面 = 行索引 h,S 平面 = 列索引 s hue_plane = np.tile(np.arange(180)[:, np.newaxis], (1, 256)) sat_plane = np.tile(np.arange(256)[np.newaxis, :], (180, 1)) color_map = np.dstack((hue_plane, sat_plane, np.full((180, 256), 128))) color_map_bgr = cv.cvtColor(color_map, cv.COLOR_HSV2BGR) # 直方图 × 颜色映射,再去除孤立点 colored_hist = (hist_img[..., np.newaxis] * color_map_bgr // 255) colored_hist = cv.medianBlur(colored_hist, 3) cv.imshow('Color Histogram', colored_hist) cv.waitKey(0)

参数速查与源码印证

把本文涉及的参数集中对照如下,方便直接复制到项目中:

项目cv.calcHistnp.histogram2d
输入数据[hsv](图像列表)h.ravel(),s.ravel()(两个一维数组)
通道/维度[0, 1](H、S)第 1 个参数对应 H,第 2 个对应 S
bin 数[180, 256][180, 256]
取值范围[0, 180, 0, 256](拼接形式)[[0, 180], [0, 256]](嵌套形式)
返回形状180 × 256 矩阵(len(xbins), len(ybins))矩阵 + 两个边界数组

再强调两条易错点:

  1. Hue 的上界是 180 而不是 360。OpenCV 的 8 位图像 HSV 转换中,H 被压缩到 0–180 区间(cv.COLOR_BGR2HSV的约定),这与度制色相 0–360 相差 2 倍,range 写错会导致统计结果整体为零;
  2. bins 与 range 必须按维度顺序一一对应[180, 256][0, 180, 0, 256]中,前 180 属于 H,后 256 属于 S,顺序颠倒会静默地得到错误直方图而不报错。

一维直方图教程的官方代码 calcHist_Demo.py 展示了histSizehistRangeaccumulate等参数在单通道场景下的用法,与本文的双通道用法一脉相承:单通道时channels=[0]bins=[256]range=(0,256);双通道时维度翻倍,范围拼接为[0, 180, 0, 256]

小结与延伸

本篇覆盖的内容可以归纳为一句话:BGR 转 HSV → 双通道双维度统计 → 矩阵结果可视化。具体落点:

  • 统计入口:cv.calcHist([hsv], [0, 1], None, [180, 256], [0, 180, 0, 256]),或np.histogram2d(h.ravel(), s.ravel(), [180, 256], [[0,180],[0,256]])
  • 可视化三法:cv.imshow()(最快)、matplotlib.pyplot.imshow()(推荐,记得interpolation='nearest')、彩色映射相乘法(信息量最大);
  • 结果解读:X 轴为 S、Y 轴为 H,结合 Hue 数值区间判断颜色归属。

在此基础上可以继续深入仓库中的相关示例:反向投影 calcBackProject_Demo1.py 与 calcBackProject_Demo2.py(二维直方图 → 反向投影 → 相似区域检索)、直方图比较 compareHist_Demo.py(cv.compareHist的多种度量方法)、以及直方图均衡化 EqualizeHist_Demo.py。教程系列的完整入口见 py_tutorials 目录。

【免费下载链接】opencvOpen Source Computer Vision Library项目地址: https://gitcode.com/GitHub_Trending/opencv31/opencv

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 5:46:51

STM32 HAL库延时与计时原理详解:从HAL_Delay到DWT与输入捕获

简介:一份基于STM32 HAL库的延时与定时器计时开发例程,面向嵌入式入门及中级开发者,帮助读者借助STM32CubeMX图形化配置工具完成定时器、时钟树等初始化,并掌握HAL_Delay实现毫秒级延时、利用定时器中断实现精确计时的常用工程写法…

作者头像 李华
网站建设 2026/9/7 5:46:27

BOLIDE项目部署指南:AI模型本地部署与API集成实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 5:45:30

深度学习21个项目实例:从视觉检测到环境配置的实战路线

简介:《深度学习21个项目实例》是一份面向深度学习初学者的实践型资源,围绕21个可运行项目串联理论知识与编码过程,适合已经掌握Python基础、准备系统学习神经网络并完成完整训练流程的读者。压缩包共包含911个文件,整体大小约55.…

作者头像 李华
网站建设 2026/9/7 5:44:09

Isaac Lab实战教程:四足、机械臂与人形机器人强化学习训练指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 5:43:48

SpringJDBC条件进阶:JdbcTemplate动态查询与参数安全实践

1. 先理解“条件进阶”到底在解决什么问题SpringJDBC 是 Spring 框架里处理数据库访问的基础方案,很多项目在没有引入 MyBatis、JPA 这类重量级 ORM 框架时,都会直接用它来操作数据库。JDBC 本身写起来啰嗦,SpringJDBC 通过JdbcTemplate把连接…

作者头像 李华
网站建设 2026/9/7 5:42:51

ESP32驱动SES电子价签墨水屏:从拆机到中文显示实战

简介:面向物联网开发者的 ESP32 驱动 SES 价签墨水屏完整工程包,解决电子纸标签的 SPI 通信、初始化刷新及蓝牙远程改价等实际问题。资源共 13 个文件,以 C 语言源码、JSON 配置、sdkconfig 构建配置和 Markdown/txt 说明文档为主&#xff0c…

作者头像 李华