1. 程式人生 > >Blind Super-Resolution Kernel Estimation using an Internal-GAN 論文解讀

Blind Super-Resolution Kernel Estimation using an Internal-GAN 論文解讀

# 背景與思路來源 ## 目前 SR 模型中合成 LR 使用的模糊核問題 目前大多數 SR 的 model 都是用的合成下采樣圖片來進行訓練的,而這些合成的圖片常常使用的是 MATLAB 裡面的 imresize 函式來進行實現的,這樣的做法也就是會使得 SR-kernel 是固定和理想。當然還有很多是用各向同性或者各向異性的高斯核作為模糊核通過下式來得到 LR 影象: $$ I_{LR} = (I_{HR} * k_s) \downarrow_s $$ 不過用這些模糊核合成的圖片與真實場景圖片都不太符,因此出現往往資料集跑得不錯的模型,拿真實場景的圖片來測就表現不佳。在現實場景中,即便是同一個 sensor,也會因為手持時細微的相機移動或者 sensor 的光學特性而導致產生不同的 LR。 ## 思路來源 文章使用了自然場景圖片的重要性質:單張圖片中的跨尺度小圖片塊的重現 (recurrence) 性質。基本可以理解為:裁剪出一張圖片的圖片塊 (patch) 和其下采樣後圖片的圖片塊 (patch),兩張 patch 的大小可能不一樣(可能分別是 5\*5 和 7\*7),但這兩張 patch 在畫素的分佈上大體是一致的。Michaeli & Irani 使用這個性質來估計 SR-Kernel: > the correct SR-kernel is also the downscaling kernel which maximizes the similarity of patches across scales of the LR image. 也就是圖片的 downscaling SR-kernel 是使得 LR 圖片中的跨尺度 patch (跨尺度理解為同一張圖片的不同解析度,patch 理解為圖片的子圖)相似性最大的下采樣核。因此只要文章通過 LR 影象找到使得兩跨尺度子圖相似性最大的 kernel,那該 kernel 就十分接近真實核。 # KernelGAN
文章提出了 KernelGAN 使用無監督學習來估計 LR 中的 downscaling SR-kernel。其具體做法通過生成器生成出低解析度影象的 s 倍下采樣圖(s 一般為2),然後裁取一塊 patch 作為 Fake 影象,裁取生成器輸入的一塊 patch 作為 Real 影象,並一起送入判別器中進行判斷真假。也就是說生成器可以看作生成 LR 的 downscaling 影象的模組,而判別器是用來判別 LR 的 patch 和 LR 下采樣影象的 patch 孰真孰假。當判別器難以判別的時候,此時生成器的權重就接近所求的模糊核了。 ## Discriminator
論文提出的判別器全部使用了卷積層,並且卷積層沒有使用 stride,除了第一層使用了 7*7 的卷積核,其餘層均使用了 1\*1 的卷積核,也就是在最後生成的熱圖 (heat-map) 中的一個值是對輸入一個大小為 7\*7 的 patch 真假的判斷,map 裡面的值是 0 到 1 之間(包括端點)的數。輸入的 label 取決於輸入的真假,如果是真的,label 便全為 1,反之為 0。 ## Generator
生成器可以理解為一個對輸入圖片使用了 the downscaling SR-Kernel 進行了下采樣的模組。文章認為 downscaling 是由卷積和下采樣組成,而這本身是一種線性的變換。文中於是使用的生成器不包含任何非線性的啟用函式。
理論上,使用單層卷積層應該可以涵蓋所有可能 downscaling 方法的情況,但實驗中發現並不能收斂到一個正確的解(如上圖)。針對這種情況,文章進行以下推測: * 單層的生成器對於正確的解確實可以有一組引數(權重)與其對應(這樣的權重就是真值 kernel)。這意味著在優化面上,只有一個點可以是對於當前情況的全域性最小值。 * 一般當 loss 函式是凸的時候,得到該點是比較容易的。但是由於非線性網路的判別器是極度非凸的,因此從隨機初始狀態出發基於梯度下降的方法來取得全域性最優的概率幾乎可以忽略不計。 非線性的生成器也是不合適的。生成器沒有線性的顯式約束,可能會生成一些不想要的結果。這種結果往往生成任何圖片都會包含一些跟 downscaling 無關的 patch。 文章於是使用 deep linear networks,並且沒有任何的非線性啟用函式。雖然說從表達能力來說,深度線性網路和單層線性網路無差。但是在優化過程種,它有多個不同的面,可以使得有無限多個等價的全域性最優點,這對於訓練更加容易,更快。具體結構如最上面的圖,其作用相當於用一個大小為 13\*13 的模糊核對 LR 進行 blur 並進行 2 倍下采樣。為了提供比較合理的初始點,生成器的輸出被與輸入經過一種理想的 downscaling 後的圖片進行 constrain。一旦生成可以接受的輸出後,便不再使用 constraint。 ## 關於 kernel 的提取 顯然單張 LR 圖片的 kernel 可以由生成器的權重得到,文章針對為什麼要顯式地從 生成器中提取 Kernel 進行了說明: ♠ 我們最終目的是為得到 kernel,而不是得到 downscaling 的網路,並且由生成器提取的 SR-kernel k 是一組小的陣列以至於可以應用於 SR 演算法之中; ♣ 顯式的提取 kernel 以便於對其進行顯式的加入有物理意義的先驗操作。 關於第二點文章在 loss 中使用正則項來使提取出的 kernel 滿足一些限制,以達到減少一些解看起來正常,實質不行的 kernel: ♠ kernel 的總和需要為 1; ♣ kernel 的幾何中心必須在正中央位置。 以上兩項是為了保證 kernel 不會 shift 影象(應該是不會讓影象幾何變形) ♥ kernel 需要有稀疏性,以至於不會 oversmooth; ♦ kernel 希望越接近邊界越接近零,不希望非零值靠近邊界。 最後,希望提取是可微的,這樣 loss 中的正則化項(因為正則化項是由提取的 kernel 計算的)才能進行反向傳播(文中對 G 中所有的 filters 使用 -1 的 stride 進行卷積)。整個網路和正則項的定義如下: $$ \begin{aligned} \mathcal{L} &= \underset{G}{\arg \min}\underset{D}{\max}\{\mathbb{E}_{x\sim patches(I_{lr})} [\lvert D(x)-1 \rvert + \lvert D(G(x)) \rvert] + \mathcal{R}\} \\ \mathcal{R} &= \alpha \mathcal{L}_{sum\_to\_1} + \beta \mathcal{L}_{boundaries} +\gamma \mathcal{L}_{sparse} +\delta \mathcal{L}_{center} \\ where\Rightarrow \mathcal{L}_{sum\_to\_1} &= \left\lvert 1-\sum_{i,j} k_{i,j} \right\rvert \\ \mathcal{L}_{boundaries} &= \sum_{i,j} \lvert k_{i,j} \cdot m_{i,j} \rvert \\ \mathcal{L}_{sparse} &= \sum_{i,j} \lvert k_{i,j} \rvert^{1/2} \\ \mathcal{L}_{center} &= \left\lVert (x_0,y_0) - \frac{\sum_{i,j} k_{i,j} \cdot (i,j)}{\sum_{i,j} k_{i,j}}\right\rVert_{2} \end{aligned} $$ SR-kernel 除了和圖片有關,還與下采樣的倍數 s 有關。但是對於 SR-kernel 來說不同 scales 之間是有關聯的,文章訓練的是 scale 為 2 的 SR-kernel,但是可以由此[推匯出 scale 為 4 的kernel](http://www.wisdom.weizmann.ac.il/~vision/kernelgan/resources/k_4_proof.pdf)。這使得我們可以由一種 scale 的 kernel 生成多種 scale 的 kernel,並且當需要大 scale (如4)的 kernel 不需要非得去製作 scale 為 4 的 LR 來訓練得到相應的 kernel(即便得到了 scale 為 4 的 LR,也會因為其只有 HR 的十六分之一導致包含資訊太小而難以訓練。 ### 從 G 中提取 kernel 的具體方法 ```python delta = torch.Tensor([1.]).unsqueeze(0).unsqueeze(-1).unsqueeze(-1).cuda() for ind, w in enumerate(self.G.parameters()): curr_k = F.conv2d(delta, w, padding=self.conf.G_kernel_size - 1) if ind == 0 else F.conv2d(curr_k, w) self.curr_k = curr_k.squeeze().flip([0, 1]) ``` 文章使用狄拉克函式作為初始輸入,使用 G 的每一層卷積核作為提取 kernel 的卷積層的卷積核,得到的輸出作為下一層提取 kernel 的卷積層的輸入。由於初始值為 1 最後的結果與一張圖片所卷積的效果等同於使用 G 中所有卷積層與圖片卷積的效果。這便是提取到的最初始的 kernel。 [程式碼位置](https://github.com/sefibk/Ker