横浜国立大学理工学部建築都市環境系学科卒
一級鉄筋技能士
YOLOを使って物体検知のAIの精度を向上させるには画像データと画像データに付随したアノテーションデータを用意する必要があります。
YOLOの場合、アノテーションデータはxmlファイルではなくtxtファイルである必要があります。
xmlファイル形式のアノテーションデータは持っているけれども、これをtxtファイル形式にするにはどうすれば良いかわからない方も多いのではないでしょうか。
この記事ではxmlファイルをtxtファイルに変換する方法について紹介していきます。
目次
アノテーションについて
アノテーションについて簡単に確認していきましょう。
labelImgにはPascalVOC形式とYOLO形式がある
アノテーションとはAIの学習データを作成する作業で、一般的に使用されているのはlabelImgというツールです。
アノテーションデータの保存形式には2つあり、PascalVOC形式とYOLO形式があります。
YOLOで物体検知をするにはtxtファイルでアノテーションデータを保存しなければならない
YOLOを使って物体検知をする際は、xml形式ではなくtxt形式でデータを用意する必要があります。
しかし、すでに用意してあったデータの形式がPascalVOC形式でxmlファイルだったということもあるでしょう。
その場合はxmlファイルをtxtファイルに変換しなければなりません。
xmlファイルをtxtファイルに変換する
xmlファイルをtxtファイルに変換する方法を見ていきましょう。
以下のコードを参考にさせていただきました。
Convert Pascal VOC XML Annotation files to YOLO format text files
Google Driveに画像、xmlファイルを用意する
以下がxmlファイルをtxtファイルに変換するコードです。
指定するGoogle Driveディレクトリはすべて同じもので問題ないです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
############ Please edit this section only.####################################################################################################### # The paths must end with '/'. absolutepath_of_directory_with_xmlfiles = '/content/drive/MyDrive/tekkin/' # It is okay to have a mix of xml files and images in the same directory. absolutepath_of_directory_with_imgfiles = '/content/drive/MyDrive/tekkin/' absolutepath_of_directory_with_yolofiles = '/content/drive/MyDrive/tekkin/' # Yolo files will be created under this directory. absolutepath_of_directory_with_classes_txt = '/content/drive/MyDrive/tekkin/' # You do not need to create classes.txt. classes.txt will be generated automatically. absolutepath_of_directory_with_error_txt = '/content/drive/MyDrive/tekkin/' # The file names of files that do not have a paired xml or image file will be written to a text file under this directory. ##################################################################################################################################################### import os import cv2 from lxml import etree from xml.etree import ElementTree from glob import glob class GetDataFromXMLfile: def __init__(self, xmlfile_path): self.xmlfile_path = xmlfile_path self.xmlfile_datalists_list = [] def get_datalists_list(self): self.parse_xmlfile() return self.xmlfile_datalists_list def parse_xmlfile(self): lxml_parser = etree.XMLParser(encoding='utf-8') xmltree = ElementTree.parse(self.xmlfile_path, parser=lxml_parser).getroot() for object in xmltree.findall('object'): xmlfile_datalist = [] class_name = object.find('name').text xmlfile_datalist.append(class_name) bndbox = object.find("bndbox") xmlfile_datalist.append(bndbox) self.xmlfile_datalists_list.append(xmlfile_datalist) img_filename = xmltree.find('filename').text self.add_data_to_datalist(img_filename) def add_data_to_datalist(self, img_filename): for xmlfile_datalist in self.xmlfile_datalists_list: xmin = float(xmlfile_datalist[1].find('xmin').text) ymin = float(xmlfile_datalist[1].find('ymin').text) xmax = float(xmlfile_datalist[1].find('xmax').text) ymax = float(xmlfile_datalist[1].find('ymax').text) bndbox_coordinates_list = [(xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax)] xmlfile_datalist[1] = bndbox_coordinates_list self.xmlfile_datalists_list.append(img_filename) self.xmlfile_datalists_list.append(self.xmlfile_path) class CreateYOLOfile: def __init__(self, xmlfile_datalists_list, classes_list): self.xmlfile_datalists_list = xmlfile_datalists_list self.xmlfile_path = self.xmlfile_datalists_list.pop() self.img_filename = self.xmlfile_datalists_list.pop() self.yolofile_path = absolutepath_of_directory_with_yolofiles + os.path.basename(self.xmlfile_path).split('.', 1)[0] + '.txt' self.classes_list = classes_list try: (self.img_height, self.img_width, _) = cv2.imread(absolutepath_of_directory_with_imgfiles + self.img_filename).shape self.create_yolofile() except: with open(absolutepath_of_directory_with_error_txt+'xmlfiles_with_no_paired.txt', 'a') as f: f.write(os.path.basename(self.xmlfile_path)+'\n') def create_yolofile(self): for xmlfile_datalist in self.xmlfile_datalists_list: yolo_datalist = self.convert_xml_to_yolo_format(xmlfile_datalist) with open(self.yolofile_path, 'a') as f: f.write("%d %.06f %.06f %.06f %.06f\n" % (yolo_datalist[0], yolo_datalist[1], yolo_datalist[2], yolo_datalist[3], yolo_datalist[4])) def convert_xml_to_yolo_format(self, xmlfile_datalist): class_name = xmlfile_datalist[0] self.add_class_to_classeslist(class_name) bndbox_coordinates_list = xmlfile_datalist[1] coordinates_min = bndbox_coordinates_list[0] coordinates_max = bndbox_coordinates_list[2] class_id = self.classes_list.index(class_name) yolo_xcen = float((coordinates_min[0] + coordinates_max[0])) / 2 / self.img_width yolo_ycen = float((coordinates_min[1] + coordinates_max[1])) / 2 / self.img_height yolo_width = float((coordinates_max[0] - coordinates_min[0])) / self.img_width yolo_height = float((coordinates_max[1] - coordinates_min[1])) / self.img_height yolo_datalist = [class_id, yolo_xcen, yolo_ycen, yolo_width, yolo_height] return yolo_datalist def add_class_to_classeslist(self, class_name): if class_name not in self.classes_list: self.classes_list.append(class_name) class CreateClasssesfile: def __init__(self, classes_list): self.classes_list = classes_list def create_classestxt(self): with open(absolutepath_of_directory_with_classes_txt + 'classes.txt', 'w') as f: for class_name in self.classes_list: f.write(class_name+'\n') xmlfiles_pathlist = glob(absolutepath_of_directory_with_xmlfiles + "/*.xml") classes_list = [] for xmlfile_path in xmlfiles_pathlist: process_xmlfile = GetDataFromXMLfile(xmlfile_path) xmlfile_datalists_list = process_xmlfile.get_datalists_list() CreateYOLOfile(xmlfile_datalists_list, classes_list) process_classesfile = CreateClasssesfile(classes_list) process_classesfile.create_classestxt() |
自分で用意したデータを取得する
今回は画像を100枚用意しました。
用意したデータを取得してみると、しっかり準備ができました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import glob import shutil from sklearn.model_selection import train_test_split import os data_path = "/content/drive/MyDrive/tekkin/" x_list = glob.glob(data_path + "*.jpg") + glob.glob(data_path + "*.JPG") y_list = glob.glob(data_path + "*.txt") x_train, x_test, y_train, y_test = train_test_split(x_list, y_list) print("学習数", len(x_train)) print("テスト数", len(x_test)) 学習数 75 テスト数 25 |
まとめ
この記事ではxmlファイルをtxtファイルに変換する方法について紹介しました。
初学者はエラー対応だけでもとても長い時間を取られてしまいます。
この記事が、物体検知に取り組んでいる企業の皆さんのお役に立てれば幸いです。