2、黑电平校正(BLC):原理、参数配置、暗电流补偿技巧

黑电平校正,圈里人常叫它BLC。说白了,就是让传感器在完全没光的时候,输出值归零。你想想看,如果一张全黑的图拍出来是灰的,那暗部细节肯定全废了。

我刚开始调ISP那会儿,总觉得BLC是个小模块,随便配几个参数就行。后来在一个夜视项目上栽了跟头——暗部偏紫,怎么调都调不回来。折腾了两天,最后发现是BLC没校准好。嗯,从那以后我再也不敢小看这个模块了。

2.1 黑电平是怎么来的?

传感器本身有暗电流,即使完全遮光,像素也会积累电荷。温度越高,暗电流越大。另外,ADC转换也有一个基准偏移。这两个因素叠加,就产生了黑电平偏移。

我习惯把黑电平拆成两部分理解:

  • 行级黑电平:每行像素的暗电流不一致,导致横条纹
  • 全局黑电平:整个画面的整体偏移,影响所有像素

大部分传感器会在像素阵列边缘留一些光学黑像素(OB, Optical Black),这些像素被金属遮挡,不接收光线。我们就是拿它们来估算当前的黑电平值。

核心公式:校正后像素值 = 原始像素值 - 黑电平值

就这么简单?对,但难点在于——黑电平值不是固定的,它会随温度、增益、曝光时间变化。

2.2 暗电流补偿的实战技巧

暗电流补偿,说白了就是动态跟踪黑电平的变化。我见过不少工程师直接写死一个固定值,结果画面在不同温度下颜色飘得厉害。

这里分享几个我踩过的坑:

避坑指南:我曾经在一个宽动态项目里,把BLC放在HDR合成之后做。结果暗部细节全被切掉了,因为HDR合成后的像素分布已经变了。正确的做法是:BLC必须在HDR合成之前,甚至要在去噪之前做。

暗电流补偿的典型流程:

  1. 读取OB像素的平均值
  2. 减去OB像素的目标值(通常是0或一个很小的偏移)
  3. 对R、Gr、Gb、B四个通道分别计算
  4. 做空间滤波,防止单行OB像素异常导致条纹
  5. 将校正值应用到有效像素区域

个人习惯:我会在OB区域取64行像素做中值滤波,而不是简单平均。因为偶尔会有坏点混入OB区域,平均值会被拉偏,中值更鲁棒。

2.3 参数配置的要点

BLC的参数其实不多,但每个参数都牵一发动全身。我整理了一个常用配置表:

参数名称 典型范围 我的建议
OB行数 4~64行 至少16行,太少统计不稳定
OB列数 4~32列 左右各留8列,避开边缘效应
目标黑电平 0~64 LSB 10bit传感器建议设为16~32
滤波系数 0.1~0.9 温度变化大时用0.3,稳定场景用0.7
通道独立使能 ON/OFF 必须开启,R/Gr/Gb/B分开校正

你可能会问:目标黑电平为什么不设成0?

嗯,这里有个细节。如果硬把黑电平砍到0,暗部噪声会被截断,出现黑切现象——暗部细节丢失,看起来像死黑一片。留一点余量,反而能保留更多暗部纹理。

2.4 BLC在ISP流水线中的位置

我画了一张图,帮你理清BLC在整个ISP流程中的位置。这张图我每次培训都会拿出来讲:

ISP流水线中BLC的位置与数据流 RAW Sensor BLC 去噪 AWB 暗电流补偿 OB像素统计 温度补偿 ⚠ 关键原则 BLC必须在去噪、AWB、HDR合成之前完成,否则暗部信息会被错误处理

从图上你能看到,BLC紧跟在RAW Sensor之后,是所有图像处理的第一步。为什么?因为后面的去噪、白平衡、颜色校正,都假设输入的黑电平已经是正确的。如果BLC没做好,后面的模块全在错误的数据上工作,越调越乱。

2.5 调试中的常见问题

我这些年遇到的BLC问题,归纳起来就三类:

  • 暗部偏色:R/Gr/Gb/B四个通道的黑电平不一致,导致暗部偏紫或偏绿。解决办法是开启通道独立校正,分别统计各通道的OB像素。
  • 横条纹噪声:行级黑电平没处理好。我建议增加OB行数,或者对每行单独做BLC。
  • 温度漂移:开机后画面颜色慢慢变化。这是暗电流随温度变化导致的。加上温度传感器反馈,做动态补偿就能解决。

调试小技巧:我习惯在调试时先拍一张全黑图(盖上镜头盖),然后看直方图。理想情况下,直方图应该集中在0附近,且R/Gr/Gb/B四个通道的峰值位置相差不超过2个LSB。如果偏差超过5个LSB,暗部偏色肉眼可见。

2.6 代码示例:BLC参数配置

下面是一段我常用的BLC配置代码,基于一个通用的ISP驱动框架:

// BLC参数结构体
typedef struct {
    uint16_t ob_rows;          // OB行数
    uint16_t ob_cols;          // OB列数
    uint16_t target_black;     // 目标黑电平
    float    filter_coeff;     // 滤波系数 0.0~1.0
    bool     channel_independent; // 通道独立校正
    uint16_t black_level_r;    // R通道黑电平
    uint16_t black_level_gr;   // Gr通道黑电平
    uint16_t black_level_gb;   // Gb通道黑电平
    uint16_t black_level_b;    // B通道黑电平
} blc_config_t;

// 初始化BLC参数
void blc_init(blc_config_t *cfg) {
    cfg->ob_rows = 16;
    cfg->ob_cols = 8;
    cfg->target_black = 24;    // 10bit传感器,留一点余量
    cfg->filter_coeff = 0.5f;  // 中等滤波强度
    cfg->channel_independent = true;

    // 初始值从OTP读取,这里给默认值
    cfg->black_level_r = 48;
    cfg->black_level_gr = 50;
    cfg->black_level_gb = 49;
    cfg->black_level_b = 47;
}

// 动态更新黑电平(每帧调用)
void blc_update(blc_config_t *cfg, uint16_t *ob_buffer, int len) {
    if (!cfg->channel_independent) {
        // 简单模式:所有通道共用
        uint32_t sum = 0;
        for (int i = 0; i < len; i++) {
            sum += ob_buffer[i];
        }
        uint16_t avg = sum / len;
        cfg->black_level_r = avg;
        cfg->black_level_gr = avg;
        cfg->black_level_gb = avg;
        cfg->black_level_b = avg;
    } else {
        // 通道独立模式:分别统计
        // 实际项目中需要根据Bayer pattern解析
        // 这里简化处理
        uint16_t r_avg = median_filter(ob_buffer, len/4);
        uint16_t gr_avg = median_filter(ob_buffer + len/4, len/4);
        uint16_t gb_avg = median_filter(ob_buffer + len/2, len/4);
        uint16_t b_avg = median_filter(ob_buffer + 3*len/4, len/4);

        // 一阶低通滤波,防止突变
        cfg->black_level_r = cfg->filter_coeff * cfg->black_level_r 
                            + (1 - cfg->filter_coeff) * r_avg;
        cfg->black_level_gr = cfg->filter_coeff * cfg->black_level_gr 
                            + (1 - cfg->filter_coeff) * gr_avg;
        cfg->black_level_gb = cfg->filter_coeff * cfg->black_level_gb 
                            + (1 - cfg->filter_coeff) * gb_avg;
        cfg->black_level_b = cfg->filter_coeff * cfg->black_level_b 
                           + (1 - cfg->filter_coeff) * b_avg;
    }
}

这段代码里,我特别想强调中值滤波和一阶低通滤波这两个点。中值滤波能扛住OB区域的坏点,低通滤波能防止黑电平跳变导致画面闪烁。这两个技巧,是我在量产项目中反复验证过的。

总结一下BLC的核心要点:

  • 位置要放对:ISP流水线的最前端
  • 通道要分开:R/Gr/Gb/G独立校正
  • 动态要跟上:温度、增益变化时自动补偿
  • 余量要留够:目标黑电平不要设0

黑电平校正看起来简单,但它是整个图像质量的基石。基座歪了,上面盖的楼再漂亮也是危房。我见过太多工程师花大量时间调AWB、调CCM,结果暗部偏色问题一直解决不了,最后发现是BLC没做好。嗯,希望这一章能帮你少走这些弯路。