Pour préparer une banque d’image en vue de l’entrainement d’un réseau de neurones à la reconnaissance d’objet, il faut reconnaitre soit même les images de la base de données. C’est à dire leur donner un label et une zone de reconnaissance.
Ce tutoriel fait suite à l’article créer une banque d’image.
Objectif de la préparation
L’objectif est de créer des jeux de données qui permettront de faciliter l’entrainement avec les outils de TensorFlow, Yolo ou Keras
Pour ce faire, deux choix existent:
- Utiliser labelImg
- Créer une architecture de dossier et utiliser un script (entrainement avec Keras seulement)
Préparer une banque d’image avec labelImg
Vous pouvez télécharger et installer labelImg
- Linux
python3 -m pip install labelImg
labelImg
- Windows
Suivez les consignes de construction sur github. Vous pouvez aussi trouver un exécutable labelImg.exe
Ajouter une boite et un label
Lancer labelImg et sélectionner le dossier à l’aide du bouton « Open Dir ».
Pour chaque image, vous allez entourer l’objet à détecter et lui affecter un nom (label) à l’aide du bouton « Create RectBox ».
N.B.: évitez de dépasser de l’image lorsque vous dessinez la boîte. Cela peut poser des soucis lors de l’entrainement
Convertir au format PascalVOC
Convertir au format YOLO
N.B.: Vous pouvez sauvegarder les deux formats à la suite ou sauvegarder en VOC et convertir en YOLO à l’ai du script convert_voc_to_yolo.py
Préparer une banque d’image avec une architecture de dossier
L’idée est de placer les images dans des sous-dossiers du nom de la classe. Pour l’entrainement, la banque d’image doit contenir entre 1 et 3 dossiers: train, test, validation (les dossiers test et validation sont optionnels car ils peuvent être créés à partir du premier
N.B.: pour cette méthode il faut un seul objet par image
- images
- train
- cats
- dogs
- validation
- cats
- dogs
- train
Pour créer les fichiers contenant les info de nom et de zone de détection à partir des dossier images, vous pouvez utiliser le scripts generate_voc_files.py en modifiant:
- les chemins d’accès vers les différents dossiers (folders[‘train’/’validation’/’test’])
Les noms de classe seront définies par les noms de dossier et la zone de détection par la taille des images.
generate_voc_files.py
import glob import os import pickle import cv2 import xml.etree.ElementTree as ET import xml.dom.minidom from os import listdir, getcwd from os.path import join dirs = ['train', 'test'] classes = ['mug'] def getImagesInDir(dir_path): image_list = [] for filename in glob.glob(dir_path + '/**/*.png', recursive=True): image_list.append(filename) return image_list def generate_voc(image_path): #get image data dirname=os.path.dirname(image_path) foldername=dirname.split('\\')[-1] basename = os.path.basename(image_path) basename_no_ext = os.path.splitext(basename)[0] im = cv2.imread(image_path) h,w,c=im.shape root = ET.Element('annotation') folder = ET.SubElement(root, 'folder') folder.text=foldername filename = ET.SubElement(root, 'filename') filename.text=basename path = ET.SubElement(root, 'path') path.text=image_path source = ET.SubElement(root, 'source') database = ET.SubElement(source, 'database') database.text = 'Unknown' size = ET.SubElement(root, 'size') width = ET.SubElement(size, 'width') width.text='{}'.format(w) height = ET.SubElement(size, 'height') height.text='{}'.format(h) depth = ET.SubElement(size, 'depth') depth.text='{}'.format(c) segmented = ET.SubElement(root, 'segmented') segmented.text = '0' objec = ET.SubElement(root, 'object') name = ET.SubElement(objec, 'name') name.text=foldername pose = ET.SubElement(objec, 'pose') pose.text='Unspecified' truncated = ET.SubElement(objec, 'truncated') truncated.text='0' difficult = ET.SubElement(objec, 'difficult') difficult.text='0' bndbox = ET.SubElement(objec, 'bndbox') xmin = ET.SubElement(bndbox, 'xmin') xmin.text='{}'.format(0+5) ymin = ET.SubElement(bndbox, 'ymin') ymin.text='{}'.format(0+5) xmax = ET.SubElement(bndbox, 'xmax') xmax.text='{}'.format(w-5) ymax = ET.SubElement(bndbox, 'ymax') ymax.text='{}'.format(h-5) tree = ET.ElementTree(root) outxml=join(dirname,basename_no_ext+'.xml') tree.write(outxml) return outxml def convert(size, box): dw = 1./(size[0]) dh = 1./(size[1]) x = (box[0] + box[1])/2.0 - 1 y = (box[2] + box[3])/2.0 - 1 w = box[1] - box[0] h = box[3] - box[2] x = x*dw w = w*dw y = y*dh h = h*dh return (x,y,w,h) def convert_annotation(in_file): dirname=os.path.dirname(in_file) basename = os.path.basename(in_file) basename_no_ext = os.path.splitext(basename)[0] out_file = open(join(dirname, basename_no_ext + '.txt'), 'w') tree = ET.parse(in_file) root = tree.getroot() size = root.find('size') w = int(size.find('width').text) h = int(size.find('height').text) for obj in root.iter('object'): difficult = obj.find('difficult').text cls = obj.find('name').text if cls not in classes or int(difficult)==1: continue cls_id = classes.index(cls) xmlbox = obj.find('bndbox') b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text), float(xmlbox.find('ymax').text)) bb = convert((w,h), b) out_file.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n') cwd = getcwd() for dir_path in dirs: full_dir_path = join(cwd,dir_path) image_paths = getImagesInDir(full_dir_path) for image_path in image_paths: xml_path=generate_voc(image_path) #generate voc file convert_annotation(xml_path) #convert to yolo file print("Finished processing: " + dir_path)
Cette méthode permet d’avoir rapidement une base de données exploitable pour l’entrainement (TensorFlow et Yolo) même si la zone de reconnaissance est approximative.
N.B.: Une fois les fichiers XML et TXT créés, vous pouvez ouvrir lableImg pour affiner les labels ainsi que la zone de détection.