黑电平校正基础:从暗电流到图像质量
做图像传感器算法这些年,我接触过不少刚入行的工程师。他们拿到 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% 以上。
黑电平校正的核心逻辑
说了这么多,黑电平校正到底怎么做?我画了一张流程图,帮你理清整体思路。
黑电平校正的两种基本方法
实际工程中,黑电平校正主要有两种实现方式。
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。