4. RAW图基础
好,咱们进入第四章。前面几章我们把ISP的整体流程和硬件架构聊了一遍,从这章开始,要真正碰图像传感器输出的原始数据了——也就是RAW图。
说实话,我刚开始接触ISP时,觉得RAW图不就是一堆数字嘛,有什么好研究的?后来被现实狠狠教育了一顿。有一次项目调试,图像整体偏暗,我调了半天增益和曝光,效果都不理想。最后发现,是黑电平校正的参数没设对,导致整个动态范围都偏移了。从那以后,我再也不敢小看RAW图的基础处理了。
4.1 Bayer格式详解
大部分单传感器相机,每个像素只能感知光的强度,分辨不了颜色。那彩色图像怎么来的?答案就是Bayer格式——在传感器表面覆盖一层彩色滤光片阵列(CFA)。
最常见的Bayer模式是RGGB,也就是一个像素感知红色(R),两个像素感知绿色(G),一个像素感知蓝色(B)。为什么绿色有两个?因为人眼对绿色最敏感,多采点绿光能提升视觉分辨率。
| Bayer模式 | 排列方式 | 常见应用 |
|---|---|---|
| RGGB | 第一行 R G R G... 第二行 G B G B... |
最主流,大部分手机/安防传感器 |
| BGGR | 第一行 B G B G... 第二行 G R G R... |
部分Sony、OmniVision传感器 |
| GRBG | 第一行 G R G R... 第二行 B G B G... |
部分三星、安森美传感器 |
| GBRG | 第一行 G B G B... 第二行 R G R G... |
较少见,某些工业相机 |
sensor_fmt->bayer_pattern = BAYER_RGGB。如果配错了,出来的图像颜色会完全错乱,像打翻了的调色盘。
Bayer格式的RAW数据,每个像素只存一个颜色通道的值。比如一个1200万像素的传感器,输出就是1200万个单色值,而不是RGB三个值。这也就意味着,后续必须经过去马赛克(Demosaic)算法,才能恢复出每个像素的RGB信息。
嗯,这里要注意:Bayer格式的存储方式有两种——打包模式(Packed)和非打包模式(Unpacked)。打包模式是10bit数据存成16bit,高位对齐或低位对齐;非打包模式就是直接存成16bit。RK平台的ISP通常支持这两种模式,但驱动里要配对。
4.2 RAW图统计信息
拿到RAW图之后,不能直接扔给ISP处理。你得先看看这张图的质量怎么样。我个人习惯先做三件事:看直方图、算均值、找坏点。
4.2.1 直方图统计
直方图能告诉你图像的亮度分布。比如,如果直方图集中在左侧,说明图像偏暗;集中在右侧,说明偏亮;如果中间有断层,可能是有坏点或者数据丢失。
在RK平台上,ISP硬件模块会直接输出R、G、B三个通道的直方图。你可以通过驱动接口读取:
// 伪代码:读取RK ISP直方图统计
struct rkisp_hist_stats hist;
ioctl(fd, RKISP_CMD_GET_HIST_STATS, &hist);
// hist.r_hist[256] R通道直方图
// hist.g_hist[256] G通道直方图
// hist.b_hist[256] B通道直方图
// 检查是否过曝
if (hist.r_hist[255] > total_pixels * 0.01) {
printf("R通道有1%以上像素过曝,建议降低增益\n");
}
4.2.2 均值与方差
除了直方图,我还喜欢算一下每个通道的均值和方差。均值能反映整体亮度水平,方差能反映纹理丰富程度。
| 统计量 | 含义 | 正常范围(8bit RAW) |
|---|---|---|
| R均值 | 红色通道平均亮度 | 30~200(视场景而定) |
| G均值 | 绿色通道平均亮度 | 30~200 |
| B均值 | 蓝色通道平均亮度 | 30~200 |
| R/G比值 | 红绿通道比例,判断白平衡 | 0.8~1.2(D65光源下) |
| B/G比值 | 蓝绿通道比例,判断白平衡 | 0.8~1.2(D65光源下) |
你想想看,如果R/G比值到了1.5,那图像肯定偏红。这时候自动白平衡(AWB)就要开始工作了。
4.2.3 坏点检测
传感器不可能完美,总会有几个像素是坏的。坏点表现为固定亮或固定暗,在图像上就是一个小亮点或小黑点。RK平台的ISP有专门的坏点校正模块(DPC),但前提是你得先检测出来。
我常用的方法是:拍一张全黑图(盖上镜头盖),然后统计每个像素的值。如果某个像素的值明显高于周围像素(比如超过阈值3倍),那它大概率是坏点。
// 坏点检测伪代码
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
val = raw_image[y][x];
// 计算周围3x3邻域的均值
mean = get_neighbor_mean(raw_image, x, y, 3);
if (abs(val - mean) > threshold) {
mark_as_bad_pixel(x, y);
}
}
}
4.3 黑电平校正原理
黑电平校正(Black Level Correction,BLC)是ISP流水线的第一道处理工序。说白了,就是让传感器在完全无光的情况下,输出值归零。
为什么会这样?因为传感器本身有暗电流,即使没有光照,每个像素也会产生一定的电荷。这个电荷量就是黑电平偏移。如果不校正,暗部会发灰,而且会压缩动态范围。
4.3.1 黑电平的测量
传感器通常会在像素阵列的边缘留一些光学黑像素(Optical Black,OB),这些像素被遮光层挡住,永远接收不到光。它们的输出值就是黑电平的参考值。
在RK平台上,你可以通过驱动读取OB区域的数据:
// 读取OB区域均值
struct rkisp_blc_stats blc;
ioctl(fd, RKISP_CMD_GET_BLC_STATS, &blc);
// blc.ob_r OB区域R通道均值
// blc.ob_gr OB区域Gr通道均值
// blc.ob_gb OB区域Gb通道均值
// blc.ob_b OB区域B通道均值
// 黑电平校正:每个像素减去对应的OB均值
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
switch (get_bayer_color(x, y)) {
case R: raw[y][x] -= blc.ob_r; break;
case Gr: raw[y][x] -= blc.ob_gr; break;
case Gb: raw[y][x] -= blc.ob_gb; break;
case B: raw[y][x] -= blc.ob_b; break;
}
// 防止下溢
if (raw[y][x] < 0) raw[y][x] = 0;
}
}
4.3.2 黑电平与增益的关系
这里有个容易忽略的点:黑电平偏移不是固定的,它会随着增益和温度变化。增益越大,暗电流被放大的倍数也越大,黑电平偏移就越高。
| 增益倍数 | OB区域均值(10bit) | 校正值 |
|---|---|---|
| 1x | 64 | 减去64 |
| 2x | 128 | 减去128 |
| 4x | 256 | 减去256 |
| 8x | 512 | 减去512 |
4.3.3 黑电平校正的精度
黑电平校正的精度直接影响暗部信噪比。如果校正值偏大,暗部会被削掉,丢失细节;如果偏小,暗部会残留偏色。
我建议的做法是:
- 在产线阶段,采集多帧OB数据,取平均值作为基准校正值
- 在运行阶段,每隔一段时间(比如每100帧)重新统计OB均值,做动态微调
- 对于温度变化大的场景(比如车载摄像头),建立温度-黑电平查找表
嗯,这一章的内容就到这里。记住,拿到RAW图先别急着调颜色,先把黑电平校正和统计信息看明白。基础打牢了,后面才能走得稳。