3、坏点校正(DPC):坏点类型、静态坏点校正、动态坏点校正
坏点校正,简称 DPC。说白了,就是处理传感器上那些「不听话」的像素点。
我刚入行那会儿,拿到第一块 raw 图,满屏的亮点和暗点,差点以为 sensor 坏了。后来才知道,这是每个 CMOS 都有的先天毛病。你想想看,几千万个像素点,怎么可能个个都完美?
3.1 坏点的类型
坏点分三种,我习惯按它们的「性格」来记:
| 类型 | 表现 | 成因 |
|---|---|---|
| 亮点(Hot Pixel) | 固定输出高值,像一直亮着的小灯泡 | 暗电流过大,漏电严重 |
| 暗点(Dead Pixel) | 固定输出低值,像永远关着的灯 | 光电二极管损坏,完全不感光 |
| 闪烁点(Stuck Pixel) | 输出值卡在某个中间值,不随光照变化 | 读出电路故障,信号被「卡住」了 |
经验之谈: 我遇到过最头疼的是闪烁点。亮点暗点一眼就能看出来,但闪烁点跟正常像素混在一起,不仔细看根本发现不了。尤其是卡在中间灰阶的那种,拍出来的照片像蒙了一层雾。
3.2 静态坏点校正
静态校正,就是提前把坏点的位置记下来,然后直接替换掉。
怎么做?分两步:
- 坏点标定: 在产线上,用均匀光源拍一张全黑图和一张全白图。全黑图里亮的就是亮点,全白图里暗的就是暗点。把坐标存进 OTP 或 flash 里。
- 坏点替换: 读 raw 图时,遇到这些坐标,就用周围像素插值替换掉。
替换算法有很多种,我常用的是中值滤波和均值滤波:
// 简单的 3x3 中值替换
uint16_t dpc_static_correct(uint16_t *raw, int x, int y, int width) {
int idx = y * width + x;
uint16_t neighbors[8];
int n = 0;
// 收集周围 8 个像素
for (int dy = -1; dy <= 1; dy++) {
for (int dx = -1; dx <= 1; dx++) {
if (dx == 0 && dy == 0) continue;
neighbors[n++] = raw[(y + dy) * width + (x + dx)];
}
}
// 排序取中值
for (int i = 0; i < 7; i++) {
for (int j = i + 1; j < 8; j++) {
if (neighbors[i] > neighbors[j]) {
uint16_t tmp = neighbors[i];
neighbors[i] = neighbors[j];
neighbors[j] = tmp;
}
}
}
return neighbors[4]; // 返回中值
}
注意: 静态校正有个坑——坏点表是出厂时标定的。传感器用久了,新的坏点会冒出来。我曾经遇到一台设备,用了半年后画面出现大量噪点,查了半天才发现是新增的坏点没被覆盖到。
3.3 动态坏点校正
动态校正,就是不依赖预存坏点表,实时检测并修复坏点。
为什么需要动态校正?原因有三:
- 坏点会随着温度和老化增加
- 产线标定不可能覆盖所有场景
- 有些坏点只在特定增益下才显现
动态校正的核心思路:判断当前像素是否「异常」。
我常用的判断方法是:
- 取当前像素周围 3x3 或 5x5 邻域
- 计算邻域内像素的均值或中值
- 计算当前像素与均值的差值
- 如果差值超过阈值,就判定为坏点
// 动态坏点检测与校正
uint16_t dpc_dynamic_correct(uint16_t *raw, int x, int y, int width, int threshold) {
int idx = y * width + x;
uint16_t center = raw[idx];
// 计算 3x3 邻域均值(排除中心点)
uint32_t sum = 0;
int count = 0;
for (int dy = -1; dy <= 1; dy++) {
for (int dx = -1; dx <= 1; dx++) {
if (dx == 0 && dy == 0) continue;
sum += raw[(y + dy) * width + (x + dx)];
count++;
}
}
uint16_t mean = sum / count;
// 判断差值是否超过阈值
int diff = abs((int)center - (int)mean);
if (diff > threshold) {
return mean; // 坏点,用均值替换
}
return center; // 正常点,保持不变
}
关键参数: 阈值怎么设?设小了容易误判,把纹理细节当成坏点;设大了又漏检。我一般会根据 sensor 的噪声水平动态调整阈值,暗光下阈值放大,亮光下阈值收窄。
3.4 静态 vs 动态:怎么选?
两种方法各有优劣,我整理了一张对比表:
| 对比项 | 静态校正 | 动态校正 |
|---|---|---|
| 计算量 | 低(只需查表替换) | 高(需要实时检测) |
| 坏点覆盖 | 只覆盖标定过的 | 能覆盖新增坏点 |
| 误判风险 | 无(坐标是确定的) | 有(可能误判纹理) |
| 适用场景 | 产线标定、固定坏点 | 温度变化、老化场景 |
实际项目中,我通常两种一起用。先用静态校正处理已知坏点,再用动态校正兜底。这样既保证了效率,又覆盖了新增坏点。
3.5 坏点校正的整体流程
嗯,这里我把整个 DPC 的流程画了张图,方便你理解:
我的建议: 如果你刚开始做 ISP,先从静态校正入手。把坏点表标定好,能解决 80% 的问题。等后面遇到温度漂移或老化问题了,再上动态校正。别一上来就想搞大而全,容易把自己绕进去。