3. 黑电平校正(BLC):原理、校准方法及在暗光场景下的影响

黑电平校正,圈里人常叫 BLC(Black Level Correction)。这名字听着挺学术,说白了就是告诉摄像头:「喂,全黑的时候你不该输出零,你得输出一个基准值。」

我刚开始调 ISP 那会儿,觉得 BLC 不就是减个固定值嘛,有啥好研究的?后来在暗光场景下被折腾得够呛,才明白这步要是没做好,后面整条图像链路都是歪的。

3.1 为什么需要黑电平校正?

先想一个问题:你把镜头盖死,sensor 啥光也进不来,理论上输出应该是 0。但实际读出来的数字呢?往往不是 0,可能是 64、128,甚至更高。

为什么会这样?

  • Sensor 暗电流:半导体本身的热激发会产生电子,温度越高越明显。我冬天和夏天测同一颗 sensor,黑电平能差 20 个 DN。
  • ADC 偏移:模数转换器不是理想器件,总有个固定的偏置电压。
  • 像素结构差异:不同颜色通道的暗电流不一样,R、Gr、Gb、B 四个通道的黑电平往往不同。

如果不做 BLC,暗场下图像会偏绿或偏紫,而且整幅图的动态范围会被压缩——你想想看,本来 0~1023 的码值,现在 64 就对应纯黑了,那 0~63 这段不就浪费了吗?

核心结论:BLC 的本质是找到 sensor 在无光输入时的真实输出值,然后在每个像素上减去这个值,让「纯黑」回到 0。

3.2 黑电平校准的两种主流方法

实际工程中,BLC 校准分两步走:先测出黑电平值,再在 ISP 流水线里做减法。测量方法主要有两种,我分别说说。

3.2.1 光学暗像素法(OB Pixels)

大多数 sensor 在像素阵列的边缘会留几行/几列被金属遮光层盖住的像素,这些就是光学黑像素(Optical Black Pixels)。它们收不到光,但和有效像素共用同样的电路结构。

校准流程是这样的:

  1. 读出 OB 区域的像素值,通常取 16~64 行。
  2. 对每行做均值或中值滤波,剔除坏点。
  3. 按 R、Gr、Gb、B 通道分别统计,得到四个通道的黑电平基准。
  4. 在 ISP 的 BLC 模块里,每个像素根据其颜色通道减去对应的基准值。

我记得有一次调一颗 2MP 的 sensor,OB 区域只有 4 行,噪声特别大。我改用中值滤波代替均值,效果立马稳了。嗯,这里要注意:OB 行数太少时,均值容易受单颗坏点干扰。

3.2.2 机械快门/全黑帧法

有些 sensor 没有 OB 区域,或者 OB 区域不够用。这时候就得用物理方法——盖上镜头盖,拍一帧全黑图。

步骤:

  1. 在产线上用机械快门或遮光帘挡住镜头。
  2. 采集 N 帧全黑图像(我一般取 16 帧做平均)。
  3. 统计全图各通道的均值,作为黑电平基准。
  4. 写入 sensor 的寄存器或 ISP 的配置表。

避坑指南:我曾经在产线上遇到过一个问题——机械快门闭合不严,漏了一丝光进来,导致黑电平测高了 10 个 DN。结果整批相机暗光下偏暗,客户投诉。后来我要求在快门闭合后额外等 200ms 再采集,确保完全遮光。

3.3 BLC 在 ISP 流水线中的位置

下面这张图是我自己画的 BLC 在 ISP 中的位置和流程,你看一眼就明白了。

BLC 在 ISP 流水线中的位置与流程 Sensor Raw Data BLC 黑电平校正 后续 ISP 模块 方法一:OB 像素统计 方法二:全黑帧采集 分通道处理(R / Gr / Gb / B) 每个通道独立统计黑电平 → 分别写入 ISP 寄存器 典型值范围:R:48~96, Gr:64~128, Gb:64~128, B:48~96

从图上你能看到:BLC 紧接在 sensor raw data 之后,是所有 ISP 处理的第一步。为什么?因为后面的去噪、白平衡、颜色校正全都建立在「黑电平归零」的基础上。如果 BLC 没做好,后面的模块等于在歪斜的地基上盖楼。

3.4 暗光场景下的 BLC 影响

这才是本章的重点。暗光下 BLC 做不好,问题会放大好几倍。

问题 表现 根因
黑电平漂移 暗部偏色,整体发灰 温度升高导致暗电流增大,固定 BLC 值失效
通道间差异 暗部偏绿或偏紫 R/Gr/Gb/B 四个通道的黑电平变化率不同
噪声放大 暗部出现明显颗粒感 BLC 减法后,后续的增益模块把残留噪声一起放大了
动态范围损失 暗部细节丢失 黑电平偏高,压缩了有效信号的范围

我遇到过最典型的一个案例:某款 IPC 在室内正常光照下画面挺好,一到夜间模式就偏绿。排查了两天,最后发现是 BLC 的 Gr 和 Gb 通道用了同一个值。暗光下 Gr 和 Gb 的暗电流差异被放大,绿色通道失衡了。分开校准后,问题解决。

实战技巧:暗光场景下,我建议做动态 BLC——根据 sensor 温度或增益值实时调整黑电平基准。具体做法是:在 sensor 附近放一颗温度传感器,查表修正 BLC 值。温度每升高 10°C,暗电流大约翻一倍。

3.5 BLC 校准的工程实现

最后给一段实际项目中用过的 BLC 校准代码片段。这是基于 V4L2 和 ISP 驱动层的伪代码,但逻辑通用。

// BLC 校准流程 - 伪代码实现
// 假设 sensor 输出 10-bit raw,OB 区域位于前 16 行

#define OB_ROWS 16
#define SENSOR_WIDTH 1920
#define CHANNELS 4  // R, Gr, Gb, B

uint32_t blc_values[CHANNELS] = {0};

void calibrate_blc(uint16_t *raw_frame) {
    uint64_t channel_sum[CHANNELS] = {0};
    uint32_t channel_count[CHANNELS] = {0};
    
    // 遍历 OB 区域(前 16 行)
    for (int row = 0; row < OB_ROWS; row++) {
        for (int col = 0; col < SENSOR_WIDTH; col++) {
            int channel = get_channel_id(row, col);  // 根据 Bayer 模式判断通道
            uint16_t pixel = raw_frame[row * SENSOR_WIDTH + col];
            
            // 剔除坏点:偏离中值超过 3σ 的像素丢弃
            if (is_outlier(pixel, row, col)) continue;
            
            channel_sum[channel] += pixel;
            channel_count[channel]++;
        }
    }
    
    // 计算各通道均值
    for (int ch = 0; ch < CHANNELS; ch++) {
        if (channel_count[ch] > 0) {
            blc_values[ch] = (uint32_t)(channel_sum[ch] / channel_count[ch]);
        }
    }
    
    // 写入 ISP 寄存器
    write_isp_register(ISP_BLC_R,  blc_values[0]);
    write_isp_register(ISP_BLC_GR, blc_values[1]);
    write_isp_register(ISP_BLC_GB, blc_values[2]);
    write_isp_register(ISP_BLC_B,  blc_values[3]);
    
    printf("BLC values - R:%d Gr:%d Gb:%d B:%d\n",
           blc_values[0], blc_values[1], blc_values[2], blc_values[3]);
}

注意:上面代码里的 is_outlier() 函数一定要做。我曾经偷懒没加,结果 OB 区域里有一颗死像素,把整个通道的黑电平拉高了 15 个 DN,暗光下画面偏色严重。后来老老实实加了 3σ 剔除,再也没出过问题。

3.6 小结

黑电平校正看着简单,但它是 ISP 流水线的基石。我个人的经验是:

  • 优先用 OB 像素法,实时性好,能跟踪温度变化。
  • 如果 sensor 没有 OB 区域,全黑帧法也能用,但要注意遮光密封和温度补偿。
  • 暗光场景下,务必分通道校准,别偷懒用统一值。
  • 产线校准后,留一个温度补偿表,让 BLC 值随温度自适应调整。