OpenCVでSVMとHOG特徴量を使って人検出を試みてみる。【part1】
研究室の勉強会で作ったので、簡単なまとめとして。
始めに言っておきますが、精度は悪いです。
あと解説も一応入れてありますが、非常に簡易的なもので不正確なので、興味がある方は各自調べてみて下さい。
最近、二次元の女の子を自動生成するやつがtwitterで話題になってましたね。
make.girls.moe
恐らく何らかのニューラルネットワークを使ってイラストを学習させて画像生成してるんだろうなあとは思うんですが、今夏休みでただでさえ低い意識が更に低下してるので調べませんでした。
今回作ったのはニューラルネットワークを使ったディープラーニングではなく、機械学習手法の一つであるSVMを用いました。
AIとかディープラーニング(ニューラルネットワークは、ディープラーニングで用いられる手法)とか機械学習とかの区別は、nvidiaが書いたのを日本語に翻訳したやつがあったので、以下の記事を参考にしてください。
SVMって何なのさ?
SVMというのは、機械学習手法の一つです。Support Vector Machineの略称です。(Support Vectorについては、後述します。)主に2つの特徴量をクラス分けするのに用います。ではどのように分類するのでしょうか。簡単です。
この図に緑色の直線がありますが、この式を求めて、ある特徴量が、直線より左側だったらAのクラス、右側だったらBのクラス、というふうに分類すれば、特徴量のクラス分けができますね。ただ、ここで(個人的に思っているだけかもしれませんが)重要なのが、「マージン」と呼ばれている、直線と、直線に最も近い特徴量の点との距離です。例えば、もし、直線がBクラス側に偏っている状態で、クラス分けさせたら……
このように、本来、Bクラスとして分類されるべき特徴量が、Aクラスとして分類されてしまい、「誤分類」が発生してしまいます。それをできるだけ防ぐために、SVMでは、学習段階で、「マージン」を最大化するような直線の式を求めます。ちなみに、直線に最も近い特徴量点のことをSupport vectorと呼びます。マージンをサポートするベクター(ベクトル)ってことなんですかね。
SVMを実際に作成しても、上手く分類できないことがあります。ここでパラメータの調整を行って(チューニングと呼ばれています)、上手く分類できるようにします。このパラメータの話が、後でほんのすこしだけ出るので、説明します。
パラメータとして、γとCの2つがあります。まずγについて。
今までクラスを分類するのは直線と言ってきましたが、これは特徴量が2次元の値で表されるときだけで、イメージしやすい例で言えば、特徴量が3次元なら直線ではなく、2次元平面で分類することになります。特徴量が100次元なら、99次元平面で分類することになります。このように、SVMはどのような特徴量の次元でも、分類することができます。
特徴量が2次元で表されている時を考えます。このときは1次元平面、すなわち直線で分類するはずなのですが、特徴量点の分布によっては直線では分類できないことがあります。
このとき、適当な写像φによって、各特徴量をより高い次元空間内(n次元とします)に移動させます。すると、n-1次元平面で分類することができます。この写像を適用した後の空間を特徴空間と呼びます。この特徴空間内で特徴量点を2クラスに分類するn-1次元平面を決定するときに、特徴量点の内積を計算します。次元が大きくなると、この内積を計算するのがめんどくさくなります。この内積を簡単に計算しようと考えられたのが、カーネル関数というもので、例えば、以下のようなものがあります。
線形カーネル
ただの内積です。
RBFカーネル
上の関数にγがあると思うのですが、これがパラメータのγです。つまり、γの値を変更することによって、カーネル関数の値を調節することができます。カーネル関数は他にもたくさんあります。
次にCなのですが、これはコスト(Cost)のCで、マージン内に特徴量点の存在を許す、ソフトマージンSVMを使うときに使用するパラメータです。ソフトマージンSVMでは、分類する平面を求めるときに、Cに依存する値を加算することで、より分類に適した平面を求めることが出来ます(この辺りの説明は省きます)。ちなみに、ソフトマージンSVMに対して、マージン内に特徴量点が存在することを絶対に許さないものをハードマージンSVMといいます。ハードマージンSVMの方が、より厳密に分類することができるのですが、厳密すぎるせいで誤分類が発生して実用に適さなかったりします。まぁソフトマージンSVMでも誤分類が発生する可能性はあるので、どちらがいいのかは場合に依ります。
以上が、SVMについての簡単な説明です。もう少し理論的に説明しようとすると、もっと難しい数学のお話が出てきて数学が極めて苦手な僕があびゃーってなるのでしません。数学苦手じゃない人は調べてみて下さい。
参考(英語):
Understanding SVM — OpenCV-Python Tutorials 1 documentation
特徴量特徴量って言ってますが……
HOG特徴量ってなんだよ
HOG特徴量のHOGとは、Histograms of Oriented Gradientsの略称です。画像を小さいブロック、さらにもっと小さいセルに分け、そのセル中の輝度の勾配を求めてヒストグラムにし、ブロックごとに正規化して特徴量とします。よく画像中のエッジ抽出に使われるみたいです。なぜエッジ抽出に使えるのでしょうか。例えば、このような画像があったとしましょう。
大雑把に、輝度の勾配ベクトルを書いてみます。
すると、このようになります。この勾配ベクトルに対して、直角に交わるような直線を適当に引いてみると……
色の違いを分けているような線が引けます。エッジ抽出が出来ている、と考えられます。
今回は、このHOG特徴量をSVMで分類していきます。つまり、輝度の勾配が人っぽいものが人と判断されます。
参考:
http://www.vision.cs.chubu.ac.jp/joint_hog/pdf/HOG+Boosting_LN.pdf
のHOG特徴量についてのスライド部分
じゃあ実際どうやんの?
実は、OpenCVには既に学習済みの人検出SVMとHOG特徴量を使って検出する関数があります。
import cv2 import sys img_name = args[1] im = cv2.imread(img_name) hog = cv2.HOGDescriptor() hog.setSVMDetetor(cv2.HOGDescriptor_getDefaultPeopleDetector()) hogParams = {'winStride': (8, 8), 'padding': (32, 32), 'scale': 1.05} human, r = hog.detectMultiScale(im, **hogParams) for(x, y, w, h) in human: cv2.rectangle(im, (x, y), (x+w, y+h), (0, 50, 255), 3) cv2.imshow("human detection", im) while(1): wait = cv2.waitKey(1) if wait == 27: break cv2.destroyAllWindonws()
7行目辺りにほげほげPeopleDetectorとかありますが、これがの学習済みのSVMモデルです。探索も勝手にしてくれますし、探索終了したら人と判断されたところに矩形を描いてくれます。これを使えば簡単にできますね!
でもそれでは簡単すぎるので、今回は学習段階から作ってみようと思います。ただし、SVMを1から作りはしません。
まず、SVMを学習させる部分を作ります。大体どのような感じで作るかというと、
SVMに学習させるための正解画像・不正解画像を、正解・不正解を記録したラベルと共に読み込みます。
以上です。これができたら、次は実際に画像に対してSVMを適用します。この部分は、
- SVMをセットして、先程作成したSVMモデルを読み込みます。
- 画像を読み込みます。
- 読み込んだ画像中を探索しつつ、HOG特徴量を計算して、SVMに人かどうか判断させます。
- わかりやすくするため、最後に人と判断された部分に矩形を描いて表示して終了。
という風になります。
さて、まず前半の学習部分なのですが、まずは学習する画像を用意しなければなりません。幸いなことに、人検出の学習用のデータセットを作成していらっしゃる方がおりますので、今回はそちらからお借りしました。今回お借りしたデータセットは以下の2つです。
USC Pedestrian Detection Test Set
データセットも用意できたところで、早速ソースコードについて書きたいのですが、長くなってきたので、コードについてはまた次回書きます。
part2はこちら。
originfall.hatenablog.com