2. 直方图绘制
图像直方图这东西,说白了就是统计每个灰度级上有多少个像素。你想想看,一张图亮不亮、对比度高不高,看直方图一眼就能判断。我刚开始做图像处理时,总觉得直方图太简单,后来才发现——很多高级算法的基础都在这里。
这一章,我们就把直方图的四种绘制方法讲透。从最省事的 imhist,到完全自己动手写,每一步我都会把坑指出来。
1. imhist函数——最快上手
imhist 是MATLAB自带的直方图计算函数。用法极其简单:
% 读取图像
I = imread('pout.tif');
% 计算并显示直方图
imhist(I);
嗯,这里要注意——imhist 默认只接受灰度图。如果你传了彩色图,它会报错。我在项目里见过有人直接拿RGB图往里塞,结果一脸懵。
如果你想获取直方图数据而不是直接显示,可以这样:
[counts, binLocations] = imhist(I);
% counts: 每个灰度级的像素个数
% binLocations: 对应的灰度级
我个人习惯拿到 counts 后先看一眼最大值,判断图像有没有过曝或欠曝。
2. bar函数——柱状图展示
imhist 画的是竖线图,但有时候我们需要柱状图——尤其是做报告时,柱状图看起来更直观。
I = imread('cameraman.tif');
[counts, bins] = imhist(I);
bar(bins, counts, 'BarWidth', 1, 'FaceColor', [0.3 0.6 0.9]);
xlabel('灰度级');
ylabel('像素个数');
title('直方图 - bar方式');
这里 BarWidth 控制柱子宽度,设为1就是紧挨着,看起来像连续分布。我曾经在给客户做演示时,柱子太细显得稀疏,客户说“你这数据是不是不全?”——从那以后我默认都用 BarWidth 调满。
bar 会画出密密麻麻的柱子,基本看不清。这时候建议先降采样再画。
3. stem函数——火柴棍图
stem 画出来像火柴棍——每个灰度级上竖一根线,顶端带个圆圈。适合展示离散分布:
I = imread('coins.png');
[counts, bins] = imhist(I);
stem(bins, counts, 'Marker', 'o', 'LineStyle', '-', 'Color', [0.8 0.2 0.2]);
xlabel('灰度级');
ylabel('像素个数');
title('直方图 - stem方式');
说实话,stem 在实际项目中用得不多。它更适合展示稀疏数据——比如只有几个灰度级有像素的情况。我记得有一次做二值化分析,图像只有0和255两个灰度级,用 stem 画出来特别清晰。
| 绘制方式 | 适用场景 | 视觉效果 | 数据密集时表现 |
|---|---|---|---|
| imhist | 快速查看 | 竖线图 | 好 |
| bar | 报告/演示 | 柱状图 | 一般(需调BarWidth) |
| stem | 稀疏数据 | 火柴棍图 | 差(密集时重叠) |
4. 自定义直方图绘制——从零开始
为什么要自己写?因为有时候你需要控制细节——比如只统计某个灰度范围,或者做加权直方图。MATLAB自带的函数做不到这些。
核心思路就三步:
- 统计每个灰度级的像素个数
- 归一化(可选)
- 绘制
来看代码:
% 自定义直方图
I = imread('pout.tif');
I = im2uint8(I); % 确保是uint8
% 第一步:统计
counts = zeros(256, 1);
for i = 0:255
counts(i+1) = sum(I(:) == i);
end
% 第二步:归一化(变成概率分布)
prob = counts / numel(I);
% 第三步:绘制
figure;
bar(0:255, prob, 'BarWidth', 1, 'FaceColor', [0.2 0.7 0.3]);
xlabel('灰度级');
ylabel('概率');
title('自定义直方图(归一化)');
sum(I(:) == i) 这种写法虽然直观,但循环256次在大图上会很慢。实际项目中我会用 histcounts 或 accumarray 来加速。
如果你想做加权直方图——比如给图像中心区域的像素更高权重——自定义方法就派上用场了:
% 加权直方图示例
[M, N] = size(I);
weight = zeros(M, N);
centerX = N/2; centerY = M/2;
for x = 1:N
for y = 1:M
dist = sqrt((x - centerX)^2 + (y - centerY)^2);
weight(y, x) = exp(-dist / 100); % 高斯权重
end
end
weighted_counts = zeros(256, 1);
for i = 0:255
mask = (I == i);
weighted_counts(i+1) = sum(weight(mask));
end
嗯,这个加权直方图在目标检测里很常用——中心区域权重高,边缘权重低,能减少背景干扰。
5. 四种方法对比总结
我把四种方法的核心差异整理成一张表,方便你选型:
| 方法 | 代码量 | 灵活性 | 性能 | 推荐指数 |
|---|---|---|---|---|
| imhist | 1行 | 低 | 快 | ★★★★★ |
| bar | 3行 | 中 | 快 | ★★★★ |
| stem | 3行 | 中 | 快 | ★★★ |
| 自定义 | 10+行 | 高 | 中(循环慢) | ★★★★(特殊场景) |
我个人建议:日常快速分析用 imhist,做报告用 bar,遇到特殊需求(加权、局部统计)再自己写。别为了炫技而造轮子。
xlim([0 255]) 和 ylim([0 max(counts)*1.1])。不然MATLAB自动缩放会误导你。
这张图把四种方法的关系和适用场景串起来了。你想想看——从最省事的 imhist 到完全自定义,本质上是在「开发效率」和「灵活度」之间做权衡。没有的好坏,只有合不合适。