第2章 RAW图基础:Bayer阵列、黑电平与数据存储

各位同学,欢迎来到第二章。上一章我们聊了ISP的整体框架,今天咱们把镜头拉近,看看ISP处理的第一口奶——RAW图到底长什么样。

说实话,我见过不少刚入行的工程师,算法调得飞起,但连RAW数据在内存里怎么排列的都没搞清楚。结果呢?调试时出了诡异的花屏,查了半天才发现是Bayer排布读错了。嗯,这种坑,踩过一次就记住了。

2.1 Bayer阵列格式——为什么是RGGB?

人眼对绿色最敏感,所以拜耳阵列里绿色像素占了一半。典型的RGGB模式,就是一行RGRG,下一行GBGB,交替排列。说白了,每个像素只记录一种颜色,另外两种颜色要靠插值算出来。

核心要点:Bayer阵列不是“每个像素都有完整RGB”,而是“每个像素只采集一种颜色分量”。后续的去马赛克(Demosaic)算法,就是要把缺失的两种颜色猜出来。

常见的Bayer模式有四种:

模式 第一行 第二行 典型传感器
RGGB R G R G ... G B G B ... Sony IMX系列
BGGR B G B G ... G R G R ... 部分OmniVision
GRBG G R G R ... B G B G ... 某些三星传感器
GBRG G B G B ... R G R G ... 较少见

我个人习惯在项目一开始,就先把传感器的Bayer排布写死在配置头文件里。别问我为什么——有一次我接手一个老项目,文档里写的是RGGB,实际传感器输出是BGGR,结果整个pipeline跑出来的颜色都是反的,查了两天才找到原因。

小技巧:拿到一个新传感器,先拍一张均匀白板,然后看RAW数据里哪个通道的均值最高。绿色通道应该最高,红色次之,蓝色最低。如果顺序不对,八成是Bayer排布搞反了。

2.2 黑电平校正——把“纯黑”找回来

理想情况下,没有光的时候,像素输出应该是0。但现实是,传感器即使在全黑环境下,也会有一个基础输出值,这个值就叫黑电平(Black Level)。

为什么会这样?说白了,传感器电路本身有暗电流,加上ADC的偏移,导致“纯黑”不黑。如果不校正,暗部会发灰,而且动态范围也会被压缩。

黑电平校正的公式很简单:

// 伪代码示例
for each pixel in raw_image:
    corrected_pixel = raw_pixel - black_level
    if corrected_pixel < 0:
        corrected_pixel = 0

但这里有个坑——黑电平值不是固定的。我遇到过两种情况:

  • 全局黑电平:整个传感器共用一个黑电平值,比如64(12-bit RAW)。这种最简单,直接减就行。
  • 逐通道黑电平:R、G、B通道的黑电平不一样。有些传感器甚至每列都有一个OB(Optical Black)像素,用来实时计算黑电平。

注意:千万不要对所有通道用同一个黑电平值。我曾经在一个项目里偷懒,R通道和B通道的黑电平差了8个DN(Digital Number),结果暗部偏色严重,怎么调白平衡都救不回来。

实际工程中,黑电平校正通常分两步:

  1. 静态校正:从传感器规格书里拿到黑电平标称值,先做一次全局减法。
  2. 动态校正:利用传感器边缘的OB像素,实时计算当前帧的黑电平偏移,做二次微调。

避坑指南:我曾经在一个低光照项目中,发现暗部出现条纹噪声。查到最后,是黑电平校正后没有做clip(钳位),导致负值像素在后续处理中产生了溢出。记住:校正后一定要做0值钳位。

2.3 RAW图的数据存储格式

RAW图不是标准的图像格式,它本质上是传感器输出的原始数字信号。常见的位深有10-bit、12-bit、14-bit,甚至16-bit。位深越高,动态范围越大,但数据量也越大。

存储格式上,主要有两种:

格式 描述 典型场景
MIPI RAW 按MIPI协议打包,每像素按位深紧凑排列 手机摄像头、嵌入式平台
DNG Adobe的开放RAW格式,包含元数据 专业相机、后期处理
Plain RAW 裸数据,无头信息,纯像素值 算法调试、仿真验证

在ISP调试阶段,我们最常用的是Plain RAW格式。比如一个1920x1080的12-bit RAW图,每个像素占12位,但计算机按字节对齐,所以实际存储时每个像素占2字节(16位),高4位补0。

// 读取12-bit RAW数据的示例(C语言风格)
uint16_t *raw_buffer = malloc(width * height * sizeof(uint16_t));
FILE *fp = fopen("raw_12bit.raw", "rb");
fread(raw_buffer, sizeof(uint16_t), width * height, fp);
fclose(fp);

// 注意:这里读出来的是低12位有效,高4位为0
for (int i = 0; i < width * height; i++) {
    uint16_t pixel = raw_buffer[i] & 0x0FFF;  // 取低12位
}

经验之谈:我建议在调试阶段,统一把RAW数据转成16-bit无符号整型存储。虽然浪费一点空间,但处理起来方便,不会因为位深不同而出bug。等算法稳定了,再优化成紧凑存储。

2.4 本章知识结构图

下面这张图,把本章的核心知识点串起来了。你想想看,从传感器出来的RAW数据,先要搞清楚Bayer排布,然后做黑电平校正,最后按正确的格式存下来。每一步都有坑,每一步都有技巧。

RAW图基础核心流程 RAW图基础核心流程 传感器原始输出 Bayer阵列识别(RGGB/BGGR等) 黑电平校正(静态+动态) RAW数据存储(MIPI/DNG/Plain) 常见陷阱 Bayer排布搞反 → 颜色错误 | 黑电平未钳位 → 暗部噪声 | 位深对齐错误 → 数据溢出 关键参数 位深:10/12/14-bit 黑电平:通常16-64 DN Bayer模式:4种变体

2.5 本章小结

这一章我们干了三件事:

  • 搞清楚了Bayer阵列的四种排布方式,以及为什么绿色像素占一半。
  • 理解了黑电平的成因和校正方法,记住了“校正后一定要钳位”。
  • 掌握了RAW数据的常见存储格式和位深对齐技巧。

这些东西看起来基础,但我在实际项目中见过太多人在这上面翻车。你想想看,如果连RAW数据都读不对,后面的去马赛克、白平衡、降噪,全都是空中楼阁。