meideru blog

家電メーカーで働いているmeideruのブログです。主に技術系・ガジェット系の話を書いています。

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

      2018/10/20

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

人工知能(AI)

結論から言うと、初めてにしては、まぁまぁな検出精度だなと思いました笑。

とりあえず今日は、ここまでのまとめを書きたいと思います。

物体検出とは

※ 物体検出はオブジェクト検出とも呼ばれていて、多くの人はそちらの呼び名を使用しているかもしれません。

 

物体検出とは、画像内に写っているものから特定の物体を検出することです。

以下の画像を見れば、一発で理解できると思います。

@物体検出
物体検出

これが物体検出です。

物体検出の手法

多くの手法が存在しますが、どれでも根本的にやることは同じです。

画像内から領域を抽出して画像認識させ、一致する箇所を枠で囲うだけです。

候補領域の抽出方法としては、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と略す)で領域をまとめています。

このアルゴリズムは過去に記事を書きましたので、そちらをご覧ください。

最近は機械学習にのめり込んでいます。特にディープラーニングを用いた画像認識の勉強しています。これまでに手書き文字(MNIST)の認識や、一般物体認識(CIFAR-10)に挑戦しました。(CIFAR-10の記事は準備中です。。。) そして、今挑戦しているのは物体検出...

結果

まずは成功例から示します。

@成功例(NMS前)
自動車を検出@成功例(NMS後)
自動車を検出上手く検出できていますね!

NMSをかけると完璧です。

 

次に、失敗例を示します。

@失敗例 その1(NMS前)
自動車を検出

@失敗例 その2(NMS後)
自動車を検出

@失敗例 その2(NMS前)
自動車を検出@失敗例 その2(NMS後)
自動車を検出

余計な場所が検出されていますね。上手く検出できていません。

これは何故なのでしょうか?

考察します。

上手く検出できないことに対する考察

理由

上手く検出できない理由は、考えてみると、とても簡単なことでした。

分類器は、99.8%の精度だと言いました。これはすごい精度ですが、言い返せば1000枚に2枚は認識に失敗するということです。

今回、スライディングウィンドウで抽出している候補領域の数はおよそ1200個でした。

ということは、必然的に失敗する箇所が出てくるのです。

改善策

調べて見た結果、画像検出ではAdaBoostという手法が存在するようです。

AdaBoostは、1つ1つの精度はあまり良くない識別器を複数つなげることで、全体として精度の良い識別器を構成する手法です。

例えば、HOG+SVMの他にも識別器を作って、それを繋げるということになります。

こちらにわかりやすい説明が書いてありました。

感想

SVMの学習が思ったよりも速くてビックリしましたΣ(・□・;)

これまでは中心に勉強してきたディープラーニングは学習にとてもとても時間がかかります。

次回は、AdaBoostを試します!

 - 技術系