黑电平校正基础:从暗电流到图像质量

做图像传感器算法这些年,我接触过不少刚入行的工程师。他们拿到 raw 图第一件事就是做增益、做白平衡,结果发现暗部偏色、亮部发蒙。问题出在哪?说白了,就是跳过了黑电平校正这一步。

黑电平校正,是所有 ISP 流水线的第一道工序。它不复杂,但做不好,后面全是白费功夫。今天我们就把它彻底讲透。

什么是黑电平

先问一个问题:一个像素完全不受光,它的输出值应该是多少?

理想情况下是 0。但实际传感器不是这样。你把镜头盖盖上,拍一张全黑图,读出来的数值不会是 0,而是一个正数。这个正数,就是 黑电平。

我习惯把它理解成「传感器的底噪基线」。每个像素都有一个固定的暗电流贡献,加上读出电路的偏置,共同构成了这个基线值。

核心定义:黑电平(Black Level)是图像传感器在零光照条件下输出的数字码值,代表信号链路的固有偏置。

不同传感器、不同增益、不同温度下,黑电平都不一样。这也是为什么我们需要动态校正,而不是简单减一个固定值。

黑电平产生的原因

为什么会存在黑电平?主要有两个物理根源。

1. 暗电流

暗电流是半导体器件的天性。即使没有光子入射,硅基材料中的热激发也会产生电子-空穴对。这些电子被像素收集,就形成了暗信号。

温度每升高 6-8°C,暗电流大约翻一倍。我在项目中遇到过夏天户外相机黑电平漂移超过 20 个 DN 的情况,如果不做校正,整张图都偏紫。

温度 暗电流(相对值) 黑电平偏移
25°C 1× 基准
40°C ~3× +8~15 DN
60°C ~8× +20~40 DN

2. 读出噪声

读出噪声来自像素电荷到电压转换、模拟放大、ADC 量化这一整条链路。它包含复位噪声、1/f 噪声、量化噪声等。

这部分噪声叠加在暗电流之上,让黑电平不是一个固定值,而是一个 有波动的均值。你想想看,如果黑电平本身就在抖动,直接减一个固定值,反而会引入额外的噪声。

我的经验:黑电平校正不只是减偏置,它同时也在做第一级去噪。好的黑电平算法,能把读出噪声的方差压下去 30%-50%。

黑电平对图像质量的影响

黑电平校正做不好,图像质量会出哪些问题?我列几个实际案例。

暗部偏色

这是最常见的问题。RGB 三个通道的黑电平不一致,暗部就会偏色。比如红色通道黑电平偏高,暗部就偏红。

我曾经调试一款车载摄像头,夜间图像暗部总是偏绿。排查了两天,最后发现是绿色通道的黑电平校正参数没跟上温度变化。

动态范围损失

黑电平占用了 ADC 的码值范围。假设 10-bit 传感器,黑电平是 64,那么有效信号范围就从 0-1023 变成了 64-1023。说白了,你损失了 64 个码值的动态范围。

如果校正不当,把黑电平减过头了,还会出现负值截断,造成暗部细节丢失。

噪声放大

这一点容易被忽略。黑电平校正通常伴随着减法运算,但减法不会降低噪声方差。如果黑电平估计不准,减完后反而会引入额外的固定模式噪声(FPN)。

避坑指南:我曾经在批量生产中发现同一批次传感器黑电平差异达到 ±5 DN。如果所有模组用同一套参数,成品率直接掉到 70%。后来我改用每颗传感器单独标定,成品率才回到 95% 以上。

黑电平校正的核心逻辑

说了这么多,黑电平校正到底怎么做?我画了一张流程图,帮你理清整体思路。

黑电平校正核心流程 原始 RAW 数据 黑电平估计(OB 行/列) 校正计算(减法/比例) 校正后 RAW 数据 温度传感器 增益映射表 关键点 黑电平估计需要结合 OB 像素、温度和增益信息 校正后需做截断保护,避免负值或溢出

黑电平校正的两种基本方法

实际工程中,黑电平校正主要有两种实现方式。

1. 全局减法校正

从每个像素值中减去一个全局黑电平值。简单、高效,适合黑电平一致性好的传感器。

# 全局减法校正示例
def black_level_subtraction(raw_img, black_level):
    """
    raw_img: 原始 RAW 图像 (numpy array)
    black_level: 全局黑电平值
    """
    corrected = raw_img.astype(np.int16) - black_level
    corrected = np.clip(corrected, 0, None)  # 截断负值
    return corrected.astype(np.uint16)

2. 逐行/逐列校正

利用传感器预留的 OB(Optical Black)行或列,实时估计每行/每列的黑电平。精度更高,能抑制行噪声。

# 逐行校正示例(使用 OB 行)
def per_row_correction(raw_img, ob_rows):
    """
    ob_rows: 传感器预留的光学黑行数据
    """
    # 计算每行黑电平
    row_black_level = np.mean(ob_rows, axis=1)
    
    corrected = raw_img.copy()
    for row in range(raw_img.shape[0]):
        corrected[row, :] = raw_img[row, :] - row_black_level[row % len(row_black_level)]
    
    corrected = np.clip(corrected, 0, None)
    return corrected

我的建议:如果传感器支持 OB 行,优先用逐行校正。我在手机 ISP 项目中用过这个方法,行噪声降低了 40% 以上。代价是多占一点带宽,但值得。

黑电平校正的常见陷阱

最后,我总结几个实际项目中容易踩的坑。

  • OB 像素被污染:有些传感器 OB 行靠近有效像素区,强光下会有光泄露。这时候 OB 估计值偏高,校正过度。我一般会加一个异常值剔除逻辑。
  • 增益切换时的黑电平跳变:模拟增益变化会改变黑电平。如果校正参数没跟上,画面会出现亮度跳变。建议建立增益-黑电平映射表。
  • 温度补偿滞后:温度传感器响应慢,黑电平已经漂了,补偿还没到位。可以加一个预测滤波器,提前补偿。
  • 截断导致暗部细节丢失:减法校正后负值直接截断为 0,会丢失暗部纹理。我习惯保留一个小的偏置余量,比如 4-8 个 DN。