2. 黑电平校正
任务目标
图像传感器在无光照条件下仍会输出非零信号,该信号由暗电流引起,导致图像暗部发灰、对比度下降。本案例实现黑电平校正(Black Level Correction, BLC),从原始RAW数据中减去暗电流分量,恢复纯净的黑电平,提升暗部细节表现。
实现原理
黑电平校正的核心操作是:out_pixel = max(0, in_pixel - black_level)。其中black_level为传感器暗电流对应的数字值。实际场景中,传感器不同通道(R/Gr/Gb/B)的暗电流略有差异,因此需要分别统计各通道的黑电平值。本案例提供两种模式:静态模式(直接指定固定黑电平值)和动态模式(从暗行数据中实时统计各通道黑电平)。
完整实现
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#define WIDTH 640
#define HEIGHT 480
#define BAYER_PATTERN 0 // 0: RGGB, 1: BGGR
// 从RAW图像暗行(光学黑区)统计各通道黑电平
// dark_row_data: 指向暗行数据,每行包含WIDTH个像素
// num_dark_rows: 暗行数量
// black_level[4]: 输出R, Gr, Gb, B四个通道的黑电平
void calculate_black_level(uint16_t *dark_row_data, int num_dark_rows, uint16_t black_level[4]) {
uint64_t sum[4] = {0};
int count[4] = {0};
for (int row = 0; row < num_dark_rows; row++) {
for (int col = 0; col < WIDTH; col++) {
int channel = 0;
// 根据Bayer模式确定通道索引
if (BAYER_PATTERN == 0) { // RGGB
if (row % 2 == 0 && col % 2 == 0) channel = 0; // R
else if (row % 2 == 0 && col % 2 == 1) channel = 1; // Gr
else if (row % 2 == 1 && col % 2 == 0) channel = 2; // Gb
else channel = 3; // B
}
sum[channel] += dark_row_data[row * WIDTH + col];
count[channel]++;
}
}
for (int i = 0; i < 4; i++) {
black_level[i] = (count[i] > 0) ? (uint16_t)(sum[i] / count[i]) : 0;
}
}
// 黑电平校正主函数
// input: 输入RAW图像(Bayer格式)
// output: 输出校正后RAW图像
// black_level: 各通道黑电平值
void black_level_correction(uint16_t *input, uint16_t *output, uint16_t black_level[4]) {
for (int row = 0; row < HEIGHT; row++) {
for (int col = 0; col < WIDTH; col++) {
int channel = 0;
if (BAYER_PATTERN == 0) { // RGGB
if (row % 2 == 0 && col % 2 == 0) channel = 0;
else if (row % 2 == 0 && col % 2 == 1) channel = 1;
else if (row % 2 == 1 && col % 2 == 0) channel = 2;
else channel = 3;
}
int corrected = (int)input[row * WIDTH + col] - (int)black_level[channel];
output[row * WIDTH + col] = (corrected > 0) ? (uint16_t)corrected : 0;
}
}
}
// 测试入口
int main() {
// 模拟输入:全黑图像(暗电流值约64)+ 少量暗部细节
static uint16_t raw_image[HEIGHT * WIDTH];
static uint16_t corrected_image[HEIGHT * WIDTH];
static uint16_t dark_rows[10 * WIDTH]; // 10行暗行数据
// 生成模拟数据:暗行像素值在60~68之间波动
for (int i = 0; i < 10 * WIDTH; i++) {
dark_rows[i] = 60 + (i % 9); // 模拟暗电流噪声
}
// 生成主图像:暗部细节信号叠加在暗电流上
for (int i = 0; i < HEIGHT * WIDTH; i++) {
int row = i / WIDTH;
int col = i % WIDTH;
// 模拟暗部细节:部分区域有微弱信号(10~20),其余为纯暗电流
if (row > 200 && row < 280 && col > 200 && col < 440) {
raw_image[i] = 64 + ((row + col) % 15); // 细节区域
} else {
raw_image[i] = 64 + (i % 5); // 背景暗电流
}
}
// 步骤1:从暗行统计黑电平
uint16_t bl[4];
calculate_black_level(dark_rows, 10, bl);
printf("统计黑电平: R=%u, Gr=%u, Gb=%u, B=%u\n", bl[0], bl[1], bl[2], bl[3]);
// 步骤2:执行黑电平校正
black_level_correction(raw_image, corrected_image, bl);
// 验证:检查校正前后暗部区域均值
uint64_t before_sum = 0, after_sum = 0;
int sample_count = 0;
for (int row = 100; row < 150; row++) { // 纯背景区域
for (int col = 100; col < 150; col++) {
before_sum += raw_image[row * WIDTH + col];
after_sum += corrected_image[row * WIDTH + col];
sample_count++;
}
}
printf("背景区域校正前均值: %.2f\n", (double)before_sum / sample_count);
printf("背景区域校正后均值: %.2f\n", (double)after_sum / sample_count);
printf("暗部细节区域校正后最小值: %u\n", corrected_image[240 * WIDTH + 300]);
// 判定:校正后背景均值应接近0(小于5),细节区域应保留
double bg_after = (double)after_sum / sample_count;
if (bg_after < 5.0 && corrected_image[240 * WIDTH + 300] > 0) {
printf("测试通过:黑电平已消除,暗部细节保留\n");
return 0;
} else {
printf("测试失败\n");
return 1;
}
}
编译与运行
gcc -o blc_test blc_test.c -std=c99
./blc_test
验证方法
程序输出应满足以下条件:
- 统计得到的四个通道黑电平值均在60~68范围内(与模拟暗电流一致)
- 背景区域校正后均值接近0(小于5),证明暗电流被有效消除
- 暗部细节区域(坐标240,300处)校正后值大于0,证明细节信息未丢失
- 最终打印
测试通过即表示功能正确
关键说明
- 本实现使用10位RAW数据(0~1023),实际传感器位深可能不同,需按需调整数据类型
- 动态模式从暗行统计黑电平,适用于温度变化导致暗电流漂移的场景;若传感器提供固定黑电平寄存器,可直接使用静态值
- Bayer模式需与传感器输出一致,本代码默认RGGB排列,BGGR模式可修改
BAYER_PATTERN宏并调整通道映射 - 实际ISP流水线中,黑电平校正通常位于线性化之后、去马赛克之前,且需配合数字增益使用