3. 坏点校正:从检测到工程落地的全流程
坏点校正,圈里人常叫 DPC(Defect Pixel Correction)。
说白了,就是 sensor 上总有那么几个像素「不听话」——要么永远亮着(白点),要么永远暗着(黑点),要么忽明忽暗(闪点)。
我刚开始做 ISP 时,觉得这玩意儿不就是个中值滤波嘛。后来被一个「间歇性坏点」坑了一整周,才老老实实把 DPC 当回事。
核心认知: 坏点校正不是「修图」,是 sensor 出厂后的第一道质量防线。做不好,后面所有模块都在给坏点打工。
3.1 静态坏点检测与校正
静态坏点,就是 sensor 出厂时就带的那种「死像素」。位置固定,亮度偏差也固定。
检测方法其实很直接——拿一张均匀光照的图(比如灰卡),看哪些像素偏离邻域太多。
3.1.1 静态坏点表生成流程
- 采集多帧均匀图:至少 10 帧,取平均降噪。
- 计算局部均值:对每个像素,取 5x5 邻域均值(排除自身)。
- 计算偏差比:
ratio = pixel / local_mean。 - 阈值判定:ratio > 1.5 或 < 0.5,标记为坏点。
- 生成坏点坐标表:存到 OTP 或校准文件中。
我个人的习惯: 静态坏点表一定要在 sensor 工作温度下生成。温度一变,有些「准坏点」就真坏了。我在一个项目里吃过这个亏——常温校准没问题,一开机发热,满屏雪花点。
3.1.2 静态校正算法
校正就简单了——查表,找到坏点,用邻域插值替换。
// 静态坏点校正伪代码
for each pixel (x, y) in image:
if defect_table[x][y] == 1:
// 取周围 4 个方向的好像素
left = get_good_pixel(x-1, y)
right = get_good_pixel(x+1, y)
up = get_good_pixel(x, y-1)
down = get_good_pixel(x, y+1)
// 中值或均值替换
pixel[x][y] = median(left, right, up, down)
这里有个细节——方向选择。如果坏点在边缘,水平方向可能全是坏点,那就只用垂直方向。我见过有人直接取 3x3 均值,结果坏点簇没修干净,反而糊了细节。
3.2 动态坏点检测算法
动态坏点,是 sensor 使用过程中「新长出来」的坏点。温度、电压、老化都会诱发。
你想想看,sensor 用了一年,坏点数量可能翻倍。静态表根本管不住。
3.2.1 动态检测原理
核心思路:实时判断每个像素是否「异常」。
| 方法 | 原理 | 优缺点 |
|---|---|---|
| 邻域统计法 | 比较像素与周围 5x5 的均值和标准差 | 简单,但对纹理敏感 |
| 梯度检测法 | 计算水平/垂直梯度,异常点梯度突变 | 抗纹理干扰好,计算量大 |
| 多帧一致性法 | 多帧同一位置亮度是否稳定 | 准确,但需要帧缓存 |
我项目中用得最多的是梯度检测法。为什么?因为它能区分「边缘」和「坏点」——边缘的梯度是连续的,坏点的梯度是孤立的。
3.2.2 梯度检测算法详解
// 动态坏点检测 - 梯度法
bool is_defect(pixel p, int x, int y) {
// 计算四个方向的梯度
float grad_h = abs(p[x][y] - p[x][y-1]) + abs(p[x][y] - p[x][y+1]);
float grad_v = abs(p[x][y] - p[x-1][y]) + abs(p[x][y] - p[x+1][y]);
float grad_d1 = abs(p[x][y] - p[x-1][y-1]) + abs(p[x][y] - p[x+1][y+1]);
float grad_d2 = abs(p[x][y] - p[x-1][y+1]) + abs(p[x][y] - p[x+1][y-1]);
// 取最小梯度方向
float min_grad = min(grad_h, grad_v, grad_d1, grad_d2);
// 如果最小梯度都大于阈值,说明该像素与周围都不像
return min_grad > THRESHOLD_DEFECT;
}
我曾经踩过的坑: 阈值设得太死。动态坏点检测的阈值必须跟 sensor 的噪声水平联动。ISO 越高,噪声越大,阈值就得跟着放大。我一开始用固定阈值,高 ISO 下把正常像素全判成了坏点,画面像被砂纸磨过一样。
3.3 坏点校正的工程实现
算法讲完了,但工程落地才是真功夫。
我见过太多人算法跑得飞起,一上硬件就崩。为什么?实时性、带宽、资源,这三个坎过不去,算法就是废纸。
3.3.1 流水线架构设计
坏点校正通常放在 ISP 流水线的第一级——在去噪、去马赛克之前。因为坏点如果不先修掉,后面所有模块都会被污染。
3.3.2 硬件实现的关键参数
| 参数 | 推荐值 | 说明 |
|---|---|---|
| Line Buffer | 2~4 行 | 支持 3x3 或 5x5 邻域 |
| 静态表大小 | 4KB ~ 16KB | 存储坏点坐标,按行索引 |
| 动态检测阈值 | 可编程寄存器 | 与 ISO 联动,实时调整 |
| 校正模式 | 中值 / 均值 / 方向插值 | 可配置,适应不同场景 |
3.3.3 工程避坑指南
- 坏点簇处理:连续 3 个以上坏点,中值滤波会失效。我建议用「方向插值 + 多级校正」——第一轮修孤立点,第二轮修簇。
- 边界处理:图像边缘的坏点,邻域不完整。我习惯用「镜像扩展」或「只取有效方向」。
- 实时性保障:动态检测每帧都跑,计算量不小。可以降采样检测——每 4x4 块只检中心像素,坏点率低时跳过整块。
- 误检抑制:纹理丰富的区域容易误判。加一个「纹理强度」判断——纹理强的地方,放宽检测阈值。
我的工程经验: 坏点校正的「好坏」不是看修了多少点,而是看误修率。修错一个好点,比漏掉十个坏点更影响画质。我一般把误修率控制在 0.01% 以下——也就是一万个像素里最多误修一个。
3.4 静态 + 动态联合策略
实际产品中,静态和动态是配合使用的。
- 初始化:加载静态坏点表,标记已知坏点。
- 逐帧检测:动态检测模块扫描新坏点。
- 融合校正:静态表 + 动态检测结果,合并为最终坏点掩码。
- 校正执行:统一用方向插值替换。
这样做的好处是——静态表保证基础质量,动态检测应对老化漂移。我参与的一个手机项目,sensor 用了两年后坏点率从 0.01% 涨到 0.5%,全靠动态检测兜底。
一句话总结: 坏点校正不是「一次性手术」,而是贯穿 sensor 整个生命周期的「持续护理」。静态打底,动态兜底,工程实现保底线。