采无噪数据考虑到很多去噪模型还是监督学习如果没有噪声模型的话就需要采集noisy-clean成对数据。ELD的制作方式相对简单主要是通过调整ISO和长短曝以最高效的方式来制作数据集。SIDD主要是通过多帧融合来制作数据集的包括一套系统的去坏点、筛图、校正、对齐、融合流程。这几乎可以视作一种简明扼要的多帧raw图去噪流程了。SIDD还有一个很关键的发现——即使把相机固定在防震的光学平台上拍摄帧数多了仍然会存在错位甚至在他们的实验中高达2~4 pixel作者认为这种错位是由于手机的OIS光学防抖功能无法有效关闭导致的。我们在使用多台设备多种装置在多个位置复现这一现象后发现这种错位本质上并不完全是OIS导致的。经实验关闭OIS在短时间内能观察到错位略有减小而长时间下的错位则不是OIS开关的影响。范浩强做的另一个稳定性实验则隐隐戳到了这个现象的本质——这种空间错位是 不可测的环境扰动 与 手机夹的过阻尼摩擦力 共同导致的。噪声模型ELD提出了四个关键的噪声shot noise, row noise, generalized read noise, and quantization noise。之前也写过噪声模型的总结。最经典的就是泊松-高斯模型还有它的简化版本camera noise level functions (NLF)。Heteroscedastic异方差的是统计学和计量经济学中的一个术语用于描述误差项或随机变量的方差不是常数而是随着解释变量自变量的变化而变化的现象。NLF (异方差高斯模型Heteroscedastic)它认为图像中的噪声不是固定不变的同方差而是随着图像亮度的变化而变化的异方差。NLF和泊松-高斯模型相比假设噪声仅仅是高斯分布的且方差只由亮度决定并且忽略了泊松分布在低光下的非高斯特性偏斜性这里还是以泊松-高斯模型为主考虑到digital gain和anlog gain最终得到的信号D可以写成N_p就是泊松噪声和信号紧紧相关。N1和N2的区别就是N1经过了K_a的放大是ADC之前就有的暗噪声和热噪声dark-current noise and thermal noiseN2是ADC之后产生的读出噪声和量化噪声read-out circuits, and quantization noise resulted from bit-depth adjustment对于泊松噪声需要通过K_a把图像像素值和光子数X联系到一起这样泊松分布就可以写成所以关键问题就是如何得到K_a它是一个增益系数和iso有关一般通过使用ISO-15739色卡标定得到各个iso的K_a值。可不可以跳过标定的阶段呢这就需要两点进一步的观察信号相关K怎么得到呢不同的iso对应一个K。因为IKX通常的做法是在指定iso下画出不同厚度值对应的raw值斜率就是K。如果是在原始raw上做的斜率标定10bit的话原始raw的范围最大值是1024那么在对01浮点数加噪声时对应的slope要除以1024bias对应的是信号无关的方差信号无关K*N1N2所以bias要除以1024*1024。但其实最好的做法是把clean data转成1024的范围这样更符合物理而不是去缩放sclae和bias。K实际上又两部分构成quantum efficiency (QE) and the analog gain (AG) 前者由材料学限制后者是用户设置的。QE估计工艺的不同波动在30%到70%之间所以当AG固定后K的范围就差不多固定了上下限不会超过2倍。还有个重要的观察那就是K其实只影响信号相关部分的方差而不影响均值所以可以直接假设QE0.5来估计得到K这样加噪声获得的图像虽然K不准确但是仍然和clena raw的亮度是一致的所以这样训练的网络不会学习到亮度改变并且K的不准确可以看作是一种数据增广。听起来有点离谱看实验确实是这样不管使用标定好的还是随机的K结果相差不大在这里假设QE已知所以只需要知道模拟增益 (Analog Gain, AG)公式AG ISO / BaseISO计算的就是这个。含义它表示传感器内部电路对信号的相对放大倍数。基准点在最低原生 ISO即 BaseISO时模拟增益确实为 1或 0dB意味着信号没有被额外放大。索尼文章在4.1章节提到AG1时iso400那么当QE0.4时iso/400*0.4就是K换算一下Kiso/100*0.1we empirically investigate their characteristics and find that both cameras have their baseISOs (i.e., AG 1) around 400. To this end, we employ an empirical approximation equation for the system gain K: K ISO/100 ∗0.1, representing a hypothesized QE of40%.AG1时的iso就是BaseISO绘制ISO 值 vs. 饱和曝光量的曲线。转折点即为 Base ISO你会发现在低 ISO 段饱和曝光量可能保持不变这通常是数字增益区域当 ISO 增加到某一点后饱和曝光量开始随 ISO 增加而线性下降这是模拟增益区域。这个转折点通常就是模拟增益 AG1AG1 的位置。文章中说的QE应该指的是归一化之前的raw的转换效率但是我看到有的sensor其实只有10%的转换效率。安卓手机的raw可以通过exiftool查看 DNG 文件exiftool image.dng | grep -i gain查找字段如AnalogGain或BaselineExposure。注意有些厂商只记录 ISOAG 需要查表信号无关对于信号无关的噪声需要先计算多个黑帧的平均值作为Calibrated dark shading。这是完全和信号无关的底噪包括热噪声 (Thermal Noise)电子的热运动。暗电流 (Dark Current)像素在无光情况下自发产生的电荷积累。固定图案噪声 (FPN, Fixed Pattern Noise)由于制造工艺不完美每个像素对“无光”状态的响应不一致有的像素天生就比别的亮一点或暗一点。这些噪声通常是时间上一致的 (Temporally Consistent)即在相同的曝光时间、温度和增益ISO/AG设置下传感器上同一个位置的噪声值是基本固定的。黑帧采样在商汤ICCV2021的文章中直接对黑帧采样得到信号无关的噪声黑帧指定了iso但是曝光参数是自动的。和泊松噪声一起合成噪声虽然在普通场景下好于ELD noise model但是在极暗场景下还是比较差原因可能是暗噪声被采样破坏掉了直接采样也破坏了噪声的spatially-correlated特性。应对这两个问题所以提出了High-bit reconstruction和pattern-aligned patch sampling (PAP)不是对每个ISO都采样而是对log2(iso)等间隔采样物理规律ISO 每增加一倍例如从 100 到 200模拟增益Analog Gain通常也增加一倍。线性采样100, 200, 300...的问题在低 ISO 区间100-200增益变化剧烈噪声特性变化快需要采点但在高 ISO 区间比如 25600 到 25700虽然数值增加了 100但相对于基数来说增益几乎没变噪声特性也没变采这个点是浪费存储和计算资源。对数采样100, 200, 400, 800...保证了每个采样点之间的增益倍数间隔是固定的从而能最高效地描绘出噪声随增益变化的曲线。误区高iso时往往场景更暗iso高的时候采样密集一点更好。低 ISOBase ISO传感器的模拟增益Analog Gain非常低比如 1x。此时电路底噪Read Noise、暗电流的非线性、ADC 的量化误差相对于满阱容量来说非常微小且难以捕。高 ISO比如 6400传感器的模拟增益已经非常大比如 32x。此时信号和噪声都被放大了几十倍。原本微小的电路噪声已经被“淹没”在巨大的信号波动中或者已经被放大器“拉”到了线性区。高 ISO 密集采样你会发现采集回来的 10 张黑帧除了亮度方差不同统计特征几乎是一模一样的直线。这就是数据冗余。对ISO采样后每个iso采集10个黑帧。因为iso没有覆盖到所有的iso这意味着有的iso没有对应的黑帧这时候可能需要插值因为方差是一样的只有亮度的区别。但是插值的时候注意不能安装iso去线性插值iso3200和iso6400是2倍的关系和32006400本身无关所以要取log2转换到dB上去计算import numpy as np # 1. 定义 ISO 值 iso_start 3200 iso_end 6400 iso_target 4000 # 2. 计算对数域权重 (关键步骤) # 将 ISO 比值转换为 dB db_start 20 * np.log10(iso_start / iso_start) # 0 db_end 20 * np.log10(iso_end / iso_start) # ~6.02 db_target 20 * np.log10(iso_target / iso_start) # ~1.94 # 计算插值系数 alpha alpha (db_target - db_start) / (db_end - db_start) print(fISO 4000 的权重系数 alpha: {alpha:.3f}) # 输出约为 0.322说明它更靠近 3200 # 3. 估算黑电平 (均值) # 假设 mean_3200 和 mean_6400 是黑帧的平均值或黑电平图 mean_4000 (1 - alpha) * mean_3200 alpha * mean_6400 # 4. 估算噪声强度 (标准差) # 注意要在方差域(功率)进行插值而不是标准差域 var_3200 np.var(noise_3200) var_6400 np.var(noise_6400) var_4000 (1 - alpha) * var_3200 alpha * var_6400 std_4000 np.sqrt(var_4000) print(f估算的 ISO 4000 噪声标准差: {std_4000})clude code的方案感觉更靠谱# --- 准备多帧黑帧均值 FPN空间固定结构--- fpn_3200 np.mean(dark_frames_3200, axis0) # shape: (H, W) fpn_6400 np.mean(dark_frames_6400, axis0) # --- 单帧减去均值 随机 read noise --- rand_3200 single_dark_3200 - fpn_3200 rand_6400 single_dark_6400 - fpn_6400 # --- FPN线性插值它是确定性的直接插值合理--- t (5000 - 3200) / (6400 - 3200) fpn_5000 (1 - t) * fpn_3200 t * fpn_6400 # --- 随机分量插值方差后重新缩放 --- # 计算方差 var_rand_3200 rand_3200.var() var_rand_6400 rand_6400.var() # 插值目标方差 var_rand_5000 (1 - t) * var_rand_3200 t * var_rand_6400 # 从 ISO 3200 随机分量缩放保留空间相关性 scale np.sqrt(var_rand_5000 / var_rand_3200) rand_5000 rand_3200 * scale # --- 合成 ISO 5000 黑帧 --- dark_5000 fpn_5000 rand_5000但是看代码不知道为什么合成噪声使用的还是泊松高斯噪声而没有使用黑帧具体代码见https://github.com/zhangyi-3/Noise-Synthesis/blob/main/synthesize.py黑帧校准在索尼的文章中仍然使用叠加黑帧但是考虑到高比特重建可能引入复杂性和额外的错误并且低bit本来就是噪声低特性所以放弃了高比特位重建high-bit-depth noise recovery (HBNR)。因为ELD提到信号无关噪声是长尾分布long-tailed可以使用Tukey-Lambda 或者Gaussian mixture model (GMM)来拟合。通过Q-Q图对比GMM拟合更好但是泛化性也不够但是使用了校准dark shading的预处理。因为在专业摄影中都需要把拍到的图减去这个底噪。所以网络的训练和加噪声的过程中也要减去这个底噪darkshading的估计也是根据iso线性拟合除了k和b还有根据均值计算出的bledef get_darkshading(self, iso): if iso 1600: return self.pmn_dsk_low * iso self.pmn_dsb_low self.pmn_ble[iso] else: return self.pmn_dsk_high * iso self.pmn_dsb_high self.pmn_ble[iso]理论上要求dark shading比较准确要使用大量的黑帧求平均。但实际上随着使用环境的变化如高温环境连拍好久之后就需要重新标定。文章中对比了不同黑帧数量下re-calibration的效果可以看到使用10帧重新标定和180帧只差了0.1dB。事实上在训练的阶段利用了LLD每个iso400个黑帧的数据但是实验证明训练时每个ISO也只需要10帧黑帧代码中可以看到生成噪声和高比特的实现。def generate_noisy_obs( y, camera_typeNone, wp16383, noise_codep, paramNone, MultiFrameMean1, oriFalse, clipFalse ): # # Burst denoising # sig_read 10. ** np.random.uniform(low-3., high-1.5) # sig_shot 10. ** np.random.uniform(low-2., high-1.) # shot np.random.randn(*y.shape).astype(np.float32) * np.sqrt(np.maximum(y, 1e-10)) * sig_shot # read np.random.randn(*y.shape).astype(np.float32) * sig_read # z y shot read p param y y * (p[wp] - p[bl]) # p[ratio] 1/p[ratio] # 临时行为为了快速实现MFM y y / p[ratio] MFM MultiFrameMean**0.5 use_R True if r in noise_code.lower() else False use_Q True if q in noise_code.lower() else False use_TL True if g in noise_code.lower() else False use_P True if p in noise_code.lower() else False use_D True if d in noise_code.lower() else False use_black True if b in noise_code.lower() else False if use_P: # 使用泊松噪声作为shot noisy noisy_shot np.random.poisson(MFM * y / p[K]).astype(np.float32) * p[K] / MFM else: # 不考虑shot noisy noisy_shot ( y np.random.randn(*y.shape).astype(np.float32) * np.sqrt(np.maximum(y / p[K], 1e-10)) * p[K] / MFM ) if not use_black: if use_TL: # 使用TL噪声作为read noisy noisy_read stats.tukeylambda.rvs(p[lam], scalep[sigTL] / MFM, sizey.shape).astype(np.float32) else: # 使用高斯噪声作为read noisy noisy_read stats.norm.rvs(scalep[sigGs] / MFM, sizey.shape).astype(np.float32) # 行噪声需要使用行的维度h[1,c,h,w]所以-2是h noisy_row np.random.randn(y.shape[-3], y.shape[-2], 1).astype(np.float32) * p[sigR] / MFM if use_R else 0 # [1, 1000, 2000] 的图像与 [1, 1000, 1]行噪声利用广播机制相加行噪声的每一行都是同一个值 noisy_q np.random.uniform(low-0.5, high0.5, sizey.shape) if use_Q else 0 # 模拟四舍五入的量化噪声 noisy_bias p[bias] if use_D else 0 else: noisy_read 0 noisy_row 0 noisy_q 0 noisy_bias 0 # 归一化回[0, 1] z (noisy_shot noisy_read noisy_row noisy_q noisy_bias) / (p[wp] - p[bl]) # 加噪声是在10bit物理空间中所以加完噪声再归一化 # 模拟实际情况 z np.clip(z, -p[bl] / p[wp], 1) if clip is False else np.clip(z, 0, 1) if ori is False: z z * p[ratio] return z.astype(np.float32)。这里https://github.com/SonyResearch/raw_image_denoising深度学习既然很难对信号无关尤其是很暗的环境还有一个方法就是使用深度学习的方法。这里介绍一篇ICCV 2023华中科大和大疆构建了low-light raw denoising (LRD) 数据集zhangfeng的文章https://github.com/fengzhang427/LRD使用Fourier transformer discriminator (FTD)来区分噪声分布。Kullback-Leibler divergence (KLD)计算合成的噪声和真实噪声的分布。这个生成模型可以生成不同iso的信号无关噪声。首先第一个问题就是怎么衡量合成噪声和真实噪声的差异直接使用L1肯定不可以因为不可能要求噪声一模一样我们更关注的是统计学的分布。解决办法就是对真假带噪声图像分别经过预先训练的降噪模型之后再去计算L1 loss和perceptual loss此外还引入了对抗损失但是对抗损失在噪声很多的场景下会失效。参考傅立叶变换把图像内容和噪声根据频率区分开来。fast Fourier convolution (FFC) 就可以看作是傅立叶变换的CNN形式在此基础上把卷积部分替换成transformer就得到了Fourier transformer block。和FFC一样数据的一半在频域上计算一半在空域这样就得到了互补的信息。又参考TransGAN从多尺度进行判别所以完整的Fourier transformer discriminator (FTD)是不同patchsize得到的patch被序列化经过Fourier transformer block之后再转回2D通过平均池化和另外一个尺度的序列做concate。使用WGAN-GP因为Wasserstein distance训练更稳定。使用Average Kullback-Leibler divergence (AKLD)衡量不同噪声模型和真实分布的距离虽然提供了源码但是只有训练降噪模型的部分没有生成噪声的代码https://github.com/fengzhang427/LRD/tree/main此外还有对噪声分得更精细分别涉及模块去对应的深度学习方法如caoyue的LLD但只提供了数据没有相关代码。当然还有经典的使用流模型生成的noiseflow。reference1. rethinking https://arxiv.org/pdf/2110.047562. onehour https://arxiv.org/pdf/2505.000452. noiseflow https://arxiv.org/pdf/1908.084533.https://blog.csdn.net/amusi1994/article/details/132595249https://openaccess.thecvf.com/content/CVPR2023/papers/Cao_Physics-Guided_ISO-Dependent_Sensor_Noise_Modeling_for_Extreme_Low-Light_Photography_CVPR_2023_paper.pdf4.