用於影像降噪的卷積自編碼器 | 視覺進階

磐創AI發表於2019-12-20

這篇文章的目的是介紹關於利用自動編碼器實現影像降噪的內容。

神經網路世界中,對影像資料進行建模需要特殊的方法。其中最著名的是卷積神經網路(CNN或ConvNet)或稱為卷積自編碼器。並非所有的讀者都瞭解影像資料,那麼我先簡要介紹影像資料(如果你對這方面已經很清楚了,可以跳過)。然後,我會介紹標準神經網路。這個標準神經網路用於影像資料,比較簡單。這解釋了處理影像資料時為什麼首選的是卷積自編碼器。最重要的是,我將演示卷積自編碼器如何減少影像噪聲。這篇文章將用上Keras模組和MNIST資料。Keras用Python編寫,並且能夠在TensorFlow上執行,是高階的神經網路API。

瞭解影像資料

如圖(A)所示,影像由“畫素”組成。在黑白影像中,每個畫素由0到255之間的數字表示。如今大多數影像使用24位彩色或更高的顏色。一幅RGB彩色影像表示一個畫素的顏色由紅色、綠色和藍色組成,這三種顏色各自的畫素值從0到255。RGB色彩生成器(如下所示)表明,RGB色彩系統利用紅綠藍,組合成各種顏色。因此,一個畫素由含三個值的RGB(102、255、102)構成,其色號為#66ff66。

用於影像降噪的卷積自編碼器 | 視覺進階圖 (A)

寬800畫素,高600畫素的影像具有800 x 600 = 480,000畫素,即0.48兆畫素(“兆畫素”等於100萬畫素)。解析度為1024×768的影像是一個由1,024列和768行構成的網格,共有1,024×768 = 0.78兆畫素。

MNIST

MNIST資料庫是一個大型的手寫數字資料庫,通常用於訓練各種影像處理系統。Keras的訓練資料集具備60,000條記錄,而測試資料集則包含了10,000條記錄。每條記錄共有28 x 28個畫素。

from keras.layers import Input, Dense
from keras.models import Model
from keras.datasets import mnist
import numpy as np
(x_train, _), (x_test, _) = mnist.load_data()

它們看起來怎麼樣?我們用繪相簿及其影像功能imshow()展示前十條記錄。

import matplotlib.pyplot as plt

n = 10 # 顯示的記錄數
plt.figure(figsize=(20, 4))
for i in range(n):
 # 顯示原始圖片
 ax = plt.subplot(2, n, i + 1)
 plt.imshow(x_test[i].reshape(28, 28))
 plt.gray()
 ax.get_xaxis().set_visible(False)
 ax.get_yaxis().set_visible(False)

plt.show()

用於影像降噪的卷積自編碼器 | 視覺進階

影像資料的堆疊,用於訓練

如果要讓神經網路框架適用於模型訓練,我們可以在一列中堆疊所有28 x 28 = 784個值。第一條記錄的堆疊列如下所示(使用x_train[1].reshape(1,784)):

用於影像降噪的卷積自編碼器 | 視覺進階

然後,我們可以使用標準的神經網路訓練模型,如圖(B)所示。數值為784的每個值都是輸入層中的一個節點。且慢!堆疊資料會丟失很多資訊嗎?答案是肯定的。影像中的空間關係被忽略了。這使得大量的資訊丟失。那麼,我們接著看卷積自編碼器如何保留空間資訊。

用於影像降噪的卷積自編碼器 | 視覺進階圖(B)

為什麼影像資料首選卷積自編碼器?

可以看到,資料切片和資料堆疊會導致資訊大量丟失。卷積自編碼器放棄堆疊資料,使影像資料輸入時保持其空間資訊不變,並在卷積層中以溫和的方式提取資訊。圖(D)演示了將平面2D影像先提取到一個厚的正方體(Conv1),再提取到一個長方體(Conv2)和另一個長度更長的長方體(Conv3)。此過程旨在保留資料中的空間關係。這是自動編碼器的編碼過程。中間部分是一個完全連線的自動編碼器,其隱藏層僅由10個神經元組成。然後就是解碼過程。三個立方體將會展平,最後變成2D平面影像。圖(D)的編碼器和解碼器是對稱的。實際上,編碼器和解碼器不要求對稱。

用於影像降噪的卷積自編碼器 | 視覺進階圖(D)

卷積自編碼器如何工作?

上面的資料析取似乎很神奇。資料析取究竟是如何進行的?這包括以下三層:卷積層,線性整流層和池化層。

用於影像降噪的卷積自編碼器 | 視覺進階圖 (E): 特徵圖

1.    卷積層

卷積步驟會生成很多小塊,稱為特徵圖或特徵,如圖(E)的綠色、紅色或深藍色的正方形。這些正方形保留了輸入影像中畫素之間的關係。如圖(F)所示,每個特徵掃描原始影像。這一產生分值的過程稱為卷積。

用於影像降噪的卷積自編碼器 | 視覺進階圖 (F): 過濾過程

掃描完原始影像後,每個特徵都會生成高分值和低分值的濾波影像,如圖(G)所示。如果匹配完美,那塊正方形的得分就高。如果匹配度低或不匹配,則得分低或為零。例如,原始影像有四個區域與紅色方塊完全匹配,那麼這四個區域的得分都很高。

用於影像降噪的卷積自編碼器 | 視覺進階

圖 (G)

過濾器越多,模型可以提取的特徵就越多。但是,特徵越多,訓練時間也就越長。因此,最好還是選擇最少的過濾器提取特徵。

1.1填充

特徵如何確定匹配項?一種引數是填充,有兩種選擇:(i)用零填充原始影像以符合該特徵,或(ii)刪除原始影像中不符的部分並保留有效部分。

1.2步長

卷積層的另一個引數:步長。步長是輸入矩陣上移動的畫素個數。當步長為1時,過濾器一次移動1個畫素。在Keras程式碼中,我們將其視為引數

2.線性整流步驟

線性整流單位(ReLU)的步驟與典型的神經網路相同。它將所有的負值校正為零,確保數學運算正確。

3.最大池化

池化會縮小影像尺寸。在圖(H)中,一個2 x 2的視窗(稱為池的大小)掃描每個濾波影像,並將該2 x 2視窗的最大值劃分給新影像中大小為1 x 1的正方形。如圖(H)所示,第一個2 x 2視窗的最大值分數高(用紅色表示),因此高分劃分給1 x 1正方形。

用於影像降噪的卷積自編碼器 | 視覺進階圖 (H): 最大池化

除了採用最大值之外,其他不常用的池化方法還包括“平均池化”(取平均值)或“總和池化”(總和)。

用於影像降噪的卷積自編碼器 | 視覺進階圖 (J)

池化後,會生成新的更小的濾波影像。現在我們拆分這個濾波影像,然後堆疊為一列,如圖(J)所示。

Keras模型

以上三層是卷積神經網路的構建塊。Keras具有以下兩個功能:

•    Conv2D(filters, kernel_size, activation = 'reLu', strides=1):核尺寸(kernel_size)是2D卷積視窗的高度和寬度。圖(E)使用的是2×2正方形,所以例子中核尺寸將為(2,2)。步長是輸入矩陣上移動的畫素個數。我們一次將濾鏡移動了1個畫素,所以步長為1。

•    MaxPooling2D(pool_size=(2,2)):在圖(H)中,我們使用2×2視窗作為池的大小。因此,我們將在以下程式碼中使用(2,2)。

你可以在卷積自編碼器中構建許多卷積層。在圖(E)中,在編碼部分有三層,分別標記為Conv1,Conv2和Conv3。因此,我們要進行相應的構建。

•    下面的程式碼input_img = Input(shape=(28,28,1)表明輸入的2D影像為28 x 28。

•    然後,它構建了Conv1,Conv2和Conv3。

•    請注意,Conv1在Conv2內部,而Conv2在Conv3內部。

•    要是過濾器無法適應輸入影像,填充將指定下一步該做什麼。padding='valid'表示過濾器不符合,影像的一部分將被丟棄;padding='same'用零填充圖片以適應圖片。

from keras.layers import Input, Dense, Conv2D, MaxPooling2D, UpSampling2D
from keras.models import Model

# 編碼過程
input_img = Input(shape=(28, 28, 1)) 

############
# 編碼 #
############

# Conv1 #
x = Conv2D(filters = 16, kernel_size = (3, 3), activation='relu', padding='same')(input_img)
x = MaxPooling2D(pool_size = (2, 2), padding='same')(x)

# Conv2 #
x = Conv2D(filters = 8, kernel_size = (3, 3), activation='relu', padding='same')(x)
x = MaxPooling2D(pool_size = (2, 2), padding='same')(x) 

# Conv 3 #
x = Conv2D(filters = 8, (3, 3), activation='relu', padding='same')(x)
encoded = MaxPooling2D(pool_size = (2, 2), padding='same')(x)

# 注意:
# padding 是一個引數,值'valid' or 'same'. 
# "valid" 意味不需要填充 
# "same" 填充輸入,使輸出具有與原始輸入相同的長度。 

然後,解碼過程繼續。因此,下面解碼部分已全部完成編碼和解碼過程。

############
# 解碼 #
############

# DeConv1
x = Conv2D(8, (3, 3), activation='relu', padding='same')(encoded)
x = UpSampling2D((2, 2))(x)

# DeConv2
x = Conv2D(8, (3, 3), activation='relu', padding='same')(x)
x = UpSampling2D((2, 2))(x)

# Deconv3
x = Conv2D(16, (3, 3), activation='relu')(x)
x = UpSampling2D((2, 2))(x)
decoded = Conv2D(1, (3, 3), activation='sigmoid', padding='same')(x)

該Keras API需要模型和優化方法的宣告:

•  Model (inputs= input_img,outputs= decoded):

在解碼給定輸入資料input_img的情況下,模型包括計算輸出所需的所有層。compile(optimizer='adadelta',loss='binary_crossentropy'):優化程式會像漸變梯度一樣執行優化操作。最常見的是隨機梯度下降(SGD),自適應梯度(Adagrad)和Adadelta(Adadelta是Adagrad的擴充套件)。有關詳細資訊,請參見Keras優化器文件。損失函式可以查詢Keras損失文件。

# 宣告模型
autoencoder = Model(input_img, decoded)
autoencoder.compile(optimizer='adadelta', loss='binary_crossentropy')

下面,我使用x_train作為輸入和輸出來訓練模型。batch_size是樣本量和epochs是迭代的次數。我指定shuffle=True打亂訓練資料。

# 訓練模型
autoencoder.fit(x_train, x_train,
 epochs=100,
 batch_size=128,
 shuffle=True,
 validation_data=(x_test, x_test)
 )

我們可以列印出前十張原始影像和相同十張影像的預測。

decoded_imgs = autoencoder.predict(x_test)

n = 10

plt.figure(figsize=(20, 4))
for i in range(n):
 # 顯示原始影像
 ax = plt.subplot(2, n, i + 1)
 plt.imshow(x_test[i].reshape(28, 28))
 plt.gray()
 ax.get_xaxis().set_visible(False)
 ax.get_yaxis().set_visible(False)

 # 顯示重構後的影像
 ax = plt.subplot(2, n, i+1+n)
 plt.imshow(decoded_imgs[i].reshape(28, 28))
 plt.gray()
 ax.get_xaxis().set_visible(False)
 ax.get_yaxis().set_visible(False)
plt.show()

用於影像降噪的卷積自編碼器 | 視覺進階

如何構建影像降噪卷積自編碼器?

影像降噪的想法是訓練一個模型,輸入噪聲資料,並輸出它們各自清晰的資料。這是與上述模型的唯一區別。首先讓我們向資料新增噪音

noise_factor = 0.4
x_train_noisy = x_train + noise_factor * np.random.normal(loc=0.0, scale=1.0, size=x_train.shape) 
x_test_noisy = x_test + noise_factor * np.random.normal(loc=0.0, scale=1.0, size=x_test.shape) 

x_train_noisy = np.clip(x_train_noisy, 0., 1.)
x_test_noisy = np.clip(x_test_noisy, 0., 1.)

前十張噪聲影像如下所示:

n = 10
plt.figure(figsize=(20, 2))
for i in range(n):
 ax = plt.subplot(1, n, i+1)
 plt.imshow(x_test_noisy[i].reshape(28, 28))
 plt.gray()
 ax.get_xaxis().set_visible(False)
 ax.get_yaxis().set_visible(False)
plt.show()

用於影像降噪的卷積自編碼器 | 視覺進階

然後,我們訓練模型時將輸入噪聲資料,輸出乾淨的資料。

autoencoder.fit(x_train_noisy, x_train,
 epochs=100,
 batch_size=128,
 shuffle=True,
 validation_data=(x_test_noisy, x_test)
 )

最後,我們列印出前十個噪點影像以及相應的降噪影像。

decoded_imgs = autoencoder.predict(x_test)

n = 10

plt.figure(figsize=(20, 4))
for i in range(n):
 # 顯示原始影像
 ax = plt.subplot(2, n, i + 1)
 plt.imshow(x_test_noisy[i].reshape(28, 28))
 plt.gray()
 ax.get_xaxis().set_visible(False)
 ax.get_yaxis().set_visible(False)

 # 顯示重構後的影像
 ax = plt.subplot(2, n, i+1+n)
 plt.imshow(decoded_imgs[i].reshape(28, 28))
 plt.gray()
 ax.get_xaxis().set_visible(False)
 ax.get_yaxis().set_visible(False)
plt.show()

用於影像降噪的卷積自編碼器 | 視覺進階

是否可以使用任何經過訓練的CNN程式碼嗎?

可以的。如果你有興趣學習程式碼,Keras提供了幾個經過預訓練的CNN,包括Xception,VGG16,VGG19,ResNet50,InceptionV3,InceptionResNetV2,MobileNet,DenseNet,NASNet和MobileNetV2。值得一提的是,你可以出於研究目的付錢或下載此大型影像資料庫ImageNet。

相關文章