【Deep Learning】Keras-yolo3のパッケージ化
Keras-yolo3の簡単なパッケージ化の例を載せておきます。
フォルダ構成は以下のようになっていて、各種フォルダからsrc内のKerasYolo3を呼び出すイメージになります。
KerasYolo3フォルダないで__init__.pyファイルを作成し、以下のように記述します。
from src.kerasYolo3.image_detection import ImageDetector
これにより、KerasYolo3をパッケージとして認識され、import KerasYolo3をした際に、image_detectionモジュールもimportされるようになります。
以下、image_detectionモジュールです。簡単なYoLO呼び出しモジュールになります。
import colorsys import os,sys from timeit import default_timer as timer import numpy as np from keras import backend as K from keras.models import load_model from keras.layers import Input from PIL import Image, ImageFont, ImageDraw """ call yolo modules from yolo folder here """ from .yolo import YOLO class ImageDetector(): def __init__(self, model_path, classes_path): try: if not os.path.isfile(model_path): print('model path does not exist') return None if not os.path.isfile(classes_path): print('class path does not exist') return None self.yolo = YOLO(model_path=model_path, classes_path=classes_path) except Exception as e: print('Open Model Error:', e) return None # detect image function def detect_image(self, img_path): try: image = Image.open(img_path) except Exception as e: print('Open Image Error:', img_path, e) return None, None else: r_image, result = self.yolo.detect_image(image) return r_image, result def close_session(self): self.yolo.close_session()
srcと同じフォルダ内で実行ファイルを作成します。以下のようにモジュール呼び出しをしてみます。
import src.kerasYolo3 as yolo imageDetect = yolo.ImageDetector("model_data/種類モデル/type-model.h5", "class/種類クラス/class.txt")
実行すると、クラスから上手くインスタンスが作成されることがありあります。
【Deep Learning】アノテーションの効果
画像認識AI作成に当たって行うであろう、アノテーションについて、アノテーションの仕方によるAIの画像認識の仕方の違いについて記述する。
1. 概要
1枚の画像に対して、複数認識対象がある画像に対して、ある1対象のみをアノテーションする場合と、全対象をアノテーションした場合での学習結果、及びテスト結果を表す。 以下、それぞれのモデルを旧モデル、新モデルと表す。
2. 学習結果
旧モデルと新モデルの比較として、それぞれ同様のテストデータ(学習で使用した既知のデータ)を100枚読み込ませた結果を 以下のフォーマットで比較(新モデルに関しては同様に1000枚テストデータでテストを行って結果も追加)
・クラス正誤マトリックス
・学習時の損失の推移
3. アノテーションを比較
旧アノテーション画像は基本的に対象を1つに絞ってアノテーションを行っている。対して、新アノテーションでは画像に写っている対象を出来る限り全てアノテーションを行っている。
例で表したように同様なアノテーションを合計1517枚行い、そのうちの9割を学習に使用し、1割を内部のテスト(今回行ったテストは別でyolo内で行っているテスト)を行った。
4. 認識結果
5. 考察
結果を見る限り、モデルの大幅な向上が見られたのは明白である。課題としては、未知のデータを使用したテスト結果も必要となる。(同様に100枚で実行)
また、結果から以下の2点で考えられることがある。
モデル精度と学習の損失推移の関係性
アノテーションの増減の影響
【モデル精度と学習の損失推移の関係性】
基本的に損失は低いに越したことはなく、学習の度にグラフ上で緩やかな曲線を描いて収束していくのが一般的に最適な学習を行っているといえる。その点、今回の学習は回数が一見少なく、十分に学習を行っていないように見えるが、画像認識に関しては情報量が多く、1度の学習で1000枚以上認識を行っており、他の単純な入力値の少ない学習と比べて学習回数が少ないと考える。そう考えた時に、今回の両モデルの学習は急激に損失が上がるなど過学習をしておらず、比較的最適な学習であったと言える。
そこで本題に入るが、2つの学習結果を比較した時に損失の大きさが新モデルの方が1.5倍程大きな値で収束しているのがわかる。しかし、テスト結果レポートを比較すると、新モデルの方が大幅に向上した精度が確認出来る。
故に学習時の損失が直接、モデルの精度を表すわけではないとわかる。原因として考えられるのは、1枚の画像に対するアノテーションの個数が増えたことにより、認識後の評価回数が増えたことが起因すると考えられる。
【アノテーションの増減の影響】
今回、画像の例としてブドウを認識対象として両モデルを比較したが、アノテーションの増減によって、対象の認識の仕方が変化したと考えられる。というのも、旧モデルで認識した際、ブドウ3個全体を1つのブドウと捉えて認識している。ブドウを認識すること自体は両モデル共に出来ているが、正確な認識領域の抽出に関しては、新モデルの方が見てわかるように結果が良いとわかる(ブドウの個数が合っている点)。また、領域の正確さはIOUという方式を用いて数値化することも可能であり、ある画像に対して、「何が写っている」から「どこに何が写っている」がある程度の精度で実現可能であると考えられる。
【Deep Learning】Kersa-yolo3の学習自動化
Keras-yolo3における学習リストを用いた学習の自動化を説明します。学習リストをまとめたリストを読み込ませ、1行ずつ学習を行う仕組みです。
前提として学習リストが作成済で、そのまとめリストが以下のようになります。
学習リスト/水増しデータ/調整データ/通常_上下反転_左右反転_90度回転_270度回転_上下反転左右反転/品種/6_桃/train.txt 学習リスト/水増しデータ/調整データ/通常_上下反転_左右反転_90度回転_270度回転_上下反転左右反転/品種/7_さくらんぼ/train.txt
例のパスは水増しデータ、水増しした元データ、水増し内容、種類または品種か、品種、学習リストの順になっています。
パスの構成は学習の構成に依存します。以下のスクリプトでパスを扱っている箇所を編集する必要があります。
import os, glob, shutil import numpy as np import keras.backend as K from keras.layers import Input, Lambda from keras.models import Model from keras.optimizers import Adam from keras.callbacks import TensorBoard, ModelCheckpoint, ReduceLROnPlateau, EarlyStopping from yolo3.model import preprocess_true_boxes, yolo_body, tiny_yolo_body, yolo_loss from yolo3.utils import get_random_data import datetime import matplotlib.pyplot as plt train_list_file = 'train_list.txt' train_list = [] with open(train_list_file, 'r', encoding='UTF-8') as f: line = f.readline() while line: train_list.append(line.replace("\n", "")) line = f.readline() log_path = 'logs' classes_path = 'model_data' #クラスパス anchors_path = 'model_data/yolo_anchors.txt' #これはyolo設定なのでとりあえずデフォルト first_weight = 'model_data/darknet53_weights.h5' def get_classes(classes_path): '''loads the classes''' with open(classes_path) as f: class_names = f.readlines() class_names = [c.strip() for c in class_names] return class_names def get_anchors(anchors_path): '''loads the anchors from a file''' with open(anchors_path) as f: anchors = f.readline() anchors = [float(x) for x in anchors.split(',')] return np.array(anchors).reshape(-1, 2) def create_model(input_shape, anchors, num_classes, load_pretrained=True, freeze_body=2, weights_path='model_data/yolo_weights.h5'): '''create the training model''' K.clear_session() # get a new session image_input = Input(shape=(None, None, 3)) h, w = input_shape num_anchors = len(anchors) y_true = [Input(shape=(h//{0:32, 1:16, 2:8}[l], w//{0:32, 1:16, 2:8}[l], \ num_anchors//3, num_classes+5)) for l in range(3)] model_body = yolo_body(image_input, num_anchors//3, num_classes) print('Create YOLOv3 model with {} anchors and {} classes.'.format(num_anchors, num_classes)) if load_pretrained: model_body.load_weights(weights_path, by_name=True, skip_mismatch=True) print('Load weights {}.'.format(weights_path)) if freeze_body in [1, 2]: # Freeze darknet53 body or freeze all but 3 output layers. num = (185, len(model_body.layers)-3)[freeze_body-1] for i in range(num): model_body.layers[i].trainable = False print('Freeze the first {} layers of total {} layers.'.format(num, len(model_body.layers))) model_loss = Lambda(yolo_loss, output_shape=(1,), name='yolo_loss', arguments={'anchors': anchors, 'num_classes': num_classes, 'ignore_thresh': 0.5})( [*model_body.output, *y_true]) model = Model([model_body.input, *y_true], model_loss) return model def create_tiny_model(input_shape, anchors, num_classes, load_pretrained=True, freeze_body=2, weights_path='model_data/tiny_yolo_weights.h5'): '''create the training model, for Tiny YOLOv3''' K.clear_session() # get a new session image_input = Input(shape=(None, None, 3)) h, w = input_shape num_anchors = len(anchors) y_true = [Input(shape=(h//{0:32, 1:16}[l], w//{0:32, 1:16}[l], \ num_anchors//2, num_classes+5)) for l in range(2)] model_body = tiny_yolo_body(image_input, num_anchors//2, num_classes) print('Create Tiny YOLOv3 model with {} anchors and {} classes.'.format(num_anchors, num_classes)) if load_pretrained: model_body.load_weights(weights_path, by_name=True, skip_mismatch=True) print('Load weights {}.'.format(weights_path)) if freeze_body in [1, 2]: # Freeze the darknet body or freeze all but 2 output layers. num = (20, len(model_body.layers)-2)[freeze_body-1] for i in range(num): model_body.layers[i].trainable = False print('Freeze the first {} layers of total {} layers.'.format(num, len(model_body.layers))) model_loss = Lambda(yolo_loss, output_shape=(1,), name='yolo_loss', arguments={'anchors': anchors, 'num_classes': num_classes, 'ignore_thresh': 0.7})( [*model_body.output, *y_true]) model = Model([model_body.input, *y_true], model_loss) return model def data_generator(annotation_lines, batch_size, input_shape, anchors, num_classes): '''data generator for fit_generator''' n = len(annotation_lines) i = 0 while True: image_data = [] box_data = [] for b in range(batch_size): if i==0: np.random.shuffle(annotation_lines) image, box = get_random_data(annotation_lines[i], input_shape, random=True) image_data.append(image) box_data.append(box) i = (i+1) % n image_data = np.array(image_data) box_data = np.array(box_data) y_true = preprocess_true_boxes(box_data, input_shape, anchors, num_classes) yield [image_data, *y_true], np.zeros(batch_size) def data_generator_wrapper(annotation_lines, batch_size, input_shape, anchors, num_classes): n = len(annotation_lines) if n==0 or batch_size<=0: return None return data_generator(annotation_lines, batch_size, input_shape, anchors, num_classes) def save_history(history, path): plt.plot(history.history['loss'],"o-",label="loss") plt.plot(history.history['val_loss'],"o-",label="val_loss") plt.title('model loss') plt.xlabel('epoch') plt.ylabel('loss') plt.legend(loc='lower right') #plt.tight_layout() plt.savefig(os.path.join(path, 'history.png')) plt.show() for train in train_list: """ make log folder """ if not os.path.isfile(train): print("train file does not exist:", train) exit(1) target_type = train.split("/")[-2] if target_type == "種類": classes_file = os.path.join(classes_path, target_type, "class.txt") train_type = train.split("/")[-3] train_source = train.split("/")[-4] log_dir = os.path.join(log_path, train_source, train_type, target_type) else: veriety = train.split("/")[-2] target_type = train.split("/")[-3] train_type = train.split("/")[-4] train_source = train.split("/")[-5] addition = train.split("/")[-6] classes_file = os.path.join(classes_path, target_type, veriety, "class.txt") log_dir = os.path.join(log_path, addition, train_source, train_type, target_type, veriety) if not os.path.isdir(log_dir): os.makedirs(log_dir) print("log folder made:", log_dir) shutil.copyfile(train, os.path.join(log_dir, "train.txt")) print("copy", train, "to", os.path.join(log_dir, "train.txt")) shutil.copyfile(classes_file, os.path.join(log_dir, "class.txt")) print("copy", classes_file, "to", os.path.join(log_dir, "class.txt")) """ training preparetion """ class_names = get_classes(classes_file) num_classes = len(class_names) anchors = get_anchors(anchors_path) # 画像のサイズを416x416とする input_shape = (416,416) # multiple of 32, hw # モデルのインスタンス作成 model = create_model(input_shape, anchors, num_classes, freeze_body=2, weights_path=first_weight) # make sure you know what you freeze logging = TensorBoard(log_dir=log_dir) checkpoint = ModelCheckpoint(os.path.join(log_dir, 'ep{epoch:03d}-loss{loss:.3f}-val_loss{val_loss:.3f}.h5'), monitor='val_loss', save_weights_only=True, save_best_only=True, period=3) reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.1, patience=3, verbose=1) # ある条件で学習をストップさせる設定 early_stopping = EarlyStopping(monitor='val_loss', min_delta=0, patience=10, verbose=1) # 訓練データと検証データに分けるとこ(とりあえずランダムで9:1) val_split = 0.1 with open(train) as f: lines = f.readlines() np.random.seed(10101) np.random.shuffle(lines) np.random.seed(None) num_val = int(len(lines)*val_split) num_train = len(lines) - num_val """ training """ # Train with frozen layers first, to get a stable loss. # Adjust num epochs to your dataset. This step is enough to obtain a not bad model. if True: model.compile(optimizer=Adam(lr=1e-3), loss={ # use custom yolo_loss Lambda layer. 'yolo_loss': lambda y_true, y_pred: y_pred}) batch_size = 8 print('Train on {} samples, val on {} samples, with batch size {}.'.format(num_train, num_val, batch_size)) model.fit_generator(data_generator_wrapper(lines[:num_train], batch_size, input_shape, anchors, num_classes), steps_per_epoch=max(1, num_train//batch_size), validation_data=data_generator_wrapper(lines[num_train:], batch_size, input_shape, anchors, num_classes), validation_steps=max(1, num_val//batch_size), epochs=10, initial_epoch=0, callbacks=[logging, checkpoint]) model.save_weights(os.path.join(log_dir, 'trained_weights_stage_1.h5')) # Unfreeze and continue training, to fine-tune. # Train longer if the result is not good. if True: for i in range(len(model.layers)): model.layers[i].trainable = True model.compile(optimizer=Adam(lr=1e-4), loss={'yolo_loss': lambda y_true, y_pred: y_pred}) # recompile to apply the change print('Unfreeze all of the layers.') batch_size = 8 # note that more GPU memory is required after unfreezing the body print('Train on {} samples, val on {} samples, with batch size {}.'.format(num_train, num_val, batch_size)) history = model.fit_generator(data_generator_wrapper(lines[:num_train], batch_size, input_shape, anchors, num_classes), steps_per_epoch=max(1, num_train//batch_size), validation_data=data_generator_wrapper(lines[num_train:], batch_size, input_shape, anchors, num_classes), validation_steps=max(1, num_val//batch_size), epochs=100, initial_epoch=10, callbacks=[logging, checkpoint, reduce_lr, early_stopping]) model.save_weights(os.path.join(log_dir, 'trained_weights_final.h5')) save_history(history, log_dir) # Further training if needed. del model K.clear_session()
最近ネタ切れです。今年はAI学習とAWSを突き進むか悩み物です。資格取得もあり、時間が許すなら他の分野にも手を出したいです。
【Deep Learning】画像水増し自動化
今回はKeras-yolo3の学習で使用する画像の水増しを自動化するツールの備忘録を載せておきます。フォルダ構成等は合わせてもらう必要ありますが、基本的な動作としては指定したフォルダ内の画像に対して指定した水増し処理を行って、新たにフォルダを作成して画像を生成していきます。
コードは以下のようになります。
import matplotlib.pyplot as plt import matplotlib.image as mpimg import os from PIL import Image, ImageOps, ImageEnhance import glob import shutil import sys #インプットの画像フォルダ inputImageFolderPath = '../学習データ/混合データ/品種/image' #フルーツ固有ですが、全ての品種に対して処理する場合はTrue allFruits = True # 0:彩度変更, 1:コントラスト変更, 2:明度変更, 3:シャープネス変更, 4:拡大・縮小, 5:上下反転, 6:左右反転, 7 processList = [8] # 出力のフォルダを指定。いじらなくていいはずです。 outputRoot = '../学習データ/水増しデータ/混合データ' # 入力テキストフォルダ。いじらない。 inputTextFolderPath = inputImageFolderPath.replace('image', 'annotation') # 彩度調整パラメータ。1.0以上で明るく、1.0未満で暗く saturationRatio = 1.5 # コントラスト調整パラメータ。1.0以上で明るく、1.0未満で暗く contrastRatio = 1.5 # 明度調整パラメータ。1.0以上で明るく、1.0未満で暗く brightnessRatio = 1.5 # シャープネス調整パラメータ。1.0以上でくっきり、1.0未満でぼやける sharpnessRatio = 1.5 # 大きさ調整パラメータ。縦横がこの倍率で大きくなります。 expansionRatio = 1.2 def makeFolder(root, folderName, imageFile): outputPath = os.path.join(root, folderName) if not os.path.exists(outputPath): os.mkdir(outputPath) outputPath = os.path.join(outputPath, imageFile.split('\\')[-5]) if not os.path.exists(outputPath): os.mkdir(outputPath) outputAnnotationPath = os.path.join(outputPath, "annotation") if not os.path.exists(outputAnnotationPath): os.mkdir(outputAnnotationPath) outputImagePath = os.path.join(outputPath, "image") if not os.path.exists(outputImagePath): os.mkdir(outputImagePath) imagePathList = imageFile.split('\\') outputAnnotationPath = os.path.join(outputAnnotationPath, imagePathList[-3]) if not os.path.exists(outputAnnotationPath): os.mkdir(outputAnnotationPath) outputImagePath = os.path.join(outputImagePath, imagePathList[-3]) if not os.path.exists(outputImagePath): os.mkdir(outputImagePath) outputAnnotationPath = os.path.join(outputAnnotationPath, imagePathList[-2]) if not os.path.exists(outputAnnotationPath): os.mkdir(outputAnnotationPath) outputAnnotationPath = os.path.join(outputAnnotationPath, 'classes.txt') with open(outputAnnotationPath, mode = 'w') as f: f.write("") outputImagePath = os.path.join(outputImagePath, imagePathList[-2]) if not os.path.exists(outputImagePath): os.mkdir(outputImagePath) def decideOutputFolderName(): outputFolderName = "" for process in processList: if process == 0: outputFolderName += "彩度" + str(saturationRatio) elif process == 1: outputFolderName += "コントラスト" + str(contrastRatio) elif process == 2: outputFolderName += "明度" + str(brightnessRatio) elif process == 3: outputFolderName += "シャープネス" + str(sharpnessRatio) elif process == 4: outputFolderName += "大きさ" + str(expansionRatio) elif process == 5: outputFolderName += "上下反転" elif process == 6: outputFolderName += "左右反転" elif process == 7: outputFolderName += "90度回転" elif process == 8: outputFolderName += "270度回転" return outputFolderName def copy(inputObject, outputObject): if os.path.exists(inputObject): shutil.copy(inputObject, outputObject) def processing(image, coorList): for process in processList: if process == 0: image = changeSaturation(image, saturationRatio) elif process == 1: image= changeContrast(image, contrastRatio) elif process == 2: image = changeBrightness(image, brightnessRatio) elif process == 3: image = changeSharpness(image, sharpnessRatio) elif process == 4: image, coorList = changeSize(image, coorList, expansionRatio) elif process == 5: image, coorList = flipVertical(image, coorList) elif process == 6: image, coorList = flipHorizontal(image, coorList) elif process == 7: image, coorList = rotation90(image, coorList) elif process == 8: image, coorList = rotation270(image, coorList) return image, coorList def changeSaturation(image, ratio): saturation = ImageEnhance.Color(image) return saturation.enhance(ratio) def changeContrast(image, ratio): contrast = ImageEnhance.Contrast(image) return contrast.enhance(ratio) def changeBrightness(image, ratio): brightness = ImageEnhance.Brightness(image) return brightness.enhance(ratio) def changeSharpness(image, ratio): sharpness = ImageEnhance.Sharpness(image) return sharpness.enhance(ratio) def changeSize(image, coorList, ratio): w, h = image.size resizedImage = image.resize((int(w * ratio), int(h * ratio))) resizedCoorList = [] for oneCoorList in coorList: xmin = int(oneCoorList[0]) ymin = int(oneCoorList[1]) xmax = int(oneCoorList[2]) ymax = int(oneCoorList[3]) resizedCoorList.append([str(int(xmin * ratio)), str(int(ymin * ratio)), str(int(xmax * ratio)), str(int(ymax * ratio)), oneCoorList[4]]) return resizedImage, resizedCoorList def flipVertical(image, coorList): flippedImage = ImageOps.flip(image) flippedCoorList = [] w, h = image.size for oneCoorList in coorList: xmin = int(oneCoorList[0]) ymin = int(oneCoorList[1]) xmax = int(oneCoorList[2]) ymax = int(oneCoorList[3]) flippedCoorList.append([str(xmin), str(h - ymax), str(xmax), str(h - ymin), oneCoorList[4]]) return flippedImage, flippedCoorList def flipHorizontal(image, coorList): mirrorImage = ImageOps.mirror(image) mirrorCoorList = [] w, h = image.size for oneCoorList in coorList: xmin = int(oneCoorList[0]) ymin = int(oneCoorList[1]) xmax = int(oneCoorList[2]) ymax = int(oneCoorList[3]) mirrorCoorList.append([str(w - xmax), str(ymin), str(w - xmin), str(ymax), oneCoorList[4]]) return mirrorImage, mirrorCoorList def rotation90(image, coorList): rotatedImage = image.rotate(90, expand = True) rotatedCoorList = [] w, h = image.size for oneCoorList in coorList: xmin = int(oneCoorList[0]) ymin = int(oneCoorList[1]) xmax = int(oneCoorList[2]) ymax = int(oneCoorList[3]) rotatedCoorList.append([str(ymin), str(w - xmax), str(ymax), str(w - xmin), oneCoorList[4]]) return rotatedImage, rotatedCoorList def rotation270(image, coorList): rotatedImage = image.rotate(270, expand = True) rotatedCoorList = [] w, h = image.size for oneCoorList in coorList: xmin = int(oneCoorList[0]) ymin = int(oneCoorList[1]) xmax = int(oneCoorList[2]) ymax = int(oneCoorList[3]) rotatedCoorList.append([str(h - ymax), str(xmin), str(h - ymin), str(xmax), oneCoorList[4]]) return rotatedImage, rotatedCoorList outputFolderName = decideOutputFolderName() imageName = r'\*\*.jpg' if allFruits: imageName = r'\*\*\*.jpg' ### folder作成 for inputImageFile in glob.glob(inputImageFolderPath + imageName): splitInputImageFile = inputImageFile.split('/') if 'Thumbs.jpg' in splitInputImageFile[-1]: continue makeFolder(outputRoot, outputFolderName, inputImageFile) ### 画像ファイル取得 splitinputImageFolderPath = inputImageFolderPath.split('/') outputImageFolderPath = os.path.join(outputRoot, outputFolderName, splitinputImageFolderPath[-3], splitinputImageFolderPath[-2], splitinputImageFolderPath[-1]) print(inputImageFolderPath) for image in glob.glob(os.path.join(inputImageFolderPath, "**", "*.jpg"), recursive=True): splitImage = image.split('/') if 'Thumbs.jpg' in splitImage[-1]: continue originalImage = Image.open(image).convert("RGB") originalText = image.replace("image", "annotation", 1).replace(".jpg", ".txt") coorList = [] if os.path.exists(originalText): with open(originalText) as f: for s_line in f: if len(s_line.split(',')) == 1: pass else: coorList.append(s_line.split(',')) ### 画像水増し処理 processedImage, processedCoorList = processing(originalImage, coorList) ### 出力 outputImageFolder = os.path.join(outputRoot, outputFolderName, splitImage[-5], splitImage[-4], splitImage[-3], splitImage[-2]) outputImageFile = os.path.join(outputImageFolder, splitImage[-1]) if not os.path.isdir(outputImageFolder): print("folder made:", outputImageFolder) os.makedirs(outputImageFolder) processedImage.save(outputImageFile) outputTextFolder = outputImageFolder.replace("image", "annotation", 1) if not os.path.isdir(outputTextFolder): print("folder made:", outputTextFolder) os.makedirs(outputTextFolder) outputTextFile = outputImageFile.replace("image", "annotation").replace(".jpg", ".txt") with open(outputTextFile, mode = 'w') as f: for oneCoorList in processedCoorList: f.write(oneCoorList[0] + "," + oneCoorList[1] + "," + oneCoorList[2] + "," + oneCoorList[3] + "," + oneCoorList[4])
機会があれば、ぞれぞれの水増し結果も見てみましょう。
【Deep Learning】Keras-yolo3でアノテーション補助
前回まででアノテーションツールなどの紹介を行いました。しかし、アノテーション作業自体は地味で
時間がとてもかかります。そこで予めモデルを使って大まかな予測を行い、アノテーションをある程度行ってしまい、
アノテーションのズレなどを補正するような作業を行えば、素直に一からアノテーションするよりも大幅に時間短縮になります。
以下、コードを参考までに
import sys sys.path.append('../') import matplotlib.pyplot as plt import os, glob from src import kerasYolo3 from src.util import * target_dir = '../../学習データ/追加データ/整理/image' model_path = '../model_data/種類モデル/type-model.h5' classes_path = '../class/種類クラス/class.txt' class_list = get_class_list(classes_path) print(class_list) model = kerasYolo3.ImageDetector(model_path, classes_path) resultNum=0 error_list=[] for type_path in glob.glob(os.path.join(target_dir, '*')): if not os.path.isdir(type_path): continue print(os.path.basename(type_path)) annotation_path = type_path.replace('image', 'annotation') if not os.path.isdir(annotation_path): os.makedirs(annotation_path) print("folder made:",annotation_path) for veriety_path in glob.glob(os.path.join(type_path, '*')): annotation_path = veriety_path.replace('image', 'annotation') if not os.path.isdir(annotation_path): os.makedirs(annotation_path) print("folder made:",annotation_path) print(" "+os.path.basename(veriety_path)) for img in glob.glob(os.path.join(veriety_path, '*')): try: r_img, results = model.detect_image(img) annotation_path = img.replace('image', 'annotation').replace('.jpg', '.txt').replace('.png', '.txt').replace('.jpeg', '.txt').replace('.gif', '.txt') with open(annotation_path, 'w', encoding='UTF-8') as f: for result in results: result = [str(r) for r in result] f.write(",".join(result[2:]) + "," + str(class_list.index(result[0])) + "\n") resultNum += 1 except Exception as e: error_list.append(img + '\n' + str(e.__traceback__)) print(" "+img) print("Done:",resultNum) print("Error:",len(error_list)) print("Error list below") print(error_list)
KerasYoloクラスに関しては自作になります。次回時間あれば詳しく説明します。
簡単にyoloをパッケージ化した物になります。
【Deepl Learning】学習リストの作成
Keras-yolo3での学習に使用される学習リストの作成備忘録です。
まだ、学習のスクリプトが決まったのが出来てないので、部品的なスクリプトの小出しです。
以下、スクリプトです。基本的にパスを変えて動作させます。
例としてフルーツの画像判定を使ってます。種類と品種によって異なったモデルの学習リストを作成します。
#!/usr/bin/env python # coding: utf-8 # # フォルダパスから指定した長さの学習リストを作成する # ## モジュールインポート import os, glob, random # ## 定数宣言 target_path = '../train_set/追加データ(keras-yolo3)' #学習対象フォルダ train_name = 'train.txt' #学習リスト名 target_type = '品種' classes_path = 'model_data/品種モデル/6_桃' #クラスファイル名 output_path = '学習リスト' list_number = 100 # ## 学習リスト作成 if target_type == "品種": output_dir = os.path.join(output_path, os.path.basename(target_path), target_type, os.path.basename(classes_path), str(list_number)) else: output_dir = os.path.join(output_path, os.path.basename(target_path), target_type, str(list_number)) if not os.path.isdir(output_dir): os.makedirs(output_dir) print("folder made:", output_dir) class_names = [] classes_file = os.path.join(classes_path, "class.txt") with open(classes_file, 'r', encoding='UTF-8') as f: line = f.readline() while line: class_names.append(line.split("\n")[0]) line = f.readline() target_dir = os.path.join(target_path, target_type, "image") if target_type == "品種": target_dir = os.path.join(target_dir, os.path.basename(classes_path)) if not os.path.isdir(target_dir): print("folder does not exist:", target_dir) exit(1) train_list = [] for img_path in glob.glob(os.path.join(target_dir, '*')): if not os.path.isdir(img_path): continue class_name = os.path.basename(img_path).split("_")[1] class_index = class_names.index(class_name) count = 0 image_list = glob.glob(os.path.join(img_path, '**', '*.jpg'), recursive=True) random.shuffle(image_list) for img_file in image_list[:list_number]: annotation_file = img_file.replace('image', 'annotation').replace('.jpg', '.txt') if not os.path.isfile(annotation_file): continue with open(annotation_file, 'r', encoding='UTF-8') as f: # Keras-yolo3のフォーマットに合わせる # x_min, y_min, x_max, y_maxの順 line = f.readline() annotation_list = [] while line: annotation = line.split("\n")[0] annotation_list.append(annotation) line = f.readline() train_list.append([img_file, annotation_list]) count += 1 print("train_list:",len(train_list)) train_file = os.path.join(output_dir, train_name) with open(train_file, 'w', encoding='utf-8') as f: for line in train_list: jpg = line[0] last = line[1][-1] f.write(jpg+' ') for annotation in line[1]: f.write(annotation) f.write(' ') f.write('\n') print("train file made:",train_file)
最近忙しいので、部品スクリプトを小出しする感じになりそうです。
【DeepLearning】PascalVoc=>keras-yolo3変換
前回の続きです。前回はこちら
buffalokusojima.hatenablog.com
今回は前回作成したファイルのフォーマットをkeras-yolo3に変換するプログラム説明です。
以下、ソース参照
import os, glob from xml.etree import ElementTree target_dir = 'image' #対象のフォルダ output_dir = 'image' #アウトプットフォルダ classes_path = 'class.txt' #クラスファイル class_list = [] with open(classes_path, 'r', encoding='UTF-8') as f: line = f.readline() while line: class_list.append(line.replace('\n', '')) line = f.readline() print(class_list) file_count = 0 error_list = [] for file in glob.glob(os.path.join(target_dir, '*.xml')): """ 以下、XMLパースしていく """ try: tree = ElementTree.parse(file) elem = tree.getroot() objects = elem.findall("object") annotation_list = [] #アノテーションの数分回す for obj in objects: #ラベルをクラスファイルを元に数値化する label = class_list.index(obj.find("name").text) #bboxを取得し、座標を格納 for bbox in obj.findall("bndbox"): xmin = bbox.find("xmin").text xmax = bbox.find("xmax").text ymin = bbox.find("ymin").text ymax = bbox.find("ymax").text annotation_list.append([xmin, ymin, xmax, ymax, str(label)]) #取得したアノテーションを改行してファイルに記載する annotation_file = os.path.join(output_dir, os.path.basename(file).replace("xml", "txt")) with open(annotation_file, 'w', encoding='UTF-8') as f: for annotation in annotation_list: f.write(",".join(annotation)) f.write('\n') print("writing:", annotation_file) file_count += 1 except: error_list.append(file) print("file making done:", file_count) print("error list:", error_list)
以上のコードで指定したフォルダに対して実行すると
<annotation> <folder>image</folder> <filename>sample1.jpg</filename> <path>keras-yolo3/image/sample1.jpg</path> <source> <database>Unknown</database> </source> <size> <width>510</width> <height>340</height> <depth>3</depth> </size> <segmented>0</segmented> <object> <name>リンゴ</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>57</xmin> <ymin>96</ymin> <xmax>252</xmax> <ymax>311</ymax> </bndbox> </object> <object> <name>リンゴ</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>252</xmin> <ymin>105</ymin> <xmax>452</xmax> <ymax>314</ymax> </bndbox> </object> <object> <name>リンゴ</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>252</xmin> <ymin>25</ymin> <xmax>406</xmax> <ymax>150</ymax> </bndbox> </object> <object> <name>リンゴ</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>101</xmin> <ymin>39</ymin> <xmax>248</xmax> <ymax>141</ymax> </bndbox> </object> </annotation>
上記のようなxmlファイルが以下のようなtxtファイルとして新たに作成されます。
57,96,252,311,0 252,105,452,314,0 252,25,406,150,0 101,39,248,141,0
後は、学習リストを作成する際に、学習のフォーマットに合わせてこんファイルから座標を取得すれば学習が出来ます。
【おわりに】
次回からそろそろ学習スクリプトとかをやっていこうと思います。