3. 坏点校正(DPC):静态坏点检测、动态坏点校正、坏点校正参数调优

坏点校正,简称 DPC。说白了,就是处理 sensor 上那些「不听话」的像素点。

我刚开始做 ISP 调优那会儿,遇到过一台 IPC,画面一到晚上就全是白点。客户以为是灰尘,擦了又擦。结果呢?是 sensor 本身的坏点。从那以后,我对 DPC 这块就特别上心。

坏点分两种:静态坏点和动态坏点。静态坏点出厂就定了,位置固定。动态坏点呢?会随着温度、增益变化冒出来。处理方式完全不同。

核心观点: 坏点校正不是简单的「把坏点抹掉」。抹得太狠,细节没了;抹得太轻,坏点还在。关键是找到那个平衡点。

3.1 静态坏点检测

静态坏点检测,一般在 sensor 出厂时做。厂家会提供一个坏点列表(Defect Pixel List),里面记录了每个坏点的坐标和类型。

但说实话,这个列表不一定准。我遇到过好几次,厂家给的列表不全,或者干脆就没给。这时候就得自己动手。

静态坏点检测的常用方法:

  1. 全黑帧检测: 盖上镜头盖,拍一张全黑图。亮度明显高于周围像素的点,就是坏点。
  2. 均匀亮场检测: 对着均匀光源拍一张灰卡图。亮度明显偏离平均值的点,也是坏点。
  3. 多帧对比: 连续拍多张图,位置固定的异常点,基本就是坏点。

我个人习惯用全黑帧 + 多帧对比结合。先快速定位,再交叉验证,误检率能降到很低。

小技巧: 静态坏点列表建议存成表格,方便后续查表校正。格式可以参考下面这样:
列名 说明 示例值
row 行坐标 1024
col 列坐标 768
type 坏点类型(0=亮点,1=暗点) 0
gain 校正增益 1.5

3.2 动态坏点校正

动态坏点,才是真正让人头疼的。它不固定,温度一高、增益一拉,就冒出来了。

动态坏点校正的核心思路:实时检测 + 实时替换。

具体怎么做?

  • 检测: 用当前像素和周围像素比较。如果差异超过阈值,就认为是坏点。
  • 替换: 用周围像素的中值或均值,替换掉坏点。

这里有个坑。阈值设得太低,会把正常细节当坏点抹掉。设得太高,坏点又漏过去了。

我曾经踩过的坑: 有一次为了追求画面干净,把动态坏点阈值压得很低。结果呢?画面上的细纹理全没了,客户投诉说「画面像油画」。后来我才明白,动态坏点校正必须和降噪、锐化配合着调。

动态坏点校正的流程,我画了张图,你看一眼就明白了:

动态坏点校正流程图 原始像素数据 坏点检测 与邻域像素比较,差异超阈值? 是坏点? 是 中值/均值替换 否 保留原始值 校正后像素数据 邻域窗口:3x3/5x5

3.3 坏点校正参数调优

参数调优,是 DPC 里最考验经验的环节。我给你列几个关键参数:

参数名 作用 调优建议
静态坏点阈值 判断像素是否为静态坏点 建议从 3σ 开始,逐步收紧
动态坏点阈值 判断像素是否为动态坏点 低增益场景可设 10-20,高增益场景放宽到 30-50
邻域窗口大小 用于中值/均值计算的像素范围 3x3 保留细节好,5x5 平滑能力强
校正强度 替换像素的比例 0.5-1.0 之间,建议从 0.8 开始

调优步骤,我一般这么走:

  1. 先静态,后动态。 先把静态坏点列表搞定,再处理动态的。
  2. 低增益场景调基线。 在低增益下把参数调好,保证画面干净。
  3. 高增益场景做验证。 拉高增益,看坏点有没有漏出来。
  4. 交叉验证。 换不同场景、不同温度,反复测。
我的个人习惯: 调参时,我会在画面上叠加一个「坏点标记层」。把检测到的坏点用红色标出来。这样一眼就能看出哪些是误检,哪些是漏检。比盯着像素值看效率高多了。

3.4 代码示例:动态坏点校正

下面是一段简化的动态坏点校正代码。实际工程中会复杂得多,但核心逻辑就是这样:

// 动态坏点校正 - 简化示例
void dpc_process(uint16_t *frame, int width, int height, int threshold) {
    for (int y = 1; y < height - 1; y++) {
        for (int x = 1; x < width - 1; x++) {
            // 取 3x3 邻域
            uint16_t center = frame[y * width + x];
            uint16_t neighbors[8] = {
                frame[(y-1)*width + (x-1)], frame[(y-1)*width + x], frame[(y-1)*width + (x+1)],
                frame[y*width + (x-1)],                         frame[y*width + (x+1)],
                frame[(y+1)*width + (x-1)], frame[(y+1)*width + x], frame[(y+1)*width + (x+1)]
            };

            // 计算中值
            uint16_t median = get_median(neighbors, 8);

            // 判断是否为坏点
            if (abs(center - median) > threshold) {
                // 替换为中值
                frame[y * width + x] = median;
            }
        }
    }
}
注意: 这段代码只做教学演示。实际产品中要考虑性能优化、边界处理、多通道并行等问题。千万别直接往产品里搬。

好了,坏点校正这块,核心就是这些。记住:静态坏点靠列表,动态坏点靠阈值。调参时多试、多看、多想,慢慢就有感觉了。