1. RAW图像基础:从光子到数字的起点

各位同学,欢迎来到第一课。做图像处理这么多年,我始终觉得,理解RAW格式是通往专业影像的必经之路。说白了,RAW就是相机传感器最原始的“底片”,它记录的是每个像素点感受到的光子数量,未经任何修饰。今天,我们就来彻底拆解它。

1.1 RAW格式原理:为什么它如此重要?

你想想看,我们平时看到的JPEG图片,其实是经过相机内部“烹饪”过的成品——白平衡、锐化、降噪、色彩映射,一套组合拳下来,很多原始信息就丢失了。而RAW文件,保存的是传感器每个像素的原始电压值,通常是12位或14位,甚至更高。

我个人习惯把RAW比作“数字底片”。它有几个核心特点:

  • 高位深:12bit RAW能记录4096级亮度,14bit是16384级,而JPEG只有256级。这意味着后期拉阴影、压高光时,RAW有巨大的宽容度。
  • 线性数据:RAW的数值与场景亮度呈线性关系,不像人眼或sRGB曲线那样有非线性映射。这为精确的算法处理提供了基础。
  • 未去马赛克:每个像素只记录一种颜色(R/G/B),需要通过插值算法才能变成全彩图像。

核心概念:RAW文件 = 传感器原始数据 + 元数据(相机型号、ISO、快门速度、镜头信息等)。元数据是后期精准还原的关键。

1.2 拜耳阵列与CFA模式:颜色是怎么来的?

单反或手机摄像头,一个像素只能感知光的强度,无法区分颜色。那彩色照片怎么来的?答案就是——在传感器表面覆盖一层彩色滤光片阵列(CFA, Color Filter Array)。最常见的,就是拜耳阵列(Bayer Pattern)。

拜耳阵列的排列很有讲究:它用50%的绿色、25%的红色、25%的蓝色。为什么绿色占一半?因为人眼对绿色最敏感,这样设计能模拟人眼的亮度感知。我在项目中遇到过,有些同学误以为拜耳阵列是均匀分布的,其实不是——它通常是RGGB或BGGR模式。

常见的CFA模式有这几种:

模式名称 排列方式 常见于
RGGB 第一行:R G R G...
第二行:G B G B...
索尼、尼康大部分机型
BGGR 第一行:B G B G...
第二行:G R G R...
部分佳能机型
GRBG 第一行:G R G R...
第二行:B G B G...
某些手机传感器
X-Trans 6x6周期性排列,非拜耳 富士相机

避坑指南:我曾经在开发通用RAW解码器时,忘记处理不同厂商的CFA模式偏移,结果图像颜色完全错乱。记住:解码前一定要从元数据中读取CFA模式,不要硬编码。

1.3 RAW文件结构解析:里面到底装了什么?

一个标准的RAW文件,通常包含三个部分:文件头、元数据区、图像数据区。不同厂商(佳能.CR2/CR3、尼康.NEF、索尼.ARW、富士.RAF)的格式略有差异,但核心逻辑一致。

我画了一张结构图,帮你理清思路:

RAW文件结构解析 文件头 标识符、偏移量、字节序 元数据区 EXIF、CFA模式、黑电平 图像数据区 拜耳原始像素值 文件头包含 • 文件魔数(如CR2的“II”或“MM”) • 主图像数据偏移量 • 字节序(大端/小端) 元数据包含 • 相机型号、ISO、快门 • CFA模式(RGGB/BGGR等) • 黑电平、白平衡增益 图像数据包含 • 未压缩或无损压缩 • 每个像素12/14bit • 可能包含JPEG预览图 ⚠️ 注意:不同厂商的RAW格式差异很大 解码时务必参考各厂商的官方规范,或使用libraw等成熟库 读取流程:文件头 → 解析元数据 → 定位图像数据 → 读取拜耳阵列

嗯,这里要注意:图像数据区通常不是直接可读的。厂商会做无损压缩(比如尼康的NEF使用类似TIFF的压缩),或者打包成特定格式。我早期做解码时,就踩过字节序的坑——小端模式读成大端,出来的图像全是条纹。

1.4 黑电平与白平衡:RAW解码的第一步

拿到RAW数据后,不能直接当RGB用。有两个关键预处理:

  • 黑电平校正:传感器在无光照时也有微弱信号(暗电流),需要减去这个基准值。不同通道的黑电平可能不同,我见过有些相机红色通道黑电平是512,绿色是511。
  • 白平衡增益:因为CFA对不同波长的光透过率不同,需要乘以增益系数,让中性灰在R/G/B三个通道上数值相等。

重要提醒:黑电平校正必须在去马赛克之前做!否则插值算法会把黑电平误差扩散到相邻像素,导致偏色。我曾经在项目中因为顺序搞反,花了三天才定位到问题。

1.5 实战:用Python读取RAW文件头

光说不练假把式。我们写一小段代码,读取一个RAW文件的头信息,验证一下结构。

import struct

def read_raw_header(file_path):
    with open(file_path, 'rb') as f:
        # 读取前4字节判断字节序和格式
        magic = f.read(4)
        print(f"魔数: {magic.hex()}")
        
        # 假设是CR2格式,偏移量在0x10处
        f.seek(0x10)
        offset = struct.unpack('<I', f.read(4))[0]
        print(f"图像数据偏移量: {offset}")
        
        # 读取图像尺寸(通常在偏移量附近)
        f.seek(offset - 8)
        width, height = struct.unpack('<II', f.read(8))
        print(f"图像尺寸: {width} x {height}")

# 使用示例(请替换为实际RAW文件路径)
# read_raw_header('example.CR2')

这段代码很基础,但能帮你理解RAW文件的二进制布局。实际项目中,我建议用rawpy或libraw,它们封装了各种厂商的差异。

1.6 本章小结

好了,第一课的内容就到这里。我们讲了RAW的本质——它是线性的、高位深的、未去马赛克的传感器数据。拜耳阵列用RGGB模式让绿色占一半,模拟人眼亮度感知。文件结构分为头、元数据、图像数据三部分,解码时要注意字节序和黑电平。

记住一句话:RAW是算法的起点,也是质量的底线。理解它,你才能在后期的AI增强管线中,做出真正专业的效果。