3、黑电平校正(BLC):把“假黑”变成“真黑”
黑电平校正,英文叫 Black Level Correction,简称 BLC。很多刚入行的工程师觉得这步很简单——不就是减个值嘛?其实没那么简单。
我刚开始做 ISP 调优时,就吃过黑电平的亏。有一款 sensor 在暗光下画面偏紫,我调了半天 AWB 都没用。后来才发现,是黑电平没校好,导致 R 通道的底噪被当成了信号。嗯,从那以后,我再也不敢小看 BLC 了。
3.1 黑电平是怎么产生的?
先问一个问题:sensor 在完全无光的情况下,输出应该是 0 吗?
答案是:不是。理想情况下是 0,但实际 sensor 会输出一个非零值。这个值就是黑电平。
为什么会这样?主要有三个原因:
- 暗电流:半导体本身的热激发会产生电子,温度越高,暗电流越大。夏天做测试和冬天做测试,黑电平可能差好几个码值。
- ADC 偏移:模数转换器本身有一个固定的偏置,目的是避免信号在零点附近被截断。说白了,就是给信号加了一个“安全垫”。
- 像素结构差异:不同通道(R/Gr/Gb/B)的像素物理特性不完全一致,导致各通道的黑电平也不一样。
关键认知:黑电平不是噪声,它是信号的一部分。如果不校正,整个图像的动态范围会被压缩,暗部细节丢失,颜色也会偏掉。
3.2 BLC 校正原理:到底在做什么?
BLC 的原理一句话就能说清:测出黑电平的值,然后在每个像素上减去它。
但实际做起来,有几个细节要注意。
3.2.1 黑电平的测量方式
sensor 通常会提供两种方式来获取黑电平:
| 方式 | 说明 | 优缺点 |
|---|---|---|
| OB(Optical Black)像素 | sensor 边缘有一圈被遮挡的像素,直接读取其输出值 | 实时性好,但 OB 像素数量有限,容易受噪声影响 |
| 暗场标定 | 盖上镜头盖,采集多帧图像取平均 | 精度高,但无法实时更新,温度变化后需要重新标定 |
我在项目中常用的做法是:先用暗场标定拿到基准值,然后在运行中利用 OB 像素做微调。这样既保证了精度,又能适应温度变化。
3.2.2 分通道校正
不同通道的黑电平不一样,必须分开处理。尤其是 Bayer 格式下的 R、Gr、Gb、B 四个通道,Gr 和 Gb 之间都可能存在差异。
举个例子,某款 sensor 在 30°C 下的黑电平值:
| 通道 | 黑电平(10bit) |
|---|---|
| R | 64 |
| Gr | 60 |
| Gb | 62 |
| B | 66 |
如果不分开校正,Gr 和 Gb 的差异会导致画面出现网格状固定噪声,尤其在暗部非常明显。
3.2.3 校正后的钳位处理
减去黑电平后,可能会出现负值。这时候需要做钳位(clamp),把负值置为 0。
但这里有个坑:如果黑电平测大了,减多了,暗部细节就被削掉了。我曾经在一个项目中,因为 OB 像素受温度影响偏大,导致暗部一片死黑,后来改用多帧平均才解决。
我的建议:在调 BLC 参数时,留一点余量。比如测出来黑电平是 64,你可以设成 62,宁可保留一点点底噪,也不要削掉暗部细节。底噪可以在后面的降噪模块处理。
3.3 BLC 参数调优实战
好了,理论讲完了,咱们来点实际的。BLC 调优通常分三步走。
3.3.1 第一步:标定基准黑电平
操作步骤:
- 把 sensor 放在目标工作温度下(比如 25°C、45°C、60°C)。
- 盖上镜头盖,确保完全无光。
- 采集 100 帧图像,逐像素取平均。
- 分别统计 R、Gr、Gb、B 四个通道的平均值。
代码示例(伪代码):
// 假设 raw_data 是 10bit Bayer 图像
// 分辨率为 1920x1080
uint32_t sum_r = 0, sum_gr = 0, sum_gb = 0, sum_b = 0;
uint32_t cnt_r = 0, cnt_gr = 0, cnt_gb = 0, cnt_b = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if (y % 2 == 0 && x % 2 == 0) { // R
sum_r += raw_data[y][x];
cnt_r++;
} else if (y % 2 == 0 && x % 2 == 1) { // Gr
sum_gr += raw_data[y][x];
cnt_gr++;
} else if (y % 2 == 1 && x % 2 == 0) { // Gb
sum_gb += raw_data[y][x];
cnt_gb++;
} else { // B
sum_b += raw_data[y][x];
cnt_b++;
}
}
}
blc_r = sum_r / cnt_r;
blc_gr = sum_gr / cnt_gr;
blc_gb = sum_gb / cnt_gb;
blc_b = sum_b / cnt_b;
3.3.2 第二步:验证校正效果
标定完成后,把参数写进 ISP 寄存器,然后做验证:
- 看直方图:校正后的暗场直方图应该集中在 0 附近,且各通道的分布基本重合。
- 看暗部细节:在 1-5 lux 的极暗光下拍摄,检查画面是否偏色、是否有网格噪声。
- 看温度漂移:让设备从冷启动到稳定工作温度,观察黑电平的变化量。
注意:如果校正后暗部出现明显的色偏(比如偏绿或偏紫),大概率是某个通道的黑电平没校准。不要急着调 AWB,先回头检查 BLC。
3.3.3 第三步:温度补偿策略
安防监控设备的工作温度范围很宽,从 -20°C 到 60°C 都很常见。黑电平随温度的变化是非线性的,不能用一个固定值搞定。
我常用的做法是:
- 在 3-5 个温度点做标定(比如 -10°C、10°C、30°C、50°C、70°C)。
- 建立温度-黑电平查找表(LUT)。
- 运行时通过 sensor 内置的温度传感器读取当前温度,查表插值得到当前黑电平。
表格示例:
| 温度 (°C) | R 通道 | Gr 通道 | Gb 通道 | B 通道 |
|---|---|---|---|---|
| -10 | 58 | 55 | 56 | 60 |
| 10 | 61 | 58 | 59 | 63 |
| 30 | 64 | 60 | 62 | 66 |
| 50 | 70 | 66 | 68 | 72 |
| 70 | 78 | 74 | 76 | 80 |
你想想看,如果不做温度补偿,夏天和冬天拍出来的画面暗部表现会差很多。这在安防监控里是不能接受的。
3.4 BLC 在 ISP Pipeline 中的位置
为了让你更直观地理解 BLC 在整个 ISP 流程中的位置,我画了一张图:
从图上可以看得很清楚:BLC 是 Raw 域的第一个校正模块。它做不好,后面的去噪、AWB、CCM 全都会跟着跑偏。这就是为什么我总跟团队说,调 ISP 要先调 BLC,就像盖房子要先打地基。
3.5 避坑指南
最后,分享几个我踩过的坑:
- 不要只看 OB 像素的平均值。OB 像素本身也有噪声,建议做中值滤波或多次采样。
- 注意 sensor 的线性度。有些 sensor 在低照度下响应是非线性的,这时候 BLC 校正后还需要做线性化补偿。
- 调试时别用自动曝光。我曾经犯过这个错——一边调 BLC 一边让 AE 自动跑,结果画面忽明忽暗,根本看不出 BLC 的效果。调 BLC 时,请固定曝光时间和增益。
- 量产时要留校准接口。同一型号的 sensor,不同批次的黑电平可能有差异。最好在产线上做一次快速标定,把参数写进设备的 EEPROM。
一个小技巧:如果你手头没有专业的黑电平标定工具,可以用一张均匀的灰卡(18% 灰),在中等光照下拍摄,然后看直方图。各通道的峰值位置如果错开了,说明 BLC 没校好。这个方法虽然不精确,但做快速验证足够了。