4. 直方图规定化

直方图均衡化大家已经掌握了,它确实好用——自动拉伸对比度,效果立竿见影。但有个问题:均衡化后的直方图是固定的,你没法控制它长什么样。

举个例子。你有一张偏暗的医学X光片,希望增强后直方图呈现某种“中间集中、两边渐弱”的分布,而不是均衡化那种近似平坦的分布。这时候,直方图规定化就派上用场了。

说白了,直方图规定化就是“按需定制”。你给一个目标直方图,算法就把原图的直方图往那个形状上靠。我当年第一次用这个功能时,觉得它比均衡化“聪明”多了——虽然实现起来稍微绕一点。

核心思想:直方图规定化 = 直方图均衡化 + 逆映射。先对原图和目标图分别做均衡化,再通过“逆变换”把原图的灰度映射到目标图的灰度空间上。

直方图规定化核心流程 原图 f(x,y) 目标直方图 p(z) 均衡化 T(r) 均衡化 G(z) 逆映射 z = G⁻¹[T(r)] 规定化结果 g(x,y) 核心:找到原图灰度 r 到目标灰度 z 的映射关系

4.1 直方图规定化的数学原理

原理其实不复杂。我尽量用大白话讲清楚:

  1. 对原图做均衡化:得到映射关系 s = T(r),其中 r 是原图灰度,s 是均衡化后的灰度。
  2. 对目标直方图做均衡化:得到映射关系 v = G(z),其中 z 是目标灰度,v 是均衡化后的灰度。
  3. 关键一步:让 s = v,然后反解出 z = G⁻¹(s) = G⁻¹[T(r)]。

你想想看,s 和 v 都是均匀分布的,所以它们可以一一对应。找到这个对应关系后,原图的每个灰度 r 就能映射到目标灰度 z 上。

我的经验:实际计算时,G(z) 的逆映射往往不是整数。我习惯用“最近邻查找”或“线性插值”来处理。MATLAB 的 imhistmatch 函数内部已经处理好了,但如果你自己写代码,这个细节容易踩坑。

4.2 MATLAB 中的 imhistmatch 函数

MATLAB 从 R2017b 开始提供了 imhistmatch 函数,用起来非常方便。它的核心语法就两行:

% 基本用法:将 A 的直方图匹配到 Ref 的直方图
B = imhistmatch(A, Ref);

% 指定灰度级数(默认 64)
B = imhistmatch(A, Ref, n);

参数说明:

参数 含义 注意事项
A 待处理的输入图像 支持 uint8、uint16、double 等类型
Ref 参考图像(提供目标直方图) 不要求尺寸与 A 相同
n 灰度级数(可选) 默认 64,越大匹配越精细,但计算量也大
B 输出图像 数据类型与 A 一致

注意:imhistmatch 要求输入图像 A 和参考图像 Ref 的数据类型一致。我曾经遇到过 uint8 和 double 混用导致结果全黑的情况——嗯,排查了半天才发现是类型问题。

4.3 实战:用 imhistmatch 做规定化

来看一个完整的例子。假设我们有一张偏暗的图片,想把它匹配成一张明亮图片的直方图风格:

% 读取原图和参考图
I = imread('dark_image.jpg');
Ref = imread('bright_image.jpg');

% 直方图规定化
J = imhistmatch(I, Ref);

% 显示结果
figure;
subplot(2,3,1); imshow(I); title('原图(偏暗)');
subplot(2,3,2); imshow(Ref); title('参考图(明亮)');
subplot(2,3,3); imshow(J); title('规定化结果');

% 绘制直方图对比
subplot(2,3,4); imhist(I); title('原图直方图');
subplot(2,3,5); imhist(Ref); title('参考图直方图');
subplot(2,3,6); imhist(J); title('结果直方图');

运行这段代码,你会看到结果图的直方图形状和参考图非常接近——这就是规定化的威力。

效果对比:均衡化是把直方图“拉平”,规定化是把直方图“捏成”你想要的形状。前者是自动的,后者是可控的。

4.4 自定义映射:自己写规定化代码

有时候,imhistmatch 不能满足所有需求。比如你想把直方图规定成某个数学分布(高斯分布、均匀分布等),或者你想控制映射的细节。这时候就得自己写。

我给大家一个自己实现规定化的模板:

function J = my_histmatch(I, target_hist, L)
    % I: 输入图像(uint8)
    % target_hist: 目标直方图(1×L 向量)
    % L: 灰度级数(默认 256)
    
    if nargin < 3
        L = 256;
    end
    
    % 1. 计算原图的累积分布
    src_hist = imhist(I, L);
    src_cdf = cumsum(src_hist) / sum(src_hist);
    
    % 2. 计算目标直方图的累积分布
    target_cdf = cumsum(target_hist) / sum(target_hist);
    
    % 3. 建立映射表
    map = zeros(L, 1, 'uint8');
    for r = 1:L
        % 找到使 src_cdf(r) 最接近 target_cdf(z) 的 z
        [~, z] = min(abs(src_cdf(r) - target_cdf));
        map(r) = z - 1;  % MATLAB 索引从 1 开始,灰度从 0 开始
    end
    
    % 4. 应用映射
    J = intlut(I, map);
end

避坑指南:我曾经在写这个映射时直接用 round(src_cdf(r) * (L-1)) 来算 z,结果发现效果很差。后来改用 min(abs(...)) 做最近邻查找,效果才正常。原因是累积分布函数不是严格线性的,直接取整会丢失精度。

4.5 应用场景:什么时候用规定化?

规定化不是万能的,但在以下几个场景中特别好用:

场景 说明 我的建议
图像风格迁移 把一张图的色调风格匹配到另一张图 配合颜色空间转换(如 HSV)效果更好
多传感器图像融合 让不同传感器拍摄的图像具有相似的亮度分布 先做规定化再做融合,能减少拼接痕迹
医学图像标准化 不同设备、不同参数下拍摄的影像,直方图差异大 规定化到同一个参考直方图,方便医生对比
遥感图像增强 让不同时相的遥感影像具有一致的视觉表现 选一张质量好的作为参考,其他都往它匹配

注意:规定化不是“万能钥匙”。如果原图和目标直方图差异太大(比如一个全黑、一个全白),映射结果可能会出现“断层”或“伪轮廓”。我建议先做一次轻微的均衡化,再做规定化,效果会更平滑。

4.6 规定化 vs 均衡化:一张表说清楚

对比项 直方图均衡化 直方图规定化
目标 让直方图近似均匀分布 让直方图匹配指定形状
控制性 自动,不可控 手动,可控
实现复杂度 低(单次映射) 中(两次映射 + 逆映射)
适用场景 通用增强,快速提升对比度 需要特定视觉风格或标准化处理
MATLAB 函数 histeq imhistmatch

我个人觉得,均衡化是“开盲盒”,规定化是“点菜”。日常快速处理用均衡化,有明确需求时用规定化。两者配合使用,效果往往更好。

总结一下:直方图规定化给了你“定制直方图”的能力。理解它的原理——先均衡化再逆映射——你就掌握了核心。MATLAB 的 imhistmatch 函数让这件事变得简单,但自己写一遍自定义映射能帮你深入理解细节。应用时注意数据类型一致、灰度级数选择、以及原图与目标直方图的匹配度,这些细节决定了最终效果的好坏。