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を試します!
スポンサーリンク
関連記事