3. 坏点校正(DPC):静态坏点检测、动态坏点校正、坏点校正参数调优
坏点校正,简称 DPC。说白了,就是处理 sensor 上那些「不听话」的像素点。
我刚开始做 ISP 调优那会儿,遇到过一台 IPC,画面一到晚上就全是白点。客户以为是灰尘,擦了又擦。结果呢?是 sensor 本身的坏点。从那以后,我对 DPC 这块就特别上心。
坏点分两种:静态坏点和动态坏点。静态坏点出厂就定了,位置固定。动态坏点呢?会随着温度、增益变化冒出来。处理方式完全不同。
核心观点: 坏点校正不是简单的「把坏点抹掉」。抹得太狠,细节没了;抹得太轻,坏点还在。关键是找到那个平衡点。
3.1 静态坏点检测
静态坏点检测,一般在 sensor 出厂时做。厂家会提供一个坏点列表(Defect Pixel List),里面记录了每个坏点的坐标和类型。
但说实话,这个列表不一定准。我遇到过好几次,厂家给的列表不全,或者干脆就没给。这时候就得自己动手。
静态坏点检测的常用方法:
- 全黑帧检测: 盖上镜头盖,拍一张全黑图。亮度明显高于周围像素的点,就是坏点。
- 均匀亮场检测: 对着均匀光源拍一张灰卡图。亮度明显偏离平均值的点,也是坏点。
- 多帧对比: 连续拍多张图,位置固定的异常点,基本就是坏点。
我个人习惯用全黑帧 + 多帧对比结合。先快速定位,再交叉验证,误检率能降到很低。
小技巧: 静态坏点列表建议存成表格,方便后续查表校正。格式可以参考下面这样:
| 列名 | 说明 | 示例值 |
|---|---|---|
| row | 行坐标 | 1024 |
| col | 列坐标 | 768 |
| type | 坏点类型(0=亮点,1=暗点) | 0 |
| gain | 校正增益 | 1.5 |
3.2 动态坏点校正
动态坏点,才是真正让人头疼的。它不固定,温度一高、增益一拉,就冒出来了。
动态坏点校正的核心思路:实时检测 + 实时替换。
具体怎么做?
- 检测: 用当前像素和周围像素比较。如果差异超过阈值,就认为是坏点。
- 替换: 用周围像素的中值或均值,替换掉坏点。
这里有个坑。阈值设得太低,会把正常细节当坏点抹掉。设得太高,坏点又漏过去了。
我曾经踩过的坑: 有一次为了追求画面干净,把动态坏点阈值压得很低。结果呢?画面上的细纹理全没了,客户投诉说「画面像油画」。后来我才明白,动态坏点校正必须和降噪、锐化配合着调。
动态坏点校正的流程,我画了张图,你看一眼就明白了:
3.3 坏点校正参数调优
参数调优,是 DPC 里最考验经验的环节。我给你列几个关键参数:
| 参数名 | 作用 | 调优建议 |
|---|---|---|
| 静态坏点阈值 | 判断像素是否为静态坏点 | 建议从 3σ 开始,逐步收紧 |
| 动态坏点阈值 | 判断像素是否为动态坏点 | 低增益场景可设 10-20,高增益场景放宽到 30-50 |
| 邻域窗口大小 | 用于中值/均值计算的像素范围 | 3x3 保留细节好,5x5 平滑能力强 |
| 校正强度 | 替换像素的比例 | 0.5-1.0 之间,建议从 0.8 开始 |
调优步骤,我一般这么走:
- 先静态,后动态。 先把静态坏点列表搞定,再处理动态的。
- 低增益场景调基线。 在低增益下把参数调好,保证画面干净。
- 高增益场景做验证。 拉高增益,看坏点有没有漏出来。
- 交叉验证。 换不同场景、不同温度,反复测。
我的个人习惯: 调参时,我会在画面上叠加一个「坏点标记层」。把检测到的坏点用红色标出来。这样一眼就能看出哪些是误检,哪些是漏检。比盯着像素值看效率高多了。
3.4 代码示例:动态坏点校正
下面是一段简化的动态坏点校正代码。实际工程中会复杂得多,但核心逻辑就是这样:
// 动态坏点校正 - 简化示例
void dpc_process(uint16_t *frame, int width, int height, int threshold) {
for (int y = 1; y < height - 1; y++) {
for (int x = 1; x < width - 1; x++) {
// 取 3x3 邻域
uint16_t center = frame[y * width + x];
uint16_t neighbors[8] = {
frame[(y-1)*width + (x-1)], frame[(y-1)*width + x], frame[(y-1)*width + (x+1)],
frame[y*width + (x-1)], frame[y*width + (x+1)],
frame[(y+1)*width + (x-1)], frame[(y+1)*width + x], frame[(y+1)*width + (x+1)]
};
// 计算中值
uint16_t median = get_median(neighbors, 8);
// 判断是否为坏点
if (abs(center - median) > threshold) {
// 替换为中值
frame[y * width + x] = median;
}
}
}
}
注意: 这段代码只做教学演示。实际产品中要考虑性能优化、边界处理、多通道并行等问题。千万别直接往产品里搬。
好了,坏点校正这块,核心就是这些。记住:静态坏点靠列表,动态坏点靠阈值。调参时多试、多看、多想,慢慢就有感觉了。