4、颜色空间变换:RGB到RAW的逆向映射、颜色抖动、白平衡模拟、色彩偏移增强

颜色空间变换,说白了就是让数据在「人眼看到的颜色」和「传感器记录的颜色」之间来回折腾。

我们做RAW域增强,核心思路是:先把sRGB图像逆向映射回RAW空间,然后在RAW域里做各种颜色层面的数据增强,最后再映射回去。这样训练出来的模型,才能学会传感器层面的颜色特性。

核心观点:RAW域的颜色增强,不是简单的RGB调色。它要模拟真实ISP管线的物理过程——白平衡、颜色校正、色彩偏移。你做的每一步,都要有物理依据。

4.1 RGB到RAW的逆向映射

先说说逆向映射。sRGB图像是经过ISP管线处理后的结果,它已经包含了白平衡、Gamma校正、颜色空间转换等操作。我们要做的,就是把这些操作「反着来一遍」。

我习惯把逆向映射拆成三步:

  1. 反Gamma:sRGB的Gamma大约是2.2,反Gamma就是做一次幂运算,指数取1/2.2。
  2. 反颜色校正矩阵(CCM):ISP会把RAW的RGB映射到标准颜色空间,逆向就是乘一个逆矩阵。
  3. 反白平衡:把R、G、B通道的增益去掉,恢复到传感器原始响应。

嗯,这里要注意:逆向映射不是完美的。ISP管线里有很多非线性操作(比如色调映射、去马赛克),这些操作不可逆。所以我们只能做一个「近似逆向」,保留主要物理过程就好。

def srgb_to_raw_approx(srgb_img, ccm_matrix, wb_gains):
    """
    sRGB → RAW 近似逆向映射
    srgb_img: [H, W, 3], 范围 [0, 1]
    ccm_matrix: 3x3 颜色校正矩阵
    wb_gains: [R_gain, G_gain, B_gain]
    """
    # 1. 反Gamma
    linear_img = np.where(srgb_img <= 0.04045,
                          srgb_img / 12.92,
                          ((srgb_img + 0.055) / 1.055) ** 2.4)
    
    # 2. 反CCM
    inv_ccm = np.linalg.inv(ccm_matrix)
    raw_linear = linear_img @ inv_ccm.T
    
    # 3. 反白平衡
    raw_img = raw_linear / np.array(wb_gains)
    
    return np.clip(raw_img, 0, 1)

我的经验:逆向映射时,我一般会保留一个「残差项」。因为ISP管线里还有去马赛克、降噪等操作,这些没法逆向。保留残差可以让模型自己学这部分差异。

4.2 颜色抖动

颜色抖动,就是在RAW域里对颜色做随机扰动。为什么要做这个?因为真实场景下,传感器的颜色响应会有波动——温度变化、曝光时间、镜头老化,都会影响颜色。

我常用的颜色抖动方法有三种:

方法 原理 适用场景
通道独立抖动 对R、G、B通道分别加随机增益 模拟传感器通道响应差异
颜色相关抖动 在颜色空间(如YUV)里做扰动 保持亮度不变,只改变色度
局部颜色抖动 按区域随机调整颜色偏移 模拟镜头渐晕或色差

我个人习惯用通道独立抖动,因为它最简单,而且效果足够好。抖动幅度一般控制在5%~15%之间,太大了会破坏颜色结构。

def color_jitter_raw(raw_img, jitter_strength=0.1):
    """
    RAW域颜色抖动
    raw_img: [H, W, 3], 范围 [0, 1]
    jitter_strength: 抖动幅度
    """
    # 生成随机增益,范围 [1 - strength, 1 + strength]
    gains = 1.0 + np.random.uniform(-jitter_strength, jitter_strength, 3)
    
    # 逐通道应用
    jittered = raw_img * gains.reshape(1, 1, 3)
    
    return np.clip(jittered, 0, 1)

避坑指南:我曾经在颜色抖动时忘了限制增益范围,结果训练出来的模型对极端颜色很敏感,一遇到偏色就崩。后来我加了截断,抖动幅度不超过20%,效果就稳了。

4.3 白平衡模拟

白平衡模拟,是RAW域增强里最实用的一个技巧。为什么?因为真实场景的光源千变万化——日光、白炽灯、荧光灯、LED,每种光源的色温都不一样。模型如果只在一种光源下训练,换个场景就废了。

白平衡模拟的核心思路:在RAW域里,通过调整R和B通道的增益,模拟不同色温下的白平衡效果。

具体做法:

  1. 设定色温范围(比如2500K~7500K)
  2. 根据色温计算R和B通道的增益
  3. 随机采样一个色温,应用对应的增益

你想想看,这样做的好处是什么?模型在训练时见过各种色温下的数据,推理时就能自动适应不同光源。

def simulate_white_balance(raw_img, temp_range=(2500, 7500)):
    """
    白平衡模拟
    temp_range: 色温范围 (K)
    """
    # 随机采样色温
    temp = np.random.uniform(*temp_range)
    
    # 简化的色温-增益映射
    # 实际项目中可以用查表法或多项式拟合
    r_gain = 1.0 + 0.5 * (temp - 5000) / 5000
    b_gain = 1.0 - 0.5 * (temp - 5000) / 5000
    g_gain = 1.0
    
    gains = np.array([r_gain, g_gain, b_gain])
    wb_img = raw_img * gains.reshape(1, 1, 3)
    
    return np.clip(wb_img, 0, 1), temp

我的经验:白平衡模拟的色温范围不要设得太宽。我曾经试过1000K~10000K,结果模型学了一堆极端偏色,反而影响了正常场景的表现。现在我用2500K~7500K,覆盖了绝大多数室内外场景。

4.4 色彩偏移增强

色彩偏移增强,比白平衡模拟更「激进」一些。它不只是调整色温,而是在颜色空间里做更复杂的变换。

我常用的色彩偏移增强方法:

  • HSV偏移:在HSV空间里随机调整H(色相)、S(饱和度)、V(明度)
  • 颜色混合:把当前颜色和另一个随机颜色做线性混合
  • 通道置换:随机交换R、G、B通道(这个比较暴力,慎用)

说白了,色彩偏移增强就是让模型学会「颜色变了,但内容不变」。这样模型就不会过度依赖颜色特征,而是更关注纹理和结构。

def color_shift_raw(raw_img, shift_strength=0.2):
    """
    RAW域色彩偏移增强
    """
    # 转HSV
    hsv = cv2.cvtColor(raw_img, cv2.COLOR_RGB2HSV)
    
    # 随机偏移H、S、V
    hsv[:,:,0] = (hsv[:,:,0] + np.random.uniform(-shift_strength, shift_strength)) % 1.0
    hsv[:,:,1] = np.clip(hsv[:,:,1] * np.random.uniform(0.8, 1.2), 0, 1)
    hsv[:,:,2] = np.clip(hsv[:,:,2] * np.random.uniform(0.9, 1.1), 0, 1)
    
    # 转回RGB
    shifted = cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB)
    
    return shifted

避坑指南:色彩偏移增强一定要控制幅度。我曾经在一次项目里把饱和度偏移设到0.5,结果模型把红色和绿色搞混了,在测试集上直接崩了。后来我把偏移幅度控制在0.2以内,效果才稳定。

4.5 本章知识体系

下面这张图,把本章的核心逻辑串起来了:

颜色空间变换知识体系 颜色空间变换知识体系 RAW域颜色空间 逆向映射 颜色抖动 白平衡模拟 色彩偏移 反Gamma 反CCM 反白平衡 通道独立抖动 颜色相关抖动 局部颜色抖动 色温范围设定 增益计算 随机采样应用 HSV偏移 颜色混合 通道置换 核心原则:所有变换都要有物理依据,幅度要可控

这张图把本章的四个核心方法串起来了。你可以看到,逆向映射是基础,它把sRGB数据拉回RAW域;颜色抖动、白平衡模拟、色彩偏移是三种不同的增强策略,它们可以组合使用,也可以单独使用。

我个人建议:白平衡模拟是必选项,因为它最贴近真实场景;颜色抖动和色彩偏移可以根据任务需求选择。如果任务对颜色敏感(比如肤色检测),色彩偏移的幅度要小一点;如果任务对颜色不敏感(比如边缘检测),可以大胆用。

总结:颜色空间变换是RAW域增强的核心环节。它让模型学会「颜色变了,但内容不变」,从而提升模型的泛化能力。记住:物理依据 + 可控幅度 = 有效增强。