4. 坏点校正(DPC):静态坏点与动态坏点、坏点检测算法、校正流程与硬件实现

坏点校正,简称 DPC(Defect Pixel Correction)。

说白了,就是 sensor 上有些像素点“坏了”。它们不听话,要么一直亮,要么一直暗,要么随机乱跳。如果不处理,拍出来的照片上就会出现固定位置的亮点、暗点,或者闪烁的噪点。

我刚开始做 ISP 的时候,第一次拿到 raw 图,满屏的亮点,还以为是 sensor 坏了。后来才发现,坏点其实是 sensor 的“出厂标配”,只是数量多少的问题。

4.1 坏点的两种类型:静态 vs 动态

坏点分两类,处理思路完全不同。

类型 成因 表现 处理方式
静态坏点 制造缺陷、物理损伤 固定位置,始终偏亮或偏暗 标定 + 查表替换
动态坏点 温度、增益、老化 随机出现,时好时坏 实时检测 + 动态校正

关键区别:静态坏点位置固定,可以在产线标定一次,之后直接查表。动态坏点位置不固定,必须靠算法实时抓出来。

我在项目中遇到过一种情况:sensor 在低温下坏点很少,一到夏天高温,坏点数量翻倍。这就是典型的动态坏点受温度影响。所以只做静态校正是不够的。

4.2 坏点检测算法:均值法 vs 中值法

检测坏点的核心思路很简单:看这个像素跟周围邻居比,是不是“不正常”。

4.2.1 均值法

取当前像素周围 3x3 或 5x5 邻域的均值,跟当前像素值比较。如果差值超过阈值,就判为坏点。

// 伪代码:均值法坏点检测
uint16_t mean = (pixel[0] + pixel[1] + pixel[2] + 
                 pixel[3] + pixel[5] + 
                 pixel[6] + pixel[7] + pixel[8]) / 8;
int16_t diff = abs(pixel[4] - mean);
if (diff > threshold) {
    pixel[4] = bad_pixel;  // 标记为坏点
}

优点:计算简单,硬件开销小。
缺点:如果邻域内有其他坏点,均值会被拉偏,导致误检或漏检。

我曾经踩过的坑:用均值法做动态检测,遇到坏点簇(连续几个坏点挨在一起),均值法直接失效。因为邻域里全是坏点,算出来的均值本身就是错的。

4.2.2 中值法

取邻域像素的中值,跟当前像素比较。中值对异常值不敏感,鲁棒性更好。

// 伪代码:中值法坏点检测
uint16_t neighbors[8] = {pixel[0], pixel[1], pixel[2], 
                         pixel[3], pixel[5], 
                         pixel[6], pixel[7], pixel[8]};
sort(neighbors, 8);  // 排序
uint16_t median = neighbors[4];  // 取中值
int16_t diff = abs(pixel[4] - median);
if (diff > threshold) {
    pixel[4] = bad_pixel;
}

优点:抗干扰能力强,适合动态坏点检测。
缺点:需要排序,硬件实现比均值法复杂。

我的建议:静态坏点用均值法就够了,因为位置已知,不需要实时检测。动态坏点推荐用中值法,虽然硬件成本高一点,但效果明显更好。

4.3 坏点校正流程

完整的 DPC 流程分三步走:

  1. 标定阶段:产线拍摄均匀灰卡或黑场,统计每个像素的响应,标记静态坏点,生成坏点列表。
  2. 检测阶段:对每一帧 raw 图,用均值法或中值法检测动态坏点。
  3. 校正阶段:对检测到的坏点,用邻域像素插值替换。

校正方式通常有两种:

  • 线性插值:取上下左右四个方向的好像素,加权平均。
  • 方向插值:先判断边缘方向,沿边缘方向插值,避免模糊细节。

注意:校正时一定要跳过坏点本身,只用好像素做插值。否则坏点会“传染”给邻居。

4.4 硬件实现要点

DPC 在硬件里通常放在 ISP 流水线的最前面,紧接在 BLC(黑电平校正)之后。为什么?因为坏点如果不先去掉,后面的去噪、去马赛克都会把坏点当成真实信号处理,越处理越糟糕。

硬件实现时,我一般关注这几个点:

  • Line Buffer:至少需要 3 行 buffer,才能做 3x3 邻域计算。5x5 需要 5 行。
  • 排序模块:中值法需要硬件排序器,通常用并行比较网络,3x3 邻域 8 个值排序,3 级比较器就能搞定。
  • 阈值自适应:阈值不能固定。增益高的时候,噪声也大,阈值要跟着放大。我习惯用增益乘以一个基值。
  • 坏点列表存储:静态坏点列表用 OTP 或 efuse 存储,上电直接加载到 SRAM 查表。
// 硬件友好的中值排序网络(3x3 邻域,8 个值)
// 第一级:两两比较
cmp(p0, p1) -> min01, max01
cmp(p2, p3) -> min23, max23
cmp(p4, p5) -> min45, max45
cmp(p6, p7) -> min67, max67
// 第二级:取四个最小值中的最大值,四个最大值中的最小值
// 第三级:得到中值
median = median_of_8(min01, min23, min45, min67, 
                     max01, max23, max45, max67);

硬件实现时容易忽略的问题:边界处理。图像边缘的像素没有完整的 3x3 邻域。我的做法是:边界像素不做动态检测,直接用静态坏点列表处理,或者复制边缘像素补齐邻域。

4.5 知识结构总览

下面这张图把 DPC 的整体流程和关键决策点串起来了,你可以对照着看。

坏点校正(DPC)知识结构图 Raw 图像输入 静态坏点标定 动态坏点检测 产线标定 → 坏点列表 均值法 / 中值法 + 阈值 查表替换坏点 邻域插值校正 校正后 Raw 输出 💡 硬件实现要点: Line Buffer ≥ 3行 | 排序网络并行比较 | 阈值随增益自适应 | 边界像素特殊处理

嗯,DPC 这部分内容就这些。核心就是:静态靠标定,动态靠算法,硬件上管好 buffer 和排序。你想想看,如果 sensor 出厂时坏点标定没做好,后面 ISP 再怎么调也救不回来。所以 DPC 是 ISP 流水线的第一道防线,马虎不得。