HOG + SVMで物体検出をやってみた
HOG + SVMで物体検出をやってみました。

結論から言うと、初めてにしては、まぁまぁな検出精度だなと思いました笑。
とりあえず今日は、ここまでのまとめを書きたいと思います。
目次
スポンサーリンク
物体検出とは
※ 物体検出はオブジェクト検出とも呼ばれていて、多くの人はそちらの呼び名を使用しているかもしれません。
物体検出とは、画像内に写っているものから特定の物体を検出することです。
以下の画像を見れば、一発で理解できると思います。
@物体検出

これが物体検出です。
物体検出の手法
多くの手法が存在しますが、どれでも根本的にやることは同じです。
画像内から領域を抽出して画像認識させ、一致する箇所を枠で囲うだけです。
候補領域の抽出方法としては、Selective Searchやスライディングウィンドウなどがあります。
アルゴリズムの詳細については、この場で解説しませんので、興味のある方は調べてみてください。
HOGとSVMとは
HOG(Histograms of Oriented Gradients)とは、特徴ベクトルの一種です。輝度の勾配をヒストグラム化したものです。
SVM(Support Vector Machine)とは、分類アルゴリズムの一種です。分類アルゴリズムの中では、最も強力なものの一つと言われています。
こちらも、アルゴリズムの詳細については、この場で解説しませ。興味のある方は調べてみてください。
HOGとSVMで画像検出をやってみた
何を検出するのか
横向きの自動車を検出します。
@学習データ

なぜ、それをやろうと思ったのかと言うと、学習データがあったからです。
初めての物体検出としては良い題材だと思います。
学習データは、以下のURLから無料でダウンロードできます。
(URL:https://cogcomp.cs.illinois.edu/Data/Car/)
学習方法
学習データに入っている正例データ(positive data)と負例データ(negative data)を学習させます。
すべての学習データからHOG特徴量を計算して、SVMで学習させます。
このとき、HOGはscikit-image、SVMはscikit-learnというライブラリを用います。
以下が学習部分のコードです。
リファクタリングしていないので汚いです。ご了承ください。。。
from functions import *
from skimage.feature import hog
import random
from sklearn import svm
from sklearn.svm import SVC
from sklearn.externals import joblib
from sklearn.grid_search import GridSearchCV
'''
学習モデルを作成
'''
# 設定
posPath = "./data/posneg/positive/" # 正例データの保存パス
negPath = "./data/posneg/negative/" # 負例データの保存パス
modelPath = "./data/model/model.pkl" # 学習データの保存パス
# 定数(変更するな)
posMaxFileNum = 549 # 正例データのファイル番号の最大値
negMaxFileNum = 499 # 正例データのファイル番号の最大値
# 特徴ベクトルとラベル
hogValueList = [] # [(特徴ベクトル1), (特徴ベクトル2), ...]
labelList = [] # ['positive', 'negative', ...]
# 特徴ベクトルを抽出
## 正例データ
for i in range(posMaxFileNum + 1):
sys.stdout.write("\rPositive Data : %d/%d" % (i, posMaxFileNum))
fileName = 'pos-' + str(i) + '.pgm'
fullPath = posPath + fileName
img = cv.imread(fullPath)
# 2D化
img2d = img[:, :, 0]
hogValue = hog(img2d, block_norm='L2-Hys')
hogValueList.append(hogValue)
labelList.append('positive')
print("")
## 負例データ
for i in range(negMaxFileNum + 1):
sys.stdout.write("\rNegative Data : %d/%d" % (i, negMaxFileNum))
fileName = 'neg-' + str(i) + '.pgm'
fullPath = negPath + fileName
img = cv.imread(fullPath)
# 2D化
img2d = img[:, :, 0]
hogValue = hog(img2d, block_norm='L2-Hys')
hogValueList.append(hogValue)
labelList.append('negative')
# 学習
tuned_parameters = [
{'C': [1, 10, 100, 1000], 'kernel': ['linear']},
{'C': [1, 10, 100, 1000], 'kernel': ['rbf'], 'gamma': [0.001, 0.0001]},
{'C': [1, 10, 100, 1000], 'kernel': ['poly'], 'degree': [2, 3, 4], 'gamma': [0.001, 0.0001]},
{'C': [1, 10, 100, 1000], 'kernel': ['sigmoid'], 'gamma': [0.001, 0.0001]}
]
clf = GridSearchCV(SVC(), tuned_parameters, cv=5)
clf.fit(hogValueList, labelList)
# 結果表示
print(clf.grid_scores_)
print("")
print(clf.best_params_)
print("")
print(clf.grid_scores_)
print("")
# モデルを保存
joblib.dump(clf, modelPath)
ポイントはSVMの学習にグリッドサーチを使用している点です。
これを用いると、色々なハイパラメータを組み合わせて最適なモデルを作成することができます。
学習データに対して、クロスバリデーションで平均精度99.8%まで到達できました。
検出方法(予測方法)
学習したモデルを活かして、物体を検出させる方法について書きます。
候補領域の抽出には、スライディングウィンドウを採用しました。
以下がコードです。こちらもリファクタリングしていないので汚いですorz
from functions import *
from skimage.feature import hog
import random
from sklearn import svm
from sklearn.externals import joblib
'''
候補領域をスライディングウィンドウで抽出
'''
# 設定
valiPath = './data/validation/' # テスト画像のパス
modelPath = "./data/model/model.pkl" # 学習データのパス
while(True):
print('インデックスを入力(例, 343)')
index = int(input('Index : '))
fullPath = valiPath + 'test-' + str(index) + '.pgm'
mat = cv.imread(fullPath)
# 候補領域を取得
ppRects = SlidingWindows([mat.shape[1], mat.shape[0]], [100, 40], 3, 3)
print(len(ppRects))
# 画像を切り出してリサイズ
ppImg = CutResizeImageAll2(ppRects, mat, [100, 40])
# 特徴ベクトルを抽出
## 特徴ベクトル
hogValueList = [] # [(特徴ベクトル1), (特徴ベクトル2), ...]
## 抽出
for i, img in enumerate(ppImg):
# 2D化
img2d = img[:, :, 0]
hogValue = hog(img2d, block_norm='L2-Hys')
hogValueList.append(hogValue)
# 分類
clf = joblib.load(modelPath) # モデルを読み込む
prediction = clf.predict(hogValueList) # 予測
# 最終的な領域を求める
## 領域
detectionList = []
## 領域を求める
for i, val in enumerate(prediction):
if val == 'positive':
detectionList.append(ppRects[i])
# 画像を表示
PrintRectsAndLabels2(mat, detectionList)
## list→Numpyに変換
transedRects = TransListToNumpyRects(detectionList)
## Non-Maximum-Suppression
nmsRectsList = non_max_suppression_slow(transedRects, 0.1)
# 画像を表示
PrintRectsAndLabels2(mat, nmsRectsList)
説明を忘れていましたが、最後にNon-Maximum Suppression(以下NMSと略す)で領域をまとめています。
このアルゴリズムは過去に記事を書きましたので、そちらをご覧ください。

結果
まずは成功例から示します。
@成功例(NMS前)
@成功例(NMS後)
上手く検出できていますね!
NMSをかけると完璧です。
次に、失敗例を示します。
@失敗例 その1(NMS前)

@失敗例 その2(NMS後)

@失敗例 その2(NMS前)
@失敗例 その2(NMS後)

余計な場所が検出されていますね。上手く検出できていません。
これは何故なのでしょうか?
考察します。
上手く検出できないことに対する考察
理由
上手く検出できない理由は、考えてみると、とても簡単なことでした。
分類器は、99.8%の精度だと言いました。これはすごい精度ですが、言い返せば1000枚に2枚は認識に失敗するということです。
今回、スライディングウィンドウで抽出している候補領域の数はおよそ1200個でした。
ということは、必然的に失敗する箇所が出てくるのです。
改善策
調べて見た結果、画像検出ではAdaBoostという手法が存在するようです。
AdaBoostは、1つ1つの精度はあまり良くない識別器を複数つなげることで、全体として精度の良い識別器を構成する手法です。
例えば、HOG+SVMの他にも識別器を作って、それを繋げるということになります。
こちらにわかりやすい説明が書いてありました。
感想
SVMの学習が思ったよりも速くてビックリしましたΣ(・□・;)
これまでは中心に勉強してきたディープラーニングは学習にとてもとても時間がかかります。
次回は、AdaBoostを試します!
スポンサーリンク
関連記事