OpenCVの画像認識について勉強を始め、カスケード分類器を自作してみたくなったが、自前のpcだと計算能力が心許ない。そんな事を考えていたときにgoogle ColaboratoryのGPU提供について知り、計算を肩代わりしてもらおうと考えた。
#0.今回の目標
以下の写真からヴァイオリン族を認識するカスケード分類器を、google colaboratoryを用いて作成する。もちろんヴァイオリン・ヴィオラ・チェロ・コントラバスの区別を付けられるようになるのが理想であるが、(クラシック音楽をあまり知らない)人にすら難しい事を要求するのは酷であるため、ハードルを低くしている。
#1. 準備
1-1. google colab環境の準備
1-2. 画像データの準備
1-3. mergevec.pyのダウンロード
###1-1. google colab環境の準備
goole colabはgoogle driveの任意のフォルダ内で新規ノートブックを作成する。
今回は /drive(マイドライブ)/Colab Notebooks/opencv/instruments/ で作業する。
instrumentsフォルダの構成は以下のようにする。
<DIR> pos #正解画像を保存
<DIR> vec #正解画像からvecファイルを作成して保存
<DIR> neg #不正解画像を保存
<DIR> neglist #不正解画像からリストを作成して保存
<DIR> cascade #訓練結果を出力
mergevec.py #vecファイルの結合に使用
train_flow #新規Colabノートブックを作成
###1-2. 画像データの準備
正解画像
自前もしくはネットから拾う。今回は200×300ピクセルのjpgファイルに加工したものを6枚用意してposに格納。
不正解画像
カラーの画像を取り敢えずたくさん。今回は50 free Machine Learning Datasets: Image Datasetsから"2017 Val images"をダウンロードするなどして計8000枚くらい用意。
これをnegに格納するのだが、アップロードに時間がかかる上にdriveの容量を1GB以上使うので、状況に応じて量を減らすなどする事をおすすめさせて頂く。
###1-3. mergevec.pyのダウンロード
githubのmaergevecページからダウンロードする。
#2. ノートブック作成
train_flowというcolabノートを新規作成し、中身を作成する。GPUを使いたいので編集タブからノートブックの設定をする。割り当てられたGPUの種類に応じて計算速度が異なる可能性があるが、ここでは割愛する。
2-1. 環境変更
2-2. 正解ファイル作成
2-3. 不正解ファイル作成
###2-1. 環境変更
やらなければならない事は3つ。
・openCVのダウンバージョン
・driveのマウント
・カレントディレクトリの変更
openCVのダウンバージョン
colabに標準搭載のopenCV4では後述のcreatesamplesやtraincascadeが使えないため、openCV3にダウンバージョンする。
#openCVのアンインストール
#2回'y'を入力
!pip3 uninstall opencv-python opencv-contrib-python
#openCVのインストール
!pip3 install opencv-python==3.4.4.19 opencv-contrib-python==3.4.4.19
#セッションの再起動
exit()
driveのマウント
colabノートとdrive間でデータを入出力するために以下を実行
from google.colab import drive
drive.mount('/content/drive')
カレントディレクトリの変更
自分のディレクトリ構成に応じて設定。ここではinstrumentsをカレントディレクトリにする。
import os
os.chdir('/content/drive/My Drive/Colab Notebooks/opencv/instruments')
#3. 訓練用ファイルの準備
正解画像と不正解画像からそれぞれ訓練用のファイルを作成する。
3-1. 正解データファイルの作成
3-2. 不正解データファイルの作成
###3-1. 正解データファイルの作成
openCVのcreatesamplesを使って正解データを量産し、別ファイルとして保存する。方法には2通りある。
画像を直接指定する方法
以下のコマンドで1枚の画像から1000枚の正解データを作成。widthとheightは正解画像の縦横比に合わせているが、効果が出ているかは正直わからない。
#画像から1000枚分の正解データを作成しvecファイルとして出力
!opencv_createsamples -img pos/Va001.jpg -vec vec/Va001.vec -num 1000 -w 40 -h 60
Info file name: (NULL)
Img file name: drive/My Drive/Colab Notebooks/opencv/instruments/pos/Va006.jpg
Vec file name: drive/My Drive/Colab Notebooks/opencv/instruments/vec/Va006.vec
BG file name: (NULL)
Num: 1000
BG color: 0
BG threshold: 80
Invert: FALSE
Max intensity deviation: 40
Max x angle: 1.1
Max y angle: 1.1
Max z angle: 0.5
Show samples: FALSE
Width: 40
Height: 60
Max Scale: -1
Create training samples from single image applying distortions...
Done
画像をリストにして指定する方法
私のやり方が悪いのか上手くいかなかったが、一応書いておく。
以下のように、正解ファイルの場所をパス指定し、正解対象物の個数、正解対象物の座標(x軸始点、y軸始点、x軸終点、y軸終点)を書いたリストをposフォルダに用意しておく。
#posフォルダに作成
#ファイル名 個数 座標
pos/Va001.jpg 1 0 0 200 300
pos/Va002.jpg 1 0 0 200 300
pos/Va003.jpg 1 0 0 200 300
pos/Va004.jpg 1 0 0 200 300
pos/Va005.jpg 1 0 0 200 300
pos/Va006.jpg 1 0 0 200 300
!opencv_createsamples -info pos/poslist.txt -vec vec/pos.vec -num 6000 -w 40 -h 60
これでtxtに記された画像から合計6000枚の正解データを作成してくれる、と期待したがColaboratory上では上手くいかなった。
セル5-1のような方法でも学習させられないことはないが、元の画像が1枚では十分な認識精度まで持っていくのは難しそうである。そこで対処法を調べたところ、複数のvecファイルを結合してくれるコードがあるらしい。
複数のvecファイルをひとまとめにする
セル5-1のようにしてposフォルダに置いた画像からそれぞれvecファイルを作成した後、instruments下においたmergevec.pyを実行する。
!opencv_createsamples -img pos/Va002.jpg -vec vec/Va002.vec -num 1000 -w 40 -h 60
!opencv_createsamples -img pos/Va003.jpg -vec vec/Va003.vec -num 1000 -w 40 -h 60
!opencv_createsamples -img pos/Va004.jpg -vec vec/Va004.vec -num 1000 -w 40 -h 60
!opencv_createsamples -img pos/Va005.jpg -vec vec/Va005.vec -num 1000 -w 40 -h 60
!opencv_createsamples -img pos/Va006.jpg -vec vec/Va006.vec -num 1000 -w 40 -h 60
#vecフォルダの中データを結合してpos.vecという名前で保存
!python mergevec.py -v vec -o vec/pos.vec
これで1000×6の正解画像ファイルを作成できた。
###3-2. 不正解データファイルの作成
neg画像のファイルの場所を記したnglist.txtを作成する必要がある。
#negフォルダの中身をnglist.txt に書き出す
!ls neg | xargs -I {} echo neg/{} > neglist/nglist.txt
neg/000000000139.jpg
neg/000000000285.jpg
neg/000000000632.jpg
...
これで全ての下準備が完了。
#4. 分類器の作成
以下のコードを実行する。
!opencv_traincascade -data cascade -vec vec/pos.vec -bg neglist/nglist.txt -numPos 5500 -numNeg 3000 -numStages 20 -featureType LBP -w 40 -h 60
numPos : 正解に使う画像数。画像を全て使い切ってしまうと学習が中断されることがあるので、少なめに設定する。
numNeg : 正解と不正解が2:1くらいになると良いらしい。
featureType : HAAR, LBPなどが選べる。HAARは圧倒的に時間がかかる。
w, h : 正解ファイルを作成した時と同じ値に設定。
#5. 実行経緯
colabのGPUは12時間まで連続動作してくれる。一晩計算させてみたが、14/20ステージまでしか計算が終わらず、1ステージごとに計算時間が2倍になる指数的増加だったのでパラメータを調整する必要があると判断。
PARAMETERS:
cascadeDirName: cascade/trained_data/
vecFileName: vec/pos.vec
bgFileName: neglist/nglist.txt
numPos: 5500
numNeg: 3000
numStages: 20
precalcValBufSize[Mb] : 1024
precalcIdxBufSize[Mb] : 1024
acceptanceRatioBreakValue : -1
stageType: BOOST
featureType: LBP
sampleWidth: 40
sampleHeight: 60
boostType: GAB
minHitRate: 0.995
maxFalseAlarmRate: 0.5
weightTrimRate: 0.95
maxDepth: 1
maxWeakCount: 100
Number of unique features given windowSize [40,60] : 153400
===== TRAINING 0-stage =====
<BEGIN
POS count : consumed 5500 : 5500
NEG count : acceptanceRatio 3000 : 1
tcmalloc: large alloc 1073758208 bytes == 0x5650ef23e000 @ 0x7f54c034f1e7 0x7f54bf549382 0x7f54bf64821b 0x5650e5fc5608 0x5650e5fc5d42 0x5650e5fc5e1a 0x5650e5fcf1a9 0x5650e5fbbfff 0x7f54be80cb97 0x5650e5fbcc1a
Precalculation time: 20
+----+---------+---------+
| N | HR | FA |
+----+---------+---------+
| 1| 1| 1|
+----+---------+---------+
| 2| 0.997818| 0.225333|
+----+---------+---------+
END>
Training until now has taken 0 days 0 hours 5 minutes 21 seconds.
...
===== TRAINING 14-stage =====
<BEGIN
POS count : consumed 5500 : 5725
NEG count : acceptanceRatio 3000 : 2.41651e-06
Precalculation time: 17
+----+---------+---------+
| N | HR | FA |
+----+---------+---------+
| 1| 1| 1|
+----+---------+---------+
| 2| 1| 1|
+----+---------+---------+
| 3| 0.998364| 0.712667|
+----+---------+---------+
| 4| 0.997455| 0.632|
+----+---------+---------+
| 5| 0.996545| 0.449|
+----+---------+---------+
END>
Training until now has taken 0 days 6 hours 59 minutes 8 seconds.
そこでnumStageを15に設定したところ、stage15に到達する事なく精度が十分高まったとかで学習が終了、所要時間は1時間ちょっとであった。不思議。
#6. 評価
colabではopenCVで画像を表示することができないようなので、ローカルでの作業を行う。cascadeに出力された複数のファイルのうち、cascade.xmlをダウンロードしてくれば良い。
import cv2
img = cv2.imread('instruments.jpg')
cascade = cv2.CascadeClassifier('cascade.xml')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#認識精度が悪いときにはminSizeを変える事も検討
Va = cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=3, minSize=(140,210))
#赤枠で対象をマーク
for(x,y,w,h) in Va:
cv2.rectangle(img, (x,y), (x+w, y+h), (0,0,200),3)
#result.jpgを出力
cv2.imwrite("result.jpg", img)
#別ウィンドウで画像を表示
cv2.imshow('image', img)
cv2.waitKey(0)
出力結果は以下の通り。チェロが2箇所判定されたこと、コントラバスだけ仲間外れになったことが残念。
不正解画像を2000枚にして訓練したときには以下の通りになった。シンバル・ユーフォ?間やヴィオラ・チェロ間の曲線が楽器に見えるのは面白いと感じた。不正解画像を増やすことに意味はあるらしい。
#7. まとめ
・分類器を作成してヴァイオリン族の楽器を認識させることができた。
・colabratoryで分類器を作成する方法がわかった。
・画像認識の計算手法やパラメータについてぼんやりと理解が進んだ。
#参考にさせていただいたサイトなど
OpenCVの勉強③(分類器を作成してみる)(https://qiita.com/takanorimutoh/items/5bd88f3d17239a147581)
OpenCVのCascade分類器を複数のpos画像で学習する(https://pfpfdev.hatenablog.com/entry/20200715/1594799186)