3. 黑电平的物理意义:暗电流的产生机制、黑电平在传感器中的定义、为什么需要减去黑电平

好,我们进入第三章。前两章我们把 RAW 域的基础概念和传感器结构聊清楚了。这一章,咱们来啃一个硬骨头——黑电平。

很多刚入行的朋友,拿到 RAW 图第一件事就是直接做去马赛克或者调颜色。结果发现暗部偏绿,或者画面整体有一层灰蒙蒙的雾。我当年也犯过这个错。后来被一位老前辈点醒:“你黑电平减了吗?”

嗯,这一章我们就彻底搞明白:黑电平到底是什么?它从哪来?为什么非减不可?

3.1 暗电流:传感器里的“幽灵信号”

先问一个问题:你把镜头盖盖上,让传感器完全不见光。这时候读出的一帧数据,应该是全 0 吗?

理论上是的。但实际不是。

传感器里的每个像素,本质上是一个光电二极管加一个电容。即使没有光子打进来,硅材料内部的热激发也会产生电子-空穴对。这些电子会被电容收集,形成微弱的电流。这就是暗电流。

暗电流的核心特征:

  • 温度敏感:温度每升高 6-8°C,暗电流大约翻一倍。我做过一个车载摄像头项目,夏天中午传感器温度能到 70°C,暗电流比实验室 25°C 时大了将近 20 倍。
  • 时间累积:曝光时间越长,暗电流积累的电荷越多。长曝光摄影里,暗电流噪声是主要噪声源。
  • 像素不均匀:每个像素的暗电流大小不完全一样,这就形成了固定模式噪声(FPN)。

说白了,暗电流就是传感器在“无光”状态下自己产生的假信号。它叠加在真实的光电信号上,像幽灵一样挥之不去。

3.2 黑电平在传感器中的定义

既然暗电流存在,那传感器输出的 RAW 数据里,纯黑像素的数值就不是 0。这个“纯黑像素对应的 RAW 值”,就是黑电平。

传感器厂商会在芯片内部做一件事:在每行像素的起始位置,放置一些被金属遮挡的像素(称为光学黑像素,Optical Black Pixels)。这些像素永远不见光,只用来测量当前的暗电流水平。

我画个图帮你理解:

传感器像素阵列与黑电平参考区域 有效像素区域(感光) 光学黑像素列 光学黑像素列 黑电平参考 有效信号 黑电平参考 遮光金属

你看,传感器左右两侧各有一列被遮光的像素。它们输出的 RAW 值,就是当前帧的黑电平参考值。ISP 会读取这些像素的平均值,作为整帧图像的黑电平。

我的经验:不同传感器的黑电平值差异很大。我见过 8-bit RAW 的黑电平是 16,也见过 10-bit RAW 的黑电平是 64,还有 12-bit 的黑电平在 256 左右。拿到一个新传感器,第一件事就是读 datasheet 里的黑电平参数,或者自己拍一张全黑帧来统计。

3.3 为什么需要减去黑电平?

这个问题看似简单,但背后有很深的道理。我分三点来讲:

3.3.1 恢复真实的“零光”响应

如果不减黑电平,纯黑区域的 RAW 值就不是 0。比如一个 10-bit 传感器,黑电平是 64。那么纯黑像素输出是 64,而不是 0。

这会导致什么问题?

  • 暗部偏色:RGB 三个通道的黑电平可能不完全一致。我遇到过一颗传感器,R 通道黑电平 64,G 通道 63,B 通道 66。如果不减,暗部就会偏青。
  • 动态范围浪费:本来 0-1023 的动态范围,被压缩成了 64-1023,损失了低端 64 个码值的细节。

3.3.2 消除温度漂移的影响

暗电流随温度变化。同一颗传感器,冬天和夏天的黑电平不一样。如果不做黑电平校正,图像的整体亮度会随着温度波动。

我曾经调试一个安防摄像头,白天画面正常,到了晚上红外灯一开,传感器温度升高,画面整体变亮,暗部噪声也变大了。查了半天,发现是黑电平没有实时更新。后来改成每帧从光学黑像素读取黑电平值,问题就解决了。

3.3.3 为后续处理提供正确的数据基础

RAW 域后面的处理流程——去马赛克、白平衡、颜色校正、Gamma 映射——都假设输入数据是“零光为 0”的线性数据。如果黑电平没减干净,这些算法都会出错。

举个例子:白平衡算法通过统计场景中的“灰色”区域来估计色温。如果黑电平没减,暗部像素的 RAW 值偏高,会被误判为灰色区域,导致白平衡偏移。

注意:黑电平减法必须在 RAW 域的最前端完成,在所有其他处理之前。我见过一些方案把黑电平减法放在去马赛克之后,结果导致颜色矩阵计算错误,画面出现奇怪的色偏。

3.4 黑电平校正的两种方式

实际工程中,黑电平校正有两种主流做法:

方式 原理 优点 缺点
全局固定值 从 datasheet 读取固定黑电平值,对所有像素统一减去该值 实现简单,计算量小 无法适应温度变化和像素不均匀性
逐行动态校正 每行读取光学黑像素的平均值,作为该行的黑电平 能实时跟踪暗电流变化,校正精度高 需要额外的行缓存和统计逻辑

我个人习惯用逐行动态校正。虽然硬件开销大一点,但效果确实好。特别是做手机摄像头这种对画质要求高的产品,固定值校正根本过不了评测。

3.5 黑电平校正的代码实现

最后,给一段简单的 C 代码示例,展示黑电平校正的核心逻辑:

// 黑电平校正函数
// 输入:RAW 图像数据,10-bit,黑电平参考值从光学黑像素读取
void black_level_correction(uint16_t *raw_data, int width, int height, 
                            uint16_t black_level_r, 
                            uint16_t black_level_gr,
                            uint16_t black_level_gb, 
                            uint16_t black_level_b) {
    
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            int idx = y * width + x;
            uint16_t pixel = raw_data[idx];
            
            // 根据 Bayer 模式选择对应的黑电平
            uint16_t bl;
            if ((y % 2 == 0) && (x % 2 == 0)) {
                bl = black_level_r;      // R 像素
            } else if ((y % 2 == 0) && (x % 2 == 1)) {
                bl = black_level_gr;     // Gr 像素
            } else if ((y % 2 == 1) && (x % 2 == 0)) {
                bl = black_level_gb;     // Gb 像素
            } else {
                bl = black_level_b;      // B 像素
            }
            
            // 减去黑电平,并钳位到 0
            if (pixel > bl) {
                raw_data[idx] = pixel - bl;
            } else {
                raw_data[idx] = 0;
            }
        }
    }
}

注意:减法后一定要做钳位(clamp),防止出现负数。有些硬件实现会直接截断低 bit,但软件实现里最好显式判断一下。