Bayer格式详解:从阵列到RAW数据读取

做图像处理的人,天天跟Bayer格式打交道。说白了,Bayer就是CMOS传感器输出的一种原始数据格式。为什么叫Bayer?因为发明它的人叫Bryce Bayer,柯达的工程师。1976年提出的方案,到现在还在用,你想想看这有多经典。

我个人习惯把Bayer格式理解成「传感器的语言」。传感器本身分不清颜色,它只能感知光的强度。那怎么得到彩色图像?靠的就是在像素表面覆盖一层彩色滤光片阵列——这就是Bayer阵列。

Bayer阵列模式

标准的Bayer阵列,每2x2个像素为一组。每个像素只允许一种颜色的光通过。绿色占一半,红色和蓝色各占四分之一。为什么绿色多?因为人眼对绿色最敏感。这个设计很聪明,对吧?

常见的Bayer模式有四种:

模式名称 2x2排列 常见传感器
RGGB R G / G B Sony IMX系列
GRBG G R / B G OmniVision OV系列
GBRG G B / R G 部分三星传感器
BGGR B G / G R 部分安森美传感器

这里有个坑。我曾经在项目中接过一批RAW图,怎么处理颜色都不对。折腾了两天,最后发现是Bayer模式搞错了。传感器文档写的是RGGB,实际输出是GRBG。从那以后,我拿到新传感器第一件事就是拍一张纯白画面,检查Bayer模式。

⚠️ 避坑指南: 我曾经因为Bayer模式搞错,导致整个去马赛克算法输出偏色。调试了三天才发现问题。建议你在代码里加一个Bayer模式校验函数,用纯色图验证。

Bayer阵列的物理结构

你想想看,一个2000万像素的传感器,实际上有2000万个感光点。每个点上面盖着一层微小的滤光片。这些滤光片的排列方式,就是Bayer阵列。

我画了一张图,帮你理解Bayer阵列的结构:

Bayer阵列结构示意图 R G G B R G G B R G G B R G G B 每个2x2单元包含:1个R + 2个G + 1个B 绿色像素占50%,红色和蓝色各占25%

RAW数据读取

RAW数据,说白了就是传感器直接输出的原始数据。没有经过任何处理,没有去马赛克,没有白平衡,没有伽马校正。每个像素就是一个数字,代表该点的光强度。

读取RAW数据,我一般用Python。下面是我常用的读取代码:

import numpy as np
import struct

def read_raw_bayer(file_path, width, height, bpp=10):
    """
    读取Bayer RAW数据
    file_path: RAW文件路径
    width, height: 图像宽高
    bpp: 每像素位数,常见10bit/12bit/14bit
    """
    with open(file_path, 'rb') as f:
        raw_data = f.read()
    
    # 计算每像素占用的字节数
    if bpp == 10:
        # 10bit数据,5个字节存4个像素
        pixel_count = width * height
        expected_size = (pixel_count * 10 + 7) // 8
        raw_data = raw_data[:expected_size]
        
        # 解包10bit数据
        raw_10bit = np.frombuffer(raw_data, dtype=np.uint16)
        # 这里需要做位操作,略
    elif bpp == 12:
        # 12bit数据,3个字节存2个像素
        raw_12bit = np.frombuffer(raw_data, dtype=np.uint16)
        # 位操作提取12bit值
    else:
        # 8bit或16bit直接读取
        dtype = np.uint8 if bpp == 8 else np.uint16
        raw_array = np.frombuffer(raw_data, dtype=dtype)
    
    # 重塑为2D数组
    bayer_image = raw_array.reshape((height, width))
    return bayer_image
💡 个人经验: 我建议你在读取RAW数据后,先做一步数据校验。检查最大值是否在合理范围内(10bit最大1023,12bit最大4095)。如果发现数据异常,大概率是bpp参数设置错了。

Bayer数据的内存布局

嗯,这里要注意。Bayer数据在内存里是逐行存储的。第一行是RGRGRG...,第二行是GBGBGB...,以此类推。你想想看,如果搞错了行顺序,出来的图像就是错位的。

我整理了一个表格,帮你理解不同Bayer模式的行数据:

模式 奇数行 偶数行
RGGB R G R G R G ... G B G B G B ...
GRBG G R G R G R ... B G B G B G ...
GBRG G B G B G B ... R G R G R G ...
BGGR B G B G B G ... G R G R G R ...

我曾经犯过一个低级错误。读取RAW数据时,把行顺序搞反了。结果出来的图像颜色完全不对,红色变成了蓝色。排查了半天,才发现是numpy的reshape方向错了。

Bayer数据的可视化

读取完RAW数据,我习惯先看一眼数据分布。用matplotlib显示一下:

import matplotlib.pyplot as plt

def visualize_bayer(bayer_image, mode='RGGB'):
    plt.figure(figsize=(10, 8))
    
    # 显示原始Bayer数据
    plt.subplot(2, 2, 1)
    plt.imshow(bayer_image, cmap='gray')
    plt.title('Bayer RAW Data')
    
    # 分离通道
    h, w = bayer_image.shape
    r_channel = np.zeros((h//2, w//2))
    g_channel = np.zeros((h//2, w//2))
    b_channel = np.zeros((h//2, w//2))
    
    if mode == 'RGGB':
        r_channel = bayer_image[0:h:2, 0:w:2]
        g_channel = bayer_image[0:h:2, 1:w:2]
        b_channel = bayer_image[1:h:2, 1:w:2]
    
    plt.subplot(2, 2, 2)
    plt.imshow(r_channel, cmap='Reds')
    plt.title('Red Channel')
    
    plt.subplot(2, 2, 3)
    plt.imshow(g_channel, cmap='Greens')
    plt.title('Green Channel')
    
    plt.subplot(2, 2, 4)
    plt.imshow(b_channel, cmap='Blues')
    plt.title('Blue Channel')
    
    plt.tight_layout()
    plt.show()
🔑 关键点: Bayer数据本身是单通道的。每个像素只记录一种颜色的强度。要得到彩色图像,必须经过去马赛克处理。这也是我们这门课的核心内容。

常见Bayer格式的差异

不同厂商的传感器,Bayer格式可能不一样。我整理了一些常见传感器的参数:

传感器型号 Bayer模式 位深 常见应用
Sony IMX378 RGGB 10bit 手机摄像头
OmniVision OV5640 GRBG 10bit 嵌入式摄像头
Sony IMX290 RGGB 12bit 安防监控
ON Semi AR0230 BGGR 12bit 车载摄像头

这里有个技巧。拿到一个新传感器,如果文档没写Bayer模式,你可以拍一张纯白画面。然后分别用四种模式去解析,看哪个出来的颜色最正常。我经常用这个方法快速确认。

⚠️ 重要提醒: 不同位深的RAW数据,存储格式可能不同。10bit数据有packed和unpacked两种存储方式。packed方式5个字节存4个像素,unpacked方式每个像素占2个字节。读取前一定要确认存储格式。

Bayer数据的预处理

读取完RAW数据,我一般会做几步预处理:

  1. 黑电平校正:减去暗电流带来的偏移量
  2. 坏点校正:修复传感器上的坏像素
  3. 增益校正:补偿不同通道的灵敏度差异
  4. 数据归一化:将数据映射到0-1范围

这些预处理步骤,说白了就是为后面的去马赛克和白平衡做准备。数据质量不好,后面的算法再牛也白搭。

def preprocess_bayer(bayer_image, black_level=64, white_level=1023):
    """
    Bayer数据预处理
    """
    # 黑电平校正
    bayer_corrected = bayer_image.astype(np.float32) - black_level
    
    # 裁剪到有效范围
    bayer_corrected = np.clip(bayer_corrected, 0, white_level - black_level)
    
    # 归一化到0-1
    bayer_normalized = bayer_corrected / (white_level - black_level)
    
    return bayer_normalized

嗯,到这里Bayer格式的基础知识就差不多了。你想想看,理解了Bayer阵列的结构,知道了怎么读取RAW数据,后面的去马赛克和白平衡处理就有了基础。这些内容看起来简单,但实际项目中踩坑的人不少。我建议你动手写代码试试,用真实的RAW数据跑一遍,感受一下。