Размытие — это нерезкая область изображения, вызванная движением камеры или объекта, неточной фокусировкой или использованием диафрагмы, обеспечивающей малую глубину резкости. Это приводит к уменьшению разрешения изображения, делая контуры и особенности изображения нечеткими. Таким образом, чтобы получить более четкое изображение, мы можем либо переснять ту же фотографию с предпочтительным фокусом объектива камеры, либо использовать наши знания о глубоком обучении и воспроизвести размытое изображение. Поскольку мой опыт не связан с фотографией, я могу помочь вам очистить ваши изображения с помощью методов глубокого обучения!

Это руководство не для начинающих. Вы должны знать основные концепции глубокого обучения, такие как нейронные сети, CNN, прежде чем начинать этот проект. Также будьте немного знакомы с Keras, Tensorflow и OpenCV.

Существуют различные типы размытия — размытие в движении, размытие по Гауссу, среднее размытие и т. д., но мы сосредоточимся на удалении размытия Размытие по Гауссу изображений. В этом типе размытия веса ​​пикселей неодинаковы. Размытие сильное в центре и уменьшается по краям по кривой в форме колокола.

Набор данных

Прежде чем приступить к работе с кодом, я бы посоветовал вам сначала получить набор данных, состоящий из двух наборов изображений — размытых и чистых изображений. Если вы не найдете доступных ранее существующих наборов данных, вы также можете создать набор данных самостоятельно.

Код

Теперь, когда у нас есть готовый набор данных, мы можем приступить к работе с кодом. Вы можете написать его в своем блокноте Jupyter, Google Colab или любой другой платформе, которая вам нравится. Я использовал Google Colab для этого проекта, так как мне было проще использовать набор данных, связав с ним свой Google Диск.

Импорт зависимостей

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

%matplotlib inline
import random
import cv2
import os
import tensorflow as tf
from tqdm import tqdm
from google.colab import drive #If you're using Colab and importing the images from the drive

Здесь я импортировал библиотеку tqdm, чтобы помочь мне создать индикаторы выполнения, чтобы я знал, сколько времени потребуется для выполнения определенного блока кода.

Импорт набора данных

drive.mount('/content/drive', force_remount=True)

Используя приведенный выше код, вы можете подключить свой диск Google к блокноту colab, а затем скопировать путь к чистым и размытым изображениям как:

good_frames = '/content/drive/MyDrive/mini_clean'
bad_frames = '/content/drive/MyDrive/mini_blur'

Теперь создайте по 2 списка для чистых и размытых изображений каждый. Мы будем использовать библиотеки предварительной обработки keras для преобразования изображения из расширений «.jpg», «jpeg» или «.png» в массив и добавления их в наши вновь созданные списки. Я выбираю размер изображения 128x128.

clean_frames = []
for file in tqdm(sorted(os.listdir(good_frames))):
  if any(extension in file for extension in ['.jpg', 'jpeg', '.png']):
    image = tf.keras.preprocessing.image.load_img(good_frames + '/' + file, target_size=(128,128))
    image = tf.keras.preprocessing.image.img_to_array(image).astype('float32') / 255
    clean_frames.append(image)

clean_frames = np.array(clean_frames)
blurry_frames = []
for file in tqdm(sorted(os.listdir(bad_frames))):
  if any(extension in file for extension in ['.jpg', 'jpeg', '.png']):
    image = tf.keras.preprocessing.image.load_img(bad_frames + '/' + file, target_size=(128,128))
    image = tf.keras.preprocessing.image.img_to_array(image).astype('float32') / 255
    blurry_frames.append(image)

blurry_frames = np.array(blurry_frames)

Импорт библиотек для модели

from keras.layers import Dense, Input
from keras.layers import Conv2D, Flatten
from keras.layers import Reshape, Conv2DTranspose
from keras.models import Model
from keras.callbacks import ReduceLROnPlateau, ModelCheckpoint
from keras.utils.vis_utils import plot_model
from keras import backend as K

seed = 21
random.seed = seed
np.random.seed = seed

Разделение набора данных на обучающие и тестовые наборы

Теперь мы разделим основной набор данных на 2 — обучающий и тестовый наборы данных. Я буду делить их в пропорции 80:20, вы можете попробовать и любую другую.

X = clean_frames;
y = blurry_frames;
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

Проверка формы поезда и тестовых наборов данных

print(X_train[0].shape)
print(y_train[0].shape)

r = random.randint(0, len(clean_frames)-1)
print(r)
fig = plt.figure()
fig.subplots_adjust(hspace=0.1, wspace=0.2)
ax = fig.add_subplot(1, 2, 1)
ax.imshow(clean_frames[r])
ax = fig.add_subplot(1, 2, 2)
ax.imshow(blurry_frames[r])

Вы можете проверить изображения из поезда и тестовых наборов данных с помощью приведенного выше кода, вывод будет таким:

Затем мы можем инициализировать несколько параметров, чтобы использовать их при кодировании нашей модели.

# Network Parameters
input_shape = (128, 128, 3)
batch_size = 32
kernel_size = 3
latent_dim = 256

# Encoder/Decoder number of CNN layers and filters per layer
layer_filters = [64, 128, 256]

Построение модели кодировщика

Если вы не имеете большого представления об автоэнкодерах, не волнуйтесь, вы можете перейти по этой ссылке, чтобы получить общее представление.

inputs = Input(shape = input_shape, name = 'encoder_input')
x = inputs

Мы будем напрямую использовать библиотеки, чтобы избежать громоздких ручных вычислений. Мы будем строить стек Conv2D(64) - Conv2D(128) - Conv2D(256). Модель будет иметь входную форму (128, 128, 3) и размер ядра, равный 3, и кодировщик сожмет эту форму до (16, 16, 256) и дополнительно сгладит ее в одномерный массив, который будет вход для нашего декодера.

for filters in layer_filters:
    x = Conv2D(filters=filters,
               kernel_size=kernel_size,
               strides=2,
               activation='relu',
               padding='same')(x)
shape = K.int_shape(x)
x = Flatten()(x)
latent = Dense(latent_dim, name='latent_vector')(x)

K.int_shape() здесь помогает преобразовать тензор в кортеж целых чисел.

Создание экземпляра модели кодировщика,

encoder = Model(inputs, latent, name='encoder')
encoder.summary()

Построение модели декодера

Модель декодера будет похожа на модель кодера, но будет выполнять обратные или противоположные вычисления. Сначала мы вручную преобразуем одномерный массив из модели кодировщика в форму (16, 16, 256), а затем отправим его в декодер для декодирования обратно в форму (128, 128, 3). Таким образом, стек здесь будет Conv2DTranspose (256) — Conv2DTranspose (128) — Conv2DTranspose (64).

latent_inputs = Input(shape=(latent_dim,), name='decoder_input')
x = Dense(shape[1]*shape[2]*shape[3])(latent_inputs)
x = Reshape((shape[1], shape[2], shape[3]))(x)
for filters in layer_filters[::-1]:
    x = Conv2DTranspose(filters=filters,
                        kernel_size=kernel_size,
                        strides=2,
                        activation='relu',
                        padding='same')(x)

outputs = Conv2DTranspose(filters=3,
                          kernel_size=kernel_size,
                          activation='sigmoid',
                          padding='same',
                          name='decoder_output')(x)

Создание модели декодера,

decoder = Model(latent_inputs, outputs, name='decoder')
decoder.summary()

Построение модели автоэнкодера

Теперь, когда у нас есть модели кодера и декодера, мы можем объединить их, чтобы, наконец, построить нашу модель автоэнкодера.

Модель автоэнкодера = модель кодировщика + модель декодера

Создание экземпляра модели автоэнкодера,

autoencoder = Model(inputs, decoder(encoder(inputs)), name='autoencoder')
autoencoder.summary()

Последнее, но немаловажное

Перед обучением нашей модели осталось сделать еще одну вещь — выбрать гиперпараметры.

autoencoder.compile(loss='mse', optimizer='adam',metrics=["acc"])

Я выбираю функцию потерь в качестве среднеквадратичной ошибки, оптимизатора в качестве Адама и метрики оценки в качестве точности.

Теперь определяем редуктор скорости обучения, чтобы уменьшить скорость обучения, если метрика не улучшилась,

lr_reducer = ReduceLROnPlateau(factor=np.sqrt(0.1),
                               cooldown=0,
                               patience=5,
                               verbose=1,
                               min_lr=0.5e-6)

Называя это в каждую эпоху,

callbacks = [lr_reducer]

Обучение модели

history = autoencoder.fit(X_train,
                      y_train,
                      validation_data=(X_test, y_test),
                      epochs=100,
                      batch_size=batch_size,
                      callbacks=callbacks)

После запуска этого кода точность составила 78,07%.

Конечный результат

Теперь, когда мы успешно обучили нашу модель, давайте посмотрим на прогнозы нашей модели.

print("\n       Input                        Ground Truth                  Predicted Value")
for i in range(3):
    
    r = random.randint(0, len(clean_frames)-1)

    x, y = blurry_frames[r],clean_frames[r]
    x_inp = x.reshape(1,128,128,3)
    result = autoencoder.predict(x_inp)
    result = result.reshape(128,128,3)

    fig = plt.figure(figsize=(12,10))
    fig.subplots_adjust(hspace=0.1, wspace=0.2)

    ax = fig.add_subplot(1, 3, 1)
    ax.imshow(x)

    ax = fig.add_subplot(1, 3, 2)
    ax.imshow(y)

    ax = fig.add_subplot(1, 3, 3)
    plt.imshow(result)

Выход для этого будет,

Таким образом, мы видим, что модель довольно хорошо справилась с устранением размытости изображений и почти смогла восстановить исходные изображения. Мы можем дополнительно оптимизировать наше решение путем настройки гиперпараметров, что может повысить точность модели. Для этого мы можем построить графики функции потерь и точности, чтобы иметь возможность принимать более обоснованные решения.

Для функции потерь

plt.figure(figsize=(12,8))
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.legend(['Train', 'Test'])
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.xticks(np.arange(0, 101, 25))
plt.show()

Из этого мы видим, что потери значительно уменьшались, а затем начали стагнировать примерно с 80-й эпохи.

Теперь о точности

plt.figure(figsize=(12,8))
plt.plot(history.history['acc'])
plt.plot(history.history['val_acc'])
plt.legend(['Train', 'Test'])
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.xticks(np.arange(0, 101, 25))
plt.show()

Из этого мы видим, что точность значительно увеличилась, и она могла бы увеличиться еще больше, если бы было больше эпох. Таким образом, вы можете попробовать увеличить размер эпохи и проверить, действительно ли точность улучшается или нет.

Заключение и будущая работа

Мы подошли к концу этой статьи, но это не конец, этот проект можно было бы улучшить за счет:

  • Получение большего количества данных для повышения точности модели
  • Настройка гиперпараметров
  • Подробное изучение проблем переобучения

Спасибо, что дочитали до сюда, и дайте мне знать, если у вас есть какие-либо отзывы!

Ссылки

https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.303.227&rep=rep1&type=pdf