4. 黑电平校正(BLC):从源头理解暗电流

黑电平校正,简称 BLC,是 ISP 流水线里非常靠前的一步。说白了,就是让传感器在完全没光的时候,输出值应该是 0,而不是一个正数。

但现实很骨感。你拿手机摄像头对着镜头盖拍一张全黑图,放大看像素值,大概率不是 0,可能是 64、128,甚至更高。这个偏移量,就是黑电平。

我刚开始做 ISP 调试时,就踩过这个坑。当时觉得黑电平嘛,减掉一个固定值不就完了?结果发现不同增益下、不同温度下,黑电平完全不一样。嗯,这里面的门道,咱们今天好好捋一捋。

4.1 黑电平是怎么产生的?

黑电平的根源,是传感器的暗电流。即使没有光子入射,光电二极管里的电子也会因为热激发而跃迁,产生微弱电流。温度越高,暗电流越大。

除了暗电流,还有几个因素也会贡献黑电平:

  • 像素复位噪声:每次读出前复位,电荷不完全归零
  • 读出电路偏移:模拟前端(AFE)的运放有固有偏置
  • ADC 偏移:模数转换器本身有零点漂移

这些因素叠加起来,就形成了我们看到的黑电平。我见过最夸张的一次,某款传感器在 60°C 环境下,黑电平直接飙到了 200 多(12bit 数据)。如果不校正,整张图都是灰蒙蒙的,暗部细节全没了。

核心结论:黑电平不是固定值,它随温度、增益、曝光时间变化。所以校正算法必须考虑这些因素。

4.2 全局 BLC vs 局部 BLC

校正策略分两种:全局 BLC 和局部 BLC。我个人的习惯是,先搞清楚传感器有没有提供 OB(Optical Black)像素,再决定用哪种。

4.2.1 全局 BLC

全局 BLC 就是整张图减去同一个值。适用于黑电平在空间上比较均匀的传感器。

实现方式:

// 全局 BLC 示例(C 风格伪代码)
uint16_t blc_value = 64;  // 从传感器寄存器读取或标定得到
for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
        pixel[y][x] = (pixel[y][x] > blc_value) ? 
                       (pixel[y][x] - blc_value) : 0;
    }
}

优点:简单、快、硬件友好。缺点:如果传感器有行噪声或列噪声,全局减同一个值会留下条纹。

避坑指南:我曾经在一个项目中用了全局 BLC,结果发现图像左侧偏暗、右侧偏亮。后来一查,是传感器读出电路左右两侧的偏置不同。这种情况必须上局部 BLC。

4.2.2 局部 BLC

局部 BLC 按区域或按行列计算黑电平。常见做法是利用传感器边缘的 OB 像素(被遮挡的像素,不感光)来实时估算黑电平。

按行校正:

// 局部 BLC - 按行校正
for (int y = 0; y < height; y++) {
    // 取该行 OB 像素的平均值
    uint32_t sum_ob = 0;
    for (int x = 0; x < ob_width; x++) {
        sum_ob += pixel_ob[y][x];
    }
    uint16_t blc_row = sum_ob / ob_width;
    
    // 对该行所有像素做校正
    for (int x = 0; x < width; x++) {
        pixel[y][x] = (pixel[y][x] > blc_row) ? 
                       (pixel[y][x] - blc_row) : 0;
    }
}

局部 BLC 能有效消除行噪声和列噪声。代价是多了一行 OB 像素的读取和计算,硬件面积会大一点。

对比项 全局 BLC 局部 BLC
计算量 低 中
硬件开销 小 中等
行噪声抑制 无 好
适用场景 均匀性好的传感器 有行/列噪声的传感器
实时性 高 较高

4.3 动态黑电平校正算法

动态 BLC 是更高级的做法。它不依赖固定的标定值,而是根据当前帧的实际情况,实时估算黑电平。

为什么需要动态?因为黑电平会漂。温度变了、增益变了、曝光时间变了,黑电平都会变。固定值校正,在某个条件下准,换个条件就偏了。

4.3.1 基于 OB 像素的动态校正

这是最常用的方法。传感器会在像素阵列的四周留出几行/几列不感光的像素(OB 像素)。这些像素的输出,就是当前帧的黑电平参考。

算法流程:

  1. 读取当前帧所有 OB 像素的值
  2. 剔除异常值(比如坏点、受串扰影响的像素)
  3. 计算有效 OB 像素的均值或中位数
  4. 用这个值作为当前帧的黑电平,做全局或局部校正
// 动态 BLC - 基于 OB 像素
uint16_t calc_dynamic_blc(uint16_t *ob_buffer, int ob_count) {
    // 1. 排序
    sort(ob_buffer, ob_count);
    
    // 2. 剔除头尾 10% 的异常值
    int start = ob_count * 0.1;
    int end = ob_count * 0.9;
    
    // 3. 计算中间部分的中位数
    int mid = (start + end) / 2;
    return ob_buffer[mid];
}

注意:OB 像素也可能受串扰影响。如果 OB 区域离有效像素区太近,强光场景下 OB 像素会被光晕污染,导致黑电平估算偏高。我遇到过这种情况,最后在 OB 区域外围加了一圈 dummy 像素做隔离才解决。

4.3.2 基于直方图的动态校正

有些传感器没有 OB 像素,或者 OB 像素数量不够。这时候可以用直方图法。

思路很简单:统计整帧图像的直方图,找到最低的峰值。这个峰值对应的灰度值,就是黑电平的近似值。

为什么?因为正常图像里,总有一部分区域是接近全黑的(比如镜头盖、暗部)。这些像素的分布会形成一个峰,峰的位置就是黑电平。

// 动态 BLC - 基于直方图
uint16_t calc_blc_from_histogram(uint32_t *hist, int bins) {
    // 找到第一个明显的峰值
    int peak_pos = 0;
    uint32_t peak_val = 0;
    
    for (int i = 0; i < bins; i++) {
        if (hist[i] > peak_val) {
            peak_val = hist[i];
            peak_pos = i;
        }
        // 如果连续几个 bin 的值都接近 0,说明过了峰值区域
        if (peak_val > 0 && hist[i] < peak_val * 0.1) {
            break;
        }
    }
    
    return peak_pos;
}

直方图法的优点是无需 OB 像素,缺点是在过曝或纯色场景下可能不准。我一般把它作为 OB 像素法的备选方案。

4.3.3 温度补偿模型

对于高端应用(比如车载、安防),光靠帧级动态校正还不够。温度变化引起的黑电平漂移,需要建立数学模型来补偿。

一个简化的温度补偿模型:

blc(T) = blc_ref + k * (T - T_ref)

// blc_ref: 参考温度下的黑电平
// T_ref: 参考温度(通常 25°C)
// k: 温度系数(由传感器标定得到)
// T: 当前温度(由传感器内置温度传感器读取)

实际项目中,温度系数 k 不是常数,它本身也会随增益变化。所以更精确的做法是建一个二维查找表:增益 × 温度 → 黑电平偏移量。

我的经验:温度补偿模型在实验室标定得很准,但量产时每颗芯片的 k 值会有差异。建议在产线上做一次单点标定,用 25°C 和 60°C 两个温度点拟合出每颗芯片的 k 值。成本不高,效果提升很明显。

4.4 BLC 在 ISP 流水线中的位置

BLC 通常放在 ISP 的最前面,在坏点校正之后、去噪之前。为什么?因为黑电平不校正,后面的增益、白平衡都会把偏移量放大,导致色偏和噪声恶化。

下面这张图展示了 BLC 在 ISP 流水线中的位置和核心逻辑:

BLC 在 ISP 流水线中的位置与核心逻辑 RAW 输入 坏点校正 黑电平校正 (本章核心) 去噪 后续 BLC 内部决策逻辑 传感器 RAW 数据 有 OB? 是 OB 像素法 取均值/中位数 否 直方图法 找最低峰值 校正后 RAW 数据 温度补偿模型 作为辅助模块 叠加到上述方法上

4.5 工程实现中的几个坑

最后分享几个我在项目中踩过的坑,希望能帮你少走弯路。

  • OB 像素数量要够:至少 16 个以上,否则统计结果受噪声影响太大。我见过只用 4 个 OB 像素的方案,黑电平每帧跳变 10 多个 DN,图像闪烁明显。
  • 注意 OB 像素的坏点:OB 区域也会有坏点。如果不剔除,一个坏点就能把整帧的黑电平拉偏。建议在产线标定时同时生成 OB 区域的坏点表。
  • 黑电平校正后要防溢出:减完黑电平后,像素值可能变成负数。一定要做 clamp 到 0,否则后面的模块会出问题。
  • 增益联动:模拟增益放大后,黑电平也被放大了。所以校正应该在增益之前做,或者在增益之后用等效值校正。顺序搞反了,黑电平会跟着增益一起放大,暗部噪声爆炸。

重要提醒:BLC 不是减完就完事了。校正后的数据,暗部噪声分布会发生变化。如果你发现校正后暗部出现奇怪的条纹或色块,先检查 BLC 的精度,再检查是不是 clamp 逻辑有问题。我花了两周才定位到一次类似问题,最后发现是硬件 BLC 模块的位宽截断导致精度丢失。