ローリングコンバットピッチなう!

AIとか仮想化とかペーパークラフトとか

OpenCVで顔検出

前々からやってみたかったOpenCVで顔検出にトライ。
これ自体はググればあちこちにサンプルソースが見つかるので、まずはそれをほぼそのままコピって来て実行。静止画から探索する例とウェブカメラの動画から探索する例が有りますが、自分は相当昔に購入したウェブカメラで試しました。
顔だけだと本当にサンプルソースそのままなので、目の検出も同時にやって表示させています

# -*- Coding: utf-8 -*-

import cv2

cap = cv2.VideoCapture(0)
cascade1 = cv2.CascadeClassifier('/home/foo/anaconda3/envs/py35/share/OpenCV/haarcascades/haarcascade_eye_tree_eyeglasses.xml')
cascade2 = cv2.CascadeClassifier('/home/foo/anaconda3/envs/py35/share/OpenCV/haarcascades/haarcascade_frontalface_alt2.xml')

while(True):
	ret,frame = cap.read()
	facerect = cascade1.detectMultiScale(frame)
	for rect in facerect:
		cv2.rectangle(frame,tuple(rect[0:2]),tuple(rect[0:2] + rect[2:4]),(255,255,255),thickness = 2)
	facerect = cascade2.detectMultiScale(frame)
	for rect in facerect:
		cv2.rectangle(frame,tuple(rect[0:2]),tuple(rect[0:2] + rect[2:4]),(255,255,255),thickness = 2)
	cv2.imshow('frame',frame)
	if cv2.waitKey(1) == 27:
		break;
cap.release()
cv2.destroyAllWindows()

こんな感じのソースです。anaconda3を使ってOpenCVはローカルにインストール。
(ちなみにpython3.5環境です)
特にどうということも無く動くのですが、動画の表示がものすごくカクつく。
カメラの画像は640x480、CPUは2GHzのシングルコアCeleronですが、そもそも全く並列処理とか取り入れていないプログラムなので、クァッドコアとかでも大差無いのかも。

1フレーム毎に顔検出と目検出を両方走らせていて、これが重たいのだと思います

そこでもう少し動きが軽くなるように手を入れて見ました。あまりこーいうサンプルは載っていなかったので。

# -*- Coding: utf-8 -*-

import cv2

scaleFactor1 = 2
scaleFactor2 = 4
firstFlag = True
eyeDetect = True
faceDetect = True

cap = cv2.VideoCapture(0)
cascade1 = cv2.CascadeClassifier('/home/foo/anaconda3/envs/py35/share/OpenCV/haarcascades/haarcascade_eye_tree_eyeglasses.xml')
cascade2 = cv2.CascadeClassifier('/home/foo/anaconda3/envs/py35/share/OpenCV/haarcascades/haarcascade_frontalface_alt.xml')

while(True):
	ret,frame = cap.read()
	if ret == True:
		if firstFlag:
			check_width1 = frame.shape[1] // scaleFactor1
			check_height1 = frame.shape[0] // scaleFactor1
			check_width2 = frame.shape[1] // scaleFactor2
			check_height2 = frame.shape[0] // scaleFactor2

			firstFlag = False

		if eyeDetect == True:
			frame2 = cv2.resize(frame,(check_width1,check_height1))
			facerect = cascade1.detectMultiScale(frame2)
			for rect in facerect:
				rect_t1 = tuple(rect[0:2])
				rect_t1_scaled = tuple([x * scaleFactor1 for x in rect_t1])
				cv2.rectangle(frame,rect_t1_scaled,rect_t2_scaled,(0,0,255),thickness = 2)

		if faceDetect == True:
			frame3 = cv2.resize(frame,(check_width2,check_height2))
			facerect = cascade2.detectMultiScale(frame3)
			for rect in facerect:
				rect_t1 = tuple(rect[0:2])
				rect_t1_scaled = tuple([x * scaleFactor2 for x in rect_t1])
				rect_t2 = tuple(rect[0:2] + rect[2:4])
				rect_t2_scaled = tuple([x * scaleFactor2 for x in rect_t2])
				cv2.rectangle(frame,rect_t1_scaled,rect_t2_scaled,(255,255,255),thickness = 2)

		cv2.imshow('frame',frame)
	if cv2.waitKey(1) == 27:
		break;

cap.release()
cv2.destroyAllWindows()

ソースにコメント入れていませんが、やっていることは以下の内容です。

  1. カスケード検出器にかける前にキャプチャ画像を一定比率で縮小
  2. 縮小した画像に検出器をかける
  3. 検出された顔や目の領域の座標を元のスケールに変換
  4. 変換された座標を元にキャプチャ画像に顔や目の領域を示す矩形を描画
OpenCVのカスケード検出器の原理を解説したホームページを見ると顔や目に特有の陰影のパターンを見つけ出すことで検出する様なので、検出器にかける画像はむやみに高解像度である必要は無く、対象物を含む領域にそれなりの画素数を含めば良い。例えば画像の縮小スケールを1/2にすれば捜査する面積・ピクセル数は1/4, 縮小スケールを1/4にすれば面積・ピクセル数は1/16なので、検出器の処理量が大幅に減ります。

640x480の元画像に対して、顔検出と目検出でそれぞれ別の縮小率を適用出来る様にして実験した感じでは、顔検出は1/4でもOK,目は1/4だと厳しく、1/2だとOKで、全体的にはかなり滑らかに動画表示しつつ、顔と目を同時検出できる様になりました。

フルHDや4K画像なんかに真面目に検出器をかけると、ものすごい処理量になるので割と良く使われる手法じゃないかと思います。
ちなみにOpenCVでの画像はnumpyのndarrayで表現されているみたいですが、ndarrayの扱いに慣れていないので検出した顔や目の領域の座標変換の部分があまりイケていないコードになっている気がします
この辺はpythonについては結構初心者なので、ご容赦を。