3、RAW数据读取与解析:常见RAW格式(DNG、NEF、ARW)解析、Bayer阵列模式、黑电平与白平衡校正
做RAW域深度学习去噪,第一步就是跟RAW文件打交道。说实话,这步要是没搞对,后面模型再牛也是白搭。我见过不少同学,直接把RAW当普通图像读进来,结果训练出来的模型一塌糊涂。
今天我们就来聊聊,怎么把RAW文件里的数据,变成我们真正能用的数字信号。
3.1 常见RAW格式:DNG、NEF、ARW
市面上相机品牌那么多,RAW格式也五花八门。尼康用NEF,索尼用ARW,佳能用CR2/CR3。但好在,有个通用格式叫DNG(Digital Negative),Adobe搞的,很多相机也支持。
我个人习惯,不管什么格式,第一步都转成DNG再处理。为什么?因为DNG是开源的,解析起来最省心。NEF和ARW都是各家闭源的,虽然libraw能解,但偶尔会踩坑。
举个例子,我几年前处理索尼A7R3的ARW文件,libraw解出来暗部总有奇怪的条纹。后来发现是版本兼容问题,换成DNG就没事了。所以,如果你不想在数据读取上浪费时间,建议先转DNG。
3.1.1 用libraw读取RAW文件
libraw是C++库,Python里可以用rawpy封装。下面这段代码,是我项目里常用的读取方式:
import rawpy
import numpy as np
def read_raw(file_path):
# 用rawpy打开RAW文件
with rawpy.imread(file_path) as raw:
# 获取原始Bayer数据,不经过任何处理
raw_data = raw.raw_image.copy()
# 获取黑电平和白平衡参数
black_level = raw.black_level_per_channel
white_level = raw.white_level
camera_wb = raw.camera_whitebalance
# 获取Bayer模式
bayer_pattern = raw.raw_pattern
return raw_data, black_level, white_level, camera_wb, bayer_pattern
注意,这里用的是raw_image,不是postprocess()。后者会帮你做去马赛克、白平衡、gamma校正,但那不是我们想要的。我们要的是最原始的传感器数据。
rawpy.postprocess()来获取训练数据!它会自动做很多处理,包括插值、白平衡、色彩校正,这些都会破坏原始噪声分布。我见过有人拿处理后的图像训练去噪模型,结果模型学到的全是插值伪影。
3.2 Bayer阵列模式
大部分相机传感器用的是Bayer阵列。说白了,就是一个像素只感光一种颜色,红、绿、蓝按一定模式排列。最常见的模式是RGGB,也就是左上角是R,右边是G,下面也是G,右下是B。
为什么会这样?因为人眼对绿色最敏感,所以绿色像素占了一半,红色和蓝色各占四分之一。
但不同相机,Bayer模式可能不一样。有的用BGGR,有的用GRBG。读取的时候一定要搞清楚,不然颜色就乱了。
3.2.1 如何获取Bayer模式
从rawpy里可以直接拿到:
# 假设raw是rawpy对象
pattern = raw.raw_pattern # 返回一个2x2的数组
print(pattern)
# 比如输出 [[0, 1], [1, 2]],表示:
# 0=R, 1=G, 2=B
# 所以模式是 RGGB
拿到模式后,我们可以把Bayer数据拆成四个通道:
def split_bayer(raw_data, pattern):
h, w = raw_data.shape
# 根据pattern确定偏移
r_offset = np.where(pattern == 0)
g1_offset = np.where(pattern == 1)
g2_offset = np.where(pattern == 1) # 两个G位置不同
b_offset = np.where(pattern == 2)
# 提取各通道
r_ch = raw_data[r_offset[0]::2, r_offset[1]::2]
g1_ch = raw_data[g1_offset[0]::2, g1_offset[1]::2]
g2_ch = raw_data[g2_offset[0]::2, g2_offset[1]::2]
b_ch = raw_data[b_offset[0]::2, b_offset[1]::2]
return r_ch, g1_ch, g2_ch, b_ch
3.3 黑电平与白平衡校正
传感器读出来的原始值,不是直接就能用的。有两个东西必须处理:黑电平(Black Level)和白平衡(White Balance)。
3.3.1 黑电平校正
黑电平是什么?简单说,就是传感器在完全无光的情况下,依然会输出一个非零值。这个值通常是512或1024(12bit或14bit)。
为什么要减去黑电平?因为噪声是叠加在信号上的,如果不减掉这个偏置,噪声的均值就不为零,会影响后续处理。
我曾经犯过一个错误:直接拿原始值做归一化,没减黑电平。结果模型在暗部区域表现极差,因为暗部的信噪比被低估了。
正确的做法:
def apply_black_level(raw_data, black_level):
# black_level可能是一个值,也可能是每个通道不同
if isinstance(black_level, (list, np.ndarray)):
# 每个通道单独减
# 这里简化处理,实际要根据Bayer模式对应
raw_data = raw_data.astype(np.float32)
raw_data -= black_level[0] # 假设所有通道相同
else:
raw_data = raw_data.astype(np.float32) - black_level
# 确保不为负
raw_data = np.maximum(raw_data, 0)
return raw_data
注意,黑电平可能每个通道不一样。有些传感器R通道的黑电平比G通道高一点,这个要查datasheet。
3.3.2 白平衡校正
白平衡校正,说白了就是让白色物体在图像里看起来是白的。不同光源下,传感器对RGB的响应不一样。比如白炽灯下,红色会偏多。
相机厂商会在RAW文件里存一组白平衡增益,通常是R和B通道的增益,G通道作为参考(增益为1)。
应用白平衡:
def apply_white_balance(r_ch, g_ch, b_ch, wb_gains):
# wb_gains通常是 [R_gain, G_gain, B_gain]
# 但G_gain一般是1.0
r_gain, g_gain, b_gain = wb_gains
r_ch = r_ch * r_gain
g_ch = g_ch * g_gain
b_ch = b_ch * b_gain
return r_ch, g_ch, b_ch
3.4 完整的数据读取流程
把上面这些串起来,一个完整的RAW读取流程是这样的:
- 用rawpy读取RAW文件,获取原始Bayer数据
- 获取Bayer模式、黑电平、白平衡参数
- 减去黑电平
- 应用白平衡增益
- 将数据归一化到[0,1]范围(除以white_level - black_level)
- 保存元数据(Bayer模式、白平衡增益等)
下面这张图,是我自己总结的RAW数据解析流程,你可以参考一下:
3.5 实战中的坑与经验
最后,分享几个我实际项目中踩过的坑:
- 黑电平不是固定值:有些相机的黑电平会随ISO变化。我处理过一组数据,ISO 100时黑电平是512,ISO 6400时变成了528。如果不注意,高ISO下的暗部噪声会被错误放大。
- 白平衡增益不要超过阈值:有些极端光源下,R或B通道的增益可能很大(比如3.0以上)。这会导致该通道被过度放大,噪声也跟着放大。我一般会限制增益不超过2.5,超出部分用相邻像素插值。
- Bayer模式要验证:别完全相信相机厂商的标注。我遇到过一台相机,官方说模式是RGGB,实际读出来是BGGR。怎么验证?拍一张纯色物体,看哪个通道响应最高就知道了。
嗯,RAW数据读取这部分,说难不难,说简单也不简单。关键是要理解传感器的工作原理,知道每一步在做什么。搞清楚了这些,后面的去噪模型才能建立在可靠的数据基础上。