觀看前需要先大概了解卷積運算,和tensorflow2基本操作,本文主要詳解代碼實戰,原理僅簡單介紹
原理簡介:
模型結構為U-net,主要通過一系列卷積和反卷積,最終將原影像大小翻倍(像素翻倍),實作清晰化,通過跨層concat(拼接),可以使模型同時具有提取抽象特征以及提取細節的能力,(注意concat是某個維度上的拼接,而不是直接相加),模型的輸入是一張圖片,輸出的圖片長度寬度均為原圖的兩倍,可以達到比原圖更清晰的效果,U-net結構圖如下,轉自知乎,

代碼詳解
資料集圖片檔案處理
資料集可以是自己在一個檔案夾里放一堆圖片,圖片可以是風景,可以是漫畫,可以是其他,盡量是同一種型別的圖片(不要將現實照片與漫畫放一起),這樣有針對性的訓練效果會比較好,
import os
#引數
path1 = 'A' #存放圖片的檔案夾,需要根據自己的檔案夾路徑改
path2 = 'B' #如果只有一個檔案夾,可以把這個注釋掉
#獲取所有圖片的路徑串列
files_list1 = [os.path.join(path1, name) for name in os.listdir(path1)]
files_list2 = [os.path.join(path2, name) for name in os.listdir(path2)]
files_list = files_list1 + files_list2 #生成所有圖片的路徑串列
#洗掉不符合尺寸的圖片
kk = 0
im_size = 512
for b in files_list:
img = tf.io.read_file(b)
test_pic = tf.io.decode_image(img, channels=3) #解碼圖片
if test_pic.shape[0] <im_size or test_pic.shape[1]<im_size:
os.remove(b)
kk += 1
print('被洗掉的圖片有%d個'%kk)
os.listdir將列出當前路徑的所有檔案名,后用os.path.join把檔案夾路徑和檔案名拼接,最后添加到一個串列里,串列包含了一個檔案夾內的所有檔案路徑,
由于圖片有RGB三個通道,所以需要用tf.io.decode_image(img, channels=3)解碼圖片,解碼后的形狀為(高度,寬度,3),用if來判斷高度或寬度過小的圖片,這些圖片不利于訓練,將被洗掉
以上操作將洗掉檔案夾中圖片長度或者寬度小于512的圖片,這是為了在接下來的訓練中,能夠對資料集進行統一尺寸的裁剪,512這個數值并不是固定的,可以改成256,128之類的數值,這取決與你訓練時想要用的圖片大小,(由于會洗掉圖片,可以在運行前先備份一下圖片檔案夾,以免洗掉重要圖片)
處理完之后,可以進行下一步
資料預處理
匯入庫:
import tensorflow as tf
from tensorflow import keras
import os
資料預處理方法
#引數
train_input_size = 256 #訓練時的輸入圖片大小,可以調整
train_target_size = train_input_size*2 #訓練的目標圖片大小,最大值是上文判斷的大小
jpge_quality_min = 50 #模糊最小值(取值0-1,越大圖片越清晰)
jpge_quality_max = 75 #模糊最大值
noise_stddev=0.015 #噪聲標準差
@tf.function
def trans_func(files):
img_bin = tf.io.read_file(files)
#呼叫tf.cond判斷圖片是否為jepg(jpg),如果是則用jpeg解碼方式,如果不是則用png解碼方式
img_decode = tf.cond(
tf.image.is_jpeg(img_bin),
lambda: tf.image.decode_jpeg(img_bin, channels=3),
lambda: tf.image.decode_png(img_bin, channels=3)
)
target_img = tf.image.convert_image_dtype(img_decode, tf.float32) # 轉為tf.float32
target_img = tf.image.random_crop(target_img, [train_target_size, train_target_size, 3]) #隨機裁剪
target_img = tf.image.random_flip_left_right(target_img) # 隨機左右翻轉
target_img = tf.image.random_contrast(target_img, 0.25, 0.75) #隨機調整影像對比度
target_img = tf.image.random_brightness(target_img, 0.2) #隨機調整影像亮度
train_img = tf.image.resize(target_img, [train_input_size, train_input_size],
method=train_resize_mhd) # 縮放到固定大小,這個大小是輸出的一半
train_img = tf.image.random_jpeg_quality(train_img, min_jpeg_quality=jpge_quality_min,
max_jpeg_quality=jpge_quality_max) #模糊處理
noise = tf.random.normal(shape=[train_input_size, train_input_size, 3], stddev=noise_stddev)
train_img = train_img + noise #添加噪聲
return [train_img, target_img]
tf.cond()是先看第一個引數bool值,如果為True,則呼叫jepg(jpg)解碼方法,如果為False則用png解碼方式,
用tf.image.convert_image_dtype()而不是tf.cast(),雖然兩者對張量操作可以達到相同的效果,但是后者在之后轉unit8型別資料會出問題,導致圖片編碼例外,所以只能用前者,
這個方法,首先對圖片進行了解碼,并且轉為tf.float32便于模型訓練,并且進行了隨機翻轉和微調,增加了資料的多樣性,
因為輸出值target_image形狀大小(高度,寬度)是輸入值train_img的兩倍,之后將圖片resize成原來的一半大小,作為模型訓練的輸入值,為了讓模型有到更強的效果,所以針對輸入值train_img,要添加模糊化(random_jpeg_quality)以及噪聲(noise),增加訓練難度,
最后把[train_img, target_img]回傳
tf資料集生成
path1 = 'A' #第一個存放圖片的檔案夾
path2 = 'B' #第二個存放圖片的檔案夾,如果只有一個圖片檔案夾可以把這個刪掉
#獲取所有圖片路徑串列
files_list1 = [os.path.join(path1, name) for name in os.listdir(path1)]
files_list2 = [os.path.join(path2, name) for name in os.listdir(path2)]
files_list = files_list1 + files_list2 #圖片路徑串列拼接
print('圖片總數量:',len(files_list))
#生成tf的Dataset
ds = tf.data.Dataset.from_tensor_slices(files_list)
ds = ds.shuffle(10000) #隨機打亂
ds = ds.map(trans_func, num_parallel_calls=-1, deterministic=False)
#呼叫上面的預處理函式
ds = ds.batch(8) #設定batch大小
生成dataset,之后就可以構建模型,開始訓練了
U-net模型構建
class Repairer(keras.Model):
def __init__(self):
super(Repairer, self).__init__()
self.level_1_conv1 = keras.layers.Conv2D(64, 3, 1, 'same', activation='elu')
self.level_1_conv2 = keras.layers.Conv2D(64, 3, 1, 'same', activation='elu')
self.level_1_pool = keras.layers.MaxPooling2D()
self.level_2_conv1 = keras.layers.Conv2D(128, 3, 1, 'same', activation='elu')
self.level_2_conv2 = keras.layers.Conv2D(128, 3, 1, 'same', activation='elu')
self.level_2_pool = keras.layers.MaxPooling2D()
self.level_3_conv1 = keras.layers.Conv2D(256, 3, 1, 'same', activation='elu')
self.level_3_conv2 = keras.layers.Conv2D(256, 3, 1, 'same', activation='elu')
self.level_3_pool = keras.layers.MaxPooling2D()
self.level_4_conv1 = keras.layers.Conv2D(512, 3, 1, 'same', activation='elu')
self.level_4_conv2 = keras.layers.Conv2D(512, 3, 1, 'same', activation='elu')
self.level_4_pool = keras.layers.MaxPooling2D()
self.level_3_conv1t = keras.layers.Conv2DTranspose(256, 3, 2, 'same', activation='elu')
self.level_3_concat = keras.layers.Concatenate()
self.level_3_conv2t = keras.layers.Conv2DTranspose(256, 3, 1, 'same', activation='elu')
self.level_2_conv1t = keras.layers.Conv2DTranspose(128, 3, 2, 'same', activation='elu')
self.level_2_concat = keras.layers.Concatenate()
self.level_2_conv2t = keras.layers.Conv2DTranspose(128, 3, 1, 'same', activation='elu')
self.level_1_conv1t = keras.layers.Conv2DTranspose(64, 3, 2, 'same', activation='elu')
self.level_1_concat = keras.layers.Concatenate()
self.level_1_conv2t = keras.layers.Conv2DTranspose(64, 3, 1, 'same', activation='elu')
self.level_0_conv1t = keras.layers.Conv2DTranspose(64, 3, 2, 'same', activation='elu')
self.level_0_concat = keras.layers.Concatenate()
self.level_0_conv2t = keras.layers.Conv2DTranspose(64, 3, 2, 'same', activation='elu')
self.level_0_conv3t = keras.layers.Conv2D(64, 3, 1, 'same', activation='elu')
self.level_0_conv4t = keras.layers.Conv2D(64, 3, 1, 'same', activation='linear')
self.model_output = keras.layers.Conv2D(3, 3, 1, 'same', activation='elu')
def call(self,image_tffloat32):
level_1_conv1 = self.level_1_conv1(image_tffloat32)
level_1_conv2 = self.level_1_conv2(level_1_conv1)
level_1_pool = self.level_1_pool(level_1_conv2)
level_2_conv1 = self.level_2_conv1(level_1_pool)
level_2_conv2 = self.level_2_conv2(level_2_conv1)
level_2_pool = self.level_2_pool(level_2_conv2)
level_3_conv1 = self.level_3_conv1(level_2_pool)
level_3_conv2 = self.level_3_conv2(level_3_conv1)
level_3_pool = self.level_3_pool(level_3_conv2)
level_4_conv1 = self.level_4_conv1(level_3_pool)
level_4_conv2 = self.level_4_conv2(level_4_conv1)
level_4_pool = self.level_4_pool(level_4_conv2)
level_3_conv1t = self.level_3_conv1t(level_4_pool)
level_3_concat = self.level_3_concat([level_3_conv1t, level_4_conv2])
level_3_conv2t = self.level_3_conv2t(level_3_concat)
level_2_conv1t = self.level_2_conv1t(level_3_conv2t)
level_2_concat = self.level_2_concat([level_2_conv1t, level_3_conv2])
level_2_conv2t = self.level_2_conv2t(level_2_concat)
level_1_conv1t = self.level_1_conv1t(level_2_conv2t)
level_1_concat = self.level_1_concat([level_1_conv1t, level_2_conv1])
level_1_conv2t = self.level_1_conv2t(level_1_concat)
level_0_conv1t = self.level_0_conv1t(level_1_conv2t)
level_0_concat = self.level_0_concat([level_0_conv1t, level_1_conv1])
level_0_conv2t = self.level_0_conv2t(level_0_concat)
level_0_conv3t = self.level_0_conv3t(level_0_conv2t)
level_0_conv4t = self.level_0_conv4t(level_0_conv3t)
model_output = self.model_output(level_0_conv4t)
return model_output
我在這里全用了elu激活,也可以修改成別的,模型輸入形狀是[b,256,256,3],輸出是[b,512,512,3],可以將圖片高度與寬度翻倍,像素增加,b是batch大小,256是我的模型輸入圖高度寬度,512是模型輸出圖高度寬度,這個可以根據自己資料處理時的裁剪的大小來修改,(注意:由于模型對與形狀的限制,高與寬必須是16的倍數)
開始訓練!
epochs = 10 #迭代資料集次數
optimizer=keras.optimizers.Adam(learning_rate=0.0000003) #優化器
repairer = Repairer()
#加載曾經的權重
if os.path.exists('repairer.h5'):
print('加載權重...')
repairer.load_weights('repairer.h5')
#訓練
for epoch in range(epochs):
total_loss = []
for n,(x,y) in enumerate(ds):
with tf.GradientTape() as tape:
y_pred = repairer(x)
loss = keras.backend.mean(keras.backend.abs(y - y_pred))
grads = tape.gradient(loss, repairer.trainable_variables)
optimizer.apply_gradients(zip(grads, repairer.trainable_variables))
total_loss.append(loss)
print('epoch:',epoch,' , loss:',sum(total_loss)/(n+1))
repairer.save_weights('repairer.h5')
訓練并且自動保存模型權重檔案,如果當前檔案夾已經有模型權重檔案,則加載并開始訓練
生成清晰圖片
#生成清晰圖片保存到當前檔案夾
#注意輸入圖片的大小,必須把圖片像素裁剪成16的倍數(高寬可以不一樣)
test_pic_bin = tf.io.read_file('xxx.jpg') #讀取要被清晰化的圖片路徑
test_pic = tf.io.decode_image(test_pic_bin, channels=3) #解碼圖片
test_pic = tf.image.convert_image_dtype(test_pic, tf.float32) #轉為float32給模型訓練
tshape = test_pic.shape #獲取圖片形狀
x_tshape = int(tshape[1]/16)*16 #輸入高度,將把影像微微裁剪一點點(因為要滿足16的倍數)
y_tshape = int(tshape[0]/16)*16 #輸入寬度,將把影像微微裁剪一點點(因為要滿足16的倍數)
print(y_tshape,x_tshape)
test_pic = tf.image.random_crop(test_pic, [y_tshape, x_tshape, 3]) #裁剪
test_pic = tf.expand_dims(test_pic, axis=0) #增加一個batch維度,便于輸入模型
output = repairer(test_pic) #輸入模型,生成結果
output_image = tf.clip_by_value(output,0,1) #把數值裁剪到0-1的范圍內
output_image = tf.image.convert_image_dtype(output_image, dtype=tf.uint8) #轉為unit8
output_image = tf.image.encode_png(output_image[0]) #編碼
tf.io.write_file(r'test1.png', output_image) #保存輸出的清晰圖片到檔案夾
需要將圖片高度和寬度裁剪為16的倍數,所以先獲取圖片形狀,將其除16,并用int化為整數,再乘16,以這個尺寸為傳入影像,只會比原圖稍微小一點點,
因為顏色值的范圍是0-1之間,而模型有可能會預測出超出這個范圍的數值,所以需要把模型輸出的值全部裁剪到0-1的范圍,再進行編碼,
個人測驗效果如下


可以看出來,在簡單的任務上,該模型可以榷訓鋸齒效果,使線條更平滑,


將風景圖放大后,可以發現物體邊緣模糊的地方變得更清晰一些了,
本人水平有限,文中的不足與錯誤之處,懇請大家糾正,
如果我的文章對您有用,請點個贊或隨意打賞,您的支持將鼓勵我繼續創作!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/298754.html
標籤:其他
