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流水线中,黑电平校正通常位于线性化之后、去马赛克之前,且需配合数字增益使用