第二章 黑电平校正(BLC)实战:BLC原理、暗电流提取方法、不同温度下的BLC补偿策略

2.1 黑电平校正到底在干什么?

说实话,很多刚入行的工程师觉得BLC很简单——不就是减个值嘛?

嗯,表面上看确实是这样。但你要真这么想,那调试出来的图像在暗光下大概率会翻车。

BLC的核心任务只有一个:把传感器在无光条件下的输出值,校正到我们期望的“纯黑”水平。通常这个期望值是0,或者某些平台会留一个小的偏移量(比如16或64)。

我刚开始做车载ISP时,遇到过一台样机,晚上拍出来的画面整体发紫。排查了半天,最后发现是BLC没做好,暗电流把R通道抬高了十几个LSB。你想想看,暗光下这点偏差有多明显。

一句话总结:BLC不准,暗光偏色、动态范围损失、黑电平闪烁,全都会找上门来。

2.2 暗电流是怎么来的?

传感器在完全没有光照时,为什么还会有输出?

说白了,就是热噪声。硅基半导体在温度作用下,电子会自发跃迁,产生电荷。温度越高,这个现象越严重。

暗电流有几个特点,我列一下:

  • 与温度强相关:温度每升高6-8°C,暗电流大约翻一倍
  • 与曝光时间成正比:曝光越长,积累的暗电荷越多
  • 像素间不均匀:每个像素的暗电流大小不一样,这就是FPN(固定模式噪声)的来源
  • 与增益相关:模拟增益放大的不只是信号,暗电流也被放大了

我在调试一个800万像素的车载传感器时,发现角落像素的暗电流比中心区域高了将近30%。后来查资料才知道,这跟传感器制造工艺的微透镜倾斜角度有关。

2.3 暗电流提取方法——实战经验

提取暗电流,说白了就是找到“纯黑”条件下的传感器输出。方法有好几种,我挑最常用的三种说。

方法一:物理遮光法(最准确)

把镜头完全盖住,或者用遮光帘挡住光线。拍一组全黑图像,取平均值。

优点:最接近真实暗电流。缺点:产线上不好操作,需要专门的暗室环境。

我的习惯:实验室调试阶段,我一般用物理遮光法做基准。盖上镜头盖后,再在相机前面盖一层黑绒布,确保万无一失。

方法二:OB(Optical Black)像素法(最常用)

现在的车载传感器,基本都带有被金属层遮挡的OB像素。这些像素分布在有效像素区域的边缘,它们接收不到光,只反映暗电流水平。

具体做法:

  1. 从RAW数据中提取OB像素区域的数值
  2. 对OB像素做均值或中值滤波(我建议用中值,能剔除个别坏点)
  3. 用这个值作为当前帧的暗电流估计
// 伪代码示例:OB像素提取暗电流
uint16_t extract_black_level(uint16_t *raw, int ob_width, int ob_height) {
    uint32_t sum = 0;
    int count = 0;
    // 假设OB区域在图像左侧,宽度为ob_width
    for (int y = 0; y < ob_height; y++) {
        for (int x = 0; x < ob_width; x++) {
            // 跳过边界和已知坏点
            if (is_defect_pixel(x, y)) continue;
            sum += raw[y * stride + x];
            count++;
        }
    }
    return (uint16_t)(sum / count);
}

注意:OB像素的物理位置和有效像素不完全一样,它们的暗电流会有细微差异。我曾经遇到过OB区域温度比感光区低2°C的情况,导致BLC校正不足,画面偏暗。后来加了温度补偿系数才解决。

方法三:统计直方图法(应急用)

如果传感器没有OB像素,或者OB像素坏了怎么办?

可以拍一张暗场景图像,分析直方图。暗电流通常表现为直方图左侧的一个峰值,找到这个峰值的位置,就是暗电流的估计值。

这个方法精度有限,我一般只在产线快速标定时用。

2.4 不同温度下的BLC补偿策略

这是BLC调试里最头疼的部分。常温下调得好好的,一上车载到夏天暴晒,画面全飘了。

为什么?因为暗电流随温度变化是非线性的。我整理了一个典型车载传感器的暗电流-温度关系表:

温度(°C) 暗电流(LSB@12bit) 相对25°C的变化量
-20 2 -87%
0 5 -68%
25 16 基准
60 48 +200%
85 112 +600%
105 256 +1500%

看到没?85°C时暗电流是25°C的7倍。如果不做温度补偿,BLC直接崩了。

我的补偿策略:三段式

第一段:标定阶段

  • 在-20°C、0°C、25°C、60°C、85°C、105°C六个温度点,分别提取暗电流值
  • 建立温度-暗电流查找表(LUT)
  • 每个温度点至少采集100帧取平均,减少随机噪声影响

第二段:插值阶段

  • 运行时,根据当前传感器温度,在LUT中做线性插值
  • 如果温度超出标定范围,用最近端点值外推(但要加限幅)
// 温度补偿插值示例
float get_black_level_by_temp(int temp_c) {
    // temp_lut[] 和 black_lut[] 是标定好的数据
    // 假设有6个标定点
    if (temp_c <= temp_lut[0]) return black_lut[0];
    if (temp_c >= temp_lut[5]) return black_lut[5];
    
    for (int i = 0; i < 5; i++) {
        if (temp_c >= temp_lut[i] && temp_c < temp_lut[i+1]) {
            float ratio = (float)(temp_c - temp_lut[i]) / 
                          (temp_lut[i+1] - temp_lut[i]);
            return black_lut[i] + ratio * (black_lut[i+1] - black_lut[i]);
        }
    }
    return black_lut[0]; // fallback
}

第三段:动态微调阶段

  • 利用每帧的OB像素实时修正LUT的预测值
  • 如果OB像素均值与LUT预测值偏差超过5个LSB,触发重新标定提醒
  • 我习惯加一个IIR低通滤波,防止单帧噪声导致BLC抖动

避坑指南:我曾经在某个项目里,直接用OB像素的实时值做BLC,没做温度补偿。结果车子从地库开出来,温度骤变,BLC值跳了20多个LSB,画面闪了好几下。后来老老实实加了温度LUT + OB微调的双重策略,才彻底解决。

2.5 实战中的几个坑

最后分享几个我踩过的坑,希望能帮你省点时间:

  • 坑一:BLC校正要在增益之前做,还是在增益之后做?
    答案是增益之前。因为增益会放大暗电流,先减后放才能保证信噪比。我见过有方案在增益后做BLC,暗部噪声大了将近一倍。
  • 坑二:不同颜色通道的暗电流不一样。
    别偷懒用同一个值。R、Gr、Gb、B四个通道要分别提取暗电流,尤其是R通道通常偏高。我习惯用4个独立的BLC参数。
  • 坑三:长曝光场景下,暗电流会饱和。
    如果曝光时间超过1秒,暗电流可能积累到接近饱和值。这时候BLC已经救不了了,需要从曝光策略上限制。
  • 坑四:产线标定时,注意传感器预热。
    刚上电的传感器温度不稳定,暗电流一直在漂。我要求产线至少预热5分钟再开始标定,否则数据根本不能用。

总结一下:BLC不是简单的减法,它涉及温度、曝光、增益、通道差异等多个维度。做好BLC,你的暗光图像质量就成功了一半。

下一章我们聊去噪(Denoise),这是车载ISP里最吃经验的部分,到时候我会分享一些不太常见的降噪技巧。