4. 坏点校正(DPC):静态与动态坏点检测、校正算法选择、参数调优

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

我刚开始调 ISP 时,觉得 DPC 不就是个中值滤波嘛,有啥好调的?直到有一次,一个项目在夜视模式下满屏都是白点,客户直接退货。嗯,从那以后,我再也不敢小看 DPC 了。

坏点分两种:静态坏点和动态坏点。静态坏点出厂就固定了,动态坏点会随温度、增益变化而出现。处理方式完全不同。

核心观点: DPC 不是简单的滤波,而是 sensor 健康度的「体检医生」。调好了,画面干净;调不好,要么坏点漏检,要么好点被误伤。
坏点校正(DPC)知识体系 坏点校正 DPC 静态坏点检测 动态坏点检测 出厂坏点表 在线标定 亮度梯度法 邻域统计法 校正算法:中值滤波 · 线性插值 · 自适应加权 参数调优:阈值 · 窗口大小 · 增益补偿 检测是前提 校正是手段

4.1 静态坏点检测

静态坏点,就是 sensor 出厂时就带有的死点、亮点。这些点位置固定,不随环境变化。

检测方法主要有两种:

  • 出厂坏点表: sensor 厂商会提供一个坏点坐标列表。直接加载就行,省事。但我遇到过,有些厂商给的列表不全,漏了不少点。
  • 在线标定: 盖上镜头盖,拍一张全黑图,再拍一张均匀光照图。通过对比,找出异常点。我建议项目量产前,至少做一次在线标定,跟出厂表做交叉验证。
我的习惯: 静态坏点表我会保留两份——一份是厂商提供的原始表,一份是产线标定后生成的补充表。两份合并使用,漏检率基本能降到 0.1% 以下。

4.2 动态坏点检测

动态坏点就麻烦多了。它们平时好好的,温度一高、增益一拉,就冒出来了。你想想看,晚上监控画面突然出现一堆闪烁的白点,多吓人。

常用的检测算法:

算法 原理 优点 缺点
亮度梯度法 计算像素与邻域的梯度差,超过阈值判为坏点 计算快,硬件友好 对纹理边缘敏感,容易误检
邻域统计法 统计 5x5 或 7x7 窗口内的均值、方差,偏离过大判为坏点 鲁棒性好,抗噪能力强 计算量大,需要软件优化
自适应阈值法 根据局部亮度动态调整检测阈值 适应性强,暗场亮场都能用 参数多,调起来费劲

我个人比较喜欢用邻域统计法。虽然慢一点,但准确率高。我曾经在一个项目中,用亮度梯度法死活调不好,换成邻域统计法后,问题迎刃而解。

4.3 校正算法选择

检测到坏点后,怎么修?常见的有三种:

  1. 中值滤波: 用邻域像素的中值替换坏点。简单粗暴,但对边缘有损伤。
  2. 线性插值: 根据坏点周围的好点,做双线性或双三次插值。效果细腻,但计算量大。
  3. 自适应加权: 结合中值和插值的优点,根据局部纹理复杂度动态选择权重。我目前的主力方案。
注意: 校正算法不是越复杂越好。在低算力平台上,中值滤波+小窗口往往比复杂的自适应算法更稳定。我曾经在某个海思方案上,因为用了太复杂的插值,导致帧率直接掉了一半。

4.4 参数调优实战

参数调优,是 DPC 最磨人的部分。我总结了一套「三步走」策略:

  • 第一步:定阈值。 先拍一张均匀灰卡图,统计像素分布,确定正常范围。阈值设得太严,好点被误杀;设得太松,坏点漏网。我一般取均值 ± 3σ 作为初始值。
  • 第二步:选窗口。 3x3 窗口最快,但检测能力有限;5x5 窗口是主流;7x7 窗口更准,但边缘细节容易模糊。我建议从 5x5 开始调。
  • 第三步:增益补偿。 动态坏点在高增益下更明显。需要根据 ISP 的增益值,动态调整检测阈值。增益每增加 6dB,阈值可以放宽 10%-15%。

下面是一个简单的参数配置示例:

// DPC 参数结构体示例
typedef struct {
    uint8_t  enable;           // 0:关闭, 1:开启
    uint8_t  static_table_en;  // 静态坏点表使能
    uint8_t  dynamic_detect_en;// 动态检测使能
    uint16_t static_threshold; // 静态检测阈值, 默认 40
    uint16_t dynamic_threshold;// 动态检测阈值, 默认 60
    uint8_t  window_size;      // 检测窗口: 3,5,7
    uint8_t  correction_mode;  // 0:中值, 1:插值, 2:自适应
    uint16_t gain_compensation;// 增益补偿系数, 默认 100
} dpc_params_t;

// 初始化推荐值
dpc_params_t dpc_cfg = {
    .enable = 1,
    .static_table_en = 1,
    .dynamic_detect_en = 1,
    .static_threshold = 40,
    .dynamic_threshold = 60,
    .window_size = 5,
    .correction_mode = 2,
    .gain_compensation = 100
};
避坑指南: 我曾经在一个项目中,把动态阈值设得太低,结果把正常的星空画面里的星星全当坏点给抹掉了。后来我加了一个「亮度保护」逻辑——如果像素亮度超过某个上限,就不做 DPC 处理。这样星星保住了,坏点也没漏。

4.5 调优效果评估

调得好不好,不能光靠眼睛看。我一般用三个指标来衡量:

指标 定义 合格标准
坏点漏检率 未被检测到的坏点 / 总坏点数 < 0.5%
好点误检率 被误判为坏点的好点 / 总好点数 < 0.1%
PSNR 损失 DPC 处理前后的峰值信噪比变化 < 0.5dB

嗯,这三个指标都达标了,DPC 才算调到位。记住,坏点校正不是越强越好,而是在「去坏点」和「保细节」之间找到平衡点。