動画から顔認識させてみた(OpenCV:python版)

# きっかけ
[OpenCVを使って長い動画を一部だけ保存する方法](https://qiita.com/satsukiya/items/9647e20c4e27b3d0362a)
[動画からキャプチャ画像を撮る方法(OpenCV)](https://qiita.com/satsukiya/items/c9dfdf237f5c60c90820)
と立候補動画からOpenCVを使って画像処理で必要そうな環境を整えていました。
ここからは関数・ライブラリを使って処理してみます。
最初は顔認識です。

# 準備
Macで開発しているのでHomebrewからインストールします。

```
brew install opencv
```

それで `/usr/local/Cellar/opencv/4.1.1_2/share/opencv4/haarcascades/` 配下にはカスケード(分配器)と呼ばれる顔や体全体を認識するのに必要なパーツがあります。種類については[OpenCVを使った顔認識（Haar-like特徴分類器）](https://qiita.com/hitomatagi/items/04b1b26c1bc2e8081427#haar-like特徴分類器)に書かれています。

ここでは、

- haarcascade_frontalface_alt_tree.xml 顔正面
- haarcascade_eye.xml　両目
- haarcascade_righteye_2splits.xml 右目
- haarcascade_lefteye_2splits.xml 左目

を使ってみます。

# 処理の流れ
ここでは

1. フレームの中に顔が写っている
2. 1からROI(Region of Interest:対象領域)となる画像を切り出す。右領域と左領域でそれぞれ分割(アシュラ男爵方式)。
3. 2の画像の中に右目と左目の両方が写っているか判定
4. 3の条件が合えば顔と右目と左目を四角形で囲む

と処理しています。



# 開発

上記の処理の流れをふまえて開発。

```Python:右目左目を分けて判定したバージョン
import cv2
import os
import time

if __name__ == '__main__':

	cap = cv2.VideoCapture('one_minutes.mp4')

	cap_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
	cap_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
	fps = cap.get(cv2.CAP_PROP_FPS)

	fourcc = cv2.VideoWriter_fourcc('m','p','4','v')
	writer = cv2.VideoWriter('detect_face.mp4',fourcc, fps, (cap_width, cap_height))

	cascade_base_path = "/usr/local/Cellar/opencv/4.1.1_2/share/opencv4/haarcascades/"
    #準備　カスケードを取得
	face_cascade = cv2.CascadeClassifier(os.path.join(cascade_base_path, 'haarcascade_frontalface_alt_tree.xml'))
	right_eye_cascade = cv2.CascadeClassifier(os.path.join(cascade_base_path, 'haarcascade_righteye_2splits.xml'))
	left_eye_cascade = cv2.CascadeClassifier(os.path.join(cascade_base_path, 'haarcascade_lefteye_2splits.xml'))

	start = time.time()

	try :
		while True:

			if not cap.isOpened():
				break

			ret, frame = cap.read()

			img_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            # 1. フレームの中に顔が写っている　
			face_points = face_cascade.detectMultiScale(img_gray)
			
			for (fx,fy,fw,fh) in face_points:
				
                #2. ROI(Region of Interest:対象領域)となる画像を切り出す
                #右領域と左領域でそれぞれ分割(アシュラ男爵方式)
				width_center = fx + int(fw * 0.5)
				face_right_gray = img_gray[fy:fy+fh, fx:width_center]
				face_left_gray = img_gray[fy:fy+fh, width_center:fx+fw]
				#3. 右目と左目の両方が写っているか判定
				right_eye_points = right_eye_cascade.detectMultiScale(face_right_gray)
				left_eye_points = left_eye_cascade.detectMultiScale(face_left_gray)

				if 0 < len(right_eye_points) and 0 < len(left_eye_points):
					(rx,ry,rw,rh) = right_eye_points[0]
					(lx,ly,lw,lh) = left_eye_points[0]
				        
                    #4. 顔と右目と左目を四角形で囲む
					#右目はオレンジ
					cv2.rectangle(frame,(fx+rx,fy+ry),(fx+rx+rw,fy+ry+rh),(0,255,255),2)
					#左目は赤
					cv2.rectangle(frame,(width_center+lx,fy+ly),(width_center+lx+lw,fy+ly+lh),(0,0,255),2)
					#顔全体は緑
					cv2.rectangle(frame,(fx,fy),(fx+fw,fy+fh),(0,255,0),2)

			writer.write(frame)
	except cv2.error as e:
		print(e)

	print("処理時間 {} 秒".format(time.time() - start))
	writer.release()
	cap.release()
```

比較対象として、両目 `haarcascade_eye.xml` を使った場合のものもあげておきます。

```Python:両目バージョン
import cv2
import os
import time

if __name__ == '__main__':

	cap = cv2.VideoCapture('one_minutes.mp4')

	cap_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
	cap_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
	fps = cap.get(cv2.CAP_PROP_FPS)

	fourcc = cv2.VideoWriter_fourcc('m','p','4','v')
	writer = cv2.VideoWriter('detect_face_2.mp4',fourcc, fps, (cap_width, cap_height))

	cascade_base_path = "/usr/local/Cellar/opencv/4.1.1_2/share/opencv4/haarcascades/"

	face_cascade = cv2.CascadeClassifier(os.path.join(cascade_base_path, 'haarcascade_frontalface_alt_tree.xml'))
	eyes_cascade = cv2.CascadeClassifier(os.path.join(cascade_base_path, 'haarcascade_eye.xml'))

	start = time.time()

	try :
		while True:

			if not cap.isOpened():
				break

			ret, frame = cap.read()

			img_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
			face_points = face_cascade.detectMultiScale(img_gray)
			
			for (fx,fy,fw,fh) in face_points:
				
				face_gray = img_gray[fy:fy+fh, fx:fx+fw]				
				eyes_points = eyes_cascade.detectMultiScale(face_gray)
				
				if 0 < len(eyes_points):
					for (ex,ey,ew,eh) in eyes_points:
						#目はオレンジ
						cv2.rectangle(frame,(fx+ex,fy+ey),(fx+ex+ew,fy+ey+eh),(0,255,255),2)
						#顔全体は緑
						cv2.rectangle(frame,(fx,fy),(fx+fw,fy+fh),(0,255,0),2)

			writer.write(frame)
	except cv2.error as e:
		print(e)

	print("処理時間 {} 秒".format(time.time() - start))
	writer.release()
	cap.release()
```

# 結果

![face_main.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288206/3f33a234-3fd1-d287-9a29-9a17ccb346c8.gif)

![face_sub.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288206/1d9b4af2-6eef-0fc9-c1e6-8aa90df85036.gif)

### わかったこと

- 右目左目を分けて判定した方が精度が高かったです。特にカンペを見るときは両目よりも認識してたです！
- 両目の認識ですが目以外も誤認識してまう
- 顔が少し傾いただけれも精度がガクッと落ちてしまう。N○Hをぶっ壊せていない:tired_face: 傾いた画像やリサイズした画像を学習させたカスケードがないと認識しないっぽい。今だったらDeepLearningを使った方がベストかもしれません。確かライブラリ内で一枚のデータに対して、傾き・リサイズ・ぼかしなどをして複製させてから学習しているので精度が良さそうな気なします:thinking: (また新しいことを覚えないとダメなのか。。。:weary:気力体力がもたないっす:thermometer_face:)
- 処理時間が276.574秒かかりました。(約4分) C++だったら早く終わりそう。

# おわりに
数日前に[OpenCVでリアルタイム顔検知を試してみる](https://qiita.com/Ka-k/items/367ed8953c0350ce6e96)が投稿されて特落ちされたお:scream:
開発よりもまとめるのに時間がかかったお:weary:

# 参考にしたリンク

- [OpenCVを使った顔認識（Haar-like特徴分類器）](https://qiita.com/hitomatagi/items/04b1b26c1bc2e8081427#haar-like%E7%89%B9%E5%BE%B4%E5%88%86%E9%A1%9E%E5%99%A8)
- [【入門者向け解説】openCV顔検出の仕組と実践(detectMultiScale)](https://qiita.com/FukuharaYohei/items/ec6dce7cc5ea21a51a82)
    - 解説が詳しい。トラッキングしたい対象のものが回転していると精度が良くないっぽい