3、黑电平校正(BLC):从物理根源到硬件实现

黑电平校正,圈里人常叫 BLC(Black Level Correction)。这名字听着挺学术,说白了就是让传感器在「全黑」环境下输出正确的 0 值。你想想看,如果相机盖上镜头盖,拍出来的画面应该是纯黑的,对吧?但实际传感器不是这样——它总会输出一个偏正的数值,比如 64 或者 128。这个偏置就是黑电平。

我刚开始做 ISP 的时候,觉得 BLC 不就是减个固定值嘛,有啥好讲的?后来在项目中吃过亏,才发现这里面的门道比想象中深得多。今天咱们就把黑电平的来龙去脉、校正算法、还有硬件怎么实现,一次性讲透。

3.1 黑电平是怎么产生的?

黑电平不是 bug,它是传感器的物理特性决定的。主要有三个来源:

  • 暗电流:硅基半导体在无光照时,热激发产生的电子-空穴对。温度每升高 10°C,暗电流大约翻一倍。我在做安防摄像头项目时,夏天户外 60°C 的场景下,黑电平漂移能到 30 个 DN 以上。
  • 像素复位噪声:每个像素在曝光前需要复位到参考电压,这个复位过程本身就有噪声,导致读出值偏正。
  • ADC 偏置:模数转换器为了避开输入范围的底部非线性区,会人为加一个偏置电压。这个偏置通常是固定的,但不同通道之间可能有差异。

关键数据:典型 CMOS 传感器的黑电平值在 64~256 之间(12bit RAW 数据)。如果不做校正,暗部细节会被这个偏置吃掉,动态范围白白损失。

3.2 校正算法原理

校正算法分两大类:固定值校正和动态校正。我个人的经验是,低端 sensor 用固定值就够了,但高端 sensor 必须上动态校正。

3.2.1 减固定值校正

这是最朴素的方法。传感器厂商会在 datasheet 里给出一个推荐的黑电平值,比如 64。你直接让每个像素减去这个值就行。

// 伪代码:减固定值校正
for each pixel in frame:
    corrected_pixel = raw_pixel - fixed_black_level
    if corrected_pixel < 0:
        corrected_pixel = 0

但这里有个坑——截断处理。减完之后如果出现负数,必须 clamp 到 0。我曾经见过一个团队忘了做 clamp,结果暗部出现了奇怪的伪影,排查了两天才找到原因。

注意:减固定值校正对温度变化非常敏感。如果 sensor 温度漂移了 20°C,黑电平可能已经变了 10 个 DN,但你的校正值还是原来的,暗部就会偏色。

3.2.2 动态校正(OB 行校正)

动态校正才是工业级方案。传感器会在每帧图像中预留几行「光学黑像素(OB 行)」,这些像素被金属遮光层覆盖,永远接收不到光。它们的输出值就是当前帧的实时黑电平。

算法流程是这样的:

  1. 读取 OB 行数据(通常 4~16 行)
  2. 计算 OB 行像素的平均值(或中位数,抗噪性更好)
  3. 用这个平均值作为当前帧的校正值,逐像素减去
// 伪代码:动态 OB 校正
ob_mean = 0
for each pixel in ob_rows:
    ob_mean += pixel_value
ob_mean /= ob_pixel_count

for each pixel in active_rows:
    corrected_pixel = raw_pixel - ob_mean
    if corrected_pixel < 0:
        corrected_pixel = 0

经验之谈:OB 行数量不是越多越好。行数太多会占用有效像素区域,行数太少则统计噪声大。我一般建议用 8 行,在信噪比和面积之间取平衡。

3.3 硬件实现架构

好了,算法讲完了,咱们来看看硬件怎么落地。BLC 在 ISP 流水线中通常放在最前面,紧接在 RAW 数据输入之后。

3.3.1 整体架构

下面这张图是我自己画的 BLC 硬件架构,你一看就明白:

黑电平校正硬件架构 RAW 像素流 OB 行缓存 (8 行 × 像素宽度) 累加器 (求 OB 均值) 减法器阵列 校正后 控制状态机 OB 行在每帧 开始时写入 缓存

3.3.2 行缓存的设计要点

行缓存是 BLC 硬件里最占面积的模块。为什么需要它?因为 OB 行在帧的最前面,而有效像素在后面。你得先把 OB 行数据存起来,算完均值,等有效像素来了才能做减法。

我建议的缓存策略:

  • 双缓冲:一个 buffer 存当前帧的 OB 行,另一个 buffer 给上一帧的均值做减法。这样流水线不会 stall。
  • 位宽:如果 sensor 输出 12bit RAW,缓存位宽至少 12bit。但累加器需要更大的位宽来防止溢出——8 行 × 12bit 累加,至少需要 15bit。
  • 深度:8 行 × 每行像素数。比如 1920 像素宽的 sensor,缓存深度就是 8 × 1920 = 15360 个像素。
参数 推荐值 说明
OB 行数 8 平衡统计精度与面积
缓存位宽 12~15 bit 防止累加溢出
减法器位宽 12 bit 与 RAW 数据位宽一致
流水线延迟 8 行时间 等待 OB 均值计算完成

3.3.3 减法器的实现

减法器本身不复杂,但要注意两点:

  1. 流水线级数:减法器可以放在 1 个时钟周期内完成,但为了时序收敛,我习惯拆成 2 级流水线——第一级做减法,第二级做 clamp。
  2. Clamp 逻辑:结果小于 0 时输出 0,大于最大值时输出最大值。这个逻辑用比较器 + 多路选择器实现,面积很小。

面积估算:一个 12bit 减法器 + clamp 逻辑,在 28nm 工艺下大约占 200~300 个门。8 个并行减法器也就 2000 个门左右,在整个 ISP 芯片里几乎可以忽略不计。

3.4 避坑指南

做 BLC 硬件设计,有几个坑我踩过,分享给你:

  • OB 行污染:如果 sensor 的遮光层有漏光,OB 行数据就不准了。我曾经遇到一个项目,OB 行旁边有 LED 指示灯,漏光导致黑电平校正值偏大,暗部发紫。解决方案是在 PCB 布局时把遮光区域和光源隔开。
  • 帧间跳变:如果 OB 均值在帧与帧之间剧烈变化,画面会出现闪烁。解决办法是对 OB 均值做 IIR 滤波,比如:ob_filtered = 0.9 * ob_filtered + 0.1 * ob_current。
  • 多通道一致性:RGB 三个通道的黑电平可能不同。我建议每个通道独立计算 OB 均值,独立做减法,否则会出现暗部偏色。

我的习惯:在芯片验证阶段,我会特意把 sensor 放在恒温箱里,从 -20°C 到 80°C 扫一遍温度,看 BLC 的校正精度。这个测试能发现很多隐藏问题。