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 分两步走:

  1. 标定阶段:盖上镜头盖,采集多帧暗场图像,取平均。
  2. 运行阶段:从原始像素中减去标定好的偏移量。

但这里有个坑。我踩过:

避坑指南:

我曾经在量产时发现,同一批 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 校正的顺序有讲究。我建议:

  1. 先做 BLC,去掉全局偏移。
  2. 再做 FPN 校正,去掉像素级差异。

为什么这个顺序?因为 FPN 模板是在 BLC 之后标定的。如果先做 FPN,BLC 的全局偏移会叠加到 FPN 校正结果上,导致暗部整体偏亮或偏暗。

下面这张图展示了整个流程:

BLC 与 FPN 协同校正流程 BLC 与 FPN 协同校正流程 RAW 图像输入 BLC 全局偏移校正 FPN 逐像素校正 校正后 RAW 输出 关键说明 • BLC 先去除全局偏移 • FPN 再校正像素差异 • 顺序不可颠倒 典型参数 • BLC 偏移:16-64 DN • FPN 模板:16-bit 精度 • 标定帧数:16-64 帧

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 老化,都会影响效果。我的经验是:标定要勤快,参数要留裕量,代码要防溢出。做到这三点,暗部质量基本就稳了。