4. 黑电平校正(BLC)与固定模式噪声(FPN)消除
黑电平校正,圈内人常叫 BLC。说白了,就是让传感器在完全没光的时候,输出值归零。你想想看,一个理想的 sensor,盖上镜头盖,输出应该是 0。但现实世界哪有那么完美?
我刚开始调 ISP 时,遇到过一台相机,暗部画面偏紫。折腾了半天,最后发现是黑电平没校好。从那以后,我每次拿到新 sensor,第一件事就是看它的黑电平基线。
4.1 黑电平为什么不是零?
传感器每个像素都有暗电流。温度越高,暗电流越大。再加上 ADC 的偏置,输出值自然就抬起来了。这个抬起来的量,就是黑电平。
核心公式:
P_out = P_raw - BLC_offset
其中 P_raw 是原始码值,BLC_offset 是黑电平偏移量。
不同 sensor 的黑电平不一样。甚至同一个 sensor,不同增益下,黑电平也会漂。我见过最夸张的案例,增益从 1x 拉到 16x,黑电平漂了 30 个 DN。这种如果不校,暗部直接就废了。
4.2 BLC 的工程实现
实际工程中,BLC 分两步走:
- 标定阶段:盖上镜头盖,采集多帧暗场图像,取平均。
- 运行阶段:从原始像素中减去标定好的偏移量。
但这里有个坑。我踩过:
避坑指南:
我曾经在量产时发现,同一批 sensor,黑电平差异能到 5 个 DN。如果只用一个全局值,部分模组暗部会偏色。后来我改成每颗 sensor 单独标定,问题才解决。
代码实现其实不复杂。以 10-bit RAW 为例:
// 黑电平校正 - C 语言实现
void blc_correction(uint16_t *raw, int width, int height, int blc_offset) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int idx = y * width + x;
if (raw[idx] > blc_offset) {
raw[idx] -= blc_offset;
} else {
raw[idx] = 0; // 防止下溢
}
}
}
}
注意那个防下溢的判断。很多新手会忽略,结果暗部出现负值,画面出现奇怪的条纹。
4.3 固定模式噪声(FPN)是什么?
FPN 和 BLC 是两兄弟,但性格不同。BLC 是全局偏移,FPN 是每个像素自己的偏移。
你想想看,sensor 上几百万个像素,每个像素的暗电流不可能完全一样。有的像素偏亮,有的偏暗。这种固定的、不随时间变化的噪声,就是 FPN。
| 噪声类型 | 空间特性 | 时间特性 | 校正方式 |
|---|---|---|---|
| BLC 偏移 | 全局一致 | 随增益变化 | 单值减法 |
| FPN | 像素级差异 | 固定不变 | 逐像素校正 |
| PRNU | 像素级差异 | 随光照变化 | 逐像素增益校正 |
FPN 和 PRNU 容易搞混。简单记:FPN 是加性噪声,PRNU 是乘性噪声。FPN 在暗场下最明显,PRNU 在亮场下才看得出来。
4.4 FPN 标定流程
FPN 标定,我习惯用多帧平均法。流程如下:
- 第一步:盖上镜头盖,采集 N 帧暗场图像。N 一般取 16 到 64 帧。
- 第二步:逐像素求平均,得到 FPN 模板。
- 第三步:减去全局 BLC,剩下的就是每个像素的 FPN 偏移。
- 第四步:保存 FPN 模板到校准参数区。
工程技巧:
标定时 sensor 温度要稳定。我一般让相机预热 5 分钟再开始采集。温度每变化 1 度,FPN 可能漂 0.3 个 DN。如果标定环境和实际使用环境温差大,校正效果会打折扣。
4.5 实时 FPN 校正算法
实时校正时,每个像素都要减去对应的 FPN 值。但直接减会有问题——FPN 值可能是小数,而像素值是整数。
我的做法是:
// FPN 实时校正 - 带小数精度
void fpn_correction(uint16_t *raw, int16_t *fpn_map, int width, int height) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int idx = y * width + x;
int32_t corrected = (int32_t)raw[idx] - (int32_t)fpn_map[idx];
if (corrected < 0) corrected = 0;
if (corrected > 1023) corrected = 1023; // 10-bit 限幅
raw[idx] = (uint16_t)corrected;
}
}
}
FPN 模板用 16-bit 有符号数存,精度可以到 1/16 个 DN。这样校正后暗部过渡更平滑,不会出现阶梯状条纹。
4.6 BLC 与 FPN 的协同设计
实际 pipeline 中,BLC 和 FPN 校正的顺序有讲究。我建议:
- 先做 BLC,去掉全局偏移。
- 再做 FPN 校正,去掉像素级差异。
为什么这个顺序?因为 FPN 模板是在 BLC 之后标定的。如果先做 FPN,BLC 的全局偏移会叠加到 FPN 校正结果上,导致暗部整体偏亮或偏暗。
下面这张图展示了整个流程:
4.7 工程部署注意事项
把 BLC 和 FPN 校正部署到嵌入式平台时,有几个点要注意:
- 内存对齐:FPN 模板建议按 16 字节对齐,方便 DMA 搬运。
- 查表优化:如果平台没有硬件减法器,可以预计算校正表,用查表代替计算。
- 多增益支持:不同增益下 BLC 和 FPN 不同,需要准备多套参数。
又一个坑:
我曾经在某个平台上,FPN 校正后画面出现横条纹。排查了两天,发现是 FPN 模板的存储地址和图像 buffer 有 cache 一致性问题。后来加了 cache flush 操作,问题解决。
4.8 效果评估指标
校正效果好不好,不能光靠眼睛看。我一般用这几个指标量化:
| 指标 | 计算方法 | 合格标准 |
|---|---|---|
| 暗场均值 | 暗场图像所有像素的平均值 | < 0.5 DN |
| 暗场标准差 | 暗场图像像素值的标准差 | < 2 DN |
| FPN 残留 | 校正后暗场的行/列均值波动 | < 1 DN |
嗯,这些指标在产线测试时很有用。如果暗场标准差超过 2 个 DN,说明 FPN 校正没到位,需要重新标定。
BLC 和 FPN 校正,看似简单,但做精细了不容易。温度、增益、sensor 老化,都会影响效果。我的经验是:标定要勤快,参数要留裕量,代码要防溢出。做到这三点,暗部质量基本就稳了。