[PYTHON] Mémorandum de création de fichier TFRecord pour la détection d'objets

introduction

Dans l'article précédent (https://qiita.com/IchiLab/items/fd99bcd92670607f8f9b), nous avons résumé comment utiliser l'API de détection d'objets fournie par TensorFlow. Afin d'utiliser cette API pour enseigner "cet objet est XX" à partir d'images et de vidéos, Annoter et convertir au format TFRecord, Il sera utilisé comme données de l'enseignant et données de vérification, J'ai l'impression qu'il y a encore peu d'articles qui mentionnent le contenu spécifique de ce format TFRecord.

Dans cet article, nous allons commencer par une méthode qui peut être créée sans programmation. Nous résumerons sous différents angles, y compris comment écrire et créer du code Python. Bien sûr, je résume simplement les résultats que j'ai trouvés par essais et erreurs. Veuillez noter que certaines parties peuvent ne pas être atteintes.

Contenu à présenter

--Procédure de création d'un fichier TFRecord sur [VoTT] de Microsoft (https://github.com/Microsoft/VoTT/releases) ――Quel type de fichier est TFRecord? --Comment découper la partie d'annotation des données annotées

Procédure de création d'un fichier d'enregistrement TF avec VoTT

Installation VoTT

Téléchargeons sur le site suivant. Sélectionnez «.exe» si le système d'exploitation est Windows ou «.dmg» si le système d'exploitation est Mac. VoTT

Réglage initial

Tout d'abord, créez un nouveau projet avec "Nouveau projet". q01.png

Définissez le projet.

Lorsque VoTT spécifie chaque répertoire, il enregistre les informations de configuration telles que l'emplacement du dossier avec "Ajouter une connexion". Dans l'élément "~ Connexion", le nom du paramètre est spécifié. Par conséquent, lorsque vous l'utilisez pour la première fois, commencez par créer le paramètre avec «Ajouter une connexion» sur le côté droit. q02.png

Définissez le répertoire.

Enfin, sélectionnez "Enregistrer la connexion". q03.png

Après avoir défini respectivement "Connexion source" et "Connexion cible", l'étape suivante est le travail d'annotation. q04.png

Annotation

L'annotation peut être faite dans la seconde en partant du haut à gauche (sous la marque de maison). (La photo montre mes chats Mimi et Kitty) q05.png

Commencez par définir la balise. Il indique TAGS sur le côté droit, et il y a une icône + à côté. Si vous sélectionnez cette option, vous pouvez définir une nouvelle balise. Cette fois, j'ai défini le nom de mon chat comme "Mimmy" et "Kitty".

Sélectionnez ensuite la deuxième icône carrée en haut à gauche. Faites ensuite glisser le lieu que vous souhaitez annoter et fermez-le. q06.png

Il peut s'agir d'un carré gris au moment de la clôture. Si vous souhaitez donner un nom de balise arbitraire au moment de l'insertion, après avoir sélectionné le nom de la balise sur le côté droit, Si vous sélectionnez une icône comme une marque de verrouillage et définissez "Je vais l'attacher avec cette balise fixe" Il donnera automatiquement le nom de la balise lorsque vous l'annoterez. (Ou, sur Mac, vous pouvez effectuer la même opération en maintenant la touche Commande enfoncée et en cliquant sur le nom de la balise. Touche Ctrl dans Windows ...? Non confirmé)

Vous pouvez également le fixer à un carré en appuyant sur la touche Maj lors de l'annotation. C'est une bonne idée de s'habituer à cette zone en la touchant.

Exportation de TFRecord et json

Pour générer un TFRecord, il doit être défini à l'avance dans les paramètres d'exportation. Sélectionnez la quatrième icône de flèche depuis le haut dans l'icône de menu à gauche.

Une fois les paramètres définis, sélectionnez «Enregistrer les paramètres d'exportation» pour enregistrer les paramètres.

q07.png

Après cela, revenez à l'écran d'annotation, enregistrez le projet avec l'icône de disquette en haut à droite et exportez le format (TFRecord cette fois) défini avec l'icône de la flèche supérieure droite. En passant, le fichier json est créé sans autorisation lorsque vous l'enregistrez même si vous ne définissez rien.

q08.png

Ce qui précède est la procédure de création d'un fichier TFRecord à l'aide de VoTT.

Le suivant est le sujet principal de cet article.

Quel type de fichier est TFRecord?

Qu'est-ce que TF Record

Qu'est-ce que TFRecord en premier lieu?

Un extrait du tutoriel officiel TensorFlow dit:

Le format TFRecord est un format simple pour stocker une série d'enregistrements binaires. Les tampons de protocole sont des bibliothèques indépendantes de la plate-forme et du langage qui sérialisent efficacement les données structurées.

Référence) Utilisation de TFRecords et tf.Example

Le simple fait de lire cela ne me vient pas à l'esprit. Examinons maintenant le contenu du TFRecord qui a été exporté à l'aide de VoTT plus tôt.

Lisez le fichier TFRecord et essayez de le visualiser

Le contenu de TFRecord peut être visualisé avec les sources suivantes.

from __future__ import absolute_import, division, print_function, unicode_literals
import tensorflow as tf
import numpy as np
import IPython.display as display

#Spécifiez le chemin de TFRecord
filenames = 'VoTT/Cat/Cat-TFRecords-export/Mimmy_and_Kitty.tfrecord'
raw_dataset = tf.data.TFRecordDataset(filenames)

#Exporter le contenu lu dans un autre format
# (.Ce peut être txt. Avec json, cela dépend de l'éditeur, mais il est coloré et plus facile à voir, il est donc recommandé par rapport à txt)
tfr_data = 'tfr.json'

for raw_record in raw_dataset.take(1):
    example = tf.train.Example()
    example.ParseFromString(raw_record.numpy())
    print(example)

    #Écrivez dans un fichier. Ce n'est pas indispensable car vous pouvez le voir sur la console sans l'exporter.
    with open(tfr_data, 'w') as f:
        print(example, file=f)

Jetons un coup d'œil au fichier exporté à partir de la source ci-dessus.

features {
  feature {
    key: "image/encoded"
    value {
      bytes_list {
        value: "\377\330\377...
        .....(Comme il s'agit d'une grande quantité, il est omis)..."
      }
    }
  }
  feature {
    key: "image/filename"
    value {
      bytes_list {
        value: "Mimmy_and_Kitty.jpg "
      }
    }
  }
  feature {
    key: "image/format"
    value {
      bytes_list {
        value: "jpg"
      }
    }
  }
  feature {
    key: "image/height"
    value {
      int64_list {
        value: 1440
      }
    }
  }
  feature {
    key: "image/key/sha256"
    value {
      bytes_list {
        value: "TqXFCKZWbnYkBUP4/rBv1Fd3e+OVScQBZDav2mXSMw4="
      }
    }
  }
  feature {
    key: "image/object/bbox/xmax"
    value {
      float_list {
        value: 0.48301976919174194
        value: 0.7260425686836243
      }
    }
  }
  feature {
    key: "image/object/bbox/xmin"
    value {
      float_list {
        value: 0.3009025752544403
        value: 0.5285395383834839
      }
    }
  }
  feature {
    key: "image/object/bbox/ymax"
    value {
      float_list {
        value: 0.6981713175773621
        value: 0.8886410593986511
      }
    }
  }
  feature {
    key: "image/object/bbox/ymin"
    value {
      float_list {
        value: 0.3555919826030731
        value: 0.5664308667182922
      }
    }
  }
  feature {
    key: "image/object/class/label"
    value {
      int64_list {
        value: 0
        value: 1
      }
    }
  }
  feature {
    key: "image/object/class/text"
    value {
      bytes_list {
        value: "Mimmy"
        value: "Kitty"
      }
    }
  }
  feature {
    key: "image/width"
    value {
      int64_list {
        value: 2560
      }
    }
  }
}

D'autres clés telles que «difficile», «tronqué», «vue», «source_id» sont incluses, Ici, je n'ai extrait que les contenus que je pense nécessaires. Si vous vérifiez le contenu, vous pouvez voir qu'il a la structure suivante.

À ce stade, vous avez probablement compris de quel type de structure est constitué TFRecord.

Comment découper la partie d'annotation des données annotées

Voici une méthode pour découper la partie annotée par VoTT par programmation. Si vous pouvez faire cela, lorsque vous écrivez votre propre programme et créez un fichier TFRecord, Vous aurez peut-être besoin d'une idée pour ** combiner l'objet que vous souhaitez détecter avec l'arrière-plan **, comme décrit ci-dessous. Ou cela sera utile pour l'apprentissage automatique de la classification d'images.

C'est aussi ce que j'ai remarqué après avoir essayé, ** L'orientation de l'image vue par VoTT peut être différente de l'orientation de l'image lors du recadrage ** J'ai découvert que.

En d'autres termes, l'image que vous voyez à l'écran lorsque vous annotez en VoTT, Cela signifie que l'orientation de l'image des données d'origine lors de la découpe avec des informations json était parfois différente de 180 degrés.

Grâce à cela, une seule image d'un lieu non prévu a été découpée. Je ne sais pas si cela est annoté dans le bon sens dans TFRecord, donc Il peut être prudent de le regarder une fois.

Eh bien, l'introduction est devenue longue, mais vérifions immédiatement json pour le recadrage d'image. Vous avez mentionné précédemment que json est également automatiquement exporté lorsque vous avez terminé le travail d'annotation avec VoTT et que vous l'exportez.

Les données de chat données dans l'exemple ont été écrites comme suit.

{
    "asset": {
        "format": "jpg",
        "id": "1da8e6914e4ec2e2c2e82694f19d03d5",
        "name": "Mimmy_and_Kitty.jpg ",
        "path": "【Nom de dossier】/VoTT/Cat/IMAGES/Mimmy_and_Kitty.jpg ",
        "size": {
            "width": 2560,
            "height": 1440
        },
        "state": 2,
        "type": 1
    },
    "regions": [
        {
            "id": "kFskTbQ6Z",
            "type": "RECTANGLE",
            "tags": [
                "Mimmy"
            ],
            "boundingBox": {
                "height": 493.3142744479496,
                "width": 466.2200532386868,
                "left": 770.3105590062112,
                "top": 512.0524447949527
            },
            "points": [
                {
                    "x": 770.3105590062112,
                    "y": 512.0524447949527
                },
                {
                    "x": 1236.5306122448978,
                    "y": 512.0524447949527
                },
                {
                    "x": 1236.5306122448978,
                    "y": 1005.3667192429023
                },
                {
                    "x": 770.3105590062112,
                    "y": 1005.3667192429023
                }
            ]
        },
    ],
    "version": "2.1.0"
}

(Les informations sur l'autre balise Kitty sont omises car elles seront longues si elles sont placées.)

Comme vous pouvez le voir, il contient le nom du fichier, la taille de l'image et même les informations de coordonnées annotées. Il est tout à fait possible de découper une image en utilisant uniquement ces informations.

Les informations de coordonnées annotées sont soigneusement incluses dans deux types, «boundingBox» et «points». Il semble y avoir différentes façons, mais cette fois je suis allé voir la boundingBox et j'ai essayé de la couper. Voici le code source.

import json
import os
import fnmatch
import cv2 as cv

JSON_DIR = 'VoTT/Cat/'
IMG_DIR = 'VoTT/Cat/'
CUT_IMAGE = 'cut_images/'
CUT_IMAGE_NAME = 'cat'
IMAGE_FORMAT = '.jpg'

class Check():

    def filepath_checker(self, dir):
        
        if not (os.path.exists(dir)):
            print('No such directory > ' + dir)
            exit()

    def directory_init(self, dir):

        if not(os.path.exists(dir)) :
            os.makedirs(dir, exist_ok=True)

def main():
    
    check = Check()

    #Vérifiez si le répertoire contenant le fichier json existe
    check.filepath_checker(JSON_DIR)
    
    #'Préparer un emplacement de stockage pour l'image CUT'
    check.directory_init(CUT_IMAGE)

    #Analyser json et découper des coordonnées d'image et d'annotation
    count = 0
    for jsonName in fnmatch.filter(os.listdir(JSON_DIR), '*.json'):

        #Ouvrir json
        with open(JSON_DIR + jsonName) as f :
            result = json.load(f)

            #Obtenir le nom du fichier image
            imgName = result['asset']['name']
            print('jsonName = {}, imgName = {} '.format(jsonName, imgName))
            
            img = cv.imread(IMG_DIR + imgName)
            if img is None:
                print('cv.imread Error')
                exit()

            #Bouclez autant d'annotés
            for region in result['regions'] :
                
                height = int(region['boundingBox']['height'])
                width = int(region['boundingBox']['width'])
                left = int(region['boundingBox']['left'])
                top = int(region['boundingBox']['top'])

                cutImage = img[top: top + height, left: left + width]
                #Évitez les informations sur lesquelles vous avez accidentellement cliqué sur un point pendant l'annotation
                if height == 0 or width == 0:
                    print('<height or width is 0>  imgName = ', imgName)
                    continue
                
                #Si vous souhaitez redimensionner avant l'exportation, supprimez les commentaires
                #cutImage = cv.resize(cutImage, (300,300))

                #「cut_images/cat0000.Exportez des fichiers avec des numéros de série tels que "jpg"
                cv.imwrite(CUT_IMAGE + CUT_IMAGE_NAME + "{0:04d}".format(count + 1) + IMAGE_FORMAT, cutImage)
                print("{0:04d}".format(count+1))
                count += 1

if __name__ == "__main__":
    main()
    

Dans le code source, il y a une branche conditionnelle de ʻif height == 0 ou width == 0` La partie cliquée par erreur lors de l'annotation avec VoTT reste sous forme de données, Parce qu'il y a eu une erreur car il n'y avait pas de zone à découper J'ai essayé de l'inclure pour éviter l'erreur humaine. q09.png

Dans mon cas, il était nécessaire d'annoter beaucoup sur une feuille, donc C'était une situation de plus en plus difficile à remarquer. De plus, c'est encore plus le cas lorsqu'il y a une grande quantité de données d'image.

Eh bien, cela fait longtemps, mais écrivons un programme pour créer TFRecord à partir de maintenant.

Créer un TFRecord pour la détection d'objets en Python

Maintenant que vous avez une idée approximative de la composition du contenu de TFRecord Enfin, écrivons le code source et générons TFRecord.

Description du code source

Ce que nous faisons avec la source publiée cette fois est la suivante.

  1. Préparez à l'avance une image de l'objet (chat) à combiner avec l'image d'arrière-plan.
  2. Combinez l'image d'arrière-plan et l'image de l'objet (fixée à une position arbitraire cette fois)
  3. Organiser les informations requises pour TFRecord telles que les informations de position de coordonnées combinées
  4. Générez un fichier TFRecord

Tout d'abord, l'image d'arrière-plan a été empruntée au site matériel. c'est ici. bg.jpg

Et voici l'image de l'objet à combiner. Mimmy_image.png

Exemple de code source

Voici le code source.

import tensorflow as tf
import cv2 as cv
import utils.dataset_util as dataset_util


def img_composition(bg, obj, left, top):
    """
Fonction qui synthétise l'arrière-plan et l'objet
    ----------
    bg : numpy.ndarray ~ image d'arrière-plan
    obj : numpy.ndarray ~ image d'objet
    left :int ~ Coordonnées à combiner (gauche)
    top :int ~ Coordonnées à combiner (ci-dessus)
    """
    bg_img = bg.copy()
    obj_img = obj.copy()

    bg_h, bg_w = bg_img.shape[:2]
    obj_h, obj_w = obj_img.shape[:2]
 
    roi = bg_img[top:top + obj_h, left:left + obj_w]
    mask = obj_img[:, :, 3]

    ret, mask_inv = cv.threshold(cv.bitwise_not(mask), 200, 255, cv.THRESH_BINARY)

    img1_bg = cv.bitwise_and(roi, roi, mask=mask_inv)
    img2_obj = cv.bitwise_and(obj_img, obj_img, mask=mask)
    dst = cv.add(img1_bg, img2_obj)

    bg_img[top: obj_h + top, left: obj_w + left] = dst
    
    return bg_img


def set_feature(image_string, label, label_txt, xmins, xmaxs, ymins, ymaxs):
    """
Une fonction qui définit les informations à écrire dans TFRecord
Pour utiliser cette fonction, elle se trouve dans le répertoire "détection d'objet" de l'API de détection d'objets TensorFlow.
Vous devez apporter la bibliothèque "util"
    ----------
    image_string :octets ~ Informations d'image combinées
    label :list ~ Numéro de tag annoté
    label_txt :list ~ Nom de tag annoté
    xmins, xmaxs, ymins, ymaxs :liste ~ 0 coordonnées annotées.0~1.Valeur représentée par 0
    """
    image_shape = tf.io.decode_jpeg(image_string).shape

    feature = {
        'image/encoded': dataset_util.bytes_feature(image_string),
        'image/format': dataset_util.bytes_feature('jpg'.encode('utf8')),
        'image/height': dataset_util.int64_feature(image_shape[0]),
        'image/width': dataset_util.int64_feature(image_shape[1]),
        'image/object/bbox/xmin': dataset_util.float_list_feature(xmins),
        'image/object/bbox/xmax': dataset_util.float_list_feature(xmaxs),
        'image/object/bbox/ymin': dataset_util.float_list_feature(ymins),
        'image/object/bbox/ymax': dataset_util.float_list_feature(ymaxs),

        #Si vous ne voulez donner qu'une seule information, vous pouvez utiliser cette fonction commentée (les types correspondent)
        # 'image/object/class/label': dataset_util.int64_feature(label),
        # 'image/object/class/text': dataset_util.bytes_feature(LABEL.encode('utf8')),
        
        #Si vous souhaitez en baliser deux ou plus sur une feuille, cliquez sur "_list_Utilisez une fonction qui contient ""
        #Bien sûr, vous ne pouvez en utiliser qu'un, il est donc recommandé de l'utiliser après tout
        'image/object/class/label': dataset_util.int64_list_feature(label),
        'image/object/class/text': dataset_util.bytes_list_feature(label_txt),
    }
    return tf.train.Example(features=tf.train.Features(feature=feature))

def main():

    #Chaque nom de chemin de fichier
    bg_image_path = './comp/bg.jpg'
    obj_img_path = './comp/Mimmy_image.png'
    comp_img_path = './comp/img_comp.jpg'
    tfr_filename = './mimmy.tfrecord'

    #Pour TF Record
    tag = {'Mimmy': 0, 'Kitty': 1, 'Mimelo': 2}
    xmins = []
    xmaxs = []
    ymins = []
    ymaxs = []
    class_label_list = []
    class_text_list = []
    datas = {}

    #Réglage du nom d'étiquette
    class_label = tag['Mimmy']

    #Arrière-plan de chargement
    bg_img = cv.imread(bg_image_path, -1)
    bg_img = cv.cvtColor(bg_img, cv.COLOR_RGB2RGBA)
    bg_h, bg_w = bg_img.shape[:2]

    #Chargement d'un objet
    obj_img = cv.imread(obj_img_path, -1)
    obj_img = cv.cvtColor(obj_img, cv.COLOR_RGB2RGBA)
    scale = 250 / obj_img.shape[1]
    obj_img = cv.resize(obj_img, dsize=None, fx=scale, fy=scale)
    obj_h, obj_w = obj_img.shape[:2]

    #Combinez l'arrière-plan et l'objet
    x = int(bg_w * 0.45) - int(obj_w / 2)
    y = int(bg_h * 0.89) - int(obj_h / 2)
    comp_img = img_composition(bg_img, obj_img, x, y)
    
    #Exporter l'image composite
    cv.imwrite(comp_img_path, comp_img)

    #Ajouté à la liste d'informations sur les coordonnées TFRecord
    xmins.append(x / bg_w)
    xmaxs.append((x + obj_w) / bg_w)
    ymins.append(y / bg_h)
    ymaxs.append((y + obj_h) / bg_h)

    #Ajout d'informations d'étiquette pour TFRecord
    class_label_list.append(class_label)
    class_text_list.append('Mimmy'.encode('utf8'))
    datas[comp_img_path] = class_label

    #Processus de création de TFRecord
    with tf.io.TFRecordWriter(tfr_filename) as writer:
        for data in datas.keys():
            image_string = open(data, 'rb').read()
            tf_example = set_feature(image_string, class_label_list, class_text_list, xmins, xmaxs, ymins, ymaxs)
            writer.write(tf_example.SerializeToString())

if __name__ == "__main__":
    main()

Voici l'image créée après l'exécution du programme. img_comp.jpg

Supplément de code source

Comme mentionné dans les commentaires du code source, veuillez noter que la bibliothèque incluse dans l'API de détection d'objets TensorFlow est requise.

Cette fois, dans un souci d'explication, j'ai introduit une image pour la composition et le code source pour générer un fichier TFRecord. Cependant, en réalité, je pense qu'il est nécessaire de générer un grand nombre de fichiers TFRecord à partir de beaucoup plus d'images.

Dans mon cas, comment sélectionner et combiner au hasard des images du dossier spécifié, J'ai essayé une méthode de production de masse en faisant les coordonnées à combiner un peu au hasard.

Si vous pouvez trouver divers conseils pour la production de masse de données sur les enseignants à la suite de cet article, je serais ravi de vous entendre.

Vérifiez le contenu du TFRecord créé

Enfin, juste au cas où, le contenu du fichier TFRecord que je viens de créer, Vérifions avec la méthode introduite au premier semestre.

features {
  feature {
    key: "image/encoded"
    value {
      bytes_list {
        value: "\377\330\377...
        .....(Comme il s'agit d'une grande quantité, il est omis)..."
      }
    }
  }
  feature {
    key: "image/format"
    value {
      bytes_list {
        value: "jpg"
      }
    }
  }
  feature {
    key: "image/height"
    value {
      int64_list {
        value: 1397
      }
    }
  }
  feature {
    key: "image/object/bbox/xmax"
    value {
      float_list {
        value: 0.5151041746139526
      }
    }
  }
  feature {
    key: "image/object/bbox/xmin"
    value {
      float_list {
        value: 0.38489583134651184
      }
    }
  }
  feature {
    key: "image/object/bbox/ymax"
    value {
      float_list {
        value: 0.9878310561180115
      }
    }
  }
  feature {
    key: "image/object/bbox/ymin"
    value {
      float_list {
        value: 0.7916964888572693
      }
    }
  }
  feature {
    key: "image/object/class/label"
    value {
      int64_list {
        value: 0
      }
    }
  }
  feature {
    key: "image/object/class/text"
    value {
      bytes_list {
        value: "Mimmy"
      }
    }
  }
  feature {
    key: "image/width"
    value {
      int64_list {
        value: 1920
      }
    }
  }
}

Comme mentionné ci-dessus, vous ne devez le faire qu'une seule fois, alors vérifiez correctement le contenu et Si c'est la valeur prévue, il n'y aura pas de problème!

à la fin

Pour les données au format TFRecord utiles pour la détection d'objets dans TensorFlow C'était un long article, mais je l'ai résumé pour autant que je puisse comprendre.

Nous espérons que cet article élargira la gamme de création de données sur les enseignants. Merci d'avoir lu jusqu'au bout.

Recommended Posts

Mémorandum de création de fichier TFRecord pour la détection d'objets
Détection d'objet (Détecteur MultiBox Single Shot) Interface graphique de création de fichier XML pour les données d'image
Créer un fichier
Détection de fichier vide
Mémorandum pour moi ③ Édition de fichiers de base avec vi
Outil de création de données d'entraînement pour la détection d'objets OpenCV
Mémorandum Freecad (pour moi)
[Pour les débutants] J'ai essayé d'utiliser l'API Tensorflow Object Detection