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 流程分三步走:
- 标定阶段:产线拍摄均匀灰卡或黑场,统计每个像素的响应,标记静态坏点,生成坏点列表。
- 检测阶段:对每一帧 raw 图,用均值法或中值法检测动态坏点。
- 校正阶段:对检测到的坏点,用邻域像素插值替换。
校正方式通常有两种:
- 线性插值:取上下左右四个方向的好像素,加权平均。
- 方向插值:先判断边缘方向,沿边缘方向插值,避免模糊细节。
注意:校正时一定要跳过坏点本身,只用好像素做插值。否则坏点会“传染”给邻居。
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 这部分内容就这些。核心就是:静态靠标定,动态靠算法,硬件上管好 buffer 和排序。你想想看,如果 sensor 出厂时坏点标定没做好,后面 ISP 再怎么调也救不回来。所以 DPC 是 ISP 流水线的第一道防线,马虎不得。