3、RAW域预处理:黑电平校正(BLC)与固定模式噪声(FPN)去除
各位同学,咱们今天聊聊RAW域预处理里最基础、也最容易被忽视的两个环节——黑电平校正和固定模式噪声去除。
说实话,我刚开始做ISP的时候,觉得这俩玩意儿不就是减个值、做个减法嘛,有啥好讲的?直到我在一个夜视项目里被坑惨了——暗部全是条纹,怎么调都调不掉。后来才发现,是黑电平没校正好,FPN也没处理干净。嗯,从那以后,我再也不敢小看这两步了。
3.1 黑电平校正(BLC)
先说说黑电平。你想想看,一个完美的图像传感器,在完全无光的环境下,输出应该是0对吧?但现实不是这样的。由于传感器本身的暗电流、读出电路的偏置电压等因素,即使没有光,每个像素也会有一个基础的输出值。这个值,就是黑电平。
黑电平校正的目的很简单:把这个基础偏移减掉,让真正的黑色对应到0。
核心公式:
I_corrected = I_raw - BLC_offset
其中BLC_offset通常从传感器周边的光学黑像素(OB, Optical Black)区域统计得到。
我个人习惯的做法是,取OB区域所有像素的中位数,而不是平均值。为什么?因为OB区域也可能有坏点或者异常值,中位数更鲁棒。我在一个项目中就遇到过,OB区域有几个像素因为工艺问题一直偏高,用平均值算出来的BLC_offset偏大了,导致整个画面偏暗。换成中位数后,问题立刻解决。
3.1.1 动态BLC vs 静态BLC
这里有个重要的区分:动态BLC和静态BLC。
| 类型 | 方法 | 适用场景 |
|---|---|---|
| 静态BLC | 出厂时标定一个固定值,后续一直用这个值 | 环境温度稳定、传感器一致性好的场景 |
| 动态BLC | 每帧实时从OB区域计算BLC_offset | 温度变化大、长曝光、高增益场景 |
我建议,除非你的产品是恒温环境,否则一定要用动态BLC。温度每升高10度,暗电流差不多翻一倍。静态BLC在夏天和冬天会差很多,画面会忽明忽暗。
避坑指南:
我曾经在一个安防项目中,用了静态BLC,结果冬天画面偏暗,夏天画面偏亮。客户投诉说“你们的摄像头会变魔术”。后来改成动态BLC,每帧从OB区域取中位数,问题才解决。
3.2 固定模式噪声(FPN)去除
黑电平校正是全局的,但FPN是像素级的。说白了,每个像素的暗电流和增益特性都不一样,导致在均匀光照下,不同像素的输出值有差异。这种差异是固定的,不随时间变化,所以叫固定模式噪声。
FPN有两种主要类型:
- 列FPN:同一列像素共享读出电路,导致整列有相同的偏置。表现为竖条纹。
- 像素FPN:每个像素独立的暗电流差异。表现为颗粒感。
你想想看,如果FPN不处理,在暗光下拍一张均匀的灰卡,放大一看全是条纹和颗粒。这画面,谁敢用?
3.2.1 FPN标定方法
FPN去除通常需要标定。标定的流程是这样的:
- 盖上镜头盖,确保完全无光
- 采集多帧暗场图像(比如16帧或32帧)
- 对每帧做黑电平校正
- 对多帧取平均,消除随机噪声
- 得到FPN校正表
代码实现大概是这样:
// FPN标定流程伪代码
void calibrate_fpn(uint16_t *dark_frames[], int num_frames, int width, int height) {
// 1. 分配FPN校正表
int16_t *fpn_table = malloc(width * height * sizeof(int16_t));
// 2. 对每帧做BLC
for (int f = 0; f < num_frames; f++) {
apply_blc(dark_frames[f], width, height);
}
// 3. 多帧平均
for (int i = 0; i < width * height; i++) {
uint32_t sum = 0;
for (int f = 0; f < num_frames; f++) {
sum += dark_frames[f][i];
}
fpn_table[i] = (int16_t)(sum / num_frames);
}
// 4. 保存校正表
save_fpn_table(fpn_table, width, height);
}
个人经验:
标定时,帧数不要少于16帧。我曾经偷懒用了4帧,结果FPN校正后反而引入了新的噪声模式。另外,标定时的温度要和实际使用温度尽量一致,否则FPN会变化。
3.2.2 实时FPN校正
标定完成后,实时处理时,对每个像素减去对应的FPN校正值:
// 实时FPN校正
void apply_fpn(uint16_t *raw_frame, int16_t *fpn_table, int width, int height) {
for (int i = 0; i < width * height; i++) {
// 注意:防止下溢
if (raw_frame[i] > fpn_table[i]) {
raw_frame[i] -= fpn_table[i];
} else {
raw_frame[i] = 0;
}
}
}
这里有个细节要注意:FPN校正值可能是负数吗?理论上不会,因为暗电流只会增加输出值。但实际标定时,由于随机噪声的存在,个别像素的FPN值可能略低于0。我一般会做一次clip,把负值置为0。
3.3 BLC与FPN的关系
这两个步骤是先后关系。先做BLC,再做FPN。为什么?因为FPN校正表是在BLC之后标定的,所以应用时也要先BLC再FPN。
我画了一张流程图,帮你理清这个关系:
关键点总结:
- BLC是全局校正,FPN是逐像素校正
- 先BLC后FPN,顺序不能乱
- 动态BLC优于静态BLC,除非环境极其稳定
- FPN标定至少16帧,温度要匹配实际使用场景
又一个坑:
我曾经在一个项目中,FPN标定和实际使用温度差了20度。结果暗部出现了明显的网格状噪声。后来重新在目标温度下标定,问题才解决。记住,FPN是温度敏感的。
好了,BLC和FPN就讲到这里。这两个步骤虽然基础,但做不好,后面的所有ISP处理都会受影响。你想想看,如果RAW数据本身就有问题,后面的去噪、白平衡、色彩校正,都是在错误的基础上做文章,效果能好吗?
下一节我们聊聊坏点校正,那也是RAW域预处理的重要一环。