3、黑电平校正(BLC):把“假黑”变成“真黑”

黑电平校正,英文叫 Black Level Correction,简称 BLC。很多刚入行的工程师觉得这步很简单——不就是减个值嘛?其实没那么简单。

我刚开始做 ISP 调优时,就吃过黑电平的亏。有一款 sensor 在暗光下画面偏紫,我调了半天 AWB 都没用。后来才发现,是黑电平没校好,导致 R 通道的底噪被当成了信号。嗯,从那以后,我再也不敢小看 BLC 了。

3.1 黑电平是怎么产生的?

先问一个问题:sensor 在完全无光的情况下,输出应该是 0 吗?

答案是:不是。理想情况下是 0,但实际 sensor 会输出一个非零值。这个值就是黑电平。

为什么会这样?主要有三个原因:

  • 暗电流:半导体本身的热激发会产生电子,温度越高,暗电流越大。夏天做测试和冬天做测试,黑电平可能差好几个码值。
  • ADC 偏移:模数转换器本身有一个固定的偏置,目的是避免信号在零点附近被截断。说白了,就是给信号加了一个“安全垫”。
  • 像素结构差异:不同通道(R/Gr/Gb/B)的像素物理特性不完全一致,导致各通道的黑电平也不一样。

关键认知:黑电平不是噪声,它是信号的一部分。如果不校正,整个图像的动态范围会被压缩,暗部细节丢失,颜色也会偏掉。

3.2 BLC 校正原理:到底在做什么?

BLC 的原理一句话就能说清:测出黑电平的值,然后在每个像素上减去它。

但实际做起来,有几个细节要注意。

3.2.1 黑电平的测量方式

sensor 通常会提供两种方式来获取黑电平:

方式 说明 优缺点
OB(Optical Black)像素 sensor 边缘有一圈被遮挡的像素,直接读取其输出值 实时性好,但 OB 像素数量有限,容易受噪声影响
暗场标定 盖上镜头盖,采集多帧图像取平均 精度高,但无法实时更新,温度变化后需要重新标定

我在项目中常用的做法是:先用暗场标定拿到基准值,然后在运行中利用 OB 像素做微调。这样既保证了精度,又能适应温度变化。

3.2.2 分通道校正

不同通道的黑电平不一样,必须分开处理。尤其是 Bayer 格式下的 R、Gr、Gb、B 四个通道,Gr 和 Gb 之间都可能存在差异。

举个例子,某款 sensor 在 30°C 下的黑电平值:

通道 黑电平(10bit)
R 64
Gr 60
Gb 62
B 66

如果不分开校正,Gr 和 Gb 的差异会导致画面出现网格状固定噪声,尤其在暗部非常明显。

3.2.3 校正后的钳位处理

减去黑电平后,可能会出现负值。这时候需要做钳位(clamp),把负值置为 0。

但这里有个坑:如果黑电平测大了,减多了,暗部细节就被削掉了。我曾经在一个项目中,因为 OB 像素受温度影响偏大,导致暗部一片死黑,后来改用多帧平均才解决。

我的建议:在调 BLC 参数时,留一点余量。比如测出来黑电平是 64,你可以设成 62,宁可保留一点点底噪,也不要削掉暗部细节。底噪可以在后面的降噪模块处理。

3.3 BLC 参数调优实战

好了,理论讲完了,咱们来点实际的。BLC 调优通常分三步走。

3.3.1 第一步:标定基准黑电平

操作步骤:

  1. 把 sensor 放在目标工作温度下(比如 25°C、45°C、60°C)。
  2. 盖上镜头盖,确保完全无光。
  3. 采集 100 帧图像,逐像素取平均。
  4. 分别统计 R、Gr、Gb、B 四个通道的平均值。

代码示例(伪代码):

// 假设 raw_data 是 10bit Bayer 图像
// 分辨率为 1920x1080

uint32_t sum_r = 0, sum_gr = 0, sum_gb = 0, sum_b = 0;
uint32_t cnt_r = 0, cnt_gr = 0, cnt_gb = 0, cnt_b = 0;

for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
        if (y % 2 == 0 && x % 2 == 0) {       // R
            sum_r += raw_data[y][x];
            cnt_r++;
        } else if (y % 2 == 0 && x % 2 == 1) { // Gr
            sum_gr += raw_data[y][x];
            cnt_gr++;
        } else if (y % 2 == 1 && x % 2 == 0) { // Gb
            sum_gb += raw_data[y][x];
            cnt_gb++;
        } else {                                 // B
            sum_b += raw_data[y][x];
            cnt_b++;
        }
    }
}

blc_r  = sum_r / cnt_r;
blc_gr = sum_gr / cnt_gr;
blc_gb = sum_gb / cnt_gb;
blc_b  = sum_b / cnt_b;

3.3.2 第二步:验证校正效果

标定完成后,把参数写进 ISP 寄存器,然后做验证:

  • 看直方图:校正后的暗场直方图应该集中在 0 附近,且各通道的分布基本重合。
  • 看暗部细节:在 1-5 lux 的极暗光下拍摄,检查画面是否偏色、是否有网格噪声。
  • 看温度漂移:让设备从冷启动到稳定工作温度,观察黑电平的变化量。

注意:如果校正后暗部出现明显的色偏(比如偏绿或偏紫),大概率是某个通道的黑电平没校准。不要急着调 AWB,先回头检查 BLC。

3.3.3 第三步:温度补偿策略

安防监控设备的工作温度范围很宽,从 -20°C 到 60°C 都很常见。黑电平随温度的变化是非线性的,不能用一个固定值搞定。

我常用的做法是:

  • 在 3-5 个温度点做标定(比如 -10°C、10°C、30°C、50°C、70°C)。
  • 建立温度-黑电平查找表(LUT)。
  • 运行时通过 sensor 内置的温度传感器读取当前温度,查表插值得到当前黑电平。

表格示例:

温度 (°C) R 通道 Gr 通道 Gb 通道 B 通道
-10 58 55 56 60
10 61 58 59 63
30 64 60 62 66
50 70 66 68 72
70 78 74 76 80

你想想看,如果不做温度补偿,夏天和冬天拍出来的画面暗部表现会差很多。这在安防监控里是不能接受的。

3.4 BLC 在 ISP Pipeline 中的位置

为了让你更直观地理解 BLC 在整个 ISP 流程中的位置,我画了一张图:

ISP Pipeline 中 BLC 的位置 Raw 输入 BLC 校正 去噪 AWB 输出 BLC 校正的关键点: 1. 必须在 Raw 域、去噪和 AWB 之前完成 2. 分通道(R/Gr/Gb/B)独立校正 3. 需要做温度补偿,不能用一个固定值 4. 校正后做钳位处理,但不要过度削减暗部细节

从图上可以看得很清楚:BLC 是 Raw 域的第一个校正模块。它做不好,后面的去噪、AWB、CCM 全都会跟着跑偏。这就是为什么我总跟团队说,调 ISP 要先调 BLC,就像盖房子要先打地基。

3.5 避坑指南

最后,分享几个我踩过的坑:

  • 不要只看 OB 像素的平均值。OB 像素本身也有噪声,建议做中值滤波或多次采样。
  • 注意 sensor 的线性度。有些 sensor 在低照度下响应是非线性的,这时候 BLC 校正后还需要做线性化补偿。
  • 调试时别用自动曝光。我曾经犯过这个错——一边调 BLC 一边让 AE 自动跑,结果画面忽明忽暗,根本看不出 BLC 的效果。调 BLC 时,请固定曝光时间和增益。
  • 量产时要留校准接口。同一型号的 sensor,不同批次的黑电平可能有差异。最好在产线上做一次快速标定,把参数写进设备的 EEPROM。

一个小技巧:如果你手头没有专业的黑电平标定工具,可以用一张均匀的灰卡(18% 灰),在中等光照下拍摄,然后看直方图。各通道的峰值位置如果错开了,说明 BLC 没校好。这个方法虽然不精确,但做快速验证足够了。