第4章:黑电平校正(BLC)

黑电平校正,英文叫Black Level Correction,简称BLC。这玩意儿在ISP流水线里排得挺靠前,通常是在坏点校正之后、增益之前。说白了,它的任务就一个:把传感器在无光条件下本该是0的输出,拉回到真正的0。

你可能会问:「传感器输出0不就是0吗?有什么好拉的?」嗯,这里头还真有门道。我刚开始接触ISP时也这么想,直到被一个偏色的案子折腾了两周才明白——黑电平不准,整个画面的颜色都会跑偏。

4.1 黑电平是怎么产生的?

先说说为什么会有黑电平。理想情况下,你把镜头盖盖上,传感器啥光也收不到,输出的像素值应该是0。但现实不是这样的。

主要原因有这么几个:

  • 暗电流:半导体器件本身有热噪声,温度越高,暗电流越大。哪怕没光,电荷也在慢慢积累。
  • ADC偏置:模数转换器为了避开噪声区,会人为加一个偏置电压。这个偏置量通常就是几十个DN(Digital Number)。
  • 像素结构差异:不同像素的暗电流不一致,导致黑电平在空间上不是均匀的。

我记得有一次在调试一款安防用的CMOS传感器,夏天户外温度到了60多度,黑电平直接漂了30多个DN。如果不做校正,整张图就像蒙了一层灰雾,暗部细节全没了。

核心结论:黑电平不是0,而是一个非零的基底值。这个基底值会随温度、增益、曝光时间变化。BLC的任务就是把这个基底减掉。

4.2 校正原理与实现

校正原理其实很简单:测出黑电平值,然后从每个像素中减去它。但具体怎么测、怎么减,就有讲究了。

4.2.1 黑电平的测量方式

传感器厂商通常会在像素阵列的四周留一些光学黑像素(Optical Black, OB)。这些像素被金属层遮住了,收不到光,专门用来测黑电平。

常见的做法是:

  • 取OB区域所有像素的平均值,作为全局黑电平
  • 或者按行/列统计,做逐行校正
  • 高端一点的,会做二维校正,因为黑电平在空间上不是均匀的

我个人的习惯是:先看OB区域的统计分布。如果标准差很小,用全局均值就够了。如果标准差大,说明像素间差异大,得做逐行甚至逐像素校正。

4.2.2 代码实现示例

下面是一个最简单的全局黑电平校正实现。假设图像数据是10bit的,黑电平值是64:

// 最简单的全局BLC实现
void blc_global(uint16_t *raw, int width, int height, uint16_t black_level) {
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            int idx = y * width + x;
            // 减法,并做钳位防止下溢
            if (raw[idx] > black_level) {
                raw[idx] -= black_level;
            } else {
                raw[idx] = 0;
            }
        }
    }
}

嗯,这里有个细节要注意:减法后一定要做钳位。我曾经见过一个新手工程师没做钳位,结果暗部像素变成了65535,整张图花了一片。

4.2.3 更实用的逐行校正

实际项目中,我更推荐逐行校正。因为OB像素通常分布在每行的起始或末尾,可以实时更新黑电平值:

// 逐行BLC,OB像素在每行前16列
void blc_per_row(uint16_t *raw, int width, int height, int ob_cols) {
    for (int y = 0; y < height; y++) {
        // 计算当前行的OB均值
        uint32_t sum = 0;
        for (int x = 0; x < ob_cols; x++) {
            sum += raw[y * width + x];
        }
        uint16_t row_black = sum / ob_cols;

        // 对当前行所有像素做校正
        for (int x = 0; x < width; x++) {
            int idx = y * width + x;
            if (raw[idx] > row_black) {
                raw[idx] -= row_black;
            } else {
                raw[idx] = 0;
            }
        }
    }
}

这样做的好处是:能跟上黑电平的行间变化。比如卷帘快门模式下,不同行的曝光时间有差异,黑电平也会跟着变。

4.3 常见问题与调试

做BLC调试时,我踩过的坑不少。挑几个典型的说说:

4.3.1 黑电平测不准

OB区域本身也有噪声。如果只取几个像素的平均值,结果会抖动得很厉害。我建议:

  • OB像素数量至少要有32个以上
  • 做中值滤波或均值滤波后再用
  • 可以加一个IIR低通滤波,让黑电平值平滑变化

小技巧:调试时可以在暗室拍一张全黑图,然后看直方图。如果直方图的峰值不在0附近,说明BLC没调好。峰值位置就是残余的黑电平偏移量。

4.3.2 校正后出现条纹

有时候做完BLC,画面会出现水平或垂直的条纹。这通常是OB区域有缺陷像素导致的。比如某个OB像素坏了,输出值异常高,拉高了整行的黑电平估计值。

解决办法:

  • 对OB区域做坏点检测,剔除异常值
  • 或者用相邻行的黑电平做插值

4.3.3 暗部偏色

这是最隐蔽的问题。BLC如果没做好,暗部会偏色。因为RGB三个通道的黑电平可能不一样,减掉的量不同,暗部颜色就歪了。

我曾经调试一个手机摄像头,暗部总是偏绿。查了半天,发现是G通道的黑电平比R和B高了2个DN。校正后,暗部颜色就正常了。

注意:BLC一定要分通道做。不同通道的暗电流特性不同,黑电平值也不同。偷懒用同一个值,暗部偏色等着你。

4.4 知识体系总览

下面这张图总结了BLC的核心逻辑。我画得比较简洁,但关键点都在里面了:

黑电平校正(BLC)知识体系 产生原因 暗电流 ADC偏置 像素结构差异 校正方法 全局BLC 逐行BLC 二维BLC 实现细节 OB像素统计 减法+钳位 分通道处理 常见问题 黑电平测不准 | 校正后出现条纹 | 暗部偏色 调试建议 暗室拍全黑图 → 看直方图峰值 → 逐通道微调

这张图从左到右展示了BLC的完整链路:先搞清楚原因,再选校正方法,然后注意实现细节,最后用调试手段验证。我建议你把这个流程记在心里,以后遇到BLC相关的问题,按这个思路排查,基本不会跑偏。

4.5 调试经验总结

最后分享几个实战经验:

  • 温度影响很大:黑电平随温度变化,量产时要在高低温下分别标定。我见过一个案子,常温下BLC调得好好的,到了低温偏色得一塌糊涂。
  • 增益联动:模拟增益放大后,黑电平也会被放大。所以BLC通常放在增益之前做,或者根据增益值动态调整减去的量。
  • 别过度校正:有些工程师为了追求暗部干净,把黑电平减得太多,结果暗部细节被削没了。记住,BLC的目标是让黑电平归零,不是让暗部变黑。

一句话总结:BLC是ISP流水线的基石。它做不好,后面的白平衡、色彩校正全都会歪。花时间把BLC调稳了,后面的工作会轻松很多。


公众号:蓝海资料掘金营,微信deep3321