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...
较少见,某些工业相机
个人习惯:拿到一个新传感器,我第一件事就是确认Bayer模式。RK平台的驱动里一般会配一个字段,比如 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");
}
我曾经踩过的坑:直方图统计的bin数量要和ADC位宽匹配。比如10bit传感器,直方图应该有1024个bin,而不是256个。如果驱动里配成了256bin,你会丢失很多细节信息,导致自动曝光算法调不准。

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
我曾经犯过的错:在一个项目中,我固定了黑电平校正值为64,没考虑增益变化。结果在低光场景下(增益8x),图像暗部出现了严重的偏色和噪声。后来改成根据增益动态调整校正值,问题才解决。

4.3.3 黑电平校正的精度

黑电平校正的精度直接影响暗部信噪比。如果校正值偏大,暗部会被削掉,丢失细节;如果偏小,暗部会残留偏色。

我建议的做法是:

  • 在产线阶段,采集多帧OB数据,取平均值作为基准校正值
  • 在运行阶段,每隔一段时间(比如每100帧)重新统计OB均值,做动态微调
  • 对于温度变化大的场景(比如车载摄像头),建立温度-黑电平查找表
RAW图处理流程与黑电平校正位置 传感器输出 黑电平校正 去马赛克 后续ISP RAW图统计信息 直方图 · 均值 · 方差 · 坏点 黑电平校正细节 OB区域均值 → 逐像素减去 增益相关 · 温度相关 · 防下溢 Bayer格式(RGGB示例) R Gr Gb B 每个像素只存一个颜色通道 后续通过Demosaic恢复RGB
总结一下:RAW图处理是ISP的基石。Bayer格式决定了数据怎么组织,统计信息告诉你图像质量怎么样,黑电平校正则是让数据回到正确的起点。这三步做不好,后面的白平衡、去马赛克、降噪都会跟着出问题。

嗯,这一章的内容就到这里。记住,拿到RAW图先别急着调颜色,先把黑电平校正和统计信息看明白。基础打牢了,后面才能走得稳。