4. 坏点标定与校正:从检测到替换的完整链路

坏点校正,说白了就是给传感器“排雷”。

每个传感器出厂时都带着或多或少的瑕疵像素——有的永远亮着(白点),有的永远暗着(黑点),还有的时好时坏(闪点)。如果不处理,这些点会在最终图像上形成刺眼的噪点。我做过一个项目,客户投诉说夜景照片上全是“星星”,结果一查,就是坏点表没更新。

这一章,我们就把坏点检测、替换策略、标定流程和存储格式讲透。

4.1 坏点检测算法:静态 vs 动态

坏点检测分两大类:静态检测和动态检测。两者不是二选一,而是配合使用。

4.1.1 静态坏点检测

静态检测在产线或实验室完成。原理很简单:拍一张均匀亮度的画面(灰卡或暗场),然后找那些偏离正常值太多的像素。

  • 暗场检测:盖上镜头盖,拍全黑画面。正常像素输出值接近0(或黑电平),坏点会明显偏亮。
  • 亮场检测:拍均匀灰卡(通常18%灰),正常像素值应在均值附近,坏点会明显偏暗或偏亮。

判断标准通常是设定一个阈值。比如,像素值偏离均值超过3倍标准差,就标记为坏点。我个人习惯用5倍标准差,太严了会把正常像素误杀。

关键参数:静态检测的阈值需要根据传感器噪声水平动态调整。低噪声传感器可以用更严格的阈值。

4.1.2 动态坏点检测

动态检测在相机工作时实时进行。它不依赖预存表,而是根据当前帧的邻域信息判断。

常用的方法是:

  1. 对每个像素,取周围3x3或5x5邻域。
  2. 计算邻域像素的均值或中值。
  3. 如果当前像素值与邻域中值的差异超过阈值,就判定为坏点。

这里有个坑:边缘和纹理区域容易误判。我曾经在拍树叶时,动态检测把很多正常边缘点当成了坏点,结果图像糊成一片。后来加了梯度判断——如果邻域梯度大,就放宽阈值。

注意:动态检测不能替代静态标定。它只能捕捉使用过程中新产生的坏点(比如受温度或老化影响)。静态表是基础,动态是补充。

4.2 坏点替换策略:线性插值与中值滤波

检测到坏点后,必须用合理值替换。替换策略直接影响画质。

4.2.1 线性插值

线性插值利用坏点周围的好像素,按距离加权平均。以Bayer格式为例,坏点周围同通道的像素才有参考价值。

// 以G通道为例,坏点在位置(i,j)
// 取上下左右四个G像素
value = (G[i-1][j] + G[i+1][j] + G[i][j-1] + G[i][j+1]) / 4

优点:计算快,硬件友好。缺点:在边缘处会模糊细节。我建议只在平坦区域用线性插值。

4.2.2 中值滤波

中值滤波取邻域像素的中位数。它对孤立坏点效果极好,而且能保留边缘。

// 取3x3邻域,排序后取中间值
neighbors = [P[i-1][j-1], P[i-1][j], ..., P[i+1][j+1]]
sort(neighbors)
value = neighbors[4]  // 9个元素的中位数

中值滤波的代价是计算量大——排序操作在硬件上不便宜。我见过一些方案只在静态坏点表对应的位置做中值滤波,其他位置用线性插值,这样平衡了效果和性能。

策略 优点 缺点 适用场景
线性插值 计算快,硬件友好 边缘模糊 平坦区域、实时性要求高
中值滤波 保留边缘,抗孤立坏点 计算量大 纹理区域、静态坏点
混合策略 兼顾效果和性能 实现复杂 高端相机、ISP芯片
我的经验:实际项目中,我通常用中值滤波处理静态坏点表里的点,用线性插值处理动态检测出来的点。前者是“已知雷区”,后者是“疑似雷区”,处理力度不同。

4.3 坏点标定流程

完整的标定流程,我把它分成5步。每一步都有坑,我一个个说。

  1. 准备阶段:传感器预热到工作温度(通常5-10分钟)。温度不稳定,坏点会“漂移”。
  2. 暗场采集:盖上镜头盖,拍10-20帧全黑图像,取平均以消除随机噪声。
  3. 亮场采集:用均匀光源照射灰卡,拍10-20帧,同样取平均。
  4. 阈值判断:对每个像素,计算暗场和亮场的偏差。超过阈值的标记为坏点。
  5. 生成坏点表:将坏点坐标写入表文件,同时记录替换值(可选)。

这里有个容易被忽略的点:坏点会随温度变化。我做过一个车载摄像头项目,冬天标定的坏点表,夏天就不准了。后来我们做了多温度标定——在-20°C、25°C、60°C分别标定,生成三张表,运行时根据温度切换。

避坑指南:千万不要在传感器刚上电时就标定。热稳定前的坏点数量可能是稳定后的2-3倍。我曾经因为这个原因,标出了一张“假坏点”超多的表,校正后图像反而更差了。

4.4 坏点表存储格式

坏点表怎么存?格式选对了,能省不少存储空间和查找时间。

4.4.1 常见格式

  • 坐标列表:直接存坏点的(x, y)坐标。简单直观,但坏点多时文件大。
  • 位图掩码:每个像素用1bit表示好坏。固定大小,查找快,但坏点少时浪费空间。
  • 游程编码:对连续的好/坏像素进行编码。适合坏点成片出现的场景。
格式 存储效率 查找速度 适用场景
坐标列表 坏点少时高 O(n) 高质量传感器(坏点少)
位图掩码 固定大小 O(1) 硬件ISP、实时查找
游程编码 坏点成片时高 O(m) 老旧传感器、坏点簇

我个人推荐位图掩码。虽然占用固定空间(比如1200万像素需要1.5MB),但查找是O(1)的,硬件实现简单。坐标列表在嵌入式系统里查找太慢,每次都要遍历。

4.4.2 文件结构示例

// 坏点表文件格式(二进制)
// 文件头
uint32_t magic;          // 魔数,用于校验
uint32_t width;          // 传感器宽度
uint32_t height;         // 传感器高度
uint32_t format;         // 0=坐标列表, 1=位图掩码, 2=游程编码
uint32_t count;          // 坏点数量(仅坐标列表有效)
// 数据区
uint8_t  data[];         // 根据format决定内容
小技巧:在文件头里加一个校验和(CRC32),防止标定数据在传输或存储过程中损坏。我遇到过产线拷贝坏点表时文件截断,结果相机全屏闪点——从那以后,校验成了标配。

4.5 知识结构总览

下面这张图把坏点标定与校正的完整链路串起来了。你可以把它当作本章的“地图”。

坏点标定与校正知识结构 坏点检测 静态检测 动态检测 替换策略 线性插值 中值滤波 标定流程 暗场采集 亮场采集 坏点表存储格式 坐标列表 位图掩码 游程编码 核心原则 静态标定是基础,动态检测是补充 替换策略需根据场景选择,混合策略往往效果最佳