ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

图像处理入门021 | 直方图:图像像素分布的可视化 —— cv2.calcHist 与 4 类典型分布

图像处理入门021 | 直方图:图像像素分布的可视化 —— cv2.calcHist 与 4 类典型分布 第021期| 图像处理算法入门系列 | 2026-08-30关键词直方图、cv2.calcHist、灰度直方图、多通道直方图、归一化、CDF、直方图均衡化一、引言第020期我们讲了如何把灰度图一刀切成两半——二值化。但是在切之前必须先回答一个问题刀要落在哪里答案藏在图像的像素分布里。把一张图所有像素按灰度值数一遍画成柱状图就是直方图Histogram。直方图是图像处理里出场率第二高的工具仅次于二值化。它是看图的眼睛一眼判断偏暗 / 偏亮 / 低对比度 / 高对比度选阈值的尺子双峰直方图的谷底就是二值化的最佳阈值接续 020 期算法的前奏直方图均衡化022 期、CLAHE023 期、Otsu 自动阈值076 期、直方图匹配、色彩迁移、图像检索、相机白平衡……几乎所有需要理解图像统计规律的算法第一步都是直方图直方图的本质把 [0, 255] 切成 256 个 bin数每个 bin 里有多少像素。 像素值 k (0-255) → 第 k 个 bin 高度 1 看似一行图能讲清的事里面藏着 5 个关键概念 ① 直方图定义与计算 ② 多通道叠加 ③ 归一化PDF ④ 累积分布 CDF ⑤ 性能陷阱本期我们就把直方图从定义到实现一次性讲透最后引出下期直方图均衡化。二、核心概念与原理2.1 直方图的数学定义设图像为I(x, y) ∈ [0, L-1]灰度图 L256灰度级 k 的概率密度为p(k) n_k / N 其中 n_k : 灰度值 k 的像素个数 N : 图像总像素数 p(k) : 归一化后的概率密度PDF所有 p(k) 之和 1由此衍生两个关键函数概率密度函数PDF p(k) n_k / N ← 归一化直方图 累积分布函数CDF c(k) Σ_{i0..k} p(i) ← 022 期均衡化的核心2.2 cv2.calcHist 函数签名histcv2.calcHist(images,channels,mask,histSize,ranges[,hist[,accumulate]])参数 images:输入图像列表用[img]包裹单张灰度图也行彩色图也行 channels:统计哪些通道-灰度图[0]-彩色图可以[0]只算 B[1]只算 G[2]只算 R 也可以[0,1]/[0,1,2]多维直方图 mask:掩膜None表示全图统计非None时只统计 mask0的区域 histSize:每个通道的分箱数Bins灰度图常用[256]ranges:每个通道的取值范围灰度图[0,256]彩色图[0,256,0,256,0,256]返回值 hist:形状为(256,1)的 numpy 数组2.3 灰度直方图 vs 多通道直方图灰度直方图 1 个通道 → 1 条曲线 → 反映整体亮度分布 多通道直方图 3 个通道 → 3 条曲线叠加 → 反映色彩平衡B/G/R 各占多少 N 维直方图 N 通道联合 → 高维矩阵 → 反映色彩出现频率图像检索/分割常用2.4 归一化的意义原始直方图 纵轴是像素个数——受图像分辨率影响1M 像素 vs 1K 像素差 1000 倍 归一化直方图纵轴是概率密度——和分辨率无关可以跨图比较 两套做法等价 ① 手动hist_norm hist / hist.sum() ② OpenCVcv2.normalize(hist, None, 0, 1, cv2.NORM_MINMAX)三、实验6 个动手环节实验 1灰度直方图基础合成一张高对比度双峰灰度图30 与 220绘制直方图importcv2importnumpyasnpimportmatplotlib.pyplotasplt# 合成测试图30% 暗像素 70% 亮像素叠加少量噪声size(360,540)rngnp.random.default_rng(7)highnp.zeros(size,dtypenp.uint8)maskrng.random(size)0.5high[mask]220high[~mask]30highnp.clip(high.astype(np.int16)rng.integers(-8,8,size),0,255).astype(np.uint8)# 计算并绘制直方图histcv2.calcHist([high],[0],None,[256],[0,256])# 形状 (256, 1)plt.figure(figsize(8,4))plt.plot(hist,color#2c3e50,lw1.5)plt.fill_between(np.arange(256),hist.ravel(),alpha0.25,color#3498db)plt.axvline(30,color#e74c3c,ls--,label左峰 ≈ 30)plt.axvline(220,color#27ae60,ls--,label右峰 ≈ 220)plt.xlim(0,256)plt.xlabel(灰度级);plt.ylabel(像素计数)plt.title(灰度直方图256 bins)plt.grid(True,alpha0.3);plt.legend()plt.show()运行结果可以清楚看到两个尖峰左峰在 30 附近背景区右峰在 220 附近前景区中间几乎为 0——这就是典型的双峰直方图是 020 期选阈值的依据。实验 2多通道直方图彩色图像分 B/G/R 三个通道统计颜色与通道对应colorfulcv2.imread(colorful.jpg)# BGR 顺序colors(b,g,r)# OpenCV 用 BGRfori,cinenumerate(colors):histcv2.calcHist([colorful],[i],None,[256],[0,256])plt.plot(hist,colorc,labelf通道{i}({c.upper()}),lw1.6)plt.xlim(0,256)plt.xlabel(像素值);plt.ylabel(像素计数)plt.title(BGR 三通道直方图)plt.legend();plt.grid(True,alpha0.3)运行结果可以看到蓝/绿/红三块区域的直方图分别集中在不同像素值段没有任何交叉——这正是彩色直方图判断图像颜色偏向的最常用方法。实验 3归一化直方图与统计量直方图本身是绝对计数但跨图比较时需要归一化graycv2.imread(low_contrast.jpg,cv2.IMREAD_GRAYSCALE)histcv2.calcHist([gray],[0],None,[256],[0,256])# 两种归一化做法hist_manualhist/hist.sum()# 手动除以总数hist_cvcv2.normalize(hist,None,0,1,cv2.NORM_MINMAX)# OpenCV 归一到 [0,1]# 直方图统计量print(f像素总数:{gray.size:,})print(f最小值 :{gray.min()})print(f最大值 :{gray.max()})print(f均值 :{gray.mean():.2f})print(f中位数 :{np.median(gray):.1f})print(f标准差 :{gray.std():.2f})运行结果指标数值形状(360, 540)像素总数194,400最小值15最大值241均值127.51中位数127.0标准差24.97观察发现均值和中位数几乎相等都是 127 左右说明这张图的分布大致对称标准差 24.97 偏小——典型的低对比度特征。实验 4四类典型图像的直方图对比最有价值的不是单张图的直方图而是多张图横向对比samples{dark:(cv2.imread(dark.jpg,0),偏暗图),bright:(cv2.imread(bright.jpg,0),偏亮图),low_contrast:(cv2.imread(low_contrast.jpg,0),低对比度),high_contrast:(cv2.imread(high_contrast.jpg,0),高对比度),}fig,axesplt.subplots(2,4,figsize(15,7))forcol,(key,(img,title))inenumerate(samples.items()):axes[0,col].imshow(img,cmapgray)axes[0,col].set_title(title);axes[0,col].axis(off)histcv2.calcHist([img],[0],None,[256],[0,256])axes[1,col].plot(hist)axes[1,col].axvline(img.mean(),colorr,ls--)axes[1,col].set_xlabel(灰度级)运行结果类型直方图形态视觉特征偏暗图集中在 0-80全图发暗细节丢失在暗部偏亮图集中在 180-255全图泛白细节丢失在亮部低对比度集中在 100-150灰蒙蒙像隔着一层雾高对比度两端各一峰黑白分明过渡锐利实操中拿到一张图先画直方图再看图——能省 80% 的盲目调参时间。实验 5累积分布函数 CDF铺垫下期把 PDF 累加起来就是 CDF这是 022 期直方图均衡化的数学核心graycv2.imread(low_contrast.jpg,cv2.IMREAD_GRAYSCALE)histcv2.calcHist([gray],[0],None,[256],[0,256]).ravel()pdfhist/hist.sum()cdfnp.cumsum(pdf)# 累积分布fig,ax1plt.subplots(figsize(8,4))ax1.plot(pdf,color#3498db,labelPDF)ax1.set_ylabel(PDF归一化直方图,color#3498db)ax2ax1.twinx()ax2.plot(cdf,color#e74c3c,labelCDF)ax2.set_ylabel(CDF累积分布,color#e74c3c)ax2.set_ylim(0,1.05)运行结果CDF 的形态是单调不减的 S 形——中间陡像素集中两端缓像素稀疏。直方图均衡化的本质就是把 CDF 拉成一条直线 yx让每个像素值区间被等概率使用——这就把 022 期的入口打开了。实验 6性能基准测试实测三种写法的耗时差异1080×720 彩色图importtime bigcv2.imread(big.jpg)# 1080×720×3# 方式 Acv2.calcHist三通道 256³t0time.perf_counter()for_inrange(50):cv2.calcHist([big],[0,1,2],None,[256,256,256],[0,256]*3)t_cv(time.perf_counter()-t0)/50*1000# 方式 Bnp.bincount单通道最快t0time.perf_counter()for_inrange(50):np.bincount(big[:,:,0].ravel(),minlength256)t_np(time.perf_counter()-t0)/50*1000# 方式 CPython 双循环极慢histnp.zeros(256,dtypenp.int64)foryinrange(0,big.shape[0],4):# 采样 1/16forxinrange(0,big.shape[1],4):hist[big[y,x,0]]1运行结果方式耗时适用场景cv2.calcHist三通道 256³24.35 ms通用特别是多通道 / 掩膜np.bincount单通道4.45 ms单通道灰度直方图的最快方案Python 双循环17.40 ms仅 1/16 区域教学演示生产环境禁用关键洞察np.bincount在单通道场景下比cv2.calcHist快约5 倍Python 双循环即使只采样 1/16 区域也比向量化方案慢——再次印证必须向量化实际项目里灰度直方图直接用 np.bincount多通道 / 掩膜场景用 cv2.calcHist四、常见陷阱陷阱 1彩色图忘了拆通道# 错误直接对彩色图算灰度直方图histcv2.calcHist([color_img],[0],None,[256],[0,256])# 只算了 B 通道# 正确三通道分别算再叠加foriinrange(3):hist_icv2.calcHist([color_img],[i],None,[256],[0,256])plt.plot(hist_i,color(b,g,r)[i])陷阱 2直方图假双峰# 反例高斯模糊过度highcv2.GaussianBlur(high,(11,11),3)# 双峰被平滑成单峰# 正解双峰图像不要做大核模糊我第一次跑实验时就踩了这个坑——加了(11,11)高斯模糊原本明显的双峰直接被磨平标准差从 95 掉到 10图就废了。陷阱 3bins 设错导致数据丢失# 把 256 个 bin 缩成 16 个 bin信息丢失严重histcv2.calcHist([gray],[0],None,[16],[0,256])# 每个 bin 合并 16 个灰度级# 通常 256 bins 是最直观的默认选择陷阱 4横轴范围错误# OpenCV 范围是半开区间 [0, 256)不是 [0, 255]histcv2.calcHist([gray],[0],None,[256],[0,256])# 正确histcv2.calcHist([gray],[0],None,[256],[0,255])# 错误丢掉灰度255五、产品推荐照片分析与直方图工具移动端随手拍 现场分析应用平台直方图能力特色Lightroom MobileiOS / Android实时 RGB 直方图 直方图裁剪警示Adobe 出品专业摄影师首选SnapseediOS / Android调色面板内嵌直方图Google 出品免费 局部调色VSCOiOS / Android调色时显示 RGB / 亮度直方图滤镜直方图参考手机自带照片信息iOS点击 i/ Android缩略图 直方图部分厂商零门槛查看照片元数据桌面端 / 专业级修图 调色 科研工具平台直方图能力特色RawTherapeeWindows / macOS / Linux全通道直方图 Lab 色空间直方图免费开源RAW 处理神器DarktableWindows / macOS / Linux实时直方图 波形图 矢量示波器摄影师用的开源版 LightroomAdobe Lightroom ClassicWindows / macOS完整 RGB 直方图 直方图裁剪警告业界标准付费Capture OneWindows / macOS高级直方图 皮肤色调直方图高端商业摄影常用ImageJ / FIJI全平台完整直方图 任意 ROI 区域直方图科研 / 医学图像分析标配GIMP全平台基础直方图免费开源入门级在线工具工具网址特色Photopeaphotopea.com浏览器内 PS免费支持直方图调色Pixlr Xpixlr.com简易在线编辑器含直方图六、本期小结维度内容核心 APIcv2.calcHist(img, channels, mask, histSize, ranges)输出numpy 数组形状 (bins, 1)归一化hist / hist.sum()或cv2.normalize(..., cv2.NORM_MINMAX)性能最优单通道场景用np.bincount比cv2.calcHist快约 5 倍形态识别单峰位置偏暗 / 偏亮、峰宽对比度、双峰高对比度 → 020 期选阈值衍生函数CDF累积分布→ 铺垫 022 期直方图均衡化七、下期预告第022期直方图均衡化Histogram Equalization—— cv2.equalizeHist 自动对比度增强我们将学习HE 数学原理CDF 映射到均匀分布全局 HEcv2.equalizeHist vs 自适应 HECLAHE023 期暗光照片 / 雾天图像的对比度提升过度均衡化的副作用与伪影产品实践手机相机的夜景模式和HDR算法底层下期不见不散~版权声明本文为「图像处理算法入门」系列第021期原创首发于 CSDN。代码与配图均开源遵循 MIT 协议。引用或转载请保留原作者与链接。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进