4. 坏点校正(DPC):坏点类型、检测与替换策略

坏点校正,简称 DPC,是 RAW 域预处理里绕不开的一环。我刚开始做 ISP 的时候,觉得这步挺简单的——不就是把坏点抹掉吗?后来踩了几个坑才发现,坏点处理的好坏,直接决定了后面去噪、锐化的效果。处理不好,整张图就毁了。

说白了,坏点就是传感器上那些「不听话」的像素。它们要么一直亮着,要么一直暗着,要么响应曲线跟别人不一样。你想想看,几千万个像素点里混进去几个这样的「刺头」,拍出来的照片就会出现亮点、暗点或者彩点。

4.1 坏点类型:静态 vs 动态

坏点分两类,处理方式完全不同。我习惯先分清楚再动手。

类型 成因 表现 处理时机
静态坏点 制造缺陷、物理损伤 固定位置,始终异常 出厂标定,一次处理
动态坏点 温度、增益、老化 随机出现,时好时坏 实时检测,逐帧处理

静态坏点,就是传感器出厂时就带有的缺陷。比如某个像素短路了,输出永远是最大值,拍出来就是个白点。这类坏点位置固定,可以在产线标定时记录下来,生成一张坏点列表(Defect Pixel List)。

动态坏点就麻烦多了。它们受温度影响很大——相机拍久了,传感器发热,一些本来正常的像素就开始「抽风」。我记得有一次做手机摄像头调试,室温下跑得好好的,放到高温箱里一测,满屏的闪烁亮点。这就是典型的动态坏点。

我的经验: 静态坏点用查表法就够了。动态坏点才是 DPC 算法的核心战场。很多方案只做静态校正,结果用户拍视频时间一长,画面就开始冒亮点。

4.2 坏点检测算法

检测坏点,核心思路就一句话:看这个像素跟邻居比,是不是「不正常」。具体怎么比,各家有各家的招。

4.2.1 基于邻域统计的检测

这是最基础的方法。对每个像素,取它周围 3x3 或 5x5 的窗口,计算均值或中值。如果当前像素值跟这个统计值差太多,就判为坏点。

// 伪代码:基于中值的坏点检测
for each pixel (x, y) {
    window = get_neighbors(x, y, 3x3);
    median = compute_median(window);
    diff = abs(pixel(x,y) - median);
    if (diff > threshold) {
        mark_as_defect(x, y);
    }
}

这个方法简单,但阈值不好设。设小了误检多,设大了漏检多。我早期做的一个项目就吃过这个亏——阈值设得太严,把正常的纹理细节当坏点给抹了,画面糊成一片。

4.2.2 基于方向梯度的检测

这个方法更聪明一些。它考虑到了图像边缘的情况——如果像素正好在边缘上,跟邻居差异大是正常的,不能判为坏点。

具体做法是:计算像素在水平、垂直、两个对角线方向上的梯度。如果四个方向上的梯度都很大,那这个像素大概率是坏点。如果只有一个方向梯度大,那可能是边缘。

关键区别: 基于邻域统计的方法容易把边缘误检为坏点。基于方向梯度的方法能区分「边缘」和「坏点」,准确率高很多。

4.2.3 自适应阈值

固定阈值的问题很明显——暗区噪声大,亮区噪声小,一刀切肯定不行。自适应阈值会根据局部亮度调整检测门限。

我常用的做法是:先估算当前区域的噪声水平,然后阈值 = 噪声标准差 × 系数。暗区噪声大,阈值自动抬高,减少误检;亮区噪声小,阈值降低,保证不漏检。

方法 优点 缺点 适用场景
邻域统计 实现简单,速度快 边缘误检,阈值难调 低端芯片,实时性要求高
方向梯度 准确率高,保护边缘 计算量大 中高端 ISP
自适应阈值 适应不同亮度场景 需要噪声估计 复杂光照环境

4.3 坏点替换策略

检测到坏点之后,怎么把它「修好」?不是简单地抹掉,而是用合理的值填回去。

4.3.1 中值替换

取邻域像素的中值来替换坏点。优点是鲁棒性好,不受单个异常值影响。缺点是会损失一些细节。

4.3.2 均值替换

取邻域像素的均值。优点是平滑效果好,缺点是对噪声敏感。如果邻域里恰好也有坏点,均值会被拉偏。

4.3.3 梯度导向插值

先判断边缘方向,然后沿着边缘方向取像素做插值。这样能最大程度保留图像细节。我比较推荐这个方法,尤其是在纹理丰富的区域。

// 梯度导向插值示例
if (horizontal_gradient < vertical_gradient) {
    // 水平方向更平滑,沿水平插值
    pixel(x,y) = (pixel(x-1,y) + pixel(x+1,y)) / 2;
} else {
    // 垂直方向更平滑,沿垂直插值
    pixel(x,y) = (pixel(x,y-1) + pixel(x,y+1)) / 2;
}
注意: 替换时一定要考虑 Bayer 格式。同一个颜色通道的像素才能用来做替换。跨通道替换会引入颜色错误,修完坏点反而出现彩点。

4.4 自适应校正策略

实际工程中,坏点校正不是「一刀切」的。我习惯把策略分成几个等级,根据场景动态切换。

  • 低增益场景: 噪声小,坏点容易识别。用严格的检测阈值,配合梯度导向插值。
  • 高增益场景: 噪声放大,坏点淹没在噪声里。适当放宽检测阈值,用中值替换更安全。
  • 高温场景: 动态坏点增多。加强检测力度,同时提高替换的平滑度。
  • 边缘区域: 保护纹理,用方向梯度检测 + 梯度导向插值。
  • 平坦区域: 简单中值替换即可,不需要复杂计算。

我曾经在一个安防摄像头项目里,把自适应策略做成了查表——根据增益和温度查表决定检测阈值和替换方式。效果比单一策略好很多,而且计算量没增加多少。

4.5 DPC 全流程框架

下面这张图是我自己总结的 DPC 处理流程,从检测到替换,每一步都有对应的策略选择。

DPC 自适应校正全流程 RAW 输入 坏点类型判断(静态/动态) 静态坏点:查表定位 动态坏点:实时检测 邻域统计 / 方向梯度 自适应阈值检测 替换策略:中值/均值/梯度导向 校正后 RAW 输出

这张图把整个流程串起来了。从输入 RAW 图开始,先判断坏点类型,静态的直接查表,动态的走实时检测。检测方法根据场景选,替换策略也根据需求定。最后输出校正后的 RAW 数据。

避坑指南: 我曾经在一个项目里把 DPC 放在去噪之后做,结果坏点被去噪算法「扩散」了,修都修不干净。正确的顺序应该是:DPC 先做,再去噪。坏点不先处理掉,后面的算法都会被带偏。

嗯,坏点校正这块,说白了就是「先发现,再修复」。发现的方法决定了准确率,修复的方法决定了画质。两者配合好了,才能做到既去掉坏点,又不损伤正常细节。我做了这么多年 ISP,DPC 始终是我不敢掉以轻心的一环——它太基础了,基础到出了问题往往要排查很久才能找到根因。