第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),结果暗部偏色严重,怎么调白平衡都救不回来。
实际工程中,黑电平校正通常分两步:
- 静态校正:从传感器规格书里拿到黑电平标称值,先做一次全局减法。
- 动态校正:利用传感器边缘的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排布,然后做黑电平校正,最后按正确的格式存下来。每一步都有坑,每一步都有技巧。
2.5 本章小结
这一章我们干了三件事:
- 搞清楚了Bayer阵列的四种排布方式,以及为什么绿色像素占一半。
- 理解了黑电平的成因和校正方法,记住了“校正后一定要钳位”。
- 掌握了RAW数据的常见存储格式和位深对齐技巧。
这些东西看起来基础,但我在实际项目中见过太多人在这上面翻车。你想想看,如果连RAW数据都读不对,后面的去马赛克、白平衡、降噪,全都是空中楼阁。