2. 图像传感器与RAW数据

做ISP调试这些年,我接触过各种各样的传感器。说实话,很多刚入行的工程师容易忽略一个事实——你调出来的图像质量,有一半在传感器阶段就已经决定了。RAW数据是ISP的起点,也是整个图像质量的根基。这一章,我们就把传感器和RAW数据这件事彻底讲透。

CCD与CMOS传感器:核心差异

先问个问题:为什么现在工业相机几乎全是CMOS,CCD反而越来越少见?

我2015年刚入行时,公司还有一条CCD相机的产线。那时候老师傅跟我说,CCD画质干净,CMOS噪声大。但你看现在,高端工业相机清一色CMOS。原因其实很简单——工艺进步让CMOS的噪声问题基本解决了,但CCD的功耗和速度瓶颈始终突破不了。

对比项 CCD CMOS
读出方式 逐行转移,统一读出 每个像素独立寻址读出
功耗 较高(需要多路高压驱动) 低(单电源即可工作)
读出速度 较慢(串行读出瓶颈) 快(并行读出,轻松上百fps)
噪声特性 读出噪声低,暗电流均匀 固定模式噪声(FPN)明显,但可校正
动态范围 通常较高 受限于像素内放大器,但近年已追平
成本 高(特殊工艺) 低(标准CMOS工艺,可集成逻辑电路)
工业应用现状 基本停产,仅特殊科研需求 主流

我的经验之谈:在工业检测场景中,CMOS的FPN(固定模式噪声)完全可以通过暗场校正和双采样技术消除。而CCD的高功耗带来的散热问题,在长曝光应用中反而会引入热噪声。所以别迷信CCD,选CMOS就对了。

Bayer格式:为什么是RGGB?

传感器本身只能感知光的强度,分辨不出颜色。那彩色图像怎么来的?答案是在像素表面覆盖彩色滤光片阵列(CFA)。最常用的就是Bayer格式。

你可能会问:为什么Bayer用RGGB,而不是RGBG或者别的排列?

嗯,这里有个生理学背景——人眼对绿色最敏感。Bayer格式中绿色像素占50%,红色和蓝色各占25%,就是为了模拟人眼的亮度感知特性。我在调试一个医疗内窥镜项目时,曾经试过把Bayer改成RGB等间距排列,结果图像细节锐度直接掉了15%。

Bayer RGGB 排列结构 R G R G G B G B R G R G G B G B R (25%) G (50%) B (25%) 每个像素只记录一种颜色分量,完整彩色图像需要通过去马赛克(Demosaic)算法插值得到

调试小技巧:拿到一款新传感器,第一件事就是确认Bayer排列顺序。不同厂商可能用RGGB、BGGR、GRBG或GBRG。我曾经因为搞错排列顺序,调试了三天才发现是Bayer读反了——图像全是紫色和绿色的伪影。

RAW数据格式与位深

传感器输出的原始数据就是RAW。它没经过任何ISP处理,是最纯粹的感光信息。

RAW数据有几个关键参数:

  • 位深(Bit Depth):常见8bit、10bit、12bit、14bit、16bit。工业相机主流是10bit或12bit。位深越高,动态范围越大,但数据量也越大。
  • 数据排列:Bayer格式下,数据按像素矩阵逐行排列。每个像素值对应一个颜色通道。
  • 黑电平(Black Level):传感器在无光照时的输出值,通常不为0。比如12bit传感器黑电平可能是64。

注意:很多工程师直接把RAW数据当成线性数据来处理,这是个大坑。RAW数据需要先减去黑电平,再做线性化校正,才能进入后续的ISP流程。否则暗部细节会丢失,亮部会提前饱和。

RAW数据读取与解析实战

下面我给出一个实际的RAW数据读取示例。假设我们有一个12bit的Bayer RAW文件,排列为RGGB。

#include <stdio.h>
#include <stdint.h>

// 解析12bit RAW数据(RGGB排列)
// 每个像素占用12bit,每2个像素打包成3个字节
void parse_raw12_rggb(const char* filename, 
                      uint16_t* r_plane, 
                      uint16_t* g_plane, 
                      uint16_t* b_plane,
                      int width, int height) {
    FILE* fp = fopen(filename, "rb");
    if (!fp) return;
    
    int row_bytes = (width * 12 + 7) / 8;  // 每行字节数
    uint8_t* line_buf = (uint8_t*)malloc(row_bytes);
    
    for (int y = 0; y < height; y++) {
        fread(line_buf, 1, row_bytes, fp);
        
        for (int x = 0; x < width; x++) {
            // 从字节流中提取12bit像素值
            int bit_offset = x * 12;
            int byte_offset = bit_offset / 8;
            int bit_remain = bit_offset % 8;
            
            uint16_t pixel = 0;
            pixel = (line_buf[byte_offset] << 4) | 
                    (line_buf[byte_offset + 1] >> 4);
            
            // 根据Bayer排列分配到对应颜色平面
            if (y % 2 == 0) {          // 偶数行
                if (x % 2 == 0) {      // 偶数列 -> R
                    r_plane[(y/2) * (width/2) + (x/2)] = pixel;
                } else {               // 奇数列 -> G
                    g_plane[y * width + x] = pixel;  // G在R行
                }
            } else {                    // 奇数行
                if (x % 2 == 0) {      // 偶数列 -> G
                    g_plane[y * width + x] = pixel;  // G在B行
                } else {               // 奇数列 -> B
                    b_plane[(y/2) * (width/2) + (x/2)] = pixel;
                }
            }
        }
    }
    
    free(line_buf);
    fclose(fp);
}

个人习惯:我一般会在解析RAW数据后,先做一个简单的直方图统计。如果直方图在低端有大量堆积,说明黑电平没减干净;如果在高端有截断,说明增益设置过高。这两个问题在RAW阶段发现,比等到ISP输出再排查要高效得多。

RAW数据预处理流程

拿到RAW数据后,别急着做去马赛克。先走一遍预处理流程:

  1. 黑电平校正:减去每通道的黑电平值。不同通道的黑电平可能不同。
  2. 增益归一化:将数字增益还原为物理光强比例。12bit数据通常映射到0-4095范围。
  3. 坏点校正:传感器总有坏点,需要在RAW阶段先做校正,否则后续处理会放大瑕疵。
  4. 镜头阴影校正(LSC):镜头边缘亮度衰减,需要在RAW域做增益补偿。

我曾经在一个高精度测量项目中,忽略了黑电平校正,结果测量结果总是偏暗0.5%。排查了两天才发现是黑电平没减干净——12bit传感器黑电平是64,对应到8bit就是1个灰阶的偏差,但在高精度场景下这个误差不可接受。

RAW数据预处理流程 传感器RAW 黑电平校正 增益归一化 坏点校正 镜头阴影校正 进入ISP管线

避坑指南:我曾经在一个项目中,坏点校正放在了去马赛克之后做。结果坏点周围的颜色信息已经被插值污染了,校正后留下明显的色斑。记住:坏点校正必须在RAW域、在去马赛克之前完成。

不同位深RAW数据的存储格式

实际项目中,你会遇到各种RAW存储格式。我整理了一份对照表:

位深 存储方式 每像素占用 常见场景
8bit 1字节直接存储 1字节 低端USB相机、预览模式
10bit 打包模式(5像素占6字节)或MSB对齐 1.25字节 工业相机主流格式
12bit 打包模式(2像素占3字节)或MSB对齐 1.5字节 高动态范围工业相机
14bit 打包模式(4像素占7字节)或MSB对齐 1.75字节 科学相机、医疗成像
16bit 2字节直接存储(低4位为0) 2字节 方便处理,但数据量大

我个人习惯在调试阶段用16bit存储,虽然数据量大一倍,但省去了位操作解析的麻烦。等算法稳定后,再根据实际带宽需求选择打包格式。

注意字节序:不同厂商的RAW文件字节序可能不同。有的用Little-Endian,有的用Big-Endian。解析时一定要确认。我见过一个团队因为字节序搞反,整批图像都出现了条纹状噪声——其实是高低字节交换导致的数值跳变。

好了,这一章的内容就到这里。传感器和RAW数据是ISP的起点,把这些基础打牢,后面的调试才能事半功倍。记住:RAW数据里藏着图像质量的全部秘密,学会读懂它,你就掌握了ISP调试的主动权。