たかのめ

Twitterでは文字数が足りないことを書きます。語録はあります。Twitter:@Originfall

OpenCVでSVMとHOG特徴量を使って人を検出するのを試みてみる。【part4】

 part3でSVMの学習を終わらせました。いよいよ、実際に画像にSVMを適用してみます。この部分では、

  • SVMをセットして、先程作成したSVMモデルを読み込みます。
  • 画像を読み込みます。
  • 読み込んだ画像中を探索しつつ、HOG特徴量を計算して、SVMに人かどうか判断させます。
  • わかりやすくするため、最後に人と判断された部分に矩形を描いて表示して終了。

というのを行います。

 part3はこちら。
originfall.hatenablog.com



 今回も、部分部分でコードを分けます。

import cv2
import sys
import numpy as np

#parameter for hogdescriptor
winSize = (44, 124)
blockSize = (16, 16)
blockStride = (4,4)
cellSize = (4, 4)
nbins = 9
derivAperture = 1
winSigma = -1
histogramNormType = 0
L2HysThreshold = 0.2
gammaCorrection = 1
nlevels = 64
signedGradients = True

hog_test =[]

args = sys.argv

people = 0
# make hog descriptor
hog = cv2.HOGDescriptor(winSize, blockSize, blockStride, cellSize, nbins, derivAperture, winSigma, histogramNormType, L2HysThreshold, gammaCorrection, nlevels, signedGradients)

 ここまでは、part3の冒頭部分とほぼ同じです。

# load svm
svm = cv2.ml.SVM_load('man_svm.xml')

# Load input image
path_load = args[1]
load_img = cv2.imread(args[1], 0)
final_img = cv2.cvtColor(load_img.copy(), cv2.COLOR_GRAY2BGR)

# get load input image's width and height
input_height, input_width = load_img.shape[:2]
# for judge detect
detect = np.zeros((input_height, input_width), dtype=np.int32)

 最初にcv2.ml.SVM_load()、でpart3で作成し、保存したSVMを読み込みます。
 また、このプログラムの実行時には、引数に検出する画像のパスをとりますが、それを使って画像を読み込みます。final_imgに入れるときにグレースケールからBGRに変換してるのは、あとで人が検出された部分に窓を描く時、窓の色がグレースケールだとわかりづらいためです。
 後で使うので、画像の高さ及び幅を取得し、(画像の高さ)×(画像の幅)からなる全要素が0の配列を作成しておきます。

# Run SVM moving search window
search_width = input_width
search_height = search_width * 3
for search_width in range(input_width, int(input_width / 10),  -5):
	for search_window_y in range(0, (input_height - search_height), 5):
		for search_window_x in range(0, (input_width - search_width), 5):

			window = load_img[search_window_y:(search_window_y + search_height), search_window_x:(search_window_x + search_width)]
			detect_part = detect[search_window_y:(search_window_y + search_height), search_window_x:(search_window_x + search_width)]
			# avoid searching already detected human area
			if np.count_nonzero(detect_part) > ((search_height * search_width) / 3):
				continue

			window_resized = cv2.resize(window, winSize)
			hog_test = hog.compute(window_resized)
			hog_reshaped = hog_test.reshape([1, 32256])
			result = svm.predict(hog_reshaped)

			if result[1][0, 0] == 1:
				people += 1
				print("found person!")
				detect_part.fill(1)
				final_img = cv2.rectangle(final_img, (search_window_x, search_window_y), ((search_window_x + search_width), (search_window_y + search_height)), (0, 255, 0), 3)		
	search_height -= 15

#show result

print("estimated people:"+str(people))
cv2.imshow('result', final_img)
while(1):
	wait = cv2.waitKey(1)
	if wait == 27:
		break

cv2.destroyAllWindows()

 探索窓を作成して動かしながら探索していきます。探索部分はforループの入れ子になっている部分です。この探索部分を簡単に説明すると、まず大きい探索窓(初期値は、幅は読み込んだ画像の幅と同じですが、高さは読み込んだ画像の幅×3です)を用意して、読み込んだ画像のうち、探索窓中の分のHOG特徴量を計算して、SVMを適用します。探索窓はまず、横に5ピクセルずつ動かし、探索窓が画像からはみ出したら、縦に5ピクセルずらしてまた左から横へ探索していきます。画像の右下まで到達したら、探索窓の幅を5ピクセル、高さを15ピクセル減少させて、また同様に探索していきます。この探索窓の幅と高さの比率は、学習画像の幅(44:124)をもとにしています。
 また、先ほどdetectという配列を作成しましたが、人を検出した場合、そのときの画像中の探索窓部分に相当する部分のdetectの要素を1にします。これは、同じ人物を2回以上カウントしてしまうのを回避するためで、探索するたびに、配列detectの探索窓部分に相当する部分を参照して、33%以上が1になっていれば、その部分のHOG特徴量の計算等を飛ばして次の部分の探索を行います。
 画像全体のHOG特徴量を計算してから探索した方が良さそうですが、計算したHOG特徴量の配列のうち、どの部分が画像のどの部分に相当するのかがわからないので、探索するごとに一々計算しています。探索時間は結構かかります。
 出力としては、画像中のどこに人がいたのか、検出された部分に窓を描いた画像を表示し、その画像中に何人いるかを表示します。

 例としてなんらかの画像で適用してみたのをお見せできればいいんですが、データセットに適用した結果の画像を公開しても構わないのかがよくわからないので、ご自分の目で確認してみてください。一応、今回使用したデータセットで、学習に使っていない画像に対して適用してみたところ、USCの方はそこそこ正しく検出できます。一方、INRIAの方は、正しく検出される場合もありますが、余計なところを人として誤検出してしまうことが結構多いです。

 実質ぼくが人生で初めて書いたプログラムなので未熟な部分も多いと思いますがご了承ください。