OpenCV+pythonで機械学習をやってみた②_基礎知識編
※本記事は、「OpenCVによるAIの実装方法」「機械学習まであと1歩!写真から顔や物体を取り出すには? 実装編第2回」「OpenCVで機械学習をさせてみた!! 実装編第3回」を再編集したものです。
みなさんこんにちは。沖田はじめです。
今回は、OpenCVで教師あり学習を行うための基礎知識についてお話していきます。
(前回の導入編の記事はこちら)
●OpenCV機械学習基礎知識
ここでは、OpenCVで教師あり学習を行う方法について説明します。
大まかな流れとしては、自動車の画像と自動車以外の画像を集めた後、カスケード分類器と呼ばれる学習データを生成し、最後に任意の画像から自動車を検出するという形になります。
具体的には、
1.正解画像(自動車)を用意する
2.正解画像のリストファイルを用意する
3.不正解画像(自動車以外)用意を用意する
4.不正解画像のリストファイルを用意する
5.ベクトルファイルを作成する
6.カスケード分類器を作成する
7.カスケード分類器を元に任意の画像から自動車を検出する
というステップになります。
ここでは、それぞれのステップについて説明します。
1.正解画像(自動車)を用意する
自動車が写っている画像を用意します。枚数が多いほど精度がよく、実用的な学習データとなるには7000枚以上が必要とされるようですが、最初は数十枚程度で試してみるとよいでしょう。画像ファイルの種類としてはjpegやpngなどが対応しています。
用意した画像は、positiveという名前のフォルダに格納します(フォルダ名は何でも構いません)。
2.正解画像のリストファイルを用意する
正解画像ファイルの名前と、画像の中のどの範囲が正解画像(自動車)であるかを矩形(長方形)で示したリストで、テキスト形式のファイルです。
例えば、自動車が1台のみ写っている画像ならば、
image_001.jpg
1 63 45 131 127
のように示します。
最初の「image_001.jpg」が画像ファイル名です。positiveファイルからの相対パスで指定可能ですので、positiveファイルの直下に置いてあるならば、画像ファイル名のみを記載すればよいです。
次の「1」が画像の中に写っている正解画像(自動車)の数です。
その次の「63 45 131 127」が、画像の中に写っている正解画像の範囲を示す矩形です。矩形の左上の「x座標 y座標 幅 高さ」の順にそれぞれピクセル単位で数値を記載します。矩形の値は、IfanView(イーファンビュー)などのソフトウェアを使うと求められます。
また、正解画像が2枚以上ある場合は、以下のようにスペース区切りで連続して指定します。
image_002.jpg 2 0 92 299 146 48 84 583 331
以上を踏まえると、正解画像のリストファイルは以下のようになります。
image_001.jpg 1 63 45 131 127
image_002.jpg 2 0 92 299 146 48 84 583 331
image_003.jpg 1 9 114 969 526
image_004.jpg 3 0 39 803 594 799 180 102 339 821 75 207 288
...
リストファイルは、poslist.txt
として、positive
フォルダの直下に置きます(positive\poslist.txt
)。
※リストファイル名、フォルダ名は何でも構いません。
3.不正解画像(自動車以外)用意を用意する
自動車以外が写っている画像を用意します。山、ビルなどの風景や、人などでもよいでしょう。こちらは正解画像よりは少なくても十分なようですが、実用的なAIレベルになるには、正解画像7000枚に対し不正解画像は3000枚が必要と言われているようです。
用意した画像は、negative
という名前のフォルダに格納します。
※フォルダ名は何でも構いません。
4.不正解画像のリストファイルを用意する
不正解画像ファイルの名前を示したリストで、テキスト形式のファイルです。
こちらは正解画像のリストと違い、矩形の指定はありません。
C:\...\data\negative\image_001.jpg
C:\...\data\negative\image_002.jpg
C:\...\data\negative\image_003.jpg
ファイル名は絶対パスで指定しないと失敗するという報告もあるため、念のため絶対パスで指定しておくとよいでしょう。
リストファイルは、neglist.txt
として、negative
フォルダの直下に置きます(negative\neglist.txt
)。
※リストファイル名は何でも構いません。
5.ベクトルファイルを作成する
opencv_createsamplesというツールを使い、正解画像を一つのベクトルファイルにまとめます。まとめたベクトルファイルは、次項の「カスケード分類器を作成する」の入力として使います。
このツールは複数の正解画像を一つのベクトルファイルにまとめるだけでなく、1枚の画像から色や角度を変えながら複数の画像を人工的に生成し、ベクトルファイルにすることもできます。
企業のロゴのような単調な正解画像の場合には有効ですが、人工的に生成した1000枚の画像よりも自分で一から100枚の画像を集めたほうが検出の精度は高くなるようです。
opencv_createsamplesは、Anaconda Prompt上で次のように入力して実行します。
opencv_createsamples.exe -info positive\poslist.txt -vec vec\positive.vec -num 50 -w 24 -h 24
それぞれのパラメータは次のような意味になります。
-info
:正解画像のリストファイル(poslist.txt)を指定します。
-vec
: 生成するベクトルファイルのパスを指定します。vecフォルダを新しく作り、positive.vecという名前で出力させます。
-num
: 正解画像のリストに記載した画像の枚数です。
-w, -h
: 次項の「カスケード分類器を生成する」で指定する探索範囲の幅と高さ(pixel)です。指定しない場合、24が使われます。
Anaconda Promptは、Minicondaをインストールすると起動できるWindowsコマンドプロンプト形式の画面です。Anaconda Promptを使うとpythonの実行が可能です。起動には、WindowsのスタートメニューにあるAnaconda3からAnaconda Promptをクリックします。
図:Anaconda Prompt
6.カスケード分類器を作成する
カスケード分類器とは、「ある狭い範囲の特徴を抽出するための分類器を複数つなげて、オブジェクト全体を検出するもの」を指します。
まず、物体検出の方法(アルゴリズム)は、世の中にいくつも存在します。OpenCVでは、「Haar」「LBP」「Hog」の3種類のアルゴリズムが利用できます(最新のOpenCVではHogは省かれてしまったようですので実質2種類となります)。ここでは例として、Haarアルゴリズムについて触れておきます。
Haarは、Haar-Like特徴と言って、効果的に物体検出を行う手法です。OpenCVのHaarは、Paul Viola氏とMichael Jone氏が2001年に”Rapid Object Detection using a Boosted Cascade of Simple Features”というタイトルで発表した論文を基にしています。数多くの正解画像と不正解画像を基に画像の特徴を学習させて、別の画像がら目的となる物体を検出します。
Haarでは、図のような白の領域と黒の領域に存在する輝度(ピクセル)の差を特徴として持ちます(この特徴1つのことをHarr-Like特徴と言います)。白と黒のパターンはいくつ存在しますが、探索窓の中でそれらのパターンの種類、サイズ、位置などを変えながら走査していくと、探索窓のサイズが24×24の場合は約160,000通りの特徴量ができあがります。
今度はその特徴量の中から良いものを見つけることになるのですが、それにはAdaBoostと呼ばれる機械学習アルゴリズムが使われています。
Haar-Like特徴(引用:OpenCV 2.2 documentation カスケード型分類器)
Haar-Like特徴を顔画像に適用した様子(引用:OpenCV 4.5.2 Cascade Classifier)
AdaBoostによって学習され、良い特徴量を持つとされた分類器(=ステージ)が複数個出力されます。それらは、例えば人間の顔で言えば、目のライン、鼻のラインといったパーツごとの強い特徴を持ったものになります。この強い特徴を表した分類器は連結されるのですが、連結する操作のことを「カスケード」と呼んでいます。
なぜ連結するのかというと、実際に任意の画像から検出する場合、検出したい対象物以外の方が見つかることが多く、リアルタイム検出の処理速度を考えてのことだそうです。
任意の画像から検出する過程においては、画像内を検出窓(ROI, Region Of Intereset)という小さい領域に分割して、それぞれの検出窓に対して分類器と照らし合わせていくのですが、それぞれの検出窓において、カスケードされた分類器のどれか1つでも当てはまらない場合は検出失敗とすることで処理時間を減らし、すべての分類器の特徴に当てはまった検出窓が検出成功となるようです。
※詳しく知りたい方は、こちらのドキュメント等を参照ください。
・OpenCV 4.5.2 Cascade Classifier
・OpenCV 2.2 documentation カスケード型分類器
・群馬大学 太田研 Haar-like特徴量を用いたカスケード分類器による前方車両の識別
・技術評論社 第3回 オブジェクト検出してみよう
・技術評論社 第4回 オブジェクト検出器の作成方法
OpenCVでカスケード分類器を作成するには、Anaconda Prompt上で次のように入力します。
opencv_traincascade.exe -data cascade -vec vec\positive.vec -bg negative\neglist.txt -numPos 50 -numNeg 20 -w 24 -h 24 -featureType HAAR -bt GAB
それぞれのパラメータは次のような意味になります。
-data
:カスケード分類器の出力先ディレクトリを指定します。カスケード分類器はcascade.xmlという名前で作られます。
-vec
:opencv_createsamplesで生成した、正解画像のベクトルファイルのパスを指定します。
-bg
:不正解画像の作成で用意した不正解画像のリストファイルを指定します。
-numPos
:正解画像のリストに記載した、正解画像の枚数です。
-numNeg
:不正解画像のリストに記載した、不正解画像の枚数です。
-w, -h
:探索窓(白と黒のパターンを探索する枠)のサイズです。opencv_createsamplesの-w,-hで指定した値と同じ値である必要があります。何も指定しない場合は 24 になります。実際に検出したい画像の検出枠のほうが大きくても、分類器は簡単にサイズ変更ができるようなので、検出画像のサイズに合わせて指定する必要はありません(opencv 2.2 documentation カスケード型分類器)
-bt
:よい特徴量を学習させるために利用する機械学習アルゴリズムです。AdaBoostにはいくつか種類があり、DAB(Discrete AdaBoost), RAB(Real AdaBoost), LB(LogitBoost), GAB(AdaBoost)の4つが選べます。何も指定しない場合は、GAB(AdaBoost)になります(OpenCV 2.4.13.7 documentation Cascade Classifier Training)。
opencv_traincascadeを実行すると、Anaconda Prompt上に以下のようなメッセージが出力され、カスケード分類器の生成が始まります。
PARAMETERS:
cascadeDirName: cascade
vecFileName: vec\positive.vec
bgFileName: negative\neglist.txt
numPos: 50
numNeg: 20
numStages: 10
precalcValBufSize[Mb] : 1024
precalcIdxBufSize[Mb] : 1024
acceptanceRatioBreakValue : -1
stageType: BOOST
featureType: HAAR
sampleWidth: 24
sampleHeight: 24
boostType: GAB
minHitRate: 0.995
maxFalseAlarmRate: 0.5
weightTrimRate: 0.95
maxDepth: 1
maxWeakCount: 100
mode: BASIC
Number of unique features given windowSize [24,24] : 162336
主要なパラメータの意味です。
numStages
:生成されるカスケード分類器の個数になります。それぞれの分類器(=ステージ)ごとの生成時間がAnaconda Prompt上に次々と出力されていきます。
stageType
:Boost(AdaBoost)が用いられています。
featureType
:Haar-Like特徴が用いられています。
Number of unique features given windowSize [24,24]
:24×24のサイズの探索窓の中で検出されるHaar-Like特徴量の数です。約160,000個が生成されるようです。もし、opencv_createsamplesやopencv_traincascadeの-w, -hで48×48サイズを指定した場合、2,570,880個と24×24に比べ15倍以上生成されるため、精度が上がるかもしれませんが、生成にかかる時間も15倍と増えてしまいます。
7.カスケード分類器を元に任意の画像から自動車を検出する
カスケード型分類器を作成したら、いよいよ物体検出が可能になります。
まず、正解画像とは別に、自動車が写っている検出したい画像を用意します。用意したら、imagesというフォルダを作ってその中に配置します。
次に、検出する方法についてです。イメージとしては、某ウィルス対策として店舗などの入室時に導入されている検温器のように、検出した物体に枠を表示するようなことを行います。
ここで、pythonを使ったプログラミングが登場します。
opencv+pythonのソースコードは以下のようになります。
————————————————————–
import cv2
#show cascaded image
def show_cascaded_image(img_path, cascade_path):
cascade = cv2.CascadeClassifier(cascade_path)
img = cv2.imread(img_path, cv2.IMREAD_COLOR)
grayed = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
objs = cascade.detectMultiScale(grayed, 1.1, 5, minSize=(24,24))
print(len(objs))
for (x, y, w, h) in objs:
img = cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv2.imshow('window', img)
show_cascaded_image( 'images/image.png', 'data/cars/cascade/cascade.xml' )
cv2.waitKey(0)
cv2.destroyAllWindows()
————————————————————–
こちらを、NotePadやサクラエディタのようなテキストエディタで開き、コピー&ペーストします。
ファイル名は「test.py」とし、ファイルの種類を「すべてのファイル(*.*)」にして保存します。.pyという名前がpythonプログラムの拡張子となります。
ここで、TARGET_URLの行を、検出したい画像ファイルの名前に変更してください(test.pyがあるフォルダからの相対パスで指定します)。
作成したプログラムは、Anaconda Prompt上で以下のように入力することで実行することができます。
python test.py
すると、図のように、検出された自動車に緑色の検出枠が表示されます。
●プログラムの説明
test.pyとして作成したソースコードについて説明しておきます。
・import cv2
OpenCVをインポートします。pythonでOpenCVを使うために必要です。
・#show cascaded image
コメントです。プログラム中に説明などを入れたい場合は、#を先頭に入れて記載します。
・def show_cascaded_image(img_path, cascade_path):
カスケード分類器を使って画像検出する関数です。
def が関数を意味します。関数は複数の処理をまとめたものです。それぞれの処理は次行以降に記載します。このとき、行の先頭に1つ以上のスペースを入れてインデントします。 同じインデントの行が1つの関数の処理のかたまりとみなされます。
関数名は好きな名前で構いません。
カッコ内は引数と呼ばれるもので、関数の入力パラメータとなります。この例では、img_pathが検出したい画像のファイルパスを、cascade_pathがopencv_traincascadeで生成したカスケード分類器(cascade.xml)のファイルパスを表します。
・cascade = cv2.CascadeClassifier(cascade_path)
OpenCVの機能で、カスケード分類器のパスからカスケード分類器を読み込みます。
・img = cv2.imread(img_path, cv2.IMREAD_COLOR)
OpenCVの機能で、検出したい画像をカラー(BGR)で読み込みます。OpenCVでは、画素 1つの並び順は青,緑,赤の順になります。
・grayed = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
OpenCVの機能で、カラー画像をモノクロ画像に変換します。以下の変換式で、青,緑,赤の3つのチャンネルを1つのチャンネルに変換します。
・gray=blue×0.11+green×0.59+red×0.3
今回、検出したい画像に対してカスケード分類器にかけるのですが、特徴量を抽出するためには輝度の変化量がわかれば良いので色情報は必要はなく、モノクロ画像に変換して使用します。
・objs = cascade.detectMultiScale(grayed, 1.20, 5, minSize=(24,24))
カスケード分類器を使って、入力画像から物体検出を行います。検出した物体は、画像中の位置と矩形情報としてobjs
に格納されます。
cascade
が読み込んだカスケード分類器です。
grayed
が入力画像(モノクロ)です。
1.20 は scaleFactor といって、各画像スケールにおける縮小量を表します(初期値1.1)。detectMultiScale
では様々なスケールで検出を行うのですが、次の検出サイズは 1.2分の1になるという形です。数値が細かい方が見落としが少なくなり精度は上がりますが、処理時間がかかります。性質上、1.0より大きい値を指定します。(opencv v2.1 documentation)
・print(len(objs))
検出数をAnaconda Prompt上に出力します。
・for (x, y, w, h) in objs:
img = cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)
検出した物体それぞれを矩形で囲み、カラー画像中(img
)に表示します。
x,y
は検出場所を、w,h
は矩形サイズを示します。
for
は、ループ(繰り返し)を表すpythonの命令文です。objs
の中から1個を取り出し、x, y, w, h
という変数に値が格納されます。
cv2.rectangle
はOpenCVの機能で、矩形(長方形)を描画する機能です。
rectangleのカッコの中の最初が入力画像、次が矩形の左上の位置、その次が矩形の右下の位置、(0, 255, 0)が矩形の色をB, G, Rの順で指定、最後の2が矩形の線の太さになります。
矩形を描画後の画像は、img = cv2.rectangle
のimg
に格納されます(今回だと、入力画像を上書きしています)。
・cv2.imshow('window', img)
OpenCVの機能で、ウィンドウを新しく起動し、img
を表示します。
'window'
は、起動したウィンドウの上に表示されるタイトル名です。
・show_cascaded_image( 'images/image.png', 'data/cars/cascade/cascade.xml' )
ここまでにおいて作成した関数を呼び出します。
関数は作成しただけでは十分でなく、呼び出す一文を記載する必要があります。
検出したい物体が写っている画像ファイルのパス、カスケード分類器のパスを指定します。
・cv2.waitKey(0)
cv2.destroyAllWindows()
これらもOpenCVの機能で、waitKey(0)
がキーボードの入力を無限に待ちます。0以外を指定すると、指定した数値のミリ秒間だけ待機したあと、次の行の処理(cv2.destroyAllWindows()
)が行われます。
destroyAllWindows()
は、このpythonプログラムから表示したすべてのウィンドウを閉じる処理です。
プログラムの説明は以上となります。
次回は、今回お話した基礎知識を元に、自動車の検出を実践していきたいと思います。
●参考文献
OpenCVによるAIの実装方法
機械学習まであと1歩!写真から顔や物体を取り出すには? 実装編第2回
OpenCVで機械学習をさせてみた!! 実装編第3回
OpenCV公式ドキュメント cascade classifier チュートリアル
OpenCV公式ドキュメント user guide traincascade
opencv_createsamplesとopencv_traincascadeについて
OpenCV公式ドキュメント カスケード型分類器
OpenCVの勉強③(分類器を作成してみる)
Data Augmentationに使えるopencv_createsamplesの使い方
Rapid Object Detection With A Cascade of Boosted Classifiers Based on Haar-like Feature
opencv_createsamplesとopencv_traincascadeについて
OpenCVで物体検出器を作成する⑤ ~createsamples~
OpenCVで物体検出器を作成する⑥ ~traincascade~
ブースティング入門
【入門者向け解説】openCV顔検出の仕組と実践(detectMultiScale)
グレースケール化 cvtColor()
EVENTS