今回はOpenCVを使って画像に映る人の検知をしてみたいと思います。物体検出において、Deep Learningが研究トレンドになる以前に多く用いられていたHOG(Histogram of Oriented Gradients)特徴量を用いて人の認識を行います。
OpenCVとは
OpenCVは、画像処理・画像解析および機械学習等の機能をまとめたオープンソースのライブラリです。
Intelが開発し、現在はPythonを始め、C/C++、Java、MATLAB用として公開されています。オープンソースなので誰でも無料で幅広い用途に使うことができ、商業利用も可能です。
OpenCVを用いることで、画像や動画の中に存在する物体の位置情報やパターン、動きをプログラムが識別できるようになります。他にも以下のような機能を利用できます(図-1)。
図-1 OpenCVの主要機能:(「CVPR 2015チュートリアル資料」から引用)
HOGとは
簡単に言うと、人のシルエットを見て、その形の特徴を、位置や角度で表現するものです。
具体的に言うと、画像における輝度(色、明るさ)の勾配方向をヒストグラム化した特徴量です。画像を複数のブロックに分割し,各ブロックの勾配をヒストグラム化することにより、画像の形状変化にロバストな特徴量を得ることが出来きます。詳細は論文を参照してください。
出典:Suleiman, Amr, et al. “Towards closing the energy gap between HOG and CNN features for embedded vision.” 2017 IEEE International Symposium on Circuits and Systems (ISCAS). IEEE, 2017.(https://arxiv.org/pdf/1703.05853.pdf)
学習準備
ライブラリ
必要なライブラリをインストールします。
1 2 3 4 |
import cv2 from PIL import Image import dlib import math |
ライブラリが読み込めない方は、下記を実行してみてください。
1 |
!pip install ライブラリ名 |
画像の読み込み
検知したい画像を読み込みます。今回は独自データを用いています。
1 2 3 4 5 6 |
img = cv2.imread('img/img01.jpg') height, width = img.shape[:2] print('画像の幅:' + str(width)) print('画像の高さ:' + str(height)) img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# カラーデータの色空間の変換 Image.fromarray(img_rgb)#画像の表示 |
【コードの説明】
1行目では、画像img01.jpgを読み込み、変数imgへ格納しました。
2行目~4行目では、変数imgに含まれる画像の高さと幅をshapeによって取り出し、出力しました。
5行目では、OpenCVの関数cvtColor()でRGB(赤、緑、青)に変換しました。理由として、Pillowでは色の順番がRGBを前提としていますが、OpenCVの関数imread()で画像を読み込むと色の順番がBGRになってしまうためです。
6行目では、画像を表示させています。
人の検知
1 2 3 4 5 6 7 8 9 10 11 |
#準備 hog = cv2.HOGDescriptor() hog.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector()) hogParams = {'winStride':(8,8),'padding':(32,32),'scale':1.05,'hitThreshold':0} #検知 img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) human, r = hog.detectMultiScale(img_gray,**hogParams) if (len(human)>0): for (x,y,w,h)in human: cv2.rectangle(img,(x,y),(x+w,y+h),(255,255,255),3) cv2.imwrite('img/img01_detect.jpg', img) #画像の保存 |
若干ズレがあり、二重になっています・・・。
【コードの説明】
2行目~3行目で、人を検知するモデルの情報を与えました。関数setSVMDetectorを用い、引数にcv2.HOGDescriptor_getDefaultPeopleDetector()を指定しました。
4行目に、実行する際のパラメーターを指定しました。
6行目に、読み込んだ画像をcvtColorによってモノクロにしました。
7行目に、detectMultiScaleによって人の検知を実行します。ここで、4行目に指定したパラメータを指定しています。
8行目に、humanに格納された、検出された人の位置情報を用いて画像に四角形を描く関数rectangleを利用しました。
人の顔の検知
OpenCVでは、顔を検知できる学習済みファイルが用意されています。学習済みファイルは下記リンク先からダウンロード可能です。今回は顔検出のために”haarcascade_frontalface_alt.xml“を使います。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#準備 cascade_file = "haarcascade_frontalface_alt.xml" cascade = cv2.CascadeClassifier(cascade_file) #検知 img = cv2.imread('img/img01.jpg') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) face_list = cascade.detectMultiScale(img_gray, minSize=(50, 50)) #検知した顔に印を付ける for (x, y, w, h) in face_list: color = (0, 0, 225) pen_w = 3 cv2.rectangle(img, (x, y), (x+w, y+h), color, thickness = pen_w) cv2.imwrite('img/img01_face.jpg', img) |
こちらは上手く行ったみたいです!
【コードの説明】 基本的に人の検知をした際のコードとあまり変わりません。留意点として、
2行目~3行目で、正面の顔を検知するモデルの情報を与えました。関数CascadeClassifierを用い、引数にhaarcascade_frontalface_alt.xmlを指定しました。
7行目に、detectMultiScaleによって人の顔の検知を実行します。
9行目に、face_listに格納された、検出された正面の顔の位置情報を用いて画像に四角形を描く関数rectangleを利用しました。
人の顔の角度の検知【発展】
Dlibというライブラリを用いることで、顔のランドマークと呼ばれる目・鼻・口・輪郭を68の特徴点で表現することができます(図-2)。これにより、人がどこに顔を向けているかを検知できます。今回も、顔のランドマークを検知できる学習済みファイルが用意されています。学習済みファイルは下記リンク先からダウンロード可能です。今回は”shape_predictor_68_face_landmarks.dat.bz2“を使います。
図-2 ランドマークの番号:https://ibug.doc.ic.ac.uk/resources/facial-point-annotations/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
#準備 predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat") detector = dlib.get_frontal_face_detector() #検知 img = cv2.imread("img/img01.jpg") dets = detector(img, 1) for k, d in enumerate(dets): shape = predictor(img, d) # 顔領域の表示 color_f = (0, 0, 225) color_l_out = (255, 0, 0) color_l_in = (0, 255, 0) line_w = 3 circle_r = 3 fontType = cv2.FONT_HERSHEY_SIMPLEX fontSize = 1 cv2.rectangle(img, (d.left(), d.top()), (d.right(), d.bottom()), color_f, line_w) cv2.putText(img, str(k), (d.left(), d.top()), fontType, fontSize, color_f, line_w) #重心を導出する箱を用意 num_of_points_out = 17 num_of_points_in = shape.num_parts - num_of_points_out gx_out = 0 gy_out = 0 gx_in = 0 gy_in = 0 for shape_point_count in range(shape.num_parts): shape_point = shape.part(shape_point_count) #print("顔器官No.{} 座標位置: ({},{})".format(shape_point_count, shape_point.x, shape_point.y)) #器官ごとに描画 if shape_point_count<num_of_points_out: cv2.circle(img,(shape_point.x, shape_point.y),circle_r,color_l_out, line_w) gx_out = gx_out + shape_point.x/num_of_points_out gy_out = gy_out + shape_point.y/num_of_points_out else: cv2.circle(img,(shape_point.x, shape_point.y),circle_r,color_l_in, line_w) gx_in = gx_in + shape_point.x/num_of_points_in gy_in = gy_in + shape_point.y/num_of_points_in #重心位置を描画 cv2.circle(img,(int(gx_out), int(gy_out)),circle_r,(0,0,255), line_w) cv2.circle(img,(int(gx_in), int(gy_in)),circle_r,(0,0,0), line_w) #顔の方位を計算 theta = math.asin(2*(gx_in-gx_out)/(d.right()-d.left())) radian = theta*180/math.pi print("顔方位:{} (角度:{}度)".format(theta,radian)) #顔方位を表示 if radian<0: textPrefix = " left " else: textPrefix = " right " textShow = textPrefix + str(round(abs(radian),1)) + " deg." cv2.putText(img, textShow, (d.left(), d.top()), fontType, fontSize, color_f, line_w) cv2.imwrite('img/img01_face_shape.jpg', img) |
かなり良く検知されていると思います。
【コードの説明】
2行目~3行目で、顔のランドマークを検知するモデルの情報を与えました。関数get_frontal_face_detectorを用い、引数にshape_predictor_68_face_landmarks.datを指定しました。写真から正面顔を検出し、それに含まれる68点の顔のランドマークから、顔の向きを割り出します。具体的には、輪郭の重心と、内側の重心との差から、顔の方位を割り出しています。検出された顔画像を見ると、何度ずれているかが、表示されています。
さいごに
OpenCVを使って人の検知をしてみた感想ですが、思ってたより検知してくれてました。ぜひ一度、独自データで実践してみてください。他にも、dlibを用いることで表情の変化などを検知することができるとのことで、今後紹介できればと思います。
参考文献
Python実践データ分析100本ノック, 下山輝昌, 松田雄馬, 三木孝行