[PYTHON] Technologie de super-résolution-SRCNN-J'ai essayé de l'implémenter (Tensorflow 2.0) Phase d'apprentissage

Aperçu

La technologie de super-résolution est une technologie qui transforme une image basse résolution en une image haute résolution. Cette fois, j'ai implémenté SRCNN, qui est une sorte de technologie de super-résolution et qui est relativement facile à mettre en œuvre.

Cette fois, c'est la ** phase d'apprentissage SRCNN **. La prochaine fois, ce sera la [Phase de prédiction] du SRCNN (https://qiita.com/hima_zin331/items/ebb6046a2a8d860254e1). En fait, j'ai essayé d'implémenter SRGAN en plus de SRCNN, donc je vais prendre cela comme une série de technologie de super-résolution.

environnement

-Software- Windows 10 Home Anaconda3 64-bit(Python3.7) Spyder -Library- Tensorflow 2.1.0 opencv-python 4.1.2.30 -Hardware- CPU: Intel core i9 9900K GPU: NVIDIA GeForce RTX2080ti RAM: 16GB 3200MHz

référence

siteArticle SRCNN[Rapport CV stagiaire] Exploration de l'histoire de la super-résolution-édition 2016-[Codage clairsemé] Avantages de la représentation clairsemée des donnéesKeras: Super résolutionJ'ai essayé une super-résolution simple avec apprentissage en profondeur ・ [Présentation de PyTorch et de la super résolution] (https://buildersbox.corp-sansan.com/entry/2019/02/21/110000) ・ [J'ai essayé d'implémenter le modèle SRCNN qui rend l'image super-résolution avec pytorch](https://nykergoto.hatenablog.jp/entry/2019/05/18/%E7%94%BB%E5%83%8F % E3% 81% AE% E8% B6% 85% E8% A7% A3% E5% 83% 8F% E5% BA% A6% E5% 8C% 96% E3% 82% 92% E3% 81% 99% E3 % 82% 8B% E3% 83% A2% E3% 83% 87% E3% 83% AB_SRCNN_% E3% 82% 92_pytorch_% E3% 81% A7% E5% AE% 9F% E8% A3% 85% E3% 81 % 97% E3% 81% A6)

programme

Je le posterai sur Github. https://github.com/himazin331/Super-resolution-CNN Le référentiel contient une phase d'apprentissage et une phase de prédiction.

Cette fois, j'ai utilisé General-100 pour l'ensemble de données. J'ai également mis l'ensemble de données dans le référentiel GitHub à des fins de démonstration.

Code source

** Veuillez noter que le code est sale ... **

srcnn_tr.py


import argparse as arg
import os
import sys

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' #Masquer le message TF

import tensorflow as tf
import tensorflow.keras.layers as kl
from tensorflow.python.keras import backend as K

import cv2
import numpy as np

import matplotlib.pyplot as plt

# SRCNN
class SRCNN(tf.keras.Model):

    def __init__(self, h, w):
        super(SRCNN, self).__init__()

        self.conv1 = kl.Conv2D(64, 3, padding='same', activation='relu', input_shape=(None, h, w, 3))
        self.conv2 = kl.Conv2D(32, 3, padding='same', activation='relu')
        self.conv3 = kl.Conv2D(3, 3, padding='same', activation='relu')

    def call(self, x):
        
        h1 = self.conv1(x)
        h2 = self.conv2(h1)
        h3 = self.conv3(h2)

        return h3

#Apprentissage
class trainer(object):

    def __init__(self, h, w):
        
        self.model = SRCNN(h, w)
        
        self.model.compile(optimizer=tf.keras.optimizers.Adam(),
                           loss=tf.keras.losses.MeanSquaredError(),
                            metrics=[self.psnr])
        
    def train(self, lr_imgs, hr_imgs, out_path, batch_size, epochs):

        #Apprentissage
        his = self.model.fit(lr_imgs, hr_imgs, batch_size=batch_size, epochs=epochs)

        print("___Training finished\n\n")

        #Enregistrer les paramètres
        print("___Saving parameter...")
        self.model.save_weights(out_path)
        print("___Successfully completed\n\n")

        return his, self.model

    # PSNR(Rapport signal / bruit de crête)
    def psnr(self, h3, hr_imgs):
        
        return -10*K.log(K.mean(K.flatten((h3 - hr_imgs))**2))/np.log(10)

#Création de l'ensemble de données
def create_dataset(data_dir, h, w, mag):

    print("\n___Creating a dataset...")
    
    prc = ['/', '-', '\\', '|']
    cnt = 0

    #Nombre de données d'image
    print("Number of image in a directory: {}".format(len(os.listdir(data_dir))))

    lr_imgs = []
    hr_imgs = []

    for c in os.listdir(data_dir):

        d = os.path.join(data_dir, c)

        _, ext = os.path.splitext(c)
        if ext.lower() == '.db':
            continue
        elif ext.lower() != '.bmp':
            continue

        #Lire, redimensionner(Image haute résolution)
        img = cv2.imread(d)
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        img = cv2.resize(img, (h, w))

        #Image basse résolution
        img_low = cv2.resize(img, (int(h/mag), int(w/mag)))
        img_low = cv2.resize(img_low, (h, w))

        lr_imgs.append(img_low)
        hr_imgs.append(img)

        cnt += 1

        print("\rLoading a LR-images and HR-images...{}    ({} / {})".format(prc[cnt%4], cnt, len(os.listdir(data_dir))), end='')

    print("\rLoading a LR-images and HR-images...Done    ({} / {})".format(cnt, len(os.listdir(data_dir))), end='')

    #Normalisation
    lr_imgs = tf.convert_to_tensor(lr_imgs, np.float32)
    lr_imgs /= 255
    hr_imgs = tf.convert_to_tensor(hr_imgs, np.float32)
    hr_imgs /= 255
    
    print("\n___Successfully completed\n")

    return lr_imgs, hr_imgs

# PSNR,Sortie graphique de valeur de perte
def graph_output(history):
    
    #Graphique PSNR
    plt.plot(history.history['psnr'])
    plt.title('Model PSNR')
    plt.ylabel('PSNR')
    plt.xlabel('Epoch')
    plt.legend(['Train'], loc='upper left')
    plt.show()  

    #Graphique de la valeur de perte
    plt.plot(history.history['loss'])
    plt.title('Model loss')
    plt.ylabel('Loss')
    plt.xlabel('Epoch')
    plt.legend(['Train'], loc='upper left')
    plt.show()

def main():

    #Créer des options de ligne de commande
    parser = arg.ArgumentParser(description='Super-resolution CNN training')
    parser.add_argument('--data_dir', '-d', type=str, default=None,
                        help='Spécification du chemin du dossier d'image(Erreur si non spécifié)')
    parser.add_argument('--out', '-o', type=str,
                        default=os.path.dirname(os.path.abspath(__file__)),
                        help='Spécifiez la destination de sauvegarde des paramètres(Valeur par défaut=./srcnn.h5')
    parser.add_argument('--batch_size', '-b', type=int, default=32,
                        help='Spécifier la taille du mini lot(Valeur par défaut=32)')
    parser.add_argument('--epoch', '-e', type=int, default=3000,
                        help='Spécifier le nombre d'apprentissage(Valeur par défaut=3000)')
    parser.add_argument('--he', '-he', type=int, default=256,
                        help='Redimensionner la spécification de hauteur(Valeur par défaut=256)')      
    parser.add_argument('--wi', '-wi', type=int, default=256,
                        help='Spécifier le redimensionnement(Valeur par défaut=256)')
    parser.add_argument('--mag', '-m', type=int, default=2,
                        help='Spécifier le taux de réduction(Valeur par défaut=2)')                           
    args = parser.parse_args()

    #Chemin du dossier d'image non spécifié->exception
    if args.data_dir == None:
        print("\nException: Folder not specified.\n")
        sys.exit()
    #Lorsqu'un dossier d'images inexistant est spécifié->exception
    if os.path.exists(args.data_dir) != True:
        print("\nException: Folder \"{}\" is not found.\n".format(args.data_dir))
        sys.exit()
    #Lorsque 0 est entré dans la hauteur de la largeur ou le grossissement de réduction->exception
    if args.he == 0 or args.wi == 0 or args.mag == 0:
        print("\nInvalid value has been entered.\n")
        sys.exit()

    #Créer un dossier de sortie(Ne pas créer si le dossier existe)
    os.makedirs(args.out, exist_ok=True)
    out_path = os.path.join(args.out, "srcnn.h5")

    #Réglage de la sortie des informations
    print("=== Setting information ===")
    print("# Images folder: {}".format(os.path.abspath(args.data_dir)))
    print("# Output folder: {}".format(out_path))
    print("# Minibatch-size: {}".format(args.batch_size))
    print("# Epoch: {}".format(args.epoch))
    print("")
    print("# Height: {}".format(args.he))
    print("# Width: {}".format(args.wi))
    print("# Magnification: {}".format(args.mag))
    print("===========================\n")

    #Création de l'ensemble de données
    lr_imgs, hr_imgs = create_dataset(args.data_dir, args.he, args.wi, args.mag)
    
    #Commencer à apprendre
    print("___Start training...")
    Trainer = trainer(args.he, args.wi)
    his, model = Trainer.train(lr_imgs, hr_imgs, out_path=out_path, batch_size=args.batch_size, epochs=args.epoch)

    # PSNR,Sortie graphique de valeur de perte
    graph_output(his)

if __name__ == '__main__':
    main()

Résultat d'exécution

Le nombre d'Epoch est de 3000 et la taille du mini lot est de 32.

Le graphique ci-dessous est un enregistrement du PSNR (Peak Signal to Noise Ratio). Les détails seront décrits plus tard. PSNR 30db est le plafond. .. ..

image.png Le graphique ci-dessous est un enregistrement des valeurs de perte. image.png

Notez que ces graphiques ne sont pas enregistrés.

commander python arcnn_tr.py -d <folder> -e <nombre d'apprentissage> -b <taille du lot> (-o <destination d'enregistrement> -he <hauteur> -wi <largeur> -m <agrandissement de réduction (entier)>)

La description

Je vais expliquer le code.

Modèle de réseau

Le modèle de réseau n'est pas très différent d'un CNN normal.

Classe SRCNN


# SRCNN
class SRCNN(tf.keras.Model):

    def __init__(self, h, w):
        super(SRCNN, self).__init__()

        self.conv1 = kl.Conv2D(64, 3, padding='same', activation='relu', input_shape=(None, h, w, 3))
        self.conv2 = kl.Conv2D(32, 3, padding='same', activation='relu')
        self.conv3 = kl.Conv2D(3, 3, padding='same', activation='relu')

    def call(self, x):
        
        h1 = self.conv1(x)
        h2 = self.conv2(h1)
        h3 = self.conv3(h2)

        return h3

La différence avec le CNN habituel est que le canal de sortie devient généralement de plus en plus grand. Dans le cas du SRCNN, le fait est que le canal de sortie est progressivement réduit.

La couche de pliage est généralement constituée de trois couches.

La première couche ** effectue une extraction de patch et une représentation éparse dans un espace à faible résolution **. La deuxième couche ** effectue un mappage non linéaire sur l'espace haute résolution de la représentation acquise dans la première couche **. La troisième couche ** reconstruit l'image haute résolution **. ([Rapport CV stagiaire] Exploration de l'histoire de la super-résolution-édition 2016-)

** Expression fragmentée ** (codage fragmenté) consiste à préparer un dictionnaire pour exprimer des données et exprimer des données avec le moins de combinaisons d'éléments possible **. (Tiré de [Sparse Coding] Advantages of Sparse Data Representation)

Pour le dire un peu plus simplement, l'expression clairsemée est à quel point elle peut être réaliste (précision approximative) en combinant un petit nombre de cartes de caractéristiques par rapport à l'image d'entrée. La combinaison de nombreuses cartes de caractéristiques a tendance à améliorer la précision de l'approximation, mais l'expression éparse n'ose pas le faire, et une expression significative peut être extraite en utilisant un petit nombre d'éléments. En d'autres termes, ** clarifiez quels éléments sont utiles et combien il est utile de représenter les données **.


Création de l'ensemble de données

** Vous n'avez besoin que d'images haute résolution **. Les images basse résolution sont créées à partir d'images haute résolution.

create_fonction d'ensemble de données


#Création de l'ensemble de données
def create_dataset(data_dir, h, w, mag):

    print("\n___Creating a dataset...")
    
    prc = ['/', '-', '\\', '|']
    cnt = 0

    #Nombre de données d'image
    print("Number of image in a directory: {}".format(len(os.listdir(data_dir))))

    lr_imgs = []
    hr_imgs = []

    for c in os.listdir(data_dir):

        d = os.path.join(data_dir, c)

        _, ext = os.path.splitext(c)
        if ext.lower() == '.db':
            continue
        elif ext.lower() != '.bmp':
            continue

        #Lire, redimensionner(Image haute résolution)
        img = cv2.imread(d)
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        img = cv2.resize(img, (h, w))

        #Image basse résolution
        img_low = cv2.resize(img, (int(h/mag), int(w/mag)))
        img_low = cv2.resize(img_low, (h, w))

        lr_imgs.append(img_low)
        hr_imgs.append(img)

        cnt += 1

        print("\rLoading a LR-images and HR-images...{}    ({} / {})".format(prc[cnt%4], cnt, len(os.listdir(data_dir))), end='')

    print("\rLoading a LR-images and HR-images...Done    ({} / {})".format(cnt, len(os.listdir(data_dir))), end='')

    #Normalisation
    lr_imgs = tf.convert_to_tensor(lr_imgs, np.float32)
    lr_imgs /= 255
    hr_imgs = tf.convert_to_tensor(hr_imgs, np.float32)
    hr_imgs /= 255
    
    print("\n___Successfully completed\n")

    return lr_imgs, hr_imgs

Commencez par charger l'image. Lors de la lecture avec OpenCV, la disposition des pixels est BGR, donc Convertissez en RVB avec cv2.cvtColor (img, cv2.COLOR_BGR2RGB). Après cela, il sera redimensionné à la taille spécifiée. Maintenant, l'image haute résolution est prête pour le moment.

Créez ensuite une image basse résolution. Réduit à la largeur et à la hauteur divisées par le rapport de réduction spécifié. Après cela, redimensionnez-le à la taille avant qu'il ne soit réduit pour terminer la création.

        #Lire, redimensionner(Image haute résolution)
        img = cv2.imread(d)
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        img = cv2.resize(img, (h, w))

        #Image basse résolution
        img_low = cv2.resize(img, (int(h/mag), int(w/mag)))
        img_low = cv2.resize(img_low, (h, w))

Cela ressemble à ceci en utilisant un diagramme. image.png

Par défaut, l'algorithme d'interpolation «cv2.resize ()» d'OpenCV utilise Bilinear.


Apprentissage

Configurez et apprenez avant de faire l'apprentissage automatique dans la classe de formateur ~~ Je ne pense pas que je dise souvent "entraîneur" dans Tensorflow, mais je suis une personne qui a commencé avec Chainer ... ~~

Tout d'abord, je vais expliquer la méthode d'instance.

classe de formateur(Méthode d'instance)


#Apprentissage
class trainer(object):

    def __init__(self, h, w):
        
        self.model = SRCNN(h, w)
        
        self.model.compile(optimizer=tf.keras.optimizers.Adam(),
                           loss=tf.keras.losses.MeanSquaredError(),
                            metrics=[self.psnr])

Appelez la méthode d'instance «init» lors de la création d'une instance pour déterminer l'algorithme de construction et d'optimisation du modèle de réseau. Avec self.model = SRCNN (h, w), les informations de hauteur et de largeur sont transmises à la méthode d'instance de la classe SRCNN. Une fois le modèle construit, définissez l'algorithme d'optimisation et la fonction de perte dans le modèle. Cette fois, l'algorithme d'optimisation est Adam **. Utilisez l'erreur quadratique moyenne ** pour la fonction de perte.

La logique est que ** l'image basse résolution s'approche progressivement de l'image haute résolution ** en recherchant l'erreur quadratique moyenne entre l'image basse résolution et l'image haute résolution ** et en réduisant la valeur **.

Dans metrics = [self.psnr], PSNR est défini dans la fonction d'évaluation. Les détails seront décrits plus tard.


Vient ensuite l'explication de la méthode du train.

classe de formateur(méthode de train)


    def train(self, lr_imgs, hr_imgs, out_path, batch_size, epochs):

        #Apprentissage
        his = self.model.fit(lr_imgs, hr_imgs, batch_size=batch_size, epochs=epochs)

        print("___Training finished\n\n")

        #Enregistrer les paramètres
        print("___Saving parameter...")
        self.model.save_weights(out_path)
        print("___Successfully completed\n\n")

        return his, self.model

Passez l'image basse résolution lr_imgs comme données de formation et l'image haute résolution hr_imgs comme étiquette de réponse correcte àself.model.fit () Commencer à apprendre. Enregistrez les paramètres dès que la formation est terminée.


Enfin, j'expliquerai la méthode PSNR.

** PSNR ** (Peak signal-to-noise ratio) est un ** indice d'évaluation ** qui indique une détérioration de l'image, appelée ** rapport signal-bruit de pic **. Quel est le signal de crête ou le bruit? Vous pensez peut-être, mais je suis désolé, je ne suis pas un spécialiste, donc je ne peux pas l'expliquer.

L'unité de cette métrique est "db (décibel)". En général, il semble que le ** PSNR 30db ou plus soit beau **. Cependant, veuillez noter que ** la perception humaine et la valeur PSNR ne correspondent pas toujours **.

Je vais mettre la formule de définition.

PSNR = 10 \log_{10}\frac{MAX^2}{MSE}\qquad(1.1)

$ MSE $ est l'erreur quadratique moyenne.

MSE = \frac{1}{n} \sum_{i=1}^{n} (SR_i - HR_i)^2\qquad(2)

$ MAX $ est ** la valeur maximale qu'un pixel peut prendre **, mais comme il est divisé par 255 et normalisé de 0 à 1, ** $ MAX $ (valeur maximale) est 1 **. Remplacer 1 par $ MAX $ dans l'équation (1.1)

PSNR = 10 \log_{10}\frac{1}{MSE}\qquad(1.2)

Et par la formule de conversion logarithmique du quotient

PSNR = -10 \log_{10}MSE\qquad(1.3)

Ce sera. Cette fois, nous utiliserons tf.keras.backend.flatten () dans le calcul de l'erreur quadratique moyenne $ MSE $. Si vous utilisez une fonction tf.keras.backend, vous ne pouvez pas utiliser la fonction numpy. J'obtiens une erreur. Donc, connectez-vous dans la formule utilise tf.keras.backend.log (), qui est une logarithmique naturelle plutôt qu'une logarithmique régulière. Par conséquent, l'équation (1.3)

PSNR = -10 \frac{\ln MSE}{\ln 10}\qquad(1.4)

Il est nécessaire de transformer l'équation en utilisant la formule de transformation du fond comme dans l'équation (1.4).

J'ai honte de dire que tf.keras.backend.log () n'est pas un logarithmique régulier, et je ne suis pas bon en mathématiques, donc je ne pouvais pas comprendre pourquoi cette transformation de formule se produirait dans la littérature. Par conséquent, en tant que mémorandum, l'état de la transformation de la formule est décrit en détail comme ceci.

Le code de l'équation (1.4) est indiqué ci-dessous.

classe de formateur(Méthode PSNR)


    # PSNR(Rapport signal / bruit de crête)
    def psnr(self, h3, hr_imgs):
        
        return -10*K.log(K.mean(K.flatten((h3 - hr_imgs))**2))/np.log(10)

K.mean (K.flatten ((h3 --hr_imgs)) ** 2 correspond à $ MSE $ (erreur quadratique moyenne). Le dénominateur est le logarithme de numpy, mais c'est aussi un logarithmique naturel. Pour une raison quelconque, si j'essaie de définir le journal du dénominateur sur tf.keras.backend.log (), j'obtiens une erreur. Je me demande pourquoi?

Eh bien, j'ai pu définir la formule PSNR comme ça. En passant, si $ SR_i $ et $ HR_i $ dans l'équation (2) sont la même image, ce sera PSNR $ + ∞ $ db.


en conclusion

J'ai essayé d'expliquer une méthode d'implémentation SRCNN simple dans la technologie de super-résolution, mais comment était-ce? Dans la prochaine "Super Resolution Technology-SRCNN-Implemented (Tensorflow 2.0) Prediction Phase Edition", je vais en fait en faire une super résolution.

Recommended Posts

Technologie de super-résolution-SRCNN-J'ai essayé de l'implémenter (Tensorflow 2.0) Phase d'apprentissage
Technologie de super-résolution-SRCNN-I a essayé de l'implémenter (Tensorflow 2.0) Phase de prédiction
Apprentissage automatique (TensorFlow) + Lotto 6
Essayez l'apprentissage en profondeur avec TensorFlow
Essayez l'apprentissage en profondeur avec TensorFlow Partie 2