RAW图基础:Bayer阵列、数据存储与黑电平白平衡
好,我们正式开始ISP的第一站——RAW图。很多初学者觉得RAW就是个原始数据,没什么好讲的。其实不然。我做了这么多年ISP,可以负责任地说:RAW图的理解深度,直接决定了你ISP管线的上限。你想想看,如果连传感器出来的原始数据都搞不清楚,后面的处理全是空中楼阁。
2.1 为什么是RAW?
相机传感器只能感知光的强度,它分不清颜色。那彩色照片怎么来的?答案就是——Bayer阵列。传感器每个像素前面盖一个微小的滤色片,只让特定颜色的光通过。最常见的模式是RGGB,也就是一个像素只记录红、绿、蓝中的一种。
我个人习惯把RAW图叫做“半成品”。它不像JPEG那样经过白平衡、去马赛克、伽马校正等一堆处理。RAW就是传感器最原始的电荷读数,每个像素一个数值,没有颜色,只有亮度。嗯,这里要注意:RAW图是灰度的,你直接看RAW文件,看到的是一张灰蒙蒙的图,别怀疑自己眼睛。
核心概念:RAW图 = 每个像素只记录一种颜色分量的原始亮度值。后续所有ISP算法,都是在这个基础上“猜”出完整彩色图像的。
2.2 Bayer阵列格式
Bayer阵列是ISP的起点。最常见的排列是RGGB,也就是第一行R、G、R、G...,第二行G、B、G、B...。为什么绿色多一倍?因为人眼对绿色最敏感,传感器厂商就多放了一半的绿色像素,模拟人眼的亮度感知。
我在项目中遇到过一个问题:客户拿了一个RAW文件,怎么调颜色都不对。后来一查,传感器用的是BGGR排列,而我们代码里写死了RGGB。你想想看,颜色通道全错位了,能对吗?所以,拿到任何RAW文件,第一件事就是确认Bayer排列。
常见的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 ... | 较少见 |
小技巧:拿到陌生RAW文件,可以先取一小块平坦区域(比如灰卡),看每个像素的数值分布。如果R和B位置数值接近,G位置数值偏高,那大概率是RGGB。如果R位置数值偏低,那就是BGGR。多试几次就有手感了。
2.3 RAW数据存储
RAW数据怎么存的?说白了就是每个像素一个数字。传感器位深常见的有10bit、12bit、14bit。手机传感器大多是10bit,专业相机能到14bit甚至16bit。
存储格式上,最常见的是MIPI RAW格式。10bit数据用16bit存?太浪费了。MIPI规定了一种打包方式:每5个10bit像素打包成64bit(8字节),这样节省带宽。我刚开始做驱动时,被这个打包折腾得够呛——解包时字节顺序搞反了,出来的图像全是条纹。
来看一个简单的RAW数据读取示例(假设10bit、RGGB、未打包):
// 伪代码:读取RAW文件
int width = 1920;
int height = 1080;
int bit_depth = 10;
// 每个像素占2字节(低10位有效)
uint16_t* raw_data = new uint16_t[width * height];
fread(raw_data, 2, width * height, file);
// 提取R通道(位置:奇数行、奇数列)
for (int row = 0; row < height; row += 2) {
for (int col = 0; col < width; col += 2) {
int idx = row * width + col;
uint16_t r_val = raw_data[idx] & 0x03FF; // 取低10位
// 处理R像素...
}
}
避坑指南:我曾经在项目里直接用了fread读取RAW文件,没注意字节序。结果在ARM小端平台上,高6位和低10位反了,图像暗部全是噪点。记住:RAW数据读取后,一定要确认字节序和位对齐。建议先读一个像素,打印出来看看数值范围是否合理。
2.4 黑电平(Black Level)
传感器在没有光照时,理论上输出应该是0。但实际不是——由于暗电流、电路噪声等因素,无光照时像素会有一个基础读数,这就是黑电平。
黑电平的值一般在传感器数据手册里给出。比如某传感器黑电平是64(10bit下),那么所有像素值都要减去64,才是真实的光电信号。如果不做这一步,暗部会偏色,而且后续的白平衡、去噪都会受影响。
我习惯把黑电平校正叫做“归零操作”。说白了就是:把传感器的“虚假零点”拉回真正的0。
// 黑电平校正
uint16_t black_level = 64; // 传感器手册给出
for (int i = 0; i < width * height; i++) {
int corrected = raw_data[i] - black_level;
if (corrected < 0) corrected = 0; // 钳位
raw_data[i] = (uint16_t)corrected;
}
经验之谈:有些传感器会提供OB(Optical Black)区域——传感器边缘被遮挡的像素,它们的读数就是实时的黑电平。我建议用OB区域的平均值作为黑电平,比固定值更准,因为黑电平会随温度、增益变化。
2.5 白平衡基础
白平衡是什么?说白了就是让白色物体在任何光源下都呈现白色。人眼会自动适应光源色温,但传感器不会。日光下拍的白纸,到了白炽灯下就偏黄,到了荧光灯下就偏蓝。
白平衡的核心是调整R、G、B三个通道的增益,让中性色(灰卡、白纸)的R、G、B值相等。数学上很简单:
// 白平衡增益应用
float r_gain = 1.8; // 红色通道增益
float g_gain = 1.0; // 绿色通道通常为1.0
float b_gain = 1.4; // 蓝色通道增益
for (int i = 0; i < width * height; i++) {
// 根据Bayer位置判断颜色
if (is_red_pixel(i)) {
raw_data[i] = (uint16_t)(raw_data[i] * r_gain);
} else if (is_blue_pixel(i)) {
raw_data[i] = (uint16_t)(raw_data[i] * b_gain);
}
// 绿色像素不变(或乘以g_gain)
}
增益怎么来的?最经典的方法是灰卡法:拍一张灰卡,计算R、G、B的平均值,然后让它们相等。比如R平均=100,G平均=120,B平均=90,那么R增益=120/100=1.2,B增益=120/90≈1.33。
注意:白平衡增益是在Bayer域(RAW图)上直接乘的,不是在RGB彩色图上。因为此时每个像素只有一种颜色,乘增益不会引入颜色插值误差。这是ISP管线中“越早处理越好”的典型例子。
2.6 本章知识体系
下面这张图总结了本章的核心逻辑:从传感器光信号到RAW数据,再到黑电平校正和白平衡,为后续的去马赛克、去噪等处理打好基础。
2.7 本章小结
这一章我们讲了RAW图最核心的三个基础:
- Bayer阵列:四种常见排列,决定了颜色通道的布局。拿到RAW文件先确认排列方式。
- RAW数据存储:位深、字节序、打包方式。读取时务必验证数据正确性。
- 黑电平与白平衡:黑电平校正让暗部归零,白平衡让中性色还原。两者都是在Bayer域完成的早期处理。
我个人觉得,这一章的内容虽然基础,但却是ISP管线中最容易出错的地方。很多新手一上来就调后面的去噪、锐化,结果发现效果不好,回头一查——黑电平没做对,或者Bayer排列搞错了。嗯,基础打牢,后面才能走得远。