Google Colab上でdarknet(YOLO)を使って物体を数える【画像認識】
こんにちは。wakuphasです。
ここ1週間ほどディープラーニングを使った画像検出・分類について色々と試していたのでその覚書を投下しておきます。
最終的に、YOLOという物体検出アルゴリズムを用いて、自前の画像データを認識させるところまでいきます。
MacのCPUで学習すると無限に時間がかかるので以下のようなやや面倒なプロセスとなっています。
Macで学習寸前までデータを準備して
↓
一度そのデータを丸ごとGoogle ColaboratoryにアップロードしてGPUで学習
↓
学習結果をまたMacに戻してCPUで画像認識させるというものになっています。
※全ての情報は2018年9月現在のものです。
◇目次
- YOLO・darknet・darkflowとは
- Google Colaboratoryとは
- 開発環境
- 1. データの準備(Mac上)
- 2. 学習する(Colab上)
- 3. 認識させる(Mac上)
- よく出たエラー
- 考察
- お世話になったサイト
YOLO・darknet・darkflowとは
YOLO:
You Only Look Onceの略語。
速度・精度、共にトップクラスの物体検出アルゴリズムの一つ(論文1, 論文2)。
v2, v3が主に使われている。
darknet:
C言語とCUDAで書かれたオープンソースのニューラルネットフレームワーク。
手軽にインストールでき、CPUとGPUで使える。
(webサイト)
darkflow:
darknetをtensorflow環境で動作するようにしたパッケージ。
Google Colaboratoryとは
基本的に機械学習に必要なパッケージは全て揃っているので環境構築がほぼ不要。
Ubuntuマシンとして普通にapt-getなどでパッケージを追加することも可能。
Jupyter Notebookから派生しているので使いやすい。
開発環境
・anaconda 3-5.0.0
・Python 3.6.2
・Tensorflow 1.5.0
+ Google Colaboratory
1. データの準備(Mac上)
1. darknetをMacにインストール
本家に従ってdarknetをgitでダウンロード。
$ git clone https://github.com/pjreddie/darknet.git
$ cd darknet
$ emacs Makefile
Mac上ではCPUを使うので、Makefileの上の方でGPU=0になっていることを確認して保存。
$ make
テスト用に既にtrainされたweightsファイルをダウンロードする。
今回使用するのはyolov2の方だが、一応yolov3のリンクも貼っておく。
https://pjreddie.com/media/files/yolov2.weights
https://pjreddie.com/media/files/yolov3.weights
ダウンロードしたらbinフォルダにyolov2.weightsを移動する。
$ mkdir bin
$ mv yolov.weights bin/
./darknetコマンドが動作するかテスト画像で確認。
$
./darknet detect cfg/yolov2.cfg bin/yolov2.weights data/dog.jpg
同じディレクトリ内に保存されたprediction.jpgを開いて物体が検出・分類されていることを確認する。
2. 認識させたい物体が写っている画像を複数枚用意
Google画像検索なりで好きな画像をたくさんとってくる。
私の場合は、飛行機を検出したかったので、airportで検索して13枚ダウンロードした(全て.jpg形式で統一させる;JPEGと混同しない)。
images_airport.zip - Google ドライブ
3. 画像の水増し
13枚だけでも学習はできるが、より高い精度で認識を行うためには画像を回転させたり反転させたりと水増ししてやることで、汎用性の高い学習モデルを作成することが好ましい。
今回は以下を参考にこのスクリプトで水増しデータを作成した。
TensorFlow + Kerasでサルを分類できるのか試してみる(2) ~ 学習データを増やして精度を上げる
まず先ほどの画像を darknet/data/increase/airplane/ に全て移動する。
(ただし、increaseとairplaneフォルダは自分で作成)
increase/ に increase_img.py をコピーし実行する。
$
python increase_img.py
...
input files = 13
output files = 130 # 32行目:range(10)で元の10倍の枚数に水増し
increase/airplane/output/ に様々な変換を受けた画像が130枚生成される。
4. 画像から物体の位置とラベルを出力
画像を用意したら、その答えも用意する必要がある。
入力:生画像
出力:対象物体のラベル("airplane")と位置(topleft xy座標, bottomright xy座標)
この出力.txtを手作業で作成してもいいが、BBox-Label-Toolという感覚的に座標を出力できるアイテムがあるのでそれを用いる。
$ cd data
$ git clone https://github.com/puzzledqs/BBox-Label-Tool.git
クローンしたディレクトリ内にあるImages/001の中に、先ほど用意した increase/airplane/output/**.jpg を全て入れる。ツールによって生成された座標のテキストデータは、Labels/001の中に生成される。
ここで、デフォルトでは、画像の拡張子が大文字の『JPEG』でないとこのツールが認識してくれないので、『jpg』で認識するように、main.pyの中に記載されている『JPEG』をすべて『jpg』に置換する(3箇所)。
main.pyを実行する。デフォルトではpython2系で実行する必要がある。
Python3で動かすにはこちらを参照。
$ python2 main.py
Imagedirに001と入力し、ロード。
マウスでバウンディングボックスを作成し、「next」ボタンで保存次の画像を表示させていく。終了するとLabels/001に****.txtでラベルが生成されている。
YOLO形式へ変換:
続いて、生成したラベルをYOLOの形式に変換する必要があるので、convert.py という変換ツールを使用する。
darknet/data/convert/フォルダを作成しそこに convert.py を保存。
$
mkdir convert
さらに convert/ に以下のようになるようにフォルダ作成&ファイル移動(画像.jpgとBBoxで生成したラベル.txt)を行う。
※ディレクトリ配置やフォルダ名はconvert.pyの中身を書き換えることで自由にいじれる。
************
darknet/data/convert/
|- images
|- stopsign
|- airport00.jpg
|- airport01.jpg
…
|- labels
|- stopsign
|- stopsign_original
|- airport00.txt
|- airport01.txt
…
|- convert.py
************
convert.pyを実行する。
$
python convert.py
labels/stopsign/ にYOLO用に形式が変換されたファイルが出力されていることを確認。
YOLO形式は次の5列で、座標系が規格化されている。
ラベル番号は今回は一つしか使っていないので 0 が airplane に対応する。
ラベル番号 x_center y_center x_width y_width
出力された labels/stopsign/airplane**.txt を darknet/data/labels/ にコピーする。
$
cp convert/labels/stopsign/airplane**.txt labels/
$
pwd
(yourpath)/darknet/data$
cp convert/labels/stopsign/airplane**.txt labels/
darknet/data/labels/ フォルダ内には元々 32_0.png みたいな画像がたくさんあるが、これはそのままにしておかないと後々エラーが出る。
さらに 画像.jpg を darknet/data/images/ にコピーする。
$
cp convert/images/stopsign/airplane**.jpg images/
最終的に以下のような配置となる。複数のフォルダにimagesとlabelsができて気持ち悪いが、とりあえずこのまま進む。ちなみにdata/にデフォルトであるファイルはlabelsフォルダ以外消しても今回はうまくいく。
************
darknet/data/
|- images
|- airport00.jpg
|- airport01.jpg
…
|- labels
|- airport00.txt
|- airport01.txt
…
|- 32_0.png
...
************
5. trainデータとtestデータの切り分け
/darknet/data/ に process.py を保存する。
(How to train YOLOv2 to detect custom object 参考)
process.pyを実行すると percentage_test = 20 % に従ってランダムにtrainデータとtestデータが分けられ、それらのパスが darknet/data/images/ に test.txt と train.txt というテキストデータで出力される。
$ python process.py
6. 学習パラメータ設定
まず以下2つを準備:
・darknet/data/images/ にクラスリスト obj.names を作成する。中身は今回は airplane と1行だけ記載して保存。
・さらに darknet/data/ に names.list という名前のファイルを作成。中身は obj.names と同じく airplane と1行だけ記載して保存。
パラメータ設定:
クラス数やデータの所在等を指定する。
$ emacs cfg/obj.data
以下を記載して保存。
classes=1
train = data/images/train.txt
valid = data/images/test.txt
labels = data/images/obj.names
backup = backup/
classes #クラス数。今回は1種類なので1
train #訓練用の画像リスト
valid #テスト用の画像リスト
labels #クラスリスト
backup #学習した重みが保存される場所
次にモデルを編集する。使いたいモデルを元に編集していく。今回はyolov2-voc.cfg。
このとき後に使用する darkflow は現在yolov3に対応していない?ためyolov2を採用。
$ cd darknet/cfg/
$ cp yolov2-voc.cfg yolo-obj.cfg
$ emacs yolo-obj.cfg
以下を編集して保存。
3行目:batch=64 にする。学習ステップごとに使う画像の枚数。コメントアウトして6行目をオンにするでもいい。
4行目:subdivisions=8 にする。バッチが8で除算される。コメントアウトして7行目をオンにするでもいい。
244行目:classes=1 にする。クラス数=airplane一つ。
237行目:filters=30 にする。filters=(classes + coords + 1) * 5で定義。
いざ、学習:
はい、これでもういつでも学習できます。
初めて学習する際、適当な重みを初期値として読み込むと収束しやすいとのことなので
以下を darknet/ に保存する。
・yolov2 用の初期値ファイル。
https://pjreddie.com/media/files/darknet19_448.conv.23
・yolov3 用の初期値ファイル
https://pjreddie.com/media/files/darknet53.conv.74
試しに以下を実行すると
$ ./darknet detector train cfg/obj.data cfg/yolo-obj.cfg darknet19_448.conv.23
ズラーとCNNの構造と数字の羅列が出てきます...が、CPUでは永遠の時を要します。そこでColabの登場です!
ちなみに学習が完了すると darknet/backup に .weights ファイルが保存されます。
重み出力間隔の設定:
weightsファイルはデフォルトでは
・1000epochsまでは100epochsごとに出力
・1000epochs以降は10000epochsごとに出力
という仕様になっており、1000-10000までの間が割と幅があるので、1000epochs以降も100epochsごとに細かく出力されるように設定したい。
darknet/examples/detector.c 138行目を以下のように書き換える。
# デフォルトでは10000epochsごとに出力
if(i%10000==0 || (i < 1000 && i%100 == 0)){
# 上記を100epochsごとに書き換える
if(i%100==0 || (i < 1000 && i%100 == 0)){
darknet/ 内で make を通す。
$ make
7. darknetフォルダをzipに圧縮
Colab上ではGPUを用いるので、 darknet/Makefile を開いて GPU=1 に書き換えます。
この際、Mac上では make は実行しない(GPUがないのでエラーが出る)。
Colabで実行するためにはgoogle driveにアップする必要があるので、この darknet フォルダごと圧縮しておく。
$ cd ../
$ zip -r darknet.zip darknet
ということで、学習準備が整ったので、無料でGPUが使えると噂のGoogle Colaboratoryを使って学習させてみましょう。
2. 学習する(Colab上)
1. google driveにzipファイルをアップロード
自分のGoogleアカウントでDriveにログインする。
適当なディレクトリにzipファイルをアップロード。
2. google colaboratoryを開く
https://github.com/wakuphas/wakuphas/blob/master/AI/Scripts/darknet_airport.ipynb
これを .ipynb として保存して、同じくGoogle Driveにアップロードする。
Drive上で右クリックし「アプリで開く」->「Google Colaboratory」を選択。
「ランタイム」タブから「ランタイムのタイプを変更」を選択し、Python3とGPUを選択する。
基本的には darknet_airport.ipynb に記載した指示通りにセルの実行を行っていけば以下の手順が完了し、学習できる...はず!!
ⅰ ) Colab上で環境構築
ⅱ ) zipを「Colab上」にアップロード
ⅲ ) makeを通す
ⅳ ) ./daknetコマンドで学習する
ⅴ ) 重みファイルを出力
2.5. Colabの諸注意
無料なので色々ルールはありますが、12時間ルールが一番気を付けるべき点です。
GPUに接続してから最大で12時間連続で稼働させることができますが、12時間をすぎると、アップロードしていたデータや出力したファイル、構築した環境まで全て消えます!笑
なので毎回先ほどのipynbファイルの環境構築を行う必要があります。
また数時間経つとGPU自体は使えるようになりますが、消えたデータは元に戻らないので、右クリックで重みファイルなどはこまめにダウンロードしておきましょう。
3. 出力されたweightsファイルをMacにダウンロード
./darknetコマンドで学習する際、最初は用意された初期値を読み込むが、一度中断しても出力された重みファイル.weightsを初期値として読み込めばそこから再開可能。
# 用意された初期値を用いる場合
$ ./darknet detector train cfg/obj.data cfg/yolo-obj.cfg darknet19_448.conv.23 > train_log.txt
# 出力した重み yolo-obj_XXX.weights を読み込む場合
$ ./darknet detector train cfg/obj.data cfg/yolo-obj.cfg backup/yolo-obj_XXX.weights > train_log.txt
train_log.txtにログを出力。Macにダウンロードして開く。
数行ごとに出てくる以下に着目。
--
937: 34.240753, 37.657242 avg, 0.000771 rate, 5.082382 seconds, 59968 images
--
最初の数字が反復回数。通常2000回は必要。 avgの手前の数字が小くなればなるほど精度が高い。 0.1以下くらいが良い?
avgの数字が減らなくなってきたら学習を止める。
Colab上の重みファイル darknet/backup/***.weights をローカルのMac上にダウンロードしておく。
筆者の場合、train_log.txtのavgの値とepoch数の関係は以下のようになっていた。
(extractor.shとplot.pyを darkflow/ にダウンロード。extractor.shの中身は適宜自分のtrain_log.txtがあるパスに書き換える。)
$ sh extractor.sh # epochavg.txtが出力
$ python plot.py
2000epochs以降はほぼ増減がないので、そこで打ち切ってもいいのだが、今回は3600回まで回したものを使用する。
最終的な avg は 2 程度でだったが、妥協する。
3. 認識させる(Mac上)
1. darkflowをインストールする
$ git clone https://github.com/thtrieu/darkflow.git
$ cd darkflow
$ python3 setup.py build_ext --inplace
2. Pythonプログラムを作成する
https://github.com/wakuphas/wakuphas/blob/master/AI/Scripts/detect.py
このdetect.pyを darkflow/ に入れる。
いじるのは主に以下。
6行目:ファイルのパスを記載。
11行目:認識したい画像のパスを記載。
21行目:検出するクラス名を指定。
35行目:confidenceが0.1以上のものを表示。
3. weightsファイルをロードして物体検出させる
以下4つを準備。
・Colabで学習して出力した weights は darkflow/backup に入れる。
・学習に用いた darknet/cfg/yolo-obj.cfg は darkflow/cfg にコピーして、batch=1、subdivisions=1に変更して保存。
・darkflow/ に labels_airplane.txt を作成してairplaneと1行だけ記載して保存。
・確認用画像として test_airplane.jpg を darkflow/ に保存。
detect.pyを実行。
$ python detect.py
こんな感じで、上空からみた飛行機の位置とラベルを認識して表示してくれたら成功!
escボタンで終了できる。
よく出たエラー
(1) ロードしたcfgとweightsファイルで4 bytesずれる
エラー:
$ python detect.py
AssertionError: expect 202314760 bytes, found 202314764
解決:
darkflow/darkflow/utils/loader.py
121行目の self.offset = 16 を 20にすると解決する。
(2) ロードしたcfgとweightsファイルが対応していないと怒られる
エラー:
$ python detect.py
AssertionError: Over-read backup/yolo-obj_3300.weights
解決:
この問題が一番苦戦したのだが、このエラーが出たら以下を確認してみてほしい。
・darkflow/cfg/yolo-obj.cfg と darknet/cfg/yolo-obj.cfg はBatchとsubdivision以外同じか
・Colab上で学習するときに使用した darknet/cfg/yolo-obj.cfg を新しく置き換える。https://github.com/pjreddie/darknet/blob/master/cfg/yolov2-voc.cfg からダウンロードしてもう一度 1-6.学習パラメータの設定 からやり直してみる。
考察
学習する飛行機の写真が、上からみたものと横から見たものが混ざっていたせいか、横からの飛行機はあまりうまく認識してくれなかった。
上から見た飛行機は基本的に全て飛行機の形状は同じだが、横から見た場合、角度が異なると形状も全く異なるので様々な角度からの写真が必要なのかもしれない。
お世話になったサイト
YOLOv2を使って自前のデータを学習させて認識させるまで。 - 可変ブログ
YOLOv2のリアルタイム物体検出をTensorFlowとPythonで実装する方法 | AI coordinator
YOLO: Real-Time Object Detection
How to train YOLOv2 to detect custom objects