網(wǎng)絡(luò)圖像去噪:殘差學(xué)習(xí)與PyTorch工程實(shí)踐)
簡(jiǎn)介資源提供基于深度卷積神經(jīng)網(wǎng)絡(luò)DnCNN的圖像去噪完整實(shí)現(xiàn)涵蓋網(wǎng)絡(luò)結(jié)構(gòu)設(shè)計(jì)、TensorFlow訓(xùn)練流程與高斯噪聲去除實(shí)驗(yàn)全過程適合深度學(xué)習(xí)圖像處理學(xué)習(xí)者、算法工程師及需要復(fù)現(xiàn)經(jīng)典模型的入門研究者。壓縮包共45個(gè)文件包含26張JPG訓(xùn)練與測(cè)試樣例圖、12張PNG去噪前后對(duì)比圖、4個(gè)Python腳本網(wǎng)絡(luò)構(gòu)建、配置、算子與主訓(xùn)練程序以及README說明文檔和許可證整體約1.39MB體積輕量。目錄按訓(xùn)練集、測(cè)試集、訓(xùn)練結(jié)果和代碼模塊劃分結(jié)構(gòu)清晰便于按步驟對(duì)照學(xué)習(xí)。目前已有6508人學(xué)習(xí)下載。通過源碼與樣例數(shù)據(jù)可掌握批量歸一化、ReLU激活、噪聲水平自適應(yīng)輸入等關(guān)鍵技術(shù)并可直接運(yùn)行復(fù)現(xiàn)DnCNN去噪效果進(jìn)一步理解如何在TensorFlow中構(gòu)建、訓(xùn)練和評(píng)估圖像復(fù)原模型為后續(xù)改進(jìn)網(wǎng)絡(luò)提供扎實(shí)基礎(chǔ)。1. 基于深度卷積神經(jīng)網(wǎng)絡(luò)圖像去噪為什么“拆噪聲”比“重建圖像”更聰明基于深度卷積神經(jīng)網(wǎng)絡(luò)圖像去噪算法說白了就是讓網(wǎng)絡(luò)從一張帶噪圖里把“噪聲”單獨(dú)拆出來(lái)再用原圖減去噪聲剩下的就是干凈圖。手機(jī)夜景、老照片掃描、低劑量醫(yī)學(xué)影像大量實(shí)際場(chǎng)景都卡在噪聲和細(xì)節(jié)之間降噪太上強(qiáng)度邊緣糊成一片保住了邊緣噪點(diǎn)又賴著不走。深度卷積網(wǎng)絡(luò)用殘差學(xué)習(xí)解決了這個(gè)矛盾在相同噪聲強(qiáng)度下常見結(jié)構(gòu)能比傳統(tǒng)塊匹配類算法高出 1~2 dB 的 PSNR視覺上邊緣和紋理保留得更完整。這篇文章不是給你念論文而是按一條能落地的路徑講先說明網(wǎng)絡(luò)為什么這樣設(shè)計(jì)再講訓(xùn)練數(shù)據(jù)怎么造、模型怎么復(fù)現(xiàn)、參數(shù)怎么調(diào)最后把最常翻車的幾個(gè)坑按“現(xiàn)象—原因—解決”列出來(lái)。適合正在做圖像降噪、準(zhǔn)備把深度學(xué)習(xí)方案接進(jìn)項(xiàng)目的開發(fā)者和算法工程師也適合剛接觸圖像復(fù)原方向、想找一份可抄作業(yè)流程的研究生。2. 深度卷積網(wǎng)絡(luò)去噪的原理殘差學(xué)習(xí)、感受野與批歸一化怎么配合要搞懂這類算法先給“去噪”一個(gè)可計(jì)算的數(shù)學(xué)定義。加性高斯噪聲模型寫作 x y n其中 x 是觀測(cè)到的噪聲圖y 是干凈圖n 是噪聲。網(wǎng)絡(luò)的任務(wù)就是從 x 估計(jì) y。傳統(tǒng)方法把去噪當(dāng)成濾波或優(yōu)化問題而深度卷積網(wǎng)絡(luò)把它當(dāng)成監(jiān)督學(xué)習(xí)問題給網(wǎng)絡(luò)喂大量 (x, y) 對(duì)讓它自己擬合映射關(guān)系。深度卷積神經(jīng)網(wǎng)絡(luò)圖像去噪算法的核心不在“用卷積”這件事上而在于三個(gè)具體設(shè)計(jì)網(wǎng)絡(luò)內(nèi)部學(xué)的是殘差還是完整圖像、卷積層數(shù)決定的感受野有多大、卷積層之間的歸一化怎么加。這三個(gè)點(diǎn)直接決定了模型的上限和訓(xùn)練穩(wěn)定性下面逐一拆開。2.1 殘差學(xué)習(xí)不是預(yù)測(cè)干凈圖而是預(yù)測(cè)噪聲圖早期把殘差學(xué)習(xí)引入圖像去噪的經(jīng)典思路解決了深度網(wǎng)絡(luò)做圖像復(fù)原的一個(gè)根本矛盾如果讓網(wǎng)絡(luò)直接輸出 y輸入是噪聲圖輸出是干凈圖那網(wǎng)絡(luò)要學(xué)習(xí)的是一個(gè)“從污染信號(hào)重建完整圖像”的映射。這個(gè)輸出空間包含大量低頻結(jié)構(gòu)、紋理和邊緣信息網(wǎng)絡(luò)需要花很大容量去記住圖像先驗(yàn)訓(xùn)練起來(lái)既慢又容易欠擬合。換一個(gè)目標(biāo)就不一樣了。定義殘差 r x - y也就是噪聲本身訓(xùn)練時(shí)讓網(wǎng)絡(luò)輸出 f(x)用損失 ‖f(x) - r‖2 去約束。因?yàn)榧有愿咚乖肼曉诳臻g上近似獨(dú)立、均值接近零它的統(tǒng)計(jì)結(jié)構(gòu)比自然圖像簡(jiǎn)單得多網(wǎng)絡(luò)學(xué)會(huì)“預(yù)測(cè)噪聲”比學(xué)會(huì)“重建圖像”容易得多。推理時(shí)的輸出直接寫成 ? x - f(x)一張?jiān)肼晥D減去網(wǎng)絡(luò)預(yù)測(cè)出的噪聲分量剩下的就是相對(duì)干凈的圖像結(jié)構(gòu)。這個(gè)設(shè)計(jì)的另一個(gè)隱性好處是網(wǎng)絡(luò)初始化不穩(wěn)時(shí)輸出接近零意味著 ? 接近 x也就是模型一開始先從“把原圖原樣返回”出發(fā)再逐步修正。相比從空白開始猜一張完整圖像這種做法的訓(xùn)練曲線要平穩(wěn)得多。實(shí)際項(xiàng)目中我一般會(huì)用這個(gè)殘差結(jié)構(gòu)作為默認(rèn)選項(xiàng)除非任務(wù)特殊否則不輕易改成直接回歸干凈圖。2.2 感受野35×35 的上下文到底意味著什么感受野指輸出特征圖上某個(gè)像素能“看到”輸入圖像多大的區(qū)域。對(duì)于 3×3 卷積、stride1 的堆疊結(jié)構(gòu)每增加一層感受野邊界向外擴(kuò) 2 個(gè)像素所以 L 層網(wǎng)絡(luò)的感受野尺寸是 2L 1。17 層網(wǎng)絡(luò)算下來(lái)就是 35×35這是這類去噪網(wǎng)絡(luò)最常見的深度配置。感受野為什么這么重要因?yàn)槿ピ氡举|(zhì)上是用周圍像素估計(jì)中心像素的“干凈值”。如果感受野只有 5×5網(wǎng)絡(luò)能參考的上下文太小遇到大塊邊緣、細(xì)紋理時(shí)很容易把圖像結(jié)構(gòu)當(dāng)成噪聲抹掉。35×35 意味著估計(jì)每個(gè)輸出像素時(shí)網(wǎng)絡(luò)能綜合周圍 35×35 范圍內(nèi)約 1200 個(gè)像素的信息對(duì)大多數(shù)自然圖像的局部結(jié)構(gòu)來(lái)說已經(jīng)夠用。增加到 20 層以上感受野更大但參數(shù)和顯存開銷漲得比收益快PSNR 往往只提升零點(diǎn)零幾 dB。這個(gè)數(shù)值也直接決定了訓(xùn)練時(shí) patch 的最小尺寸。patch 至少要能完整包住感受野否則裁出來(lái)的訓(xùn)練樣本在邊緣處“信息不全”網(wǎng)絡(luò)學(xué)到的映射和推理時(shí)不一致。我一般會(huì)把 patch 設(shè)為 40×40 起步正好略大于 35×35兼顧覆蓋范圍和樣本數(shù)量噪聲強(qiáng)度到了 σ50 時(shí)建議把 patch 提到 48×48 或 50×50。2.3 批歸一化加速收斂之外的另一層作用BatchNorm 加在每層卷積之后、激活函數(shù)之前先把中間特征圖歸一化到零均值單位方差再用可學(xué)習(xí)的縮放和平移恢復(fù)表達(dá)能力。對(duì)去噪網(wǎng)絡(luò)BN 的價(jià)值有兩層第一層很好理解深層網(wǎng)絡(luò)有了 BN 之后梯度傳播更穩(wěn)定直接用 1e-3 的學(xué)習(xí)率也能收斂去掉 BN 的話這個(gè)學(xué)習(xí)率很容易讓 loss 直接發(fā)散第二層是“分布穩(wěn)定化”去噪網(wǎng)絡(luò)的輸入是噪聲圖不同 batch 之間的噪聲分布有波動(dòng)BN 把每層輸入的分布拉回統(tǒng)一尺度相當(dāng)于給訓(xùn)練過程加了一個(gè)緩沖。但 BN 是有代價(jià)的。它依賴 batch 內(nèi)統(tǒng)計(jì)量訓(xùn)練時(shí)如果 batch size 壓到 4 以下BN 算出來(lái)的均值和方差噪聲很大訓(xùn)練會(huì)明顯抖動(dòng)推理時(shí)如果忘了切換到 eval 模式BN 繼續(xù)用當(dāng)前輸入 batch 的統(tǒng)計(jì)量而推理常常一次只進(jìn)一張圖統(tǒng)計(jì)量嚴(yán)重偏移輸出會(huì)直接崩掉。這類去噪網(wǎng)絡(luò)踩過的坑十有八九和 BN 模式切換有關(guān)后面避坑章節(jié)會(huì)展開細(xì)說。3. 訓(xùn)練數(shù)據(jù)從哪來(lái)噪聲合成、Patch 提取與驗(yàn)證集劃分深度卷積去噪是監(jiān)督方法需要成對(duì)的干凈圖和噪聲圖?,F(xiàn)實(shí)里很難拿到“同一場(chǎng)景拍兩次、一次干凈一次帶噪聲”的大量數(shù)據(jù)所以行業(yè)里的通行做法是準(zhǔn)備一批高質(zhì)量干凈圖像人為合成噪聲制造訓(xùn)練對(duì)。這一步聽起來(lái)簡(jiǎn)單但噪聲參數(shù)怎么選、patch 怎么裁、驗(yàn)證集怎么劃分直接影響模型最終能不能用。3.1 用高斯噪聲作為起點(diǎn)sigma 怎么選加性高斯白噪聲是最常用的合成模型控制噪聲強(qiáng)度的參數(shù)是標(biāo)準(zhǔn)差 σ單位是像素灰度值。σ15 對(duì)應(yīng)輕度噪聲σ25 是學(xué)術(shù)界的標(biāo)準(zhǔn)基準(zhǔn)σ50 是強(qiáng)噪聲肉眼已經(jīng)能看到明顯的顆粒感。實(shí)現(xiàn)時(shí)用 numpy 的 randn 函數(shù)生成和圖像同尺寸的高斯隨機(jī)矩陣乘以 σ 再加到干凈圖上即可。選擇 σ 時(shí)要看最終場(chǎng)景。如果任務(wù)是手機(jī)夜景照片去噪真實(shí)噪聲往往比 σ25 更強(qiáng)而且和像素亮度相關(guān)不是單純的高斯白噪聲這時(shí)訓(xùn)練集里應(yīng)該混合多個(gè) σ而不是只做單一等級(jí)。我一般會(huì)先固定 σ25 把整條數(shù)據(jù)管線跑通確認(rèn)訓(xùn)練循環(huán)、驗(yàn)證指標(biāo)都正常再擴(kuò)展到 σ ∈ {15, 25, 50} 混合訓(xùn)練。不要一上來(lái)就追求復(fù)雜噪聲模型否則出了問題都說不清是數(shù)據(jù)問題還是網(wǎng)絡(luò)問題。3.2 Patch 提取40×40 小圖塊背后的權(quán)衡為什么不能拿整張圖直接訓(xùn)練三個(gè)原因。第一是顯存1024×1024 的單張灰度圖直接進(jìn) 17 層 64 通道網(wǎng)絡(luò)一次前向就要占用數(shù) GB 顯存batch 完全跑不起來(lái)。第二是樣本量一張圖只能給一次梯度但按 40×40 大小滑窗裁開后一張圖能產(chǎn)生幾百個(gè) patch訓(xùn)練樣本數(shù)量提升兩個(gè)數(shù)量級(jí)。第三是感受野匹配patch 只需要略大于網(wǎng)絡(luò)感受野就能提供完整上下文再大并不會(huì)帶來(lái)更多有效信息反而拉慢訓(xùn)練速度。代碼實(shí)現(xiàn)可以直接用 OpenCV 讀取灰度圖滑窗裁剪后逐 patch 加噪聲import cv2 import numpy as np def synthesize_noise_pair(clean, sigma25): 輸入 0-255 的干凈灰度圖返回 (噪聲圖, 干凈圖)取值范圍 0-1。 clean clean.astype(np.float32) / 255.0 noise np.random.randn(*clean.shape).astype(np.float32) * (sigma / 255.0) noisy np.clip(clean noise, 0.0, 1.0) return noisy, clean def extract_patches(clean, patch_size40, stride20): 從單張灰度圖滑窗裁剪 patch返回干凈 patch 列表。 h, w clean.shape[:2] patches [] # 步長(zhǎng)小于 patch 尺寸讓相鄰 patch 有重疊數(shù)據(jù)量更大 for i in range(0, h - patch_size 1, stride): for j in range(0, w - patch_size 1, stride): patches.append(clean[i:ipatch_size, j:jpatch_size]) return patches # 讀圖 - 裁 patch - 逐 patch 加噪聲 img cv2.imread(clean_photo.png, cv2.IMREAD_GRAYSCALE) clean_patches extract_patches(img, patch_size40, stride20) train_pairs [synthesize_noise_pair(p, sigma25) for p in clean_patches]這里有兩個(gè)關(guān)鍵細(xì)節(jié)。第一圖像先歸一化到 0-1因?yàn)榫W(wǎng)絡(luò)輸出用 MSE 損失時(shí)0-1 范圍的梯度更穩(wěn)定σ 也同步除以 255如果保留 0-255 范圍梯度量級(jí)會(huì)大 255 倍學(xué)習(xí)率要跟著調(diào)沒有直接歸一化省心。第二stride20 表示 patch 之間有一半重疊一張 256×256 的圖能裁出大約 121 個(gè) patch如果顯存緊張或圖非常多可以調(diào) stride40 減少重疊代價(jià)是樣本總量變小、訓(xùn)練輪數(shù)要相應(yīng)增加。3.3 按圖劃分訓(xùn)練/驗(yàn)證防止數(shù)據(jù)泄漏抬高 PSNR很多初學(xué)者把所有 patch 混在一起隨機(jī)打亂后按比例切訓(xùn)練集和驗(yàn)證集看起來(lái)沒問題實(shí)際埋了一個(gè)大雷同一張干凈圖裁出的不同 patch一個(gè)進(jìn)了訓(xùn)練集、一個(gè)進(jìn)了驗(yàn)證集網(wǎng)絡(luò)訓(xùn)練時(shí)已經(jīng)見過同一張圖的其他區(qū)域驗(yàn)證 PSNR 會(huì)虛高 0.5~1 dB。拿這個(gè)數(shù)字去跟論文對(duì)比、去評(píng)效果完全沒有參考價(jià)值。正確做法是先把干凈圖像按文件列表分組比如前 90% 的圖負(fù)責(zé)裁剪訓(xùn)練 patch后 10% 的圖負(fù)責(zé)裁剪驗(yàn)證 patch保證驗(yàn)證圖在訓(xùn)練階段完全沒出現(xiàn)過。另一個(gè)細(xì)節(jié)是驗(yàn)證時(shí)固定 σ 列表和隨機(jī)種子否則每次驗(yàn)證因?yàn)殡S機(jī)噪聲不同PSNR 上下波動(dòng) 0.2 dB實(shí)驗(yàn)之間沒辦法公平對(duì)比。我習(xí)慣在驗(yàn)證腳本里寫死 np.random.seed(0)讓每一次驗(yàn)證結(jié)果可復(fù)現(xiàn)。4. 用 PyTorch 復(fù)現(xiàn)去噪網(wǎng)絡(luò)模型定義、訓(xùn)練循環(huán)與推理評(píng)估原理清楚了數(shù)據(jù)也備好了接下來(lái)是核心環(huán)節(jié)把網(wǎng)絡(luò)搭出來(lái)、跑通訓(xùn)練、驗(yàn)證有效。這一章給出的是一份可以直接改著用的 PyTorch 實(shí)現(xiàn)結(jié)構(gòu)上以 17 層深度卷積網(wǎng)絡(luò)為主這也是深度卷積神經(jīng)網(wǎng)絡(luò)圖像去噪算法里最經(jīng)典、最穩(wěn)的配置。4.1 17 層卷積網(wǎng)絡(luò)怎么寫第一層和最后一層的特殊處理網(wǎng)絡(luò)結(jié)構(gòu)如下注意首層和末層與中間層不同import torch import torch.nn as nn class DenoiseCNN(nn.Module): 17 層深度卷積去噪網(wǎng)絡(luò)輸入噪聲圖輸出殘差噪聲圖前向返回去噪結(jié)果。 def __init__(self, depth17, num_features64, in_channels1, out_channels1): super().__init__() layers [] # 第一層Conv ReLU不接 BN保留 bias layers.append(nn.Conv2d(in_channels, num_features, kernel_size3, padding1)) layers.append(nn.ReLU(inplaceTrue)) # 中間 depth-2 層Conv BN ReLUbias 置 False for _ in range(depth - 2): layers.append(nn.Conv2d(num_features, num_features, kernel_size3, padding1, biasFalse)) layers.append(nn.BatchNorm2d(num_features)) layers.append(nn.ReLU(inplaceTrue)) # 最后一層Conv 輸出單通道殘差圖不接 BN 和 ReLU layers.append(nn.Conv2d(num_features, out_channels, kernel_size3, padding1)) self.net nn.Sequential(*layers) def forward(self, x): residual self.net(x) return x - residual三個(gè)設(shè)計(jì)點(diǎn)需要說清楚。第一中間層 conv 的 biasFalse是因?yàn)楹竺婢o跟 BatchNormBN 自帶可學(xué)習(xí)的平移參數(shù)卷積的 bias 是冗余的首層和末層不接 BN必須保留 bias否則卷積輸出的均值漂移無(wú)法被補(bǔ)償。第二末層不接 BN 也不接 ReLU因?yàn)闅埐顖D可以是負(fù)值——正噪聲和負(fù)噪聲都要能表達(dá)一旦 ReLU 截?cái)嘭?fù)值網(wǎng)絡(luò)就只能學(xué)“加噪”方向沒法學(xué)“去噪”方向。第三forward 里的 x - residual 就是殘差學(xué)習(xí)的落地訓(xùn)練時(shí)網(wǎng)絡(luò)學(xué)習(xí)的目標(biāo)是殘差噪聲圖推理時(shí)原圖減噪聲得到干凈圖。4.2 訓(xùn)練超參數(shù)學(xué)習(xí)率、Batch Size 與學(xué)習(xí)率衰減訓(xùn)練循環(huán)的框架如下包含優(yōu)化器、學(xué)習(xí)率衰減、驗(yàn)證三個(gè)部分import math import torch import torch.optim as optim from torch.utils.data import DataLoader, TensorDataset # noisy_patches / clean_patches 是形狀為 (N, 1, 40, 40) 的 Tensor dataset TensorDataset(noisy_patches, clean_patches) loader DataLoader(dataset, batch_size32, shuffleTrue, num_workers4) model DenoiseCNN(depth17, num_features64, in_channels1, out_channels1) criterion nn.MSELoss() # 殘差學(xué)習(xí)使用 MSE對(duì)應(yīng)高斯噪聲的最大似然估計(jì) optimizer optim.Adam(model.parameters(), lr1e-3) scheduler optim.lr_scheduler.StepLR(optimizer, step_size10, gamma0.1) def evaluate_psnr(model, loader): model.eval() mse_sum, n_pixels 0.0, 0 with torch.no_grad(): for noisy, clean in loader: out model(noisy) mse_sum ((out - clean) ** 2).sum().item() n_pixels clean.numel() mse mse_sum / n_pixels return 10 * math.log10(1.0 / mse) # 輸入輸出均為 0-1 范圍 for epoch in range(50): model.train() total_loss 0.0 for noisy, clean in loader: optimizer.zero_grad() output model(noisy) loss criterion(output, clean) loss.backward() optimizer.step() total_loss loss.item() * noisy.size(0) scheduler.step() if (epoch 1) % 5 0: val_psnr evaluate_psnr(model, loader) print(fepoch {epoch 1:02d} loss {total_loss / len(dataset):.5f} fpsnr {val_psnr:.2f} dB)訓(xùn)練超參數(shù)按下面的常用值起步再根據(jù)實(shí)際收斂情況微調(diào)參數(shù)常用值調(diào)參方向?qū)W習(xí)率 lr1e-3訓(xùn)練不穩(wěn)時(shí)降到 3e-4batch size32顯存不夠時(shí)用 8~16但要關(guān)注 BN 統(tǒng)計(jì)網(wǎng)絡(luò)深度 depth17強(qiáng)噪聲可加到 20收益遞減特征通道數(shù)64大圖或強(qiáng)噪聲可加到 96epoch 數(shù)30~50驗(yàn)證 PSNR 進(jìn)入平臺(tái)期即停關(guān)于 evaluate_psnr注意它假設(shè)輸入輸出都是 0-1 范圍所以直接用 10log10(1/mse)不需要乘 2552。如果訓(xùn)練和推理走 0-255 范圍這里要相應(yīng)改成 10log10(2552/mse)。兩種口徑一致時(shí)PSNR 數(shù)值才是對(duì)的。4.3 推理階段全卷積推理與 YCbCr 通道處理訓(xùn)練時(shí)用 40×40 的 patch推理時(shí)輸入往往是整張 1024×1024 甚至更大的圖。好在網(wǎng)絡(luò)是全卷積的可以接受任意尺寸輸入不需要重新裁圖。下面是單張灰度圖的推理函數(shù)import torch import numpy as np def denoise_gray(model, noisy_img_bgr_or_gray, devicecuda): # 輸入 0-255 灰度圖輸出 0-255 去噪圖 model.eval() with torch.no_grad(): x torch.from_numpy(noisy_img).float().div_(255.0) x x.unsqueeze(0).unsqueeze(0).to(device) # (1, 1, H, W) out model(x).squeeze(0).squeeze(0).cpu().numpy() return np.clip(out * 255.0, 0, 255).astype(np.uint8)彩色圖一般不直接在 RGB 三通道上跑。常見做法是把圖像從 RGB 轉(zhuǎn)到 YCbCr 色彩空間只對(duì)亮度 Y 通道做深度卷積去噪CbCr 兩個(gè)色度通道保持原樣最多做一次很輕的均值濾波。原因是人眼對(duì)亮度細(xì)節(jié)最敏感色度通道的輕微噪聲感知度低這樣做省掉三分之二的計(jì)算量也避免三通道相互干擾導(dǎo)致偏色。如果場(chǎng)景要求三通道都去噪就把模型 in_channels 和 out_channels 改成 3輸入輸出保持 0-1 歸一化同樣能跑但訓(xùn)練時(shí)要保證每個(gè)通道的噪聲分布一致。5. 深度圖像去噪避坑指南5 個(gè)高頻問題與排查記錄網(wǎng)絡(luò)結(jié)構(gòu)能抄數(shù)據(jù)流程能抄但真正消耗時(shí)間的永遠(yuǎn)是訓(xùn)練和部署時(shí)那些“看著哪都對(duì)、跑出來(lái)就是不對(duì)”的問題。下面幾個(gè)坑我基本都踩過按現(xiàn)象、原因、解決的順序?qū)懩阏罩挪楸戎匦伦x論文快得多。5.1 loss 正常下降PSNR 卻只有幾 dB 甚至負(fù)數(shù)現(xiàn)象訓(xùn)練 loss 從 0.1 一路降到 0.01訓(xùn)練過程看起來(lái)完全正常但每次驗(yàn)證打印出來(lái)的 PSNR 只有幾 dB甚至出現(xiàn)負(fù)值。原因十有八九是 PSNR 的計(jì)算口徑和網(wǎng)絡(luò)輸出的歸一化范圍不一致。網(wǎng)絡(luò)在 0-1 范圍訓(xùn)練和輸出驗(yàn)證時(shí)如果直接拿 0-1 范圍的 MSE 套用標(biāo)準(zhǔn) PSNR 公式 10log10(2552/MSE)就會(huì)多算一個(gè) 10log10(2552) ≈ 48 dB 的偏置結(jié)果自然低得離譜。另一個(gè)常見誘因是網(wǎng)絡(luò)輸出沒有做 clip個(gè)別像素超出 0-1 范圍導(dǎo)致 MSE 被極端值拉大。解決統(tǒng)一口徑。輸入輸出規(guī)范在 0-1 時(shí)用 PSNR 10*log10(1/MSE)如果要在 0-255 域顯示就先把輸出乘 255 再算。順手在 PSNR 之前對(duì)輸出做 np.clip保證像素不越界。5.2 驗(yàn)證時(shí)輸出灰蒙蒙BatchNorm 的模式切換現(xiàn)象訓(xùn)練集上 loss 和肉眼效果都正常一換到驗(yàn)證集或單張推理輸出圖像發(fā)灰、細(xì)節(jié)全無(wú)像蒙了一層霧。原因模型沒有切到 eval 模式。BatchNorm 層在訓(xùn)練時(shí)用當(dāng)前 batch 的均值和方差在 eval 模式才使用訓(xùn)練階段積累的運(yùn)行統(tǒng)計(jì)量。驗(yàn)證時(shí)如果 batch 只有 1~2 張圖BN 臨時(shí)算出來(lái)的統(tǒng)計(jì)量嚴(yán)重偏離訓(xùn)練分布中間特征被錯(cuò)誤縮放輸出直接崩掉。解決推理函數(shù)里第一行必須是 model.eval()再配合 torch.no_grad() 關(guān)閉梯度計(jì)算。這兩句不是優(yōu)化項(xiàng)是 BN 類去噪網(wǎng)絡(luò)的硬性要求。如果切了 eval 模式仍然不對(duì)回頭檢查訓(xùn)練時(shí)的 batch size 是不是太小小于 8導(dǎo)致 BN 的運(yùn)行統(tǒng)計(jì)量本身就沒學(xué)準(zhǔn)。5.3 數(shù)據(jù)泄漏讓驗(yàn)證 PSNR 虛高現(xiàn)象驗(yàn)證 PSNR 高出同配置論文一大截但點(diǎn)開去噪后的圖片細(xì)看視覺上并沒有那么好紋理細(xì)節(jié)明顯不夠。原因所有 patch 混在一起隨機(jī)切分訓(xùn)練/驗(yàn)證同一張干凈圖的不同 patch 同時(shí)出現(xiàn)在兩邊。網(wǎng)絡(luò)在訓(xùn)練時(shí)見過同一張圖的鄰近區(qū)域驗(yàn)證時(shí)相當(dāng)于是“開卷考試”PSNR 被抬高。解決按圖像文件劃分而不是按 patch 劃分。先分出驗(yàn)證圖列表再對(duì)驗(yàn)證圖單獨(dú)裁 patch確保驗(yàn)證圖從不出現(xiàn)在訓(xùn)練流程里。驗(yàn)證時(shí)固定隨機(jī)種子讓每次加噪可復(fù)現(xiàn)。5.4 彩色圖去噪后偏色現(xiàn)象灰度圖效果正常換成彩色圖后輸出出現(xiàn)彩色斑點(diǎn)、整體色調(diào)偏移去噪后顏色比帶噪原圖還怪。原因直接在 RGB 三通道上逐通道訓(xùn)練忽略了通道之間的顏色相關(guān)性。相機(jī)噪聲在 RGB 通道間并不獨(dú)立簡(jiǎn)單逐通道處理會(huì)破壞顏色關(guān)系另一個(gè)常見誘因是網(wǎng)絡(luò)輸出某個(gè)通道越界后沒有 clip導(dǎo)致色平衡被破壞。解決優(yōu)先轉(zhuǎn) YCbCr 只去噪 Y 通道這是最穩(wěn)的做法如果需要 RGB 三通道同時(shí)去噪輸入輸出統(tǒng)一歸一化到 0-1推理時(shí)用 clip 把三通道都限制在合法范圍內(nèi)。還可以在訓(xùn)練時(shí)對(duì)訓(xùn)練圖做顏色擾動(dòng)增強(qiáng)讓模型學(xué)到更魯棒的通道關(guān)系。5.5 真實(shí)噪聲場(chǎng)景效果崩盤高斯預(yù)訓(xùn)練模型的邊界現(xiàn)象在合成高斯噪聲上訓(xùn)練得好好的模型放到真實(shí)夜景照片上細(xì)節(jié)被磨掉的同時(shí)噪聲還剩一大半直觀效果甚至不如傳統(tǒng)塊匹配算法。原因真實(shí)相機(jī)噪聲不是加性高斯它包含傳感器暗電流、ISP 增益放大、去馬賽克帶來(lái)的空間相關(guān)噪聲而且強(qiáng)度通常與像素亮度有關(guān)。高斯噪聲假設(shè)在弱光強(qiáng)噪場(chǎng)景下嚴(yán)重失效。解決交付項(xiàng)目前先定邊界。用真實(shí)設(shè)備采集噪聲-干凈對(duì)或者做噪聲標(biāo)定用少量真實(shí)數(shù)據(jù)在預(yù)訓(xùn)練模型上微調(diào)訓(xùn)練階段把 σ 做成隨機(jī)變量讓模型覆蓋不同噪聲強(qiáng)度對(duì)弱光圖像可以先做亮度歸一化再進(jìn)網(wǎng)絡(luò)。合成高斯噪聲基線只適合算法驗(yàn)證和消融對(duì)比直接拿去做真實(shí)產(chǎn)品一定會(huì)翻車。6. 再往前一步盲噪聲模型、test-time augmentation 與邊緣保持驗(yàn)證固定 σ 訓(xùn)練的模型碰到未知強(qiáng)度的噪聲時(shí) PSNR 掉得很快因?yàn)榫W(wǎng)絡(luò)只在某個(gè)噪聲等級(jí)附近見過樣本。改進(jìn)做法是訓(xùn)練時(shí)每個(gè) batch 隨機(jī)抽取 σ比如每張 patch 的 σ 從 [5, 50] 均勻采樣再合成噪聲。網(wǎng)絡(luò)結(jié)構(gòu)不用改只把數(shù)據(jù)合成里的 σ 從常量變成隨機(jī)變量。這樣訓(xùn)練出的模型在推理時(shí)不需要預(yù)先估計(jì)噪聲強(qiáng)度一張網(wǎng)覆蓋常見強(qiáng)弱噪場(chǎng)景這也是盲去噪的基礎(chǔ)做法。推理階段有個(gè)幾乎零成本的增強(qiáng)技巧把輸入圖水平翻轉(zhuǎn)后再送入網(wǎng)絡(luò)得到去噪結(jié)果后翻轉(zhuǎn)回來(lái)與原圖的結(jié)果取平均。由于網(wǎng)絡(luò)對(duì)翻轉(zhuǎn)后的輸入會(huì)有略微不同的預(yù)測(cè)誤差兩者平均能抵消部分偏差PSNR 通常能提升 0.1~0.3 dB代價(jià)只是多做一次前向。對(duì)彩色圖可以先轉(zhuǎn) YCbCr只對(duì) Y 通道做這個(gè)操作CbCr 通道不參與計(jì)算量可控。評(píng)估去噪效果不要只看 PSNR。我習(xí)慣在驗(yàn)證集上同時(shí)輸出 SSIM并且對(duì)去噪結(jié)果跑一遍 Canny 邊緣檢測(cè)對(duì)比原圖的邊緣響應(yīng)如果邊緣變碎、斷裂說明網(wǎng)絡(luò)把結(jié)構(gòu)當(dāng)噪聲抹了再用局部方差圖檢查平坦區(qū)域是否被過度平滑。這些指標(biāo)比單一 PSNR 更能反映“能不能用”。我自己的教訓(xùn)是別一開始就追最新的網(wǎng)絡(luò)結(jié)構(gòu)先固定 σ25 把數(shù)據(jù)管線、訓(xùn)練循環(huán)、BN 切換這些基礎(chǔ)環(huán)節(jié)跑穩(wěn)再往上面加盲噪聲、TTA 和真實(shí)噪聲微調(diào)否則出了問題根本不知道是網(wǎng)絡(luò)的問題還是流程的問題。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取