Take’s diary

Macとマイコンに関すること--ワクワクの製作日記

YOLOオリジナルデータの学習

今回は今年のMaker faire tokyoで使ったAIジャンケンのデータ作成方法を書くことにします。AIの専門家では無いので、固有名詞の間違いはご容赦願います。

         

       今回のデータを使ってジャンケンの手をリアルタイム認識させています。

 YOLOを使った画像認識が早いのは分かりました。ただし練習でおおーっと思った画像認識に使うデータは、他人が用意したものです。自分の必要なデータをどんな用途で使うかで価値が決まって来ますよね。

 用意する学習データは1クラス数千枚単位ですから、アマチュアではチョットって感じです。せっかく苦労して用意しても、使えなかったら何にもならないわけで、今回は「データー作成はそれほどでもなかった」的な記事です。メイカーフェア出展に向けて、サーモグラフィーを利用したジャンケン認識用画像の種類は3クラスとなりますが、これ以上のクラス数でも作成方法の基本は同じです。

 次回は学習済みデータをiPhoneへ移植するまでを取り上げますが、今回の学習済みデータはYOLOが動く環境なら問題なく動作することを確認しています。作成時疑問に思っていたことについても、併せて説明して行きます。

参考資料は1つだけ

 Yoloを使った学習方法の説明は、ネット上でいろいろ見つけることができますが、基本は How to train YOLOv2 to detect custom objects

のようです。疑問に思ったときのヒントは、この記事のどこかに書いてありますので、あまり日本語の記事に惑わされず熟読することが大切。今回は動画から必要画像を取り出して、ただひたすらにクラス分け作業を行う、という方法です。

必要機器

 Mac      : iMacでもMacBookでも何でもOK

 Ubuntuマシン  :必ずNVIDIAのGPUが付いているマシン(今回は1080 Ti 1個)

 iPhone    :  iPhone7以上 (認識に今回はiPhoneを使ったのでUbuntuマシン

         でもOK)

 ソフト    :特に購入ソフトは無し

1.動画を用意

 今回はジャンケンですから、iPhoneで録画したこんな感じの動画を用意します。

f:id:TAKEsan:20180815114234p:plain

 赤外線カメラ画像なのでグレースケールデータだけです。後で説明しますが、クラス分けが大変なので1クラスのみで、あらゆる方向から動画を撮影しました。今回はグー、チョキ、パー それぞれの動画ファイルを名前を変えて用意しています。

 YOLOは同一画像で複数のクラスを認識するので、最初は複数のクラスが写っている必要があるかと思いましたが、そんなことは関係ないようです。また、撮影画像のピクセル数はあまり問題にはなりませんでした。結果的に、720pや1080p、540pが混在しても認識への影響は感じられません。

2.MacのiMoveを使って編集

 iMoveを使いましたが動画編集ソフトなら何でもOKです。上の画像のままでは右の方に余計な部分が写っているので、トリムすることと、対象クラスが写っていない不必要な画像をある程度削除しておくことが目的です。(iMoveではトリムでは無くクロップという単語を使います)最終的に720p、MP4で保存しました。

f:id:TAKEsan:20180815115732p:plain

3.動画を複数のjpg画像に変換

 ここからはUbuntuマシンを使います。まず画像変換ソフトをインストール。動画から画像に変換できるソフトなら何でも良いのですが、ffmpegを使いました。ffmpegはMacでもWindowsでも使えますので、特にUbuntuマシンで無くてもOKです。インストール方法は他の記事を参考にして下さい。

 新しいフォルダを作って、その中にmp4ファイルをコピーします。そのフォルダで以下のコマンドを実行。

   ffmpeg -i input.mp4 -vcodec mjpeg -r 5 image_%03d.jpg

    input.mp4:入力動画ファイル名

    image_%03d.jpg:出力ファイル名 

 image001.jpeg.....と名称の付いたファイルがたくさん作成されますが、この例で「image」部分はクラス毎に名称を変更した方が、後からデータを追加するときに便利です。今回のデータは基本8fpsくらいで、iMoveを使って30fpsで保存しています。そのままでは同じアングルの画像がたくさんできてしまうので、1/6ぐらいに間引いて作成しています。なので作った動画に合わせて-r 5部分を変更します。

 コマンドを実行すると、フォルダ内に連番の付いた大量のjpegファイルが作成されます。今回の場合はクラスが3つなので3回実行。当然ですが連番前の名称を変えていれば、同一フォルダ内に作成してもOKです

4.2回目の間引き

 できた画像を確認して、不必要な画像を削除します。よく注意しながら目標のクラスが写っていない画像や、重複があるものはドンドン削除してしまいます。重複が多い場合は、すべて削除してffmpeg の-r以下の数値を減らして再実行した方が早いかもしれません。ここでの削除作業は後の作業効率や認識結果に関わってくるので、重要です。

5.YOLOをインストール

 以下に沿ってインストール。今回はiPhoneとの連携のためV2.0を使いましたが、最新のものでOKです。

Installing Darknet

 GPUを使わなければ意味がありませんのでmakefaileの中のGPU=1 を有効にすることと、OpenCVはあらかじめインストールしておいて OPENCV=1にしておくことも忘れずに!!。OpenCVをインストールしないと、動画系のテストプログラムが実行できません。Yoloはmakeにさほど時間がかかりません。Caffeとは雲泥の差です。

6.ラベリングする(アノテーション)

 Yoloは上記で作ったイメージファイルの他に、これらに対応したTXTファイルが必要です。(イメージファイルのどの部分に対応するクラス画像があるかを示すテキストファイルで、イメージファイルの枚数分必要です)ここら辺が一番単調で、手作業では時間のかかる部分となります。今回ラベリングソフトはlabelimgを使いました。以下インストール方法ですが、すでにpython3をインストールしている場合は3,4行目は必要ありません。

labelimgと言うフォルダができるので、この中に自分の好きな名称のフォルダを作り、先ほど作成したイメージデータを全部コピーします。最初は3つのフォルダが必要かなとも思いましたが、クラス毎にimgデータのファイル郡の名称を変えていればごちゃ混ぜにしてOK。

 次にlabelimg/data の中にあるpredefined_class.txtを修正します(これが一番手っ取り早い)。最初は20クラスぐらいあらかじめ書き込んでありますが、今回は3クラスだけなので、既に書き込んであるクラス名を全部消し、自分の好きな名称でクラス名を改行して書き込みます。今回はguu、tyoki、paaにしました。これは必ず修正が必要です。

labelimgフォルダの中でプログラムを実行します。

 python3 labelimg.py

以下のような画面が表示されるので、次のような設定を行います。

f:id:TAKEsan:20180815230111p:plain

 左側 OpenDir で先ほど作ったイメージデータが入っているディレクトリを指定

    Change Save Dir で同じディレクトリを選択。イメージもアノテーション

    ファイルも同じディレクトリに入れちゃいます。

    さらにSaveの下にある PscalVOC を選択して、必ずYOLOに変更します。

    これを忘れると、最初からやり直しになるので注意

 そして上部のメニューバーからView->Single Class Modeにチェックを入れます。

Single Class Modeとはクラス名が同一の場合、連続でデータを入力できるので、1つのクラスに対応したjpeg画像がここで生きることになります。但しSingle Class Modeにすると最初だけClass名選択をする必要があります。

 今回は連番以外の名称をクラス別に変えているので途中クラス選択の必要がありますが、その場合は一時的にSingle Class Modeを外して違うクラスを選択後、またSingle Class Modeにします。とにかく少ないデータで一度使ってみて、コツをつかんで下さい。操作は直感でできちゃいます。

 画像中のクラス画像選択はE、 選択した画面を保存して次の画面に移る場合はWキーを押します。

 E-->マウスで対角指示-->W-->E-->マウスで対角指示-->W と連続してクラス指定が可能で、慣れると1枚の画像のアノテーション所要時間は2秒程度。上手く行けば1時間で1500枚以上のクラス分け(アノテーション)が可能です。但ししばらく作業をしていると、キーボードを操作している左手がしびれてしまいますのでご注意を!!。

 ここで1枚の画像に複数のクラス画像が入っている場合、いちいちクラス名を指定しなければ無いのでグンと効率が落ちます。

ってことをマスターしてしまえば、基本データ作りは一応完成です。ここで作ったデータはtiny_Yoloでも標準Yoloでも使用できます。

 Yoloのアノテーションのファイルは以下のようになっています。内容が違っている場合や、クラス番号が正常で無い場合はlabelimgの設定を疑って下さい。

f:id:TAKEsan:20180816102939p:plain

最初の数値がクラス番号で、今回は0~2の3種類。この場合は画像に存在するクラスがTyoki=1で1個のみ。その次の4つの数値はTyokiの範囲を示します(yoloは比率で表すようです)

f:id:TAKEsan:20180816103351p:plain

       この場合は1つの画像にGuu=0とPaa=2が存在していることを示します。

7. またまたダメ押しのチェック

 これで終了とも思えるのですが、必ず間違いがあるので、もう一度ダメ押しのチェックを行います。イメージとアノテションファイルをごちゃ混ぜにした成果がここに出ます。以下のように同一名のjpgとtxtファイルができますが、たまにjpgファイルに対応しているtxtファイルが作られていない場合があるので、それを見つけ出してダブっているjpgファイルを消してしまいます。例えば普通は以下のようにjpgとtxtが交互に並びますが、txtファイルが作成されていないと、jpgファイルが続くことになります。

f:id:TAKEsan:20180816002346p:plain

8.yoloの下準備

 Darknetをインストールするとdarknetディレクトリができます。

darknet/data の中に先ほど作ったjpgとtxtの入ったごちゃ混ぜデータをディレクトリごと移動またはコピー。

 その中にprocess.pyの名称で、以下の内容のファイルを作成

import glob, os

# Current directory
current_dir = os.path.dirname(os.path.abspath(__file__))

# Directory where the data will reside, relative to 'darknet.exe'
path_data = 'data/obj/'

# Percentage of images to be used for the test set
percentage_test = 10;

# Create and/or truncate train.txt and test.txt
file_train = open('train.txt', 'w')  
file_test = open('test.txt', 'w')

# Populate train.txt and test.txt
counter = 1  
index_test = round(100 / percentage_test)  
for pathAndFilename in glob.iglob(os.path.join(current_dir, "*.jpg")):  
    title, ext = os.path.splitext(os.path.basename(pathAndFilename))

    if counter == index_test:
        counter = 1
        file_test.write(path_data + title + '.jpg' + "\n")
    else:
        file_train.write(path_data + title + '.jpg' + "\n")
        counter = counter + 1

これは

How to train YOLOv2 to detect custom objects

に掲載されてます。何をするのかというと、ディレクトリの中のデータを教師ファイルと学習用ファイルに分けてそれぞれリストファイルを作ります(train.txtとtest.txt)標準では教師ファイルは全体の10%ですが、これを変える場合はpercentage_test = 10 のところを15とか30とかにします。この数字の変更はかなりシビアで、最終的にできた学習済みファイルの結果を試して最適値を探します。最終的にと簡単には言いますがGTX1080 Tiで学習させても数時間必要ですので、容易ではありません。今回の場合30%で一番良い結果が得られました。実行方法は、

   python process.py

 実行環境はPython2である事に注意です。画像が不足していると感じた場合は、追加画像群を他の場所でアノテーションして、すべてこの場所に放り込み、再度python process.pyを実行するだけです(その場合は画像名称を変えることを忘れずに!!)。

9.cfgファイルを変更

 tinyYOLO標準YOLOでは変更内容が違うことに注意して下さい。変更ファイルは、darknet/cfgの中に入っています。

tinyYOLOの場合(今回のiPhoneを使う場合)  

  tiny-yolo-voc.cfgを変更します

   2行目 batchを64に変更(初めから書いてあったと思う)

   3行目 subdivisions=8に変更(これも初めから書いてあったと思う)

  120行目 classes=20を3に変更 (クラスの数です)

  114行目 filters=512 を 40に変更 (class+1)*5の計算値 (3+1)*5=40

                クラスの数が変わればfiltersの数値も変わることに注意!!

標準yoloの場合

    How to train YOLOv2 to detect custom objects

  に沿って、yolo-voc.cfg内容を変更します

10.あらかじめトレーニングされたデータをダウンロード

 一応 darknet/ 内に必要ファイルをダウンロードします。このファイルは学習を収束させるために必要なファイルだそうです。  

YOLO V2とV3とではデータが違います。

  YOLO V2.0の場合は

     downloaded

    darknet19_448.conv.23がダウンロードされます。

  YOLO V3.0の場合は

    wget https://pjreddie.com/media/files/darknet53.conv.74

    darknet53.conv.74がダウンロードされます。

11.さらに2つのファイルを作ります

  darknet/cfgの中にobj.namesファイルを作成

  obj.names :先ほどのpredefined_class.txtと内容が同じファイル。

        クラス名を改行しながら書きます。今回はGoo、Tyoki、Paa

f:id:TAKEsan:20180815230202p:plain

  obj.data    :必要ファイルのリンクを書き込んだファイル。以下のような感じ。

      class   必ず指定。今回の場合3

      train ディレクトリとファイル名を指定 先ほどのtrain.txt

      valid ディレクトリとファイル名を指定 先ほどのtest.txt

      names   クラスファイルの場所とファイル名 obj.names

      backup  学習済みデータの保存先 このままでOK

f:id:TAKEsan:20180815230221p:plain

12.いよいよ学習

darknet/  ディレクトリ内で以下を実行。今までの作業はこのコマンドを実行させるために必要なことだったと言うことですね。

 tiny Yoloでyolo v2.0の場合

./darknet detector train cfg/obj.data cfg/tiny-yolo-voc.cfg darknet19_448.conv.23

 

 標準YoloでYolo v3.0の場合は

./darknet detector train cfg/obj.data cfg/yolo-voc.cfg darknet53.conv.74

となります。

 今までのリンク先などの設定で、データの置き場所などが変更可能だと言うことが分かると思います。ある程度慣れてきたら自分の環境に合わせて変更してしまえば、もっと使い安くなります。

13.すんなり行けば良いのですが

 ここまでの過程が複雑でデータ量が多いことから、普通は簡単には動いてくれません!!

 実行してから、まず最初の1〜2分の段階でjpegに対応したtxtファイルがありませんのようなエラーメッセージが出たら、アノテーションが上手くいってません。上記7.をもう一度確認してjpegファイルがダブってないかチェック後、必ず 

python proxess.pyを再実行。同様のエラーが無くなるまで同じことを繰り返します。上手くいくと以下のような画像になります。

f:id:TAKEsan:20180815230846p:plain

 この場合はtiny-yolo-voc.cfgを使ってsubdivisions=8にした場合で、これを40,000回繰り返し(tiny-yolo-voc.cfgの15行目 max_batchの最後の数値を40,000にした)、100回毎にでデータがdarknet/dataの中に自動保存されます。つまりこの例では400回上書き保存されます。  

 subdivisionsの値を少なくすると計算がメモリー上に展開されるようで、全体のスピードが多少速くなりますが、GPUメモリーが不足してエラーになる可能性があります。

 上の画像のように、8回毎に平均値が出ます。avgの手前の数値が0に近づくほど良好とのこと。今回のデータでは1.455029になっていますが、これ以上続けてもほとんど変わらないことと、次で説明する認識テストもほぼ良好なので。ここで打ち切っています。

 ただし標準Yoloでの学習は確実にゼロに近づきます。また白黒で学習させているのにカラー画像でも認識するのには驚いてしまいました。Tiny Yoloではそうはいきません。

 このほか、Classがゼロだったり他の数値が表示されていない場合は、クラス分けやクラスの数の指定が各段階で上手くいっていない可能性があります。再度確認となりますが、「6.クラス分けをする」だけは慎重に作業する必要があります。ここで設定を間違うとアノテーションを最初からやり直ししなければなりません。

 今回約4,500枚のデータを使い4万回の繰り返しで、大体6時間(GTX 1080 Ti)ぐらい必要でした。学習時間はGPU性能に依存します。

14.学習結果の確認

 学習結果は、darknet/backupに保存されます。tiny-yolo-voc.backup又はyolo-voc.backupがそのデータで、

./darknet detector test cfg/obj.data cfg/yolo-voc.cfg backup/tiny-yolo-voc.backup test.jpg

(標準学習の場合はtiny-を消します)

と打ち込み認識したいサンプル写真を指定(test.jpg)すると現在の学習状況が確認できます。満足できる認識内容なら、どの段階でもCtrl+Cで打ち切り可能。その場合は.backupが学習済みデータになります。その他に各段階でデータが作成されていますので、それらを利用することも可能です。

 学習実行内容をよく見ていると、数値にかなり波があるので、avgの手前の数値が0に近付いたとしても、少なくとも1万回以上は繰り返さないと満足な結果は得られないようです。 ここら辺はデータ量や質によりなんとも言えないので、ヒーター付き集塵機(GPUの付いたUbuntuマシンを私はこう呼びます。夏場はさらに冷却用の扇風機が必要...)の電気料金が許す限り自分で試して見るしかありません。

f:id:TAKEsan:20180815235143p:plain

以上の作業は慣れるとスイスイなのですが、伝わったかなー、伝わんねーだろーなー。

 2ヶ月前の作業を思い出しながら書いたので、結構疲れた....。

 

                             では、また。