4. Retinex理论:从SSR到MSRCR
Retinex这个词,是Retina(视网膜)和Cortex(大脑皮层)的合成词。说白了,这个理论试图模仿人眼怎么感知颜色和亮度。人眼厉害的地方在于——不管光照多暗多亮,我们看一个物体的颜色基本不变。这叫“颜色恒常性”。
我刚开始接触医学图像增强时,遇到的最大问题就是:一张X光片,明明病灶区域被阴影盖住了,但直接调亮度会把正常组织也调爆。后来发现,Retinex就是专门解决这类问题的。
4.1 Retinex的核心思想
Retinex理论把一幅图像分解成两部分:
- 照射分量(Illumination)——光源的分布,低频、缓慢变化
- 反射分量(Reflectance)——物体本身的属性,高频、包含细节
数学表达很简单:
S(x,y) = I(x,y) · R(x,y)
其中S是观测到的图像,I是照射,R是反射。我们的目标就是去掉I,保留R。
关键理解:照射分量是“噪声”,反射分量是“信号”。去光照校正,本质上就是估计并去除I,得到干净的R。
4.2 单尺度Retinex(SSR)
SSR是最基础的版本。做法很简单:用高斯滤波器估计照射分量,然后在对数域做减法。
为什么取对数?因为乘法变加法,好处理。公式如下:
log R(x,y) = log S(x,y) - log [G(x,y) * S(x,y)]
其中G是高斯核,*是卷积操作。
这里有个坑——高斯核的尺度σ怎么选?
我个人习惯用σ=80作为起点。太小了,细节保留多但光照校正不足;太大了,校正彻底但图像发灰。我在项目中遇到过一张腹部CT,σ取50时血管细节很清楚,但背景不均匀;调到120后背景均匀了,血管反而模糊了。最后折中用了80。
| σ值 | 效果 | 适用场景 |
|---|---|---|
| 10-30 | 细节保留好,光照校正弱 | 光照均匀的X光片 |
| 50-100 | 平衡较好 | 大多数医学图像 |
| 150-300 | 光照校正强,图像偏灰 | 严重光照不均的MRI |
我的经验:SSR适合光照变化比较平滑的场景。如果图像里有明显的阴影边界(比如内窥镜图像),SSR会在边界处产生光晕伪影。这时候就要考虑多尺度了。
4.3 多尺度Retinex(MSR)
MSR的思路很简单:别只用一个尺度,用多个尺度加权平均。这样既能保留细节,又能做好光照校正。
公式长这样:
log R_msr = Σ w_i · [log S - log (G_i * S)]
其中w_i是权重,通常取1/N(N是尺度个数)。
我一般用3个尺度:小(σ=15)、中(σ=80)、大(σ=250)。权重各1/3。你想想看,小尺度抓细节,大尺度抓光照,中尺度做过渡——配合起来效果就稳了。
注意:MSR虽然效果好,但计算量是SSR的N倍。处理一张2048×2048的DR图像,3个尺度大概需要2-3秒。如果做实时处理,得考虑GPU加速或者降采样。
我曾经在项目里用MSR处理一批眼底照片。单尺度SSR总是要么血管看不清,要么背景不均匀。换成MSR后,血管和背景都满意了。但有个新问题——颜色偏灰了。
为什么会这样?因为MSR对三个通道独立处理,破坏了通道间的比例关系。这就引出了MSRCR。
4.4 带色彩恢复的MSR(MSRCR)
MSRCR在MSR的基础上加了一个色彩恢复因子C。目的是把颜色“拉回来”。
公式:
R_msrcr(x,y) = C_i(x,y) · R_msr_i(x,y)
其中色彩恢复因子C_i的计算方式:
C_i(x,y) = β · log [α · I_i(x,y) / Σ I_j(x,y)]
α控制非线性强度,β是增益系数。我常用的参数是α=125,β=46。
嗯,这里要注意:α不能太大,否则颜色恢复过度,图像会偏红或偏蓝。我刚开始调参数时,α设到200,结果一张正常的胃镜图像变成了紫红色,被临床医生吐槽了半天。
MSRCR vs MSR vs SSR 对比:
- SSR:速度快,单尺度,适合光照均匀场景
- MSR:多尺度融合,细节和光照都照顾到,但颜色偏灰
- MSRCR:在MSR基础上恢复颜色,效果最自然,但参数多、调参麻烦
4.5 三种方法的流程对比
下面这张图展示了SSR、MSR、MSRCR的核心流程差异:
4.6 代码实现要点
下面给出一段MSRCR的核心实现。我习惯用Python+OpenCV:
import cv2
import numpy as np
def msrcr(img, sigma_list=[15, 80, 250], alpha=125, beta=46, G=5, b=25):
"""
MSRCR实现
img: 输入BGR图像,float类型,范围[0,1]
"""
img_log = np.log(img + 1e-6)
msr = np.zeros_like(img_log)
for sigma in sigma_list:
blur = cv2.GaussianBlur(img, (0,0), sigmaX=sigma)
blur_log = np.log(blur + 1e-6)
msr += (img_log - blur_log) / len(sigma_list)
# 色彩恢复
sum_rgb = np.sum(img, axis=2, keepdims=True) + 1e-6
C = beta * (np.log(alpha * img + 1e-6) - np.log(sum_rgb))
msrcr = C * msr
# 增益和偏移
msrcr = G * (msrcr - b)
msrcr = np.clip(msrcr, 0, 1)
return msrcr
调参建议:sigma_list我固定用[15,80,250],基本覆盖大多数医学图像。α和β是色彩恢复的“油门”和“刹车”——α越大颜色越浓,β越大恢复力度越强。G和b是最后的对比度拉伸,我一般G=5, b=25起步,效果不满意再微调。
4.7 避坑指南
我曾经在项目中踩过几个坑,分享给你:
- 对数域负值问题:log(0)是负无穷。一定要加小常数ε(1e-6),否则图像会出现黑点。
- 颜色过饱和:MSRCR处理后的图像有时颜色太艳。可以适当降低β值,或者最后做一次饱和度调整。
- 噪声放大:Retinex本质上是高通滤波,会放大噪声。如果原图噪声大,建议先做一次轻度去噪。
- 边界伪影:高斯滤波在图像边界会出问题。可以用镜像填充(BORDER_REFLECT)缓解。
重要提醒:Retinex不是万能的。对于严重过曝或欠曝的图像,Retinex效果有限。这时候需要先做直方图拉伸或者伽马校正,再用Retinex做精细调整。顺序别搞反了。
好了,以上就是Retinex从SSR到MSRCR的完整脉络。说白了,核心就一句话:把图像拆成光照和反射,干掉光照,留下反射。至于用几个尺度、要不要恢复颜色,看你的具体场景来定。