4. RAW域处理(二):坏点校正(DPC)静态与动态方法、去噪(NR)在RAW域的应用
好,咱们接着聊RAW域处理。上一章讲了黑电平校正和Lens Shading,今天这两个话题——坏点校正和去噪,可以说是ISP流水线上最“磨人”的两个模块。为什么这么说?因为它们在RAW域做,效果最直接,但坑也最多。
我个人习惯把RAW域比作“毛坯房”。坏点就是墙上的钉子眼,噪声就是墙面的颗粒感。你必须在刮腻子(做Demosaic)之前把这些处理好,否则后面只会越放越大。
4.1 坏点校正(DPC)——静态与动态方法
坏点,说白了就是传感器上那些“不听话”的像素点。它们要么永远亮着(亮点),要么永远黑着(暗点),要么响应曲线跟别人不一样。我见过最夸张的一次,一颗坏点在暗光下直接让整张照片多了一个“月亮”,调试时找了半天才发现。
4.1.1 静态坏点校正
静态DPC,也叫“标定法”。工厂出厂前,用均匀光源拍一张全黑和一张全灰图,把坏点的坐标记下来,烧进OTP里。ISP运行时直接查表替换。
核心思路: 提前知道坏点位置,直接“拔掉”。
替换算法很简单,一般用周围同色通道的像素做插值:
// 以Bayer RGGB为例,坏点在G位置
// 取上下左右四个G像素的平均值
pixel_out = (G_up + G_down + G_left + G_right) / 4;
但这里有个坑——边缘像素怎么办?我曾经在项目里遇到传感器边缘区域坏点密集,直接平均插值会引入模糊。后来我改成了中值滤波,效果好了不少。
| 方法 | 优点 | 缺点 |
|---|---|---|
| 均值插值 | 计算快,硬件友好 | 边缘模糊,对密集坏点效果差 |
| 中值插值 | 保留边缘,抗孤立噪点 | 计算量稍大 |
| 梯度导向插值 | 细节保留最好 | 硬件实现复杂 |
我的经验: 静态DPC适合做“底线防守”。把那些死透了的坏点(比如永远输出0或1023)先干掉。但别指望它解决所有问题——传感器会老化,新坏点会不断冒出来。
4.1.2 动态坏点校正
动态DPC,也叫“实时检测法”。ISP在每一帧里实时判断当前像素是不是坏点,如果是,就替换掉。
判断逻辑通常是这样的:
- 计算局部梯度: 比较当前像素与周围同色像素的差值。
- 设定阈值: 如果差值超过某个阈值(比如当前亮度的20%),就标记为候选坏点。
- 二次确认: 检查这个像素在连续几帧里是否都异常,防止把真实细节误杀。
你想想看,动态DPC最难的是什么?是阈值怎么设。设太严,真实细节被磨掉;设太松,坏点漏网。我调试过一个项目,在室内灯光下效果完美,一到户外强光下,树叶边缘全被当成坏点给抹了……后来我加了一个亮度自适应阈值,问题才解决。
注意: 动态DPC千万不要在纹理密集区域(比如草地、布料)开太强。我曾经吃过这个亏,画面看起来像被橡皮擦擦过一样,客户直接打回。
4.1.3 静态 vs 动态,怎么选?
我的建议是:两者结合。静态DPC处理已知的固定坏点,动态DPC处理新出现的坏点。在资源受限的嵌入式平台上,可以只做静态DPC + 简单的动态检测,但一定要给动态部分留出调参空间。
4.2 去噪(NR)在RAW域的应用
噪声这东西,是传感器的“原罪”。暗光下尤其明显。为什么要在RAW域做去噪?因为噪声在RAW域还是“原始状态”,没有经过插值和色彩变换的放大。你想想看,如果在RGB域做,噪声已经被Demosaic扩散到相邻像素了,处理起来事倍功半。
4.2.1 RAW域噪声的特点
RAW域的噪声主要有两种:
- 光子散粒噪声(Shot Noise): 跟信号强度有关,信号越强噪声越大。符合泊松分布。
- 读出噪声(Read Noise): 跟信号无关,是电路本身的热噪声。符合高斯分布。
我习惯把噪声模型简化为:Noise = sqrt(signal * gain) + read_noise。这个公式虽然粗糙,但足够指导调参了。
4.2.2 常用的RAW域去噪方法
在嵌入式平台上,计算资源有限,我们一般用以下几种方法:
| 方法 | 原理 | 适用场景 | 硬件代价 |
|---|---|---|---|
| 双边滤波 | 同时考虑空间距离和像素值差异 | 边缘保留去噪 | 中等 |
| 非局部均值(NLM) | 在图像中搜索相似块做加权平均 | 强噪声、平滑区域 | 高 |
| 小波阈值去噪 | 变换到小波域,软阈值处理 | 高斯噪声 | 高 |
| BM3D | 块匹配 + 3D变换协同滤波 | 极致去噪质量 | 极高(不适合实时) |
我的推荐: 在嵌入式平台上,双边滤波是性价比最高的选择。我做过一个海思平台的方案,双边滤波只占不到5%的CPU资源,就能把ISO 3200的噪声压到可用水平。
4.2.3 双边滤波在RAW域的实现要点
双边滤波的公式很简单:
// 伪代码:双边滤波
for each pixel p:
sum_weight = 0
sum_value = 0
for each neighbor q in window:
w_s = exp(-|p - q|^2 / (2 * sigma_s^2)) // 空间权重
w_r = exp(-|I(p) - I(q)|^2 / (2 * sigma_r^2)) // 值域权重
w = w_s * w_r
sum_weight += w
sum_value += w * I(q)
I_out(p) = sum_value / sum_weight
但在RAW域,有几个坑要注意:
- Bayer格式问题: 双边滤波只能在同色通道内做。你不能拿R像素去平滑G像素,否则会引入颜色伪影。
- sigma_r参数: 这个参数跟ISO和增益强相关。我一般会做一个ISO到sigma_r的映射表,不同增益用不同强度。
- 边缘保护: 双边滤波虽然能保边,但在纹理区域还是会有损失。我建议在纹理检测结果上做强度调制——纹理强的地方少去噪,平滑区域多去噪。
避坑指南: 我曾经在调试时把sigma_r设得太大,结果暗部噪声是没了,但亮部边缘出现了“光晕”现象。后来我才意识到,sigma_r应该跟局部亮度挂钩——暗部用大sigma_r,亮部用小sigma_r。
4.2.4 去噪强度与细节保留的平衡
这是ISP调试里永恒的难题。去噪太强,画面像塑料;去噪太弱,画面像砂纸。我的经验是:
- 分频段处理: 高频噪声用弱去噪,低频噪声用强去噪。可以用拉普拉斯金字塔分解。
- 亮度自适应: 暗部噪声明显,去噪强度大;亮部噪声不明显,去噪强度小。
- 边缘保护: 检测到边缘的地方,降低去噪强度甚至跳过。
一个小技巧: 调试时先盯着暗部看。如果暗部噪声能接受,亮部一般没问题。反过来,如果为了亮部细节把去噪调弱,暗部往往就崩了。
嗯,RAW域的DPC和NR就聊到这儿。这两个模块是ISP画质的基石,基础打好了,后面的Demosaic和色彩校正才能发挥出真正的实力。调试时别急,一点点调,多看看极端场景(暗光、高ISO、纯色块),你会发现很多意想不到的问题。