第四章 坏点校正(DPC)
坏点校正,简称 DPC。说白了,就是处理传感器上那些「不听话」的像素点。
每个传感器都有坏点,这是物理规律。你买再贵的 sensor,也逃不掉。我刚入行时天真地以为高端 sensor 就没坏点,结果被产线数据狠狠打了脸。
核心认知: 坏点不是「有没有」的问题,而是「有多少」和「怎么修」的问题。
4.1 静态坏点检测
静态坏点,就是出厂时就存在的坏点。位置固定,亮度偏差也固定。这类坏点最好处理。
检测方法其实很直观:
- 全黑帧检测: 盖上镜头盖,拍一张全黑图。正常像素输出应该接近 0,坏点会亮得刺眼。
- 全白帧检测: 对着均匀光源,拍一张过曝图。正常像素输出接近饱和,坏点会暗一块。
- 差值法: 连续拍多帧,计算每个像素的方差。方差异常的,基本就是坏点。
我习惯在产线标定阶段做静态检测。把坏点坐标存成一张表,后面直接用。
小技巧: 静态坏点表建议用 16 位坐标存储,别用 8 位。sensor 分辨率越来越大,8 位根本不够用。我吃过这个亏。
4.2 动态坏点校正
动态坏点,是使用过程中新产生的坏点。温度、电压、老化,都会催生新坏点。这类坏点位置不固定,检测起来麻烦很多。
动态检测的核心思路:看邻居。
一个正常像素,它的值和周围像素应该差不多。如果某个像素和周围 8 个邻居差距太大,那它大概率是坏点。
具体做法:
- 取当前像素 P(i,j) 和它的 8 邻域像素值
- 计算邻域的中值或均值
- 如果 P(i,j) 与中值的差超过阈值 T,标记为坏点
- 用邻域中值替换坏点
// 动态坏点检测伪代码
for each pixel (i, j) {
neighbors = get_8_neighbors(i, j);
median = compute_median(neighbors);
diff = abs(pixel(i,j) - median);
if (diff > threshold) {
pixel(i,j) = median; // 替换
bad_pixel_count++;
}
}
阈值 T 怎么定?我建议用自适应阈值。暗区阈值小一点,亮区阈值大一点。为什么?因为亮区的噪声本身就大,阈值太小会把正常像素误判为坏点。
避坑指南: 我曾经把阈值设得太死,结果在暗光场景下把大量正常像素当坏点修了。画面变得像磨皮过度一样,细节全没了。阈值一定要留余量。
4.3 坏点校正的工程实践
理论说完了,聊聊实际干活时要注意的事。
4.3.1 坏点校正的流程
4.3.2 坏点替换策略
| 替换方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 中值替换 | 抗噪性强,边缘保持好 | 计算量大一点 | 大多数场景 |
| 均值替换 | 计算快 | 容易模糊细节 | 平坦区域 |
| 插值替换 | 边缘方向性好 | 实现复杂 | 纹理丰富区域 |
| 相邻像素复制 | 最快 | 可能产生条纹 | 实时性要求极高 |
我个人习惯用中值替换。虽然慢一点,但效果最稳。你想想看,一个坏点周围如果有边缘,均值会模糊边缘,中值不会。
4.3.3 工程中的坑
- 坏点扩散: 一个坏点修完,可能会影响周围像素的检测。我建议先修静态坏点,再做动态检测。
- 阈值调优: 别指望一组阈值打天下。暗光、正常光、强光,阈值要分开调。
- 性能优化: 动态检测要遍历每个像素,1080P 的图就是 200 万次计算。用 NEON 指令集或者 DSP 加速,别在 CPU 上硬算。
- 边界处理: 图像边缘的像素没有完整的 8 邻域。我一般直接跳过边缘 1 行 1 列,或者用镜像填充。
血的教训: 我曾经在某个项目里,动态坏点检测没做边界处理。结果图像四边出现了一圈奇怪的条纹。排查了两天才发现是边界像素的邻域取到了无效值。从那以后,边界处理我都是第一个写。
4.3.4 坏点校正的评估指标
怎么判断 DPC 做得好不好?看三个指标:
- 漏检率: 该修的坏点没修。画面会有亮点或暗点。
- 误检率: 不该修的像素被修了。画面细节丢失。
- 处理速度: 每秒能处理多少帧。实时系统要求至少 30fps。
这三个指标是互相矛盾的。漏检率低了,误检率可能就高了。怎么平衡?看产品需求。安防监控可以容忍一点误检,但医疗影像不行。
我的经验: 先保证漏检率低于 0.01%,再慢慢优化误检率。用户看到画面有亮点会直接退货,但细节稍微模糊一点,大部分人看不出来。
嗯,坏点校正就讲到这里。记住一句话:坏点校正不是把坏点修没,而是让用户看不出来有坏点。这两者区别很大,你品品。