【Deep Learning】Keras-yolo3のパッケージ化

Keras-yolo3の簡単なパッケージ化の例を載せておきます。
フォルダ構成は以下のようになっていて、各種フォルダからsrc内のKerasYolo3を呼び出すイメージになります。

folder_architecture
フォルダ構成

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枚テストデータでテストを行って結果も追加)

・クラス正誤マトリックス
・学習時の損失の推移

old_confusion_matrix_100
旧モデルマトリックス(100枚)
new_confusion_matrix_100
新モデルマトリックス(100)
new_confusion_matrix_1000
新モデルマトリックス(1000)
old_history
旧モデル損失推移
new_history
新モデル損失推移

3. アノテーションを比較
アノテーション画像は基本的に対象を1つに絞ってアノテーションを行っている。対して、新アノテーションでは画像に写っている対象を出来る限り全てアノテーションを行っている。
例で表したように同様なアノテーションを合計1517枚行い、そのうちの9割を学習に使用し、1割を内部のテスト(今回行ったテストは別でyolo内で行っているテスト)を行った。

row_image
アノテーション元画像
old_annotation
アノテーション
new_annotation
アノテーション

4. 認識結果

result
認識結果

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

後は、学習リストを作成する際に、学習のフォーマットに合わせてこんファイルから座標を取得すれば学習が出来ます。


【おわりに】
次回からそろそろ学習スクリプトとかをやっていこうと思います。