4、黑电平校正(BLC):原理、实现与调参实战
黑电平校正,圈里人常叫 BLC(Black Level Correction)。
说白了,就是告诉相机:“你觉得的黑色,到底是不是真的黑色?”
我刚开始接触 ISP 时,觉得这步很简单——不就是减个值嘛。后来被一个暗部偏色的案子折腾了两周,才发现这里面的水挺深。今天咱们就把 BLC 的原理、暗电流补偿、以及调参方法彻底聊透。
4.1 为什么需要黑电平校正?
理想情况下,sensor 在全黑环境(没有光进入)时,输出的 RAW 数据应该是 0。但现实是,sensor 会输出一个非零的数值。
为什么会这样?主要有两个原因:
- 暗电流(Dark Current):sensor 的像素单元即使没有光照,由于热激发也会产生电子。温度越高,暗电流越大。我曾在夏天户外测试过一台设备,暗电流比实验室环境高了将近一倍。
- ADC 偏移(ADC Offset):模数转换器本身有一个固定的偏置电压,保证信号不会落到转换器的非线性区。
这个非零的底值,就是黑电平(Black Level)。如果不校正,你会发现:
- 暗部发灰、发绿(因为不同通道的黑电平可能不同)
- 动态范围被压缩(本来属于信号的码值被黑电平占用了)
- 后续的 AWB、CCM 都会算不准
一句话总结:BLC 就是把 sensor 输出的 RAW 数据减去一个“黑底值”,让真正的黑色对应到 0。
4.2 BLC 的核心原理
BLC 的数学模型非常简单:
// 基本公式
P_out = P_in - B
// 如果考虑不同通道差异(RGGB)
P_out(R) = P_in(R) - B_R
P_out(Gr) = P_in(Gr) - B_Gr
P_out(Gb) = P_in(Gb) - B_Gb
P_out(B) = P_in(B) - B_B
其中:
P_in:sensor 输出的原始 RAW 值P_out:校正后的 RAW 值B:黑电平值(可以是全局值,也可以是每个通道独立的值)
但实际工程中,事情没那么简单。你想想看,暗电流是随温度变化的,ADC 偏移也可能随增益变化。所以 BLC 参数通常不是固定死的。
4.3 暗电流补偿:BLC 的进阶玩法
暗电流补偿,说白了就是“动态 BLC”。
大多数 sensor 会在像素阵列的边缘预留一些 光学黑像素(Optical Black, OB)。这些像素被金属层遮挡,收不到光,但和有效像素一样产生暗电流。
我常用的做法是这样的:
- 读取 OB 区域:从 sensor 的 OB 行/列中读出当前帧的暗电流水平
- 计算均值:对 OB 像素取平均(有时会去掉最大最小值,防止坏点干扰)
- 逐行/逐帧补偿:用这个均值去校正当前帧的有效像素
我的经验:OB 区域不要取太多行,一般取 4~8 行就够了。取多了反而会引入噪声。我曾经在一个项目中取了 32 行 OB,结果暗电流均值波动很大,导致画面出现闪烁。后来改成 8 行,问题就解决了。
暗电流补偿的流程可以用下面这张图来理解:
4.4 BLC 的工程实现
实际代码实现时,我一般会考虑几个细节:
// 伪代码:BLC 处理函数
void blc_process(uint16_t *raw, int width, int height,
int blc_r, int blc_gr, int blc_gb, int blc_b) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int idx = y * width + x;
int channel = get_bayer_channel(x, y); // RGGB
int blc_val;
switch (channel) {
case 0: blc_val = blc_r; break; // R
case 1: blc_val = blc_gr; break; // Gr
case 2: blc_val = blc_gb; break; // Gb
case 3: blc_val = blc_b; break; // B
}
// 减法,并做 clamp 防止下溢
int corrected = (int)raw[idx] - blc_val;
raw[idx] = (uint16_t)CLAMP(corrected, 0, 65535);
}
}
}
注意:减法后一定要做 clamp 到 0。如果不做,负值在后续处理中会变成很大的正数(因为 uint16_t 的溢出),导致画面出现“雪花”噪点。我曾经在一个项目中忘记加 clamp,结果暗部全是亮点,排查了整整一天才发现是这里的问题。
4.5 BLC 参数调试方法
调试 BLC 参数,我有一套固定的流程:
4.5.1 静态 BLC 标定
在实验室环境下,用遮光罩盖住镜头,拍摄全黑帧:
- 盖上镜头盖,确保完全遮光
- 拍摄 10~20 帧 RAW 数据
- 分别统计 R、Gr、Gb、B 四个通道的均值
- 这些均值就是初始的黑电平值
| 通道 | 典型 10bit RAW 值 | 典型 12bit RAW 值 |
|---|---|---|
| R | 64 | 256 |
| Gr | 66 | 264 |
| Gb | 65 | 260 |
| B | 63 | 252 |
你看,不同通道的黑电平其实是有差异的。如果统一用一个值减,暗部就会出现色偏。
4.5.2 动态 BLC 调试
静态标定只是起点。实际产品中,温度和增益变化会改变黑电平。我建议:
- 温度补偿:在 sensor 附近放一个温度传感器,建立“温度 vs 黑电平”的查找表
- 增益补偿:模拟增益增大时,暗电流噪声也会被放大,黑电平需要相应调整
我的经验:调试时不要只看全黑帧。我习惯拍一组“暗部灰阶卡”(比如 0~5% 反射率的色块),观察 BLC 校正后暗部的线性度。如果暗部出现“死黑”(全部为 0)或者“发灰”(校正不足),说明 BLC 参数需要微调。
4.5.3 常见问题与避坑
- 问题:暗部偏绿 → 通常是 Gr/Gb 通道的黑电平值偏小,导致绿色通道校正不足。适当增大 G 通道的 BLC 值。
- 问题:画面闪烁 → 可能是动态 BLC 的 OB 均值波动太大。增加 OB 像素的统计行数,或者做时域滤波。
- 问题:暗部细节丢失 → BLC 值减得太多,把有效信号也减掉了。检查一下是不是把暗电流和 ADC 偏移算重复了。
我曾经踩过的坑:有一次为了追求“纯净的黑色”,我把 BLC 值设得偏大。结果在低光照场景下,暗部细节全部丢失,画面像被“挖掉”了一块。后来才意识到,BLC 不是越大越好,要留出一定的余量给暗部信号。
4.6 调试工具与验证方法
我常用的验证方法很简单:
- 直方图检查:校正后的全黑帧直方图应该集中在 0 附近,且没有明显的“拖尾”
- 暗部线性度:用不同曝光时间拍暗部,检查 RAW 值的线性关系
- 温度漂移测试:把设备放在恒温箱里,从 -20°C 到 60°C 测试黑电平变化
嗯,BLC 这部分就聊到这儿。记住一句话:黑电平校正不是简单的减法,而是要在“去掉噪声”和“保留暗部细节”之间找到平衡点。这个平衡点,就是调优的价值所在。