3. 坏点校正(DPC):静态坏点与动态坏点、坏点检测算法、校正策略与调试经验
坏点校正,简称 DPC,是 ISP Pipeline 里非常基础但又极其关键的一环。说白了,就是 sensor 本身有瑕疵,拍出来的图像上会有一些不该出现的亮点或暗点。如果不处理,后面做降噪、锐化,这些坏点会被放大,非常难看。
我刚开始接触 ISP 时,觉得坏点校正不就是把坏点找出来替换掉吗?后来在实际项目中才发现,这里面的坑远比想象的多。尤其是动态坏点,稍不注意就会把图像细节误判成坏点,或者漏掉真正的坏点。
3.1 静态坏点与动态坏点
先搞清楚两种坏点的区别。这是做 DPC 的前提。
| 类型 | 定义 | 表现 | 成因 |
|---|---|---|---|
| 静态坏点 | 位置固定,亮度值始终异常 | 全黑下为亮点(hot pixel)或全白下为暗点(dead pixel) | sensor 制造缺陷、物理损伤 |
| 动态坏点 | 位置不固定,随温度、增益、曝光时间变化 | 高增益或长曝光时出现,低增益时消失 | sensor 暗电流不均匀、温度噪声 |
静态坏点好处理。出厂时做一次标定,把坏点坐标存到 OTP 里,后面直接查表替换就行。我遇到过最头疼的是动态坏点。有一次在暗光场景下,用户发现画面里时不时冒出一个白点,但位置不固定。查了半天,原来是 sensor 温度升高后,某些像素的暗电流突然增大,变成了临时坏点。
3.2 坏点检测算法
检测坏点,核心思路就是看当前像素和周围邻居的差异是否过大。常用的方法有两种:梯度法和中值法。
3.2.1 梯度法
梯度法比较直观。计算当前像素在四个方向(水平、垂直、两个对角线)上的梯度值。如果某个方向的梯度远大于其他方向,且当前像素值明显偏离邻域均值,就判定为坏点。
// 伪代码:梯度法检测坏点
// 以 3x3 窗口为例,中心像素为 P(i,j)
grad_h = |P(i,j-1) - P(i,j+1)|
grad_v = |P(i-1,j) - P(i+1,j)|
grad_d1 = |P(i-1,j-1) - P(i+1,j+1)|
grad_d2 = |P(i-1,j+1) - P(i+1,j-1)|
min_grad = min(grad_h, grad_v, grad_d1, grad_d2)
max_grad = max(grad_h, grad_v, grad_d1, grad_d2)
if (max_grad / min_grad > threshold) && (|P(i,j) - median| > diff_threshold):
mark as defect pixel
梯度法的优点是计算量小,适合硬件实现。但缺点也很明显:对纹理边缘敏感。你想想看,如果当前像素正好在一条细线上,梯度法很容易把它误判成坏点。我在一个项目中就吃过这个亏,拍出来的文字边缘全是斑点,后来不得不降低梯度阈值,改用中值法。
3.2.2 中值法
中值法更鲁棒一些。取当前像素周围邻域的中值,如果当前像素与中值的差异超过某个阈值,就认为是坏点。
// 伪代码:中值法检测坏点
// 5x5 窗口,取周围 24 个像素的中值
median_val = median(neighbors_24)
diff = |P(i,j) - median_val|
if diff > threshold:
mark as defect pixel
中值法对纹理的误判率更低,因为中值本身不受单个异常值影响。但计算中值需要排序,硬件开销比梯度法大。实际工程中,我一般会结合两种方法:先用梯度法做粗筛,再用中值法做确认。这样既能保证速度,又能降低误判。
3.3 坏点校正策略
检测到坏点后,怎么替换?常用的策略有两种:插值替换和自适应滤波。
3.3.1 插值替换
最简单的做法。用坏点周围正常像素的平均值或中值替换坏点。
// 伪代码:插值替换
// 以 3x3 窗口为例,只取非坏点像素做平均
valid_pixels = []
for each neighbor in 3x3 window:
if not is_defect(neighbor):
valid_pixels.append(neighbor)
if len(valid_pixels) > 0:
P(i,j) = mean(valid_pixels)
else:
// 周围全是坏点,扩大窗口
P(i,j) = interpolate_from_larger_window()
插值替换的优点是简单、速度快。但缺点也很直接:如果坏点周围也有坏点,或者坏点位于纹理区域,替换后的像素会显得不自然。我记得有一次调试,坏点正好在一条边缘上,插值后边缘出现了明显的锯齿。
3.3.2 自适应滤波
自适应滤波更聪明一些。它会根据坏点周围的内容特征,选择不同的滤波方式。
- 平坦区域:用均值滤波,平滑效果好。
- 边缘区域:用方向性滤波,只沿边缘方向做插值,保护边缘。
- 纹理区域:用中值滤波,保留纹理细节。
自适应滤波的效果更好,但计算量也更大。我一般在高端产品上用自适应滤波,在低端产品上用插值替换。说白了,还是成本和性能的权衡。
3.4 调试经验与常见问题
做 DPC 调试这么多年,踩过的坑不少。挑几个典型的说说。
3.4.1 误判问题
最常见的坑。把图像细节当成坏点处理了。比如星空照片里的星星、远处灯光、甚至人脸的高光。我曾经在一个安防项目中,把监控画面里路灯的光晕全当坏点给抹掉了,结果晚上画面一片模糊。
解决办法:增加保护机制。检测到坏点后,先判断它是否属于高频纹理。如果是,就降低校正强度,甚至不处理。
3.4.2 漏判问题
动态坏点漏判,通常是因为阈值设得太高。但阈值设低了,又容易误判。这是个两难问题。
解决办法:我个人的习惯是,把阈值分成两级。第一级用较严格的阈值,确保不误判;第二级用较宽松的阈值,对第一级未覆盖的区域做补充检测。两级检测的结果取并集。
3.4.3 校正后图像模糊
坏点校正后,图像局部变模糊。这通常是因为校正算法把坏点周围的正常像素也一起平滑了。
解决办法:只替换坏点本身,不要动邻居像素。另外,校正后可以加一个微弱的锐化,补偿模糊。
3.5 DPC 知识体系总览
下面这张图总结了 DPC 的核心知识结构。你可以把它当作调试时的参考地图。
这张图把 DPC 的四个核心环节串起来了:坏点类型 → 检测算法 → 校正策略 → 调试要点。你在实际调试时,可以按这个顺序一步步排查问题。