John(筋肉)の備忘録的な何か

備忘録的な何かであり、進捗そのものでもあるよ!!怖い人は見ないで!

HOG特徴量を用いたSVM検出器を作ってみた

Johnです。機械学習関連の記事を貪るように読む春休みを過ごしています。

今回はSVM検出器で自分好みの顔含む2次絵を選別できないかと頑張ってみたpart1という感じです。

openCVやselectivesearchなんかのおかげで簡単に(精度は悪いけど)作れたので自慢したいと思います。

SVMを作るコード

# coding:utf-8

import os
import numpy as np
import cv2

# HOGをとるためのパラメータ
win_size = (60, 60)
block_size = (16, 16)
block_stride = (4, 4)
cell_size = (4, 4)
bins = 9
hog = cv2.HOGDescriptor(win_size, block_size, block_stride, cell_size, bins)

# HOGを計算し、データとラベルのリストを返す関数
def calc_hog():
    train_and_labels = []
    for file in os.listdir("./dataset/pos/"):
        img = cv2.imread(f"./dataset/pos/{file}", 0)
        img = cv2.resize(img, win_size)
        train_and_labels.append((hog.compute(img), 0))

    for file in os.listdir("./dataset/neg/"):
        img = cv2.imread(f"./dataset/neg/{file}", 0)
        img = cv2.resize(img, win_size)
        train_and_labels.append((hog.compute(img), 1))
    
    # 順番をランダムにする処理
    # もっといい方法がありそう
    np.random.shuffle(train_and_labels)
    train = []
    label = []
    for train_and_label in train_and_labels:
        train.append(train_and_label[0])
        label.append(train_and_label[1])

    return np.array(train), np.array(label)

if __name__ == "__main__":
    train, label = calc_hog()

    # SVMを新規作成
    svm = cv2.ml.SVM_create()
    # カーネル関数設定
    svm.setKernel(cv2.ml.SVM_LINEAR)
    # パラメータCを決定
    svm.setType(cv2.ml.SVM_C_SVC)
    svm.setC(0.5)
    # 訓練
    svm.train(train, cv2.ml.ROW_SAMPLE, label)
    svm.save('train.xml')

汚いコードですが、いまは書き直すのが億劫なのでにゃんです。

データは自分好みの二次絵顔検出ということで、顔だけトリミングした画像70枚ほどと、それ以外の画像50枚ほどでしました。 CNNとかに比べると圧倒的に少ないですが、調べた感じSVMはこれくらいでまぁ行けるそう。

学習時間はCPUで2秒とかだった気がする。

これによりtrain.xmlというSVMが作れるよ。

SVMを試すコード

# coding:utf-8

import os
import time
import numpy as np
import cv2
from selectivesearch import selective_search as ss

start_time = time.time()

# HOGをとるためのパラメータ
win_size = (60, 60)
block_size = (16, 16)
block_stride = (4, 4)
cell_size = (4, 4)
bins = 9
hog = cv2.HOGDescriptor(win_size, block_size, block_stride, cell_size, bins)

# 検出したい画像を選択
target_path = "/mnt/c/Users/user/Pictures/dataset_favorite/"
target = os.listdir(target_path)[0]
print(target)

# 作成したSVMをロード
svm = cv2.ml.SVM_load("train.xml")

# HOG用とselectivesearch用に2種類用意
img_bgr = cv2.imread(target_path + target)
img = cv2.cvtColor(img_bgr.copy(), cv2.COLOR_BGR2GRAY)

img_load_time = time.time()
print(f"img_load:{img_load_time - start_time}")

# selectivesearchを実行
# img_lblには範囲で区切られている画像データが入っている
# regionsはselectivesearchで得られるrectの情報とか
img_lbl, regions = ss(img_bgr, scale=500, sigma=0.9, min_size=1000)

ss_time = time.time()
print(f"ss:{ss_time - img_load_time}")

hog_list = []
hog_list_rect = []
almosit_regions = len(regions)

# HOGの結果をリストにまとめる
for index, i in enumerate(regions):
    x, y, w, h = i["rect"]
    # rectの範囲が小さいものを排除
    if h < win_size[1] or w < win_size[0]:
        continue
    print(f"hog: {index + 1}/{almosit_regions}")
    img_temp = cv2.resize(img[y:y+h, x:x+w].copy(), win_size)
    hog_list.append(hog.compute(img_temp))
    hog_list_rect.append((x, y, w, h))

# SVMにHOGを渡し、検出する
result = svm.predict(np.array(hog_list))[1].ravel()

hog_and_svm_time = time.time()
print(f"hog_and_svm:{hog_and_svm_time - ss_time}")

print(result)

# 0、つまりは検出できたもののrectを保管
recog_face_rect = [hog_list_rect[index] for index, i in enumerate(result) if i == 0.]

# 赤色
color = (0, 0, 255)

# 元画像の検出できた先にrectを描画
if recog_face_rect:
    for i in recog_face_rect:
        x, y, w, h = i
        cv2.rectangle(img_bgr, (x,y), (x+w, y+h), color, thickness=8)
    cv2.imwrite(f"./ppp.jpg", img_bgr)

end_time = time.time()
print(f"end:{end_time - hog_and_svm_time}")

print(f"total:{end_time - start_time}")

とりあえず作ったって感じで読みにくいでしょうがこれで指定した1つの画像に対して検出をかけます。

出力した画像は著作権的にアウトなので載せれはしませんが、精度は悪かったです。lbpcascade_animeface.xmlとぼんやり比べて精度わりぃ~~ってだけですけど。

まとめ

  • アニキャラの顔ならlbpcascade_animeface.xmlを使った方がよい
  • selective_search、めっちゃいい
  • データをもっと丁寧に作れば精度全然変わりそう(データでかなり手を抜いた)