3. 训练数据准备与合成

做AI降噪算法,最头疼的问题是什么?我个人觉得,不是模型结构选哪个,而是——训练数据从哪来。

你想想看,如果拿公开数据集训练出来的模型,放到自家ISP的RAW图上,效果往往惨不忍睹。为什么?因为每个传感器的噪声特性都不一样。所以这一章,咱们就聊聊怎么搞定训练数据。

3.1 真实噪声建模:Gaussian-Poisson模型

先说理论。传感器噪声到底长什么样?

我刚开始做ISP时,以为噪声就是简单的加性高斯白噪声。后来发现,根本不是这么回事。传感器噪声有两个主要来源:

  • 光子散粒噪声(Shot Noise):光子到达传感器是随机的,服从泊松分布。信号越强,噪声方差越大。
  • 读出噪声(Read Noise):电路读取电荷时引入的噪声,近似高斯分布,与信号强度无关。

所以,一个更准确的模型是:

y = Poisson(x) + Gaussian(0, σ²)

其中 x 是理想信号,y 是观测到的RAW值。这个模型叫 Gaussian-Poisson噪声模型,也叫 Poisson-Gaussian噪声模型。

核心公式:

噪声方差与信号强度的关系:σ²_total = a · x + b

其中 a 是泊松噪声增益(与ISO相关),b 是读出噪声方差。

说白了,就是噪声的方差会随着信号强度线性增加。暗区噪声小?不对,暗区信噪比低,看起来反而更明显。

3.2 RAW域噪声标定:从传感器拿到真实参数

理论模型有了,但参数怎么定?你得标定。

我在项目中遇到过最坑的事,就是拿了一颗新传感器,直接套用上一颗的噪声参数。结果模型在暗光下完全崩了。后来老老实实做标定,才发现两颗传感器的读出噪声差了将近一倍。

标定流程其实不复杂,但细节很多:

  1. 准备均匀光源:用积分球或均匀白板,保证传感器整个画幅照度一致。
  2. 拍摄多组RAW图:在不同ISO下,从暗到亮拍10-20个亮度等级。每个亮度拍50-100帧。
  3. 计算均值和方差:对每个像素位置,计算多帧的均值和方差。然后取空间平均。
  4. 拟合参数:用最小二乘法拟合 σ² = a · μ + b。

我的经验:标定时一定要关掉所有ISP处理(黑电平校正、LSC、去噪等),直接拿原始RAW数据。否则标出来的参数是"脏"的。

标定结果长什么样?给你看个例子:

ISO a(泊松增益) b(读出噪声)
100 0.0012 0.85
200 0.0025 0.92
400 0.0048 1.10
800 0.0095 1.45
1600 0.0190 2.10

你看,ISO越高,a和b都在涨。但b的涨幅没那么线性,因为读出噪声还受温度影响。

注意:温度对读出噪声影响很大。标定时要控制传感器温度,最好在常温(25°C)下进行。我曾经在夏天和冬天分别标定过同一颗传感器,b值差了30%。

3.3 合成数据对的生成流程

标定完参数,接下来就是生成训练数据了。我们的目标是:干净RAW → 加噪RAW,形成一对训练样本。

流程如下:

  1. 获取干净RAW:用低ISO(如ISO 100)拍摄多帧,然后平均。或者用高信噪比的合成图像。
  2. 生成噪声:根据标定参数,对每个像素生成泊松噪声和高斯噪声。
  3. 合成带噪RAW:干净RAW + 噪声 = 带噪RAW。
  4. 数据增强:随机裁剪、翻转、旋转、调整ISO参数等。

代码实现其实很直接:

def add_noise(clean_raw, a, b, gain=1.0):
    """
    给干净RAW添加Gaussian-Poisson噪声
    clean_raw: 归一化到[0,1]的RAW数据
    a: 泊松噪声增益
    b: 读出噪声方差
    gain: ISO增益倍数
    """
    # 1. 泊松噪声(光子散粒噪声)
    shot_noise = np.random.poisson(clean_raw * gain) / gain - clean_raw
    
    # 2. 高斯噪声(读出噪声)
    read_noise = np.random.normal(0, np.sqrt(b), clean_raw.shape)
    
    # 3. 合成
    noisy_raw = clean_raw + a * shot_noise + read_noise
    
    return np.clip(noisy_raw, 0, 1)

关键点:

  • 泊松噪声的方差与信号强度成正比,所以要用 np.random.poisson 而不是简单的高斯。
  • 增益参数 gain 对应ISO设置,ISO越高,泊松噪声越明显。
  • 最后一定要 clip 到[0,1],否则训练时会出NaN。

3.4 数据合成的进阶技巧

上面是最基础的版本。实际部署时,你会发现还有几个坑:

  • Bayer模式:RAW数据是Bayer格式的,噪声在不同颜色通道上可能有差异。我建议每个通道单独标定参数。
  • 行噪声(Row Noise):有些传感器有行噪声,表现为整行像素的偏移。这个需要额外建模。
  • 固定模式噪声(FPN):暗场下拍多帧平均,可以提取FPN。合成时加上去。

我曾经在一个项目里,模型在实验室数据上PSNR很高,但一上手机就翻车。排查了三天,发现是行噪声没建模。加上行噪声后,模型立马收敛了。

避坑指南:合成数据时,噪声参数不要用固定值。每次训练时随机采样ISO和温度参数,让模型见过各种噪声强度。这样泛化能力会好很多。

3.5 知识结构总览

下面这张图,把整个数据准备流程串起来了:

训练数据准备与合成流程 1. 噪声建模 Gaussian-Poisson模型 σ² = a·x + b 2. RAW域噪声标定 多ISO、多亮度拍摄 拟合a、b参数 3. 数据合成 干净RAW + 噪声 → 带噪RAW 详细合成流程 干净RAW 泊松噪声 (shot noise) 高斯噪声 (read noise) 合成 输出:干净RAW + 带噪RAW 数据对 进阶:Bayer通道独立标定、行噪声建模、FPN提取、ISO/温度随机采样 数据增强:随机裁剪、翻转、旋转、增益抖动

3.6 总结一下

训练数据准备,说白了就三步:

  • 建模:用Gaussian-Poisson模型描述传感器噪声。
  • 标定:通过拍摄均匀RAW图,拟合出模型参数a和b。
  • 合成:用标定参数生成大量干净-带噪数据对。

嗯,这里要注意:标定越细致,模型在真实场景下的表现就越好。别偷懒,多花两天时间把标定做扎实,后面训练和部署会省很多事。

我的建议:第一次做这个流程时,先拿一颗传感器完整跑一遍。从标定到合成到训练,全链路打通。后面换传感器,就只是换参数的事了。