传感器基础:CMOS/CCD感光原理、Bayer阵列与CFA

做图像处理这么多年,我始终觉得一个道理特别朴素:你连传感器怎么工作的都不清楚,就别谈什么校正了。镜头阴影校正也好,AWB白平衡也罢,根儿都在传感器上。今天咱们就把这块硬骨头啃下来。

从光子到数字:感光原理

说白了,无论是CMOS还是CCD,核心就一件事:把光子变成电子,再把电子变成电压,最后量化成数字。这个过程叫光电转换。

我当年刚入行时,总以为传感器是直接输出RGB值的。后来被老工程师骂了一顿才明白——传感器其实是个“色盲”,它只感知光的强度,不感知颜色。颜色?那是后期算出来的。

特性 CCD CMOS
读出方式 逐行转移,统一输出 每个像素独立读出
功耗 较高(需要多组电压) 较低(单电压供电)
噪声 读出噪声低 固定模式噪声较明显
速度 较慢 快(支持高帧率)
成本 高 低
主流应用 工业、科研、天文 手机、安防、消费电子
个人经验: 我在做手机摄像头调试时,CMOS的固定模式噪声(FPN)是个大坑。暗场校正(Dark Frame Subtraction)几乎是必做的一步,否则阴影校正的精度会被噪声吃掉一大截。

Bayer阵列:为什么是RGGB?

你想想看,传感器只感知亮度,那颜色信息从哪来?答案就是色彩滤波阵列(CFA)。最经典的CFA就是Bayer阵列,1976年由柯达的Bryce Bayer发明。

Bayer阵列的排列规律很简单:每4个像素一组,2个绿色、1个红色、1个蓝色。为什么绿色多一倍?因为人眼对绿色最敏感,这么做可以模拟人眼的亮度感知特性。

核心知识点: Bayer阵列不是随机排列的。标准Bayer有四种变体:BGGR、GBRG、GRBG、RGGB。不同传感器厂商用的顺序不一样,做ISP时第一步就是确认Bayer顺序,否则颜色全乱套。

我曾经接手过一个项目,上游给的RAW图颜色怎么调都不对。折腾了两天,最后发现是Bayer顺序搞反了——把RGGB当成了BGGR处理。从那以后,我养成了一个习惯:拿到RAW图第一件事,先画个4x4像素的灰度图,看棋盘格纹理的方向,确认Bayer顺序。

# 一个简单的Bayer顺序检测思路(伪代码)
def detect_bayer_order(raw_4x4_block):
    # 假设raw_4x4_block是4x4的RAW像素值
    # 计算四个2x2子块的平均值
    # 最亮的子块对应绿色通道(因为绿色有两个)
    # 最暗的子块对应蓝色通道
    # 中间亮度的对应红色通道
    # 通过位置关系反推Bayer顺序
    pass

色彩滤波阵列(CFA)的更多玩法

Bayer阵列虽然经典,但它不是唯一的选择。这些年我见过不少奇奇怪怪的CFA设计,各有各的算盘。

  • RGBW阵列: 在RGGB里插入一个白色(W)像素,提升低光灵敏度。代价是分辨率下降,需要复杂的去马赛克算法。
  • RYYCy阵列: 用黄色(Y)和青色(Cy)替代部分绿色,常见于一些老式摄像机。色彩还原难度大。
  • Quad Bayer阵列: 4个同色像素组成一组,用于高像素手机传感器。通过像素合并(Binning)提升暗光表现。
  • X-Trans阵列: 富士的专利,6x6的伪随机排列,减少摩尔纹。我用过几次,去马赛克算法确实复杂。
避坑指南: 我曾经在项目中遇到过第三方模组厂擅自改了CFA类型,但没更新驱动配置。结果ISP按标准Bayer处理,整张图全是彩色条纹。所以,CFA类型必须在模组规格书中明确标注,并且与ISP配置严格对齐。

从RAW到RGB:去马赛克(Demosaic)

传感器输出的RAW数据,每个像素只有一个颜色通道的值。要得到完整的RGB图像,必须通过插值算法把缺失的两个通道补回来。这个过程就叫去马赛克。

最简单的办法是双线性插值——取周围同色像素的平均值。但效果嘛……边缘全是锯齿和伪色。我早期做算法时试过,被领导骂了一顿。

稍微好一点的是边缘导向插值:先判断当前像素是水平边缘还是垂直边缘,然后沿着边缘方向插值,不跨边缘。这个算法在手机上用了很多年。

现在主流的是基于深度学习的去马赛克,效果确实好,但计算量也大。做嵌入式ISP时,还得靠传统算法。

// 一个简化的边缘导向插值(C++风格伪代码)
uint16_t demosaic_green_at_red(uint16_t R, uint16_t G_left, uint16_t G_right, 
                                uint16_t G_up, uint16_t G_down) {
    int16_t horz_diff = abs(G_left - G_right);
    int16_t vert_diff = abs(G_up - G_down);
    
    if (horz_diff < vert_diff) {
        // 水平方向更平滑,用左右平均
        return (G_left + G_right) / 2;
    } else if (vert_diff < horz_diff) {
        // 垂直方向更平滑,用上下平均
        return (G_up + G_down) / 2;
    } else {
        // 四个方向平均
        return (G_left + G_right + G_up + G_down) / 4;
    }
}

传感器与ISP的接口:RAW图格式

传感器输出的RAW数据,通常不是8bit的。常见的位深有10bit、12bit、14bit,甚至16bit。位深越大,动态范围越宽,但数据量也越大。

我见过不少新手工程师,拿到RAW图直接用8bit显示,结果一片漆黑或者一片惨白。其实RAW数据需要做黑电平校正(Black Level Correction)和线性化后才能正常显示。

位深 动态范围 常见应用
8bit 0~255 老式摄像头、预览模式
10bit 0~1023 手机主摄、安防
12bit 0~4095 专业相机、工业相机
14bit 0~16383 高端单反、科研级
小技巧: 调试时如果RAW图显示异常,先检查黑电平值。大部分传感器的黑电平在64左右(12bit下),如果直接用0作为黑电平,暗部细节全丢了。

本章知识体系

下面这张图把传感器、Bayer阵列、CFA、去马赛克和RAW格式的关系串起来了。建议你保存下来,做ISP调试时对照着看。

传感器与Bayer阵列知识体系 传感器基础与Bayer阵列知识体系 场景光线(光子) CMOS / CCD 感光 色彩滤波阵列(CFA) Bayer阵列(RGGB) RGBW / X-Trans 等 RAW 数据(10/12/14bit) 去马赛克 → 黑电平校正 → 线性化 RGB 图像(8bit 输出) Bayer顺序错误 → 颜色完全错乱 黑电平不对 → 暗部细节丢失

嗯,这张图基本把传感器到RGB的完整链路画清楚了。你仔细看,从光子到最终图像,中间每一步都有坑。后面讲镜头阴影校正时,你会发现这些基础知识一个都跑不掉——阴影校正就是在RAW域做的,Bayer顺序搞错,校正方向都是反的。