黑电平校正算法基础

黑电平校正,说白了就是给图像信号找一个干净的起点。传感器在完全没有光照时,像素并不是输出零,而是有一个固定的偏置电压。这个偏置如果不扣掉,暗部就会发灰,动态范围也会被白白浪费掉。

我刚开始做ISP时,觉得黑电平校正不就是减一个数嘛,有什么好讲的?后来在低光照场景下吃了亏,才发现这里面门道不少。今天咱们就把三种主流方法掰开揉碎了聊。

全局减法的原理与实现

全局减法是最直观的方法。假设传感器在全黑环境下输出的平均码值是64,那每一帧图像的所有像素都减去64。就这么简单。

但这里有个坑——减完后的值不能为负。图像数据通常是无符号整数,一旦减成负数,硬件会直接截断成0,或者发生下溢绕回变成65535。嗯,这画面就彻底毁了。

核心公式:

Icorrected(x,y) = max( Iraw(x,y) - Bglobal, 0 )

其中 Bglobal 是全局黑电平值,通常由传感器厂商提供或标定得到。

代码实现也很直接:

// C++ 实现:全局黑电平校正
void blackLevelCorrection_global(uint16_t* raw, int width, int height, uint16_t blackLevel) {
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            int idx = y * width + x;
            if (raw[idx] > blackLevel) {
                raw[idx] -= blackLevel;
            } else {
                raw[idx] = 0;  // 防下溢
            }
        }
    }
}

注意:全局减法只适用于传感器黑电平在空间上均匀的场景。如果传感器有行间差异或列间差异,全局减法会留下明显的竖条纹或横条纹。我曾经在一个项目里直接用全局减法,结果暗场图像上全是条纹,后来才发现是传感器设计问题。

基于暗行/暗列的校正方法

既然全局减法不够灵活,那我们就用传感器自带的暗行(Optical Black, OB)或暗列来实时估算黑电平。大多数CMOS传感器会在像素阵列的四周预留一些被遮挡的像素,这些像素不感光,输出的就是当前帧的黑电平参考值。

我个人习惯把暗行/暗列校正分成三步:

  1. 采集参考值:从暗行或暗列中读取像素值
  2. 计算校正量:对参考值做均值或中值滤波,得到当前帧的黑电平估计
  3. 逐像素校正:用这个估计值对有效像素区域做减法

为什么不用简单的均值?因为暗行里也可能有坏点。如果某个暗像素坏了,输出值异常高或低,直接取均值会把整个校正量带偏。我建议用中值滤波,或者先剔除异常值再算均值。

工程经验:暗行像素通常有几十行,取中间几行做统计,避开边缘行。边缘行容易受到封装应力或温度梯度影响,数据不稳定。我在一个车规项目中试过取全部暗行,结果温度一上来,边缘行的黑电平漂了十几个码值,校正后图像出现渐变偏色。

暗列校正的原理类似,只是参考像素在列方向。有些传感器同时提供暗行和暗列,这时候可以做一个二维校正:

// 伪代码:暗行+暗列联合校正
float rowOffset = median(obRows);    // 暗行中值
float colOffset = median(obCols);    // 暗列中值
float baseOffset = mean(rowOffset, colOffset);

for each pixel (x, y):
    corrected = raw - baseOffset - (rowOffset[y] - baseOffset) - (colOffset[x] - baseOffset)

你想想看,这样做的好处是什么?它能同时补偿行方向和列方向的黑电平不均匀性。当然,计算量也翻倍了,嵌入式平台上要权衡。

动态黑电平校正

动态黑电平校正,说白了就是让校正量跟着环境走。传感器温度一变,黑电平就漂;增益一变,黑电平也跟着变。如果用一个固定值从头减到尾,暗部细节迟早要出问题。

我记得有一次做安防相机,白天效果很好,到了晚上画面暗部发紫。查了半天,发现是温度升高导致黑电平漂了十几个码值,固定校正量不够用了。从那以后,我对动态校正格外上心。

动态校正的核心思路:每帧都重新估算黑电平。具体做法有两种:

方法 原理 优点 缺点
暗行实时跟踪 每帧从暗行提取黑电平,做IIR滤波平滑 实时性好,能跟踪温度变化 暗行区域可能被光污染(如镜头漏光)
增益查表法 预先标定不同增益下的黑电平,运行时查表插值 不受暗行噪声影响 标定工作量大,无法补偿温度变化
混合法 暗行数据做基础,增益表做辅助修正 兼顾实时性和鲁棒性 实现复杂,需要调参

我个人比较推荐混合法。具体实现时,我会给暗行数据加一个置信度判断:如果暗行均值在正常范围内,就用暗行数据;如果异常(比如镜头漏光导致暗行被照亮),就回退到增益表。

// 动态黑电平校正(混合法伪代码)
float estimateBlackLevel(float currentGain, float* obData, int obLen) {
    // 1. 从暗行数据计算候选值
    float obMean = robustMean(obData, obLen);  // 剔除异常值后求均值
    
    // 2. 从增益表查基准值
    float tableValue = lookupGainTable(currentGain);
    
    // 3. 置信度判断
    if (fabs(obMean - tableValue) < THRESHOLD) {
        // 暗行数据可信,用IIR平滑
        static float filtered = 0;
        filtered = 0.9f * filtered + 0.1f * obMean;
        return filtered;
    } else {
        // 暗行数据异常,回退到增益表
        return tableValue;
    }
}

避坑指南:我曾经在动态校正中遇到过一个问题——IIR滤波的系数设得太小,导致黑电平跟踪速度跟不上温度变化。画面从冷启动到热稳定,暗部颜色慢慢漂移,用户反馈说"相机开机后颜色要等几分钟才正常"。后来我把系数从0.05调到0.15,问题就解决了。系数调参时,建议用温度冲击实验来验证跟踪速度。

三种方法的对比与选型

说了这么多,到底该用哪种?我整理了一个对比表,方便你做技术选型:

方法 适用场景 计算量 校正精度 实现难度
全局减法 传感器黑电平均匀、环境温度稳定 极低 低 低
暗行/暗列校正 有OB区域的中高端传感器 中 中高 中
动态黑电平校正 宽温度范围、多增益场景(安防、车载) 中高 高 高

选型时记住一句话:够用就好,别过度设计。如果产品工作温度范围只有0-40度,全局减法加一个温度补偿表就足够了。非要上动态校正,反而增加调试成本和风险。

黑电平校正方法选型流程图 传感器黑电平校正 是否有暗行/暗列? 否 是 全局减法 (需标定固定黑电平) 暗行/暗列校正 (实时估算黑电平) 温度/增益变化大? (如安防、车载) 否 是 暗行校正即可 (加温度补偿表) 动态黑电平校正 (混合法推荐) 选型原则:够用就好。先评估传感器特性和工作环境,再选择最简方案

这张流程图是我在实际项目中总结出来的决策路径。你顺着走一遍,基本不会选错。记住,黑电平校正只是ISP pipeline的第一步,这一步做扎实了,后面的白平衡、去马赛克才能有好的基础。