4. 坏点标定与校正:从检测到替换的完整链路
坏点校正,说白了就是给传感器“排雷”。
每个传感器出厂时都带着或多或少的瑕疵像素——有的永远亮着(白点),有的永远暗着(黑点),还有的时好时坏(闪点)。如果不处理,这些点会在最终图像上形成刺眼的噪点。我做过一个项目,客户投诉说夜景照片上全是“星星”,结果一查,就是坏点表没更新。
这一章,我们就把坏点检测、替换策略、标定流程和存储格式讲透。
4.1 坏点检测算法:静态 vs 动态
坏点检测分两大类:静态检测和动态检测。两者不是二选一,而是配合使用。
4.1.1 静态坏点检测
静态检测在产线或实验室完成。原理很简单:拍一张均匀亮度的画面(灰卡或暗场),然后找那些偏离正常值太多的像素。
- 暗场检测:盖上镜头盖,拍全黑画面。正常像素输出值接近0(或黑电平),坏点会明显偏亮。
- 亮场检测:拍均匀灰卡(通常18%灰),正常像素值应在均值附近,坏点会明显偏暗或偏亮。
判断标准通常是设定一个阈值。比如,像素值偏离均值超过3倍标准差,就标记为坏点。我个人习惯用5倍标准差,太严了会把正常像素误杀。
4.1.2 动态坏点检测
动态检测在相机工作时实时进行。它不依赖预存表,而是根据当前帧的邻域信息判断。
常用的方法是:
- 对每个像素,取周围3x3或5x5邻域。
- 计算邻域像素的均值或中值。
- 如果当前像素值与邻域中值的差异超过阈值,就判定为坏点。
这里有个坑:边缘和纹理区域容易误判。我曾经在拍树叶时,动态检测把很多正常边缘点当成了坏点,结果图像糊成一片。后来加了梯度判断——如果邻域梯度大,就放宽阈值。
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步。每一步都有坑,我一个个说。
- 准备阶段:传感器预热到工作温度(通常5-10分钟)。温度不稳定,坏点会“漂移”。
- 暗场采集:盖上镜头盖,拍10-20帧全黑图像,取平均以消除随机噪声。
- 亮场采集:用均匀光源照射灰卡,拍10-20帧,同样取平均。
- 阈值判断:对每个像素,计算暗场和亮场的偏差。超过阈值的标记为坏点。
- 生成坏点表:将坏点坐标写入表文件,同时记录替换值(可选)。
这里有个容易被忽略的点:坏点会随温度变化。我做过一个车载摄像头项目,冬天标定的坏点表,夏天就不准了。后来我们做了多温度标定——在-20°C、25°C、60°C分别标定,生成三张表,运行时根据温度切换。
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决定内容
4.5 知识结构总览
下面这张图把坏点标定与校正的完整链路串起来了。你可以把它当作本章的“地图”。