
簡介DeepFillv2門控卷積自由形式圖像修復的PyTorch重新實現資源包主要面向計算機視覺研究者、深度學習開發者以及需要復現論文效果或進行圖像修復、風格遷移實驗的讀者。壓縮包內含91個文件以Python腳本、Web前端JS/CSS/HTML、YAML/JSON配置、Markdown/TXT說明文檔為主并帶有notebook示例、圖像素材等整體大小約3.42MB目錄結構便于檢索。目前已有460人學習下載。包內提供完整訓練與測試流程、模型及損失函數定義、CelebA/Places等數據集配置示例還包含Web演示前端與預訓練說明可幫助讀者快速搭建環境、開展訓練與推理并將門控卷積思路遷移到自己的項目或論文復現中顯著降低上手門檻。1. 自由形式圖像修復與門控卷積不只是修補矩形空洞常規圖像修復假設掩碼是規則的矩形區域而實際場景中的劃痕、遮擋物、文字覆蓋大多是任意形狀的自由形式掩碼。DeepFillv2論文提出用門控卷積替代普通卷積讓網絡在特征層面動態決定哪些像素參與修復解決了稀疏卷積和局部卷積的掩碼泄漏問題。本文按論文路線用PyTorch重新實現門控卷積、粗到細生成器和SN-PatchGAN判別器并給出可直接落地的訓練配置與掩碼生成代碼。適合想復現論文、把修復模型接到自有數據集的工程師也適合正在做圖像編輯預處理、需要自由形式區域去除能力的算法團隊。2. 門控卷積原理與DeepFillv2網絡結構拆解2.1 為什么局部卷積處理不了自由形式掩碼自由形式圖像修復的直接思路是用掩碼信息屏蔽無效像素。局部卷積是DeepFillv1的核心它對掩碼區域做歸一化并在每一層之后將掩碼二值化為0/1只有有效區域參與卷積。問題是掩碼一旦被卷積核涂抹二值化邊界會產生不自然的階梯效應而且更新規則是寫死的網絡無法針對不同語義內容調整對掩碼的信任程度。門控卷積的核心改動是用一個可學習的sigmoid門控替換固定掩碼更新規則。對于每個卷積層輸入特征經過兩個并行的卷積一個產生特征響應另一個產生門控系數最終輸出是兩者的逐通道元素級乘法。門控值在0到1之間連續分布網絡通過訓練自動學會將哪些區域視為有效、哪些區域作為邊界過渡不再依賴手工掩碼更新。從本質上看門控卷積在每一層引入了一個軟注意力機制普通卷積對所有像素一視同仁局部卷積只區分有效和無效的二值狀態而門控卷積能做到空間與通道維度上的自適應選擇。這一特性正好貼合自由形式掩碼的任意形狀。掩碼邊界附近的特征需要被半保留淺層修復結果中物體邊緣處的紋理連續性就是靠這種連續門控值維持的。論文中給出過一個直觀現象經過門控卷積后淺層門控值會在掩碼邊緣形成漸變過渡帶而深層門控值則與物體語義邊界高度相關——這說明網絡確實學會了按內容而非按掩碼來決策。2.2 DeepFillv2生成器兩階段級聯與門控卷積堆疊DeepFillv2的生成器沿用兩階段級聯結構。粗網絡接收被掩碼遮蔽的RGB圖像掩碼區域的像素填充為255輸入通道為3粗網絡輸出低層結構完整的粗略結果。細網絡的輸入是把掩碼后圖像、粗網絡輸出、原始掩碼按通道拼接形成通道數為7的張量經過另一組編碼器-解碼器輸出最終修復圖。兩階段共享同一種門控卷積基本單元但粗網絡只對整體結構負責細網絡補充高頻紋理細節。為什么必須分成兩個階段如果不分階段單一解碼器要從空洞里同時預測結構和紋理梯度信號在深層編碼器中容易被噪聲主導訓練很不穩定。級聯讓粗網絡在語義層先收斂細網絡再去學習紋理修復這也是論文能在256×256分辨率下穩定訓練的關鍵。細網絡接收的輸入通道數較多第一層門控卷積的參數量會明顯上升實現時需要注意顯存占用。2.3 判別器與訓練目標SN-PatchGAN配合WGAN-GP判別器使用譜歸一化的PatchGAN論文中稱為SN-PatchGAN。譜歸一化約束每層權重矩陣的最大奇異值讓判別器的Lipschitz常數可控配合WGAN-GP的梯度懲罰項可以不用BatchNorm也能穩定訓練。PatchGAN在輸出特征圖的每個位置上做真偽判別每個感受野是一個局部塊這讓判別器更關注紋理細節是否連貫而不是整圖是否協調。自由形式掩碼的面積和形狀是變化的全局判別很難對齊不同尺度的信息PatchGAN的局部判別方式明顯更適合該場景。提示復現時不要在生成器里加BatchNorm。門控卷積配合BatchNorm在小批量訓練時統計量漂移明顯實測用InstanceNorm或干脆不加歸一化更穩定。3. 用PyTorch重新實現門控卷積與DeepFillv2生成器3.1 最小可跑的GatedConv2d模塊門控卷積的PyTorch實現只需一個雙分支卷積加一次逐元素相乘。下面的代碼給出了不依賴任何第三方庫的最小模塊并支持通過use_sn開關控制是否啟用譜歸一化。import torch import torch.nn as nn import torch.nn.functional as F class GatedConv2d(nn.Module): def __init__(self, in_channels, out_channels, kernel_size, stride1, padding0, dilation1, use_snFalse): super().__init__() self.feature_conv nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding, dilation) self.gate_conv nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding, dilation) if use_sn: self.feature_conv nn.utils.spectral_norm(self.feature_conv) self.gate_conv nn.utils.spectral_norm(self.gate_conv) def forward(self, x): feature self.feature_conv(x) gate torch.sigmoid(self.gate_conv(x)) return feature * gate兩條卷積分支的輸入輸出通道數完全一致。feature分支不做激活gate分支過sigmoid將值壓縮到0到1之間二者相乘后結果的取值范圍完全由feature分支決定。注意不要把ReLU放在feature分支后面再乘門控這樣會讓負值特征在門控為1時也無法表達修復結果會偏灰、缺乏暗部層次。同一篇論文里的局部卷積實現要維護一個不斷更新的掩碼張量而門控卷積不需要這是兩者實現復雜度差異最大的地方。訓練時如果顯存緊張可以不先打開use_sn等判別器輸出出現了明顯的振蕩再補上。3.2 搭建編碼器-解碼器骨架與掩碼下采樣生成器每階段的核心是編碼器-解碼器。編碼器用步長為2的門控卷積做下采樣解碼器使用雙線性插值上采樣再接門控卷積。下采樣時掩碼也需要同步縮放否則編碼器深處分辨率縮小后掩碼和特征圖無法對齊。def downsample(self, x, mask, in_ch, out_ch): x self.gated_conv_down(x, in_ch, out_ch, kernel_size3, stride2, padding1) mask F.interpolate(mask, scale_factor0.5, modenearest) return x, mask def upsample(self, x, in_ch, out_ch): x F.interpolate(x, scale_factor2, modebilinear, align_cornersFalse) x self.gated_conv_up(x, in_ch, out_ch, kernel_size3, stride1, padding1) return x掩碼下采樣必須用nearest模式不能使用bilinear。因為掩碼只有0和1兩個值雙線性插值會產生0.5這樣的中間值門控卷積看到半透明的掩碼會去修復原本不需要修復的像素尤其在掩碼邊緣會產生一圈虛影。3.3 通道數配置與細階段輸入按論文慣例編碼器每下采樣一次通道數翻倍解碼器每上采樣一次通道數減半。一個適合256×256輸入的配置可以這樣設定階段輸入通道輸出通道分辨率變化模塊堆疊編碼器第1層3或732256→128GatedConv2d, stride2編碼器第2層3264128→64GatedConv2d, stride2編碼器第3層6412864→32GatedConv2d, stride2編碼器第4層12825632→16GatedConv2d, stride2解碼器第1層25612816→32Interpolate GatedConv2d解碼器第2層1286432→64Interpolate GatedConv2d解碼器第3層643264→128Interpolate GatedConv2d解碼器第4層323128→256Interpolate GatedConv2d粗網絡的輸入通道是3即掩碼填充后的原圖細網絡輸入通道是7拼接方式為torch.cat([masked_image, coarse_result, mask], dim1)。生成器的最后輸出建議接一個nn.Tanh激活將輸出限制到-1到1區間與輸入圖像的歸一化方式保持一致。4. 自由形式掩碼生成與訓練數據管道構建4.1 隨機繪制任意形狀掩碼滑光標與橢圓筆刷自由形式掩碼生成的核心是模擬用戶在涂抹、刮擦、物體移除時產生的任意形狀區域。最簡單且最接近論文做法的是滑光標方式隨機生成若干條折線段路徑沿路徑用大小可變的橢圓筆刷畫出掩碼區域。每個掩碼的張數、折線長度、筆刷半徑都從一定的范圍內隨機采樣這樣可以覆蓋從細長劃痕到大面積遮擋的各種形狀。import numpy as np from scipy.ndimage import rotate def random_brush_mask(height, width, max_vertex12, max_brush24): mask np.zeros((height, width), dtypenp.uint8) num_strokes np.random.randint(1, 4) for _ in range(num_strokes): num_vertex np.random.randint(4, max_vertex 1) start_x np.random.randint(0, width - 1) start_y np.random.randint(0, height - 1) for _ in range(num_vertex): angle np.random.uniform(0, 2 * np.pi) dist np.random.uniform(0, 0.3 * max(height, width)) end_x np.clip(start_x dist * np.cos(angle), 0, width - 1) end_y np.clip(start_y dist * np.sin(angle), 0, height - 1) brush_radius np.random.uniform(2, max_brush) draw_line(mask, (start_y, start_x), (int(end_y), int(end_x)), int(brush_radius)) start_x, start_y int(end_x), int(end_y) return mask def draw_line(mask, start, end, radius): y1, x1 start y2, x2 end dist max(abs(x2 - x1), abs(y2 - y1)) for i in range(dist 1): t i / max(dist, 1) x int(x1 t * (x2 - x1)) y int(y1 t * (y2 - y1)) cv2.circle(mask, (x, y), radius, 1, -1)掩碼面積比例需要嚴格控制。論文中訓練時隨機采樣10%到40%的掩碼面積比例這個比例既保證修復有難度又保證背景信息足夠支撐生成器做推理。面積比例過小會讓模型退化成幾乎不做任何修復也能通過判別器面積比例過大會讓生成器只能猜測顏色訓練不出紋理。4.2 圖像歸一化與掩碼注入方式圖像修復訓練不需要成對的ground truth之外的額外標注數據管道就是把原始圖像作為監督信號。輸入圖像在送入生成器之前歸一化到-1到1之間掩碼則保持0和1的整數值。被掩碼遮蔽的圖像構造方式直接決定模型看到的空缺狀態def apply_mask(image, mask): # image: [0, 1] float tensor, shape (C, H, W) # mask: 二值張量, shape (H, W), 1 表示需修復 masked image.clone() mask_bchw mask.unsqueeze(0).float() # (1, H, W) masked masked * (1 - mask_bchw) mask_bchw # 掩碼區域填充為1.0白色 return masked掩碼區域填充為白色只是論文中采用的其中一種注入方式。實踐中還可以填充為隨機噪聲、數據集平均像素值甚至像素打亂結果。填充顏色的選擇會輕微影響模型訓練初期的收斂速度但最終修復效果差別不大因為門控卷積會學會忽略掩碼區域的像素值重點提取掩碼外區域的特征。4.3 DataLoader吞吐優化與補丁采樣自由形式修復訓練在256×256分辨率下單卡跑批大小8通常只能勉強支撐完整生成器加判別器。我一般會在數據管道里先隨機裁剪512×512的大圖再縮放到256×256送入網絡這樣既增加了樣本多樣性又避免直接加載超大原圖浪費內存。對顯存仍不足的情況可以先將圖像降至128×128做粗網絡預熱訓練待損失穩定后再切換回256×256微調。DataLoader中的num_workers在圖像修復任務里的影響比較明顯推薦設置為CPU核心數的一半左右。掩碼生成運算量不小如果每次都在__getitem__里實時繪制會拖慢訓練吞吐常見的做法是預生成一批掩碼保存為npy格式訓練時按索引直接讀取減少CPU計算壓力。5. 損失函數、訓練超參設置與穩定性排查5.1 復合損失L1、感知損失與WGAN-GP的組合方式DeepFillv2訓練損失是生成器損失與判別器損失的加權組合。生成器部分包括L1像素損失、VGG感知損失和對抗損失。L1損失保證生成結果與真實圖像的逐像素距離最小感知損失約束特征空間上的語義一致性對抗損失在PatchGAN輸出的每個位置上做WGAN-GP形式的最小二乘或最小絕對值優化。l1_loss F.l1_loss(coarse_out, gt_patch) * 1.2 l1_fine F.l1_loss(fine_out, gt_patch) * 1.2 perceptual vgg_loss(fine_out, gt_patch) * 0.05 wgan_gp d_loss(fine_out, gt_patch, mask) * 1.0 g_loss l1_loss l1_fine perceptual wgan_gp權重設置中L1損失的權重最高約1.2感知損失權重在0.05左右即可這是作者開源配置的大致區間。感知損失權重過大容易讓修復區域紋理過于平滑權重過小則會在語義結構上出現斷裂。對抗損失權重設為1.0即可不需要額外的平衡系數。5.2 學習率與迭代策略生成器和判別器使用相同的學習率訓練Adam優化器的β1設為0.5、β2設為0.999是論文中的常見設置。生成器的學習率取0.0001判別器可以比生成器高一倍取0.0002兩者交替更新。學習率過高時門控分支的sigmoid輸出會迅速飽和到0或1導致門控失效學習率過低則掩碼邊界收斂極其緩慢往往需要數萬次迭代才能看到門控值產生實際變化。WGAN-GP的梯度懲罰系數lambda設為10。每訓練一個batch生成器之前先訓練3個batch的判別器這個比例能有效避免判別器被騙過。實際訓練中如果發現判別器損失降到0需要立即降低學習率并檢查譜歸一化是否在判別器每層都被啟用。5.3 訓練穩定性排查的三個常見現象門控卷積在訓練初期常見的一個問題是生成器輸出整體偏灰。原因通常是feature分支的初始權重讓卷積輸出集中在零附近門控值雖然接近0.5但乘積結果約等于原特征的一半。將feature分支的卷積權重按nn.init.kaiming_normal_初始化并把偏置置零可以在前幾千步內緩解。第二個問題是修復區域出現棋盤格偽影。這種偽影大多來自雙線性上采樣后的3×3卷積配合轉置卷積疊加。解決方案是將所有上采樣都改為雙線性插值加普通卷積不使用轉置卷積。第三個問題是掩碼邊界出現一條明顯的接縫線這通常是細網絡輸入拼接了掩碼后未經歸一化的掩碼值0/1與圖像特征量級差異過大造成的。在拼接前把掩碼減去0.5即可將差異縮小。提示訓練前先固定隨機種子做兩次相同配置的短訓練對比損失曲線是否一致。門控卷積的初始化對結果影響較大保證實驗可復現很重要。6. 模型評估、推理優化與代碼打包分發6.1 用PSNR、SSIM與FID評估自由形式修復質量自由形式修復的評估不能只用一個指標。PSNR反映逐像素誤差但自由形式掩碼的面積比例不同直接對比不同掩碼下的PSNR沒有參考意義。我一般做法是固定一組測試掩碼生成腳本保證所有對比模型使用完全相同的掩碼與輸入圖像這樣PSNR和SSIM才具有可比性。FID更關注生成分布與真實分布的差距對自由形式修復尤其關鍵建議在256×256分辨率下用3000張以上圖像計算樣本太少時FID方差很大。推理階段要注意的一個細節是掩碼在訓練時經過了下采樣與原始輸入對齊推理時也要對掩碼做同步的nearest縮放否則掩碼與圖像分辨率不一致會導致輸出出現偏移。輸入圖像在送入模型前要確認歸一化到-1到1掩碼區域的值必須和訓練時保持一致通常填充為1.0這樣模型才能正確做出缺失區域判斷。6.2 用torch.jit.script打包模型并與代碼一起分發模型訓練完成后常見的做法是導出為TorchScript格式方便在離線環境中直接加載。門控卷積模塊包含兩條獨立的卷積分支TorchScript可以正常trace但要注意sigmoid在trace時會被內聯導致腳本丟失部分調試信息。為了保留靈活性建議編寫一個forward函數明確寫出feature乘gate的操作并用torch.jit.script而非trace來導出。class InpaintModel(nn.Module): def forward(self, masked_img, mask): mask_scaled F.interpolate(mask, scale_factor0.5, modenearest) coarse self.coarse_net(masked_img, mask_scaled) fine_in torch.cat([masked_img, coarse, mask], dim1) return self.fine_net(fine_in, mask_scaled) scripted_model torch.jit.script(model) torch.jit.save(scripted_model, deepfillv2_gated.pt)代碼分發時常見的做法是將訓練腳本、掩碼生成器、模型權重和README打包成一個zip壓縮包。PyTorch權重文件本身較大建議在打包前清理臨時日志文件。如果你接收到的zip壓縮包在解壓時報出error read zip archive或提示文件損壞先去檢查壓縮包是否下載完整在命令行用unzip -t做完整性測試能快速確認是網絡傳輸問題還是文件本身就缺失了分卷壓縮的某個part。6.3 壓縮包驗證與依賴鎖定分發模型前用一條命令驗證整個zip內的文件依賴是否齊全unzip -t deepfillv2_reimplementation.zipunzip -t只校驗壓縮包內每個文件的CRC是否正確不會檢測Python import路徑。更穩妥的做法是解壓后在項目根目錄執行python -c from gated_conv import GatedConv2d; print(ok)做導入冒煙測試同時檢查requirements.txt中PyTorch版本是否與當前環境匹配。PyTorch 1.x與2.x的TorchScript兼容性存在差異如果模型在一個版本下script并且在另一個版本下加載可能會遇到無法加載的提示盡量保證訓練環境和推理環境使用同一個小版本。將代碼、權重、測試掩碼和復現說明打包成zip并不意味著分發工作結束在解壓后的全新Python環境中完整跑一遍推理腳本才能確認依賴沒有遺漏。本文還有配套的精品資源點擊獲取