简介:这份文档是数字图像处理课程实验二的配套实验报告,面向高校电子信息、计算机及自动化等专业修读图像处理课程的学生,以及需要完成MATLAB图像增强实验的学习者。内容围绕直方图均衡化展开,涵盖实验目的、设备要求、图像增强原理、实验内容与步骤、MATLAB程序示例、结果分析及总结等模块,以128×128、256级灰度的finger与cell两幅图像为对象,演示histeq函数的调用、原图与均衡化后图像及直方图的同屏对比,并讨论均衡化后直方图为何并非完全均匀分布这一常见考点。资源包为单个docx文档,约339KB,结构完整、便于直接套用实验报告模板或作为复习参考。目前已有93人学习下载,适合需要快速掌握直方图均衡化流程、补齐实验报告与理解原理细节的学生参考使用。
1. 一行 histeq 之后,显微图里的细胞边界才看得见
把 cell_128.img 拖进 MATLAB 打开,128×128、256 级灰度,肉眼看就是灰蒙蒙一片,细胞轮廓糊在一起。这不是文件坏了,而是这张图的像素值几乎全挤在中低灰度那一小段里,暗的压不下去、亮的提不上来,视觉上自然没有层次。直方图均衡化做的事很朴素:把挤在一起的灰度按累积概率拉开,重新铺满 0 到 255 的整个动态范围,让原来差几个灰度的脊线和谷线、细胞膜和背景,在新映射下差出几十个灰度。这份数字图像处理课程实验二的素材,就是两张 128×128 的灰度图 fing_128.img(指纹)和 cell_128.img(显微医学图像),用 MATLAB 的 histeq 做均衡化,再把原图、均衡化图和各自的 64 级直方图摆在一起对比。适合两类人:刚入门图像处理、要交报告又不能只交代码的;以及已经会调函数、但说不清"为什么均衡化后的直方图不是平的"的。
2. 从灰度直方图到累积分布映射
2.1 灰度直方图统计的是什么
对一幅 8 位灰度图,直方图就是一张频次表:h(k)表示灰度值恰好等于k的像素个数,k取 0 到 255。把h(k)除以总像素数MN,就得到灰度出现的概率p(k),也就是实验原理里说的"亮度分布的概率密度函数"。它丢掉了所有空间位置信息,只留统计规律——这正是它便宜、好算、能实时跑的原因,也是它分不清"两块区域各有一半暗像素"和"整幅图均匀地暗"的原因。
用imhist看直方图时,第二个参数是 bin 数,不是灰度上限。imhist(I, 64)是把 0~255 归并成 64 个区间来画,每个 bin 覆盖 4 个灰度级;写 256 才是逐级统计。实验里统一用 64,图更好看,但判断"某个灰度级是否空缺"时就不够了。
2.2 均衡化的映射函数:累积分布加取整
连续情形下,均衡化的变换是累积分布函数本身:
s = T(r) = (L - 1) * ∫ p_r(w) dw (0 到 r 积分)离散图像里积分换成求和,L是灰度级数(8 位图取 256,所以L-1 = 255):
s_k = round( (L-1)/(M*N) * Σ_{j=0}^{k} h(j) )用一个 4 灰度级、16 像素的迷你例子走一遍,比记公式管用。设原图灰度分布为h = [8, 6, 2, 0],即 16 个像素里 8 个是 0、6 个是 1、2 个是 2、没有 3:
| 输入灰度 k | 像素数 h(k) | 概率 p(k) | 累积 cdf(k) | 3 × cdf(k) | 取整输出 s(k) |
|---|---|---|---|---|---|
| 0 | 8 | 0.500 | 0.500 | 1.500 | 2 |
| 1 | 6 | 0.375 | 0.875 | 2.625 | 3 |
| 2 | 2 | 0.125 | 1.000 | 3.000 | 3 |
| 3 | 0 | 0.000 | 1.000 | 3.000 | 3 |
映射结果是 0→2、1→3、2→3、3→3。均衡后灰度为 2 的有 8 个像素,灰度为 3 的有 8 个。原本只有 3 个占用灰度级,现在变成 2 个,最高级被塞满,0 和 1 两级彻底空出来——这就是教科书说"均衡化让分布变均匀"时实际发生的事。
用 MATLAB 把这段计算打印出来,两行就够:
I = imread('finger.gif'); h = imhist(I, 256); % 逐级统计,256 个 bin cdf = cumsum(h) / numel(I); % 累积概率,长度 256 T = uint8(round(255 * cdf)); % 灰度映射表 T(k+1) 对应输入灰度 k disp([(0:255)', double(T)]); % 每行:输入灰度 -> 输出灰度imhist(I, 256)返回列向量,第k+1个元素是灰度k的像素数;cumsum做前缀和得到累积分布;round(255*cdf)完成归一化与取整,得到查找表T。后面手写均衡化时,整张图就是靠T查表完成的,没有任何逐像素运算。
2.3 离散化决定了它只能"近似均匀"
连续推导里T(r)严格单调、输出概率密度恒为常数。离散之后有两个破坏因素:一是取整,3*cdf从 1.5 跳到 2.625 再跳到 3.0,落在同一整数上的输入灰度被迫合并;二是像素不可拆分,同一灰度的 8 个像素只能整体搬到同一个输出级,不像连续情形那样能被均匀摊到整个区间。
还有个更隐蔽的原因:直方图均衡化是全局单调映射,它只改变灰度的数值分布,不会凭空造出原本不存在的灰度级。如果原图背景占了 70% 的像素并集中在灰度 30 附近,均衡化后这些像素依然抱团,只是抱到了另一个灰度上,形成一个新的尖峰。所以实验报告里"为什么均衡化后直方图并非完全均匀"这道题,标准答案应当扣住三点:灰度的离散性、取整带来的合并、以及单调映射保持信息量的约束。
3. MATLAB 里把数据读进来并跑通 histeq
3.1 先确认手上是 .img 还是 .gif
实验说明里写的是 fing_128.img 和 cell_128.img,实验代码里写的却是 finger.gif 和 cell.gif。同一张图的两种载体,读法完全不同:imread认识 gif、tif、png、jpg 这些带格式头的文件,但对裸 .img/.raw 只会报错或猜错解释方式。裸数据没有头信息,宽高和位深必须自己写死。
常见做法是fread加reshape:
fid = fopen('fing_128.img', 'r'); assert(fid > 0, '文件打不开,先检查当前工作目录和文件名大小写'); raw = fread(fid, 128 * 128, 'uint8'); % 精确读 16384 个字节 fclose(fid); I = reshape(raw, 128, 128)'; % 行优先存储需转置,列优先则去掉 ' figure; imshow(I); title('原图像');fread的第三个参数'uint8'决定每个像素占几个字节,256 级灰度就是 1 字节;128*128是元素个数而不是字节数,两者在 uint8 下相等,换成'uint16'就要写 32768。reshape按列填充,如果原文件是逐行扫描存储的,必须转置,否则图像会沿主对角线镜像。这一步错了,直方图看着正常,图像内容却是斜的,很容易被当成"均衡化副作用"误判。
3.2 histeq 的默认输出级数与两个常见误用
histeq的默认行为有个容易吃亏的地方:J = histeq(I)在输入为 uint8 时,默认把输出量化到 64 个灰度级。对一张 256 级灰度的图来说,这等于主动丢掉 2 个最低有效位,均衡化后直方图上会看到明显的锯齿和空档。要保留全动态范围,得显式写级数。
| 调用形式 | 含义 | 输出灰度级 | 适用场景 |
|---|---|---|---|
J = histeq(I) | 自动均衡 | 默认 64 级 | 快速预览、课堂演示 |
J = histeq(I, 256) | 指定输出级数 | 256 级 | 256 级灰度图正式处理 |
J = histeq(I, hgram) | 匹配到指定目标直方图 | 由 hgram 长度决定 | 直方图规定化 |
[J, T] = histeq(I, n) | 同时返回映射表 | n 级 | 查映射关系、写报告配图 |
另一个坑在数据类型。输入是 double 时,histeq默认把数据范围当作 0~1 处理。如果直接把 0~255 的 double 矩阵喂进去,超过 1 的部分会被截断,输出基本是一片白。稳妥写法是进函数前统一成 uint8,或者显式先做I = im2uint8(mat2gray(I))。
3.3 同屏显示:subplot 与 imhist 的组合
实验要求"同屏显示处理前后图像及其直方图",用 2×2 的subplot最省事:
I = imread('cell.gif'); if size(I, 3) == 3 I = rgb2gray(I); % imhist 只接受单通道,彩色图先转灰度 end J = histeq(I, 256); % 指定 256 级,避免默认 64 级量化 figure('Name', '直方图均衡化前后对比'); subplot(2, 2, 1); imshow(I); title('原图像'); subplot(2, 2, 2); imshow(J); title('直方图均衡化后的图像'); subplot(2, 2, 3); imhist(I, 64); title('原图像直方图'); subplot(2, 2, 4); imhist(J, 64); title('均衡变换后的直方图');subplot(2,2,n)的第三个参数按行优先编号,1、2 是上排,3、4 是下排;每次调用都会切换当前坐标轴,所以title要紧跟对应的绘图命令。imhist的第二个参数是 bin 数,这里用 64 是为了让柱形不至于细到看不清;如果想确认某个灰度级是否真的空缺,把它改成 256 再看一次。
提示:
imhist遇到彩色三通道图像会直接报错,读图后先用size(I,3)判断通道数,再决定是否rgb2gray。
4. 指纹图与显微图:两种直方图形态带来的不同效果
4.1 先看形态再动手,收益可以预判
均衡化的效果高度依赖原图直方图的形状,这一点在做实验前就能预判。指纹图 fing_128.img 的直方图通常又窄又呈双峰:脊线像素和谷线像素各聚在一团,峰之间隔着一段几乎为空白的灰度区间,而那段空白恰恰是决定脊谷对比度的部分。均衡化把累积概率拉伸后,两个峰被推向两端,中间被填上新的灰度,脊线谷线的视觉分离度立刻上来。
显微医学图 cell_128.img 的情况不同。这类图背景占比大、亮度偏暗,直方图往往是单峰且集中在低灰度侧,长尾拖向高灰度。均衡化同样能把细胞结构从背景里拉出来,但同时会把背景里的颗粒噪声一起放大——噪声和细胞的灰度很接近,映射函数没法区分它们。
| 对比维度 | fing_128.img(指纹) | cell_128.img(显微细胞) |
|---|---|---|
| 原图直方图形态 | 窄、双峰,峰间有空白带 | 单峰,集中在中低灰度 |
| 均衡化主要收益 | 脊线与谷线对比拉开 | 细胞边界与背景分离 |
| 主要副作用 | 纹理颗粒感增强 | 背景噪点明显放大 |
| 观察 bin 数建议 | 64 看趋势,256 查空缺 | 同左 |
| 是否适合全局均衡 | 适合 | 适合但要接受噪声代价 |
4.2 用数字给"看起来更清楚"做证据
实验报告只放图容易被追问"凭什么说清楚了",补两个标量指标就够了:灰度标准差反映对比度,熵反映平均信息量。写一个小函数复用:
function [m, s, e] = stat_gray(I) I = double(I(:)); m = mean(I); % 均值,反映整体亮度 s = std(I); % 标准差,反映对比度强弱 p = imhist(uint8(I), 256) / numel(I); % 归一化直方图 p(p == 0) = []; % 去掉零项,避免 log2(0) e = -sum(p .* log2(p)); % 一阶熵,单位 bit end调用时对原图和均衡化图各跑一次:[m1,s1,e1] = stat_gray(I); [m2,s2,e2] = stat_gray(J);。p(p==0)=[]这一句不能省,否则log2(0)会产生-Inf,求和结果变成NaN。通常均衡化后标准差上升、熵略微上升,均值向 127 附近靠拢,这三点方向和图像观感是一致的。要是标准差反而下降,多半是输入被当成 double 处理、或者输出级数设成了 64,回头查 3.2 那节。
4.3 什么时候该换方法
全局均衡化有一个硬约束:它对整幅图用同一条映射曲线。当图像不同区域的亮度差异本身就很大时——比如一半过曝、一半欠曝——一条曲线救不了两头,拉伸暗部的同时会把亮部直接压成白块。
判断标准很直接:把原图直方图按区域分块看,如果各块的峰值位置相差超过 40 个灰度级,全局方法就该让位了。医学图像、遥感影像、逆光拍摄的照片都属于这一类。实验里这两张 128×128 的图内容单一,全局均衡化足够应付,但报告讨论部分提一句方法边界,分数通常更稳。
注意:对已经接近均匀分布、或者存在大面积纯色背景的图,强行均衡化不会带来信息增益,只会把压缩噪声和传感器噪声一起抬起来。
5. 手写均衡化对齐 histeq,再用 CLAHE 补全局方法的短板
5.1 十行代码把映射表算清楚
把 histeq 当黑箱用久了,很容易在答辩时说不清内部做了什么。手写一遍再对齐输出,是最省事的验证方式:
function [J, T] = my_histeq(I, L) % 手写直方图均衡化:I 为 uint8 灰度图,L 为灰度级数(256) if ~isa(I, 'uint8') error('仅支持 uint8 输入,请先 im2uint8 转换'); end h = imhist(I, L); % 逐级频次 p = h / numel(I); % 归一化 cdf = cumsum(p); % 累积分布 T = uint8(round((L - 1) * cdf)); % 映射表,第 k+1 项对应灰度 k J = T(double(I) + 1); % 查表,MATLAB 下标从 1 开始 end关键在最后一行:MATLAB 索引从 1 起,所以灰度k要取T(k+1),写成T(double(I))会在灰度 0 处直接报索引越界。想确认和内置函数是否一致,可以跑max(abs(double(J) - double(histeq(I, 256)))),正常只会看到 0 或 1 的差——差异来自 histeq 内部对累积分布的边界处理,属于实现细节,不影响结论。这个对比过程本身就可以写进实验报告的"结果分析"里,比单纯贴运行截图有说服力得多。
5.2 全局均衡化失效时切到 CLAHE
当图像局部亮度差异大、或者背景噪声一拉伸就难看时,换成限制对比度的自适应直方图均衡化。MATLAB 里对应adapthisteq,它把图像切成若干小块分别均衡,再用双线性插值消除块间边界,同时用裁剪阈值压住噪声:
J1 = adapthisteq(I, 'ClipLimit', 0.02, 'NumTiles', [8 8]); J2 = adapthisteq(I, 'ClipLimit', 0.01, 'NumTiles', [4 4]);ClipLimit是每个小块的直方图裁剪上限,归一化到 0~1,取值越小对比度增强越保守、噪声放大越少;NumTiles是分块数,块越多越贴合局部特征,但块小到一定程度,块内像素太少、统计不稳,相当于在放大噪声。在 128×128 这种小图上,NumTiles超过[16 16]基本就没有意义了,每块只剩 64 个像素。调参的稳妥顺序是先把NumTiles定在[8 8],再从ClipLimit = 0.01起往上试,盯着背景平坦区的颗粒感做取舍。
本文还有配套的精品资源,点击获取