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、めっちゃいい
- データをもっと丁寧に作れば精度全然変わりそう(データでかなり手を抜いた)