3. 黑电平校正(BLC)——让黑色回归纯粹
黑电平校正,圈里人常叫 BLC(Black Level Correction)。
说白了,就是让传感器在完全无光的时候,输出的像素值应该是 0。但现实世界哪有那么完美?传感器本身有暗电流,读出电路有偏置,你盖上镜头盖拍一张,出来的 raw 图大概率不是全黑,而是带个底数——比如 64、128,甚至更高。
这个底数如果不干掉,后面做自动曝光、白平衡、色彩校正,全都会偏。我见过一个项目,BLC 没调好,暗部发紫,怎么调 AWB 都拉不回来。后来发现是黑电平没扣干净,暗电流补偿系数设错了。
3.1 BLC 原理与必要性
先看一张图,理解 BLC 在整个 ISP 流水线中的位置:
BLC 的必要性,我总结为三点:
- 消除暗电流偏置——传感器像元在无光照时,由于热激发产生的电子累积,形成底数。
- 统一黑电平参考——不同增益、不同温度下,暗电流不同。BLC 让黑色参考点归一化。
- 防止暗部细节丢失——如果黑电平偏高,暗部被抬升,对比度下降,画面发灰。
关键指标:BLC 精度通常要求控制在 ±1 LSB 以内。对于 10-bit RAW,黑电平目标值设为 64,实际扣减后的残差应小于 1 个码值。
3.2 暗电流补偿算法
暗电流补偿,核心就是算一个“扣减值”。但这个值不是固定的,它随温度、增益、曝光时间变化。我见过有人直接写死一个常数,结果夏天和冬天拍出来的黑电平差 10 个码值。
常用的补偿方法有几种:
| 方法 | 原理 | 适用场景 | 精度 |
|---|---|---|---|
| 固定值扣除 | 从校准数据中取一个固定偏移量 | 低端 sensor,温度稳定 | 低 |
| OB 行/列统计 | 利用 sensor 的光学黑区(OB)像素均值 | 中高端 sensor,实时性好 | 中 |
| 温度查表补偿 | 预标定不同温度下的暗电流曲线 | 工业相机,宽温范围 | 高 |
| 自适应滤波 | 对 OB 像素做低通滤波,动态更新 | 高动态场景,抗噪性强 | 高 |
我个人最常用的是 OB 行统计 + 温度补偿 的组合方案。为什么?因为 OB 行能实时反映当前帧的暗电流状态,而温度补偿能处理 OB 行本身被噪声污染的情况。
算法流程大致如下:
- 提取 sensor 输出的 OB 行(通常是前几行或后几行,被金属遮挡的像素)。
- 计算 OB 行像素的均值或中位数(中位数抗噪性更好)。
- 减去目标黑电平(比如 64),得到当前帧的补偿值 Δ。
- 对 Δ 做时间域低通滤波,防止单帧噪声导致闪烁。
- 用滤波后的 Δ 对有效像素做减法。
小技巧:OB 行统计时,记得剔除最边缘的 4-8 个像素。这些像素受读出电路边缘效应影响,值往往偏大或偏小。我曾经踩过这个坑,OB 均值偏了 2 个码值,导致整帧画面偏暗。
3.3 FPGA 实现 BLC 的流水线设计
FPGA 上做 BLC,核心是流水线。因为 raw 数据是逐像素流进来的,你不能等一帧收完再算 OB 均值——那样延迟太大了。我们要做的是 帧级统计 + 像素级处理 分离。
看这个流水线结构:
Verilog 代码片段(核心减法与钳位逻辑):
// BLC 核心模块
module blc_correction (
input wire clk,
input wire rst_n,
input wire [9:0] raw_data, // 10-bit RAW 输入
input wire data_valid,
input wire [7:0] blc_offset, // 来自 OB 统计模块的补偿值
output reg [9:0] corrected_data,
output reg data_ready
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
corrected_data <= 10'd0;
data_ready <= 1'b0;
end else if (data_valid) begin
// 减法 + 饱和钳位
if (raw_data >= blc_offset)
corrected_data <= raw_data - blc_offset;
else
corrected_data <= 10'd0; // 钳位到 0
data_ready <= 1'b1;
end else begin
data_ready <= 1'b0;
end
end
endmodule
注意:上面的代码是简化版。实际项目中,blc_offset 需要做帧同步——即当前帧的补偿值来自上一帧的 OB 统计。如果 sensor 场景变化剧烈(比如从暗室突然到强光),OB 行统计值可能跳变,建议加一个 IIR 低通滤波器:blc_filt = blc_filt * 0.875 + blc_new * 0.125。
3.4 调试案例:暗部偏紫问题
讲一个我实际遇到的案例。
某款 2MP 传感器,在低照度下(< 10 lux),暗部区域明显偏紫。一开始团队怀疑是 AWB 没调好,折腾了两周无果。后来我让测试组抓了 raw 图,用工具分析暗部 R、G、B 通道的均值。
结果发现:
| 通道 | 暗部均值(10-bit) | 目标黑电平 | 偏差 |
|---|---|---|---|
| R | 72 | 64 | +8 |
| Gr | 66 | 64 | +2 |
| Gb | 65 | 64 | +1 |
| B | 68 | 64 | +4 |
R 通道的黑电平比其他通道高了 4-7 个码值。这意味着暗部 R 通道没有被充分扣除,导致偏紫。
根因是什么?
- 该 sensor 的 R 通道暗电流温度系数与其他通道不同。
- OB 行统计用的是全通道均值,没有分通道处理。
- 温度升高后,R 通道暗电流增长更快,但补偿值没跟上。
解决方案:
- 将 BLC 改为 分通道补偿——R、Gr、Gb、B 各自统计 OB 值。
- 增加温度传感器,查表修正各通道的补偿系数。
- 在 FPGA 中为每个通道独立设置减法器。
改完后,暗部偏紫问题彻底解决。R 通道黑电平偏差从 +8 降到 +1 以内。
经验总结:BLC 调试不要只看整体均值。分通道看,分区域看。我习惯在调试阶段,让 FPGA 输出每个通道的 OB 统计值到 UART,用串口助手实时观察。这样能快速定位是 sensor 问题还是算法问题。
嗯,BLC 这部分就讲到这里。记住一句话:黑色都搞不定,后面全是白搭。