4種類の顔検出を動かしてみた [ Haar+Cascade/ HOG+SVM/ CNN/ MTCNN ]
- 概要
- コメント
- 環境条件
- Haar 特徴量+Cascade 識別器 による顔検出
- Dlib(HOG特徴量+SVM識別器)による顔検出
- CNN(mmod_human_face_detector.dat.bz2)による顔検出
- MTCNN(Multi-task Cascaded Convolutional Neural Networks for Face Detection)による顔検出
- 顔検出時間
- 顔検出イメージ(Haar/Dlib/CNN/MTCNNを一緒に検出)
概要
幾つかの顔検出方法がありますが、今回は4種類の顔検出をトライ。
- Haar 特徴量+Cascade 識別器
- Dlib(HOG特徴量+SVM識別器)
- CNN(mmod_human_face_detector.dat.bz2)
- MTCNN(Multi-task Cascaded Convolutional Neural Networks for Face Detection)
コメント
- ソースコードはカメラからキャプチャしたイメージにしています。自画像を載せたくないためフリー画像データの読み込みで。(画像読み出しの箇所はコメント文にして残しています)
- 画像は6人の顔が写っているものを使用し、顔検出した一人ずつの顔をcv2.imwriteでファイル保存(このイメージ画像は掲載しません)します。
- 各アルゴリズムで検出した顔サイズが異なる事に注意してください。最後あたりにサイズが違うイメージを入れています。
環境条件
- MacBook Pro
- CPU:2.4 GHz クアッドコアIntel Core i5
- メモリ:8 GB 2133 MHz LPDDR3
- MacOS Catalina(10.15.4)
- Python 3.7.6
- OpenCV 4.1.2
- VS code1.43.1
- Dlib 19.19
- Mtcnn 0.1.0
Haar 特徴量+Cascade 識別器 による顔検出
- 顔検出時間:約0.01sec
- フレームレート:約12fps
■ 顔検出イメージ(Haar 特徴量+Cascade 識別器)
■ ソースコード(Haar 特徴量+Cascade 識別器)
#!/usr/bin/env python # -*- coding: utf-8 -*- import cv2 import time import copy # OpenCV cascade_fn = "./haarcascade_frontalface_alt.xml" cascade = cv2.CascadeClassifier(cascade_fn) #カメラデバイスオープン cap = cv2.VideoCapture(0) # カメラフレームサイズをVGAに変更する cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) # カメラ画像の横幅を640に設定 cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) # カメラ画像の縦幅を480に設定 # フレームサイズ取得 frame_width = cap.get(cv2.CAP_PROP_FRAME_WIDTH) frame_height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT) while(cap.isOpened() == True): #FPS算出のため、事前に時間を取得 tick = cv2.getTickCount() # カメラキャプチャ ret, frame = cap.read() # frame = cv2.imread("./face_image.png") # frame = cv2.resize(frame,dsize=(int(frame_width),int(frame_height))) # 画像をコピー face_frame = copy.deepcopy(frame) # 画像のグレースケールと平滑化 gray_image = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) gray_image = cv2.equalizeHist(gray_image) t1 = time.time() # OpenCV顔検出 dets = cascade.detectMultiScale(gray_image, scaleFactor=1.3, minNeighbors=3, minSize=(30, 30), flags=cv2.CASCADE_SCALE_IMAGE) t2 = time.time() face_cnt = 0 # OpenCVの顔検出箇所の矩型描画ループ for(x,y,w,h) in dets: # 顔のトリミング face_image = face_frame[y:y+h, x:x+w] cv2.putText(frame, "Haar", (x,y-4),cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1, cv2.LINE_AA) # 顔箇所を四角で描画 cv2.rectangle(frame, (x,y), (x+w,y+h), (0,255,0), 2) # 顔だけをファイルに保存 cv2.imwrite('haar_face_image'+ str(face_cnt)+'.png',face_image) face_cnt = face_cnt + 1 # FPS算出と表示用テキスト作成 fps = cv2.getTickFrequency() / (cv2.getTickCount() - tick) # 検出時間を算出 detect_time = t2 - t1 # FPS表示 cv2.putText(frame, "FPS:{}".format(int(fps)),(10,450), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1, cv2.LINE_AA) # 顔検出時間表示 cv2.putText(frame, "DetectTime:{:.2f}".format(detect_time),(10,465), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1, cv2.LINE_AA) # フレームサイズ表示 cv2.putText(frame, str(int(frame_width))+"*"+str(int(frame_height)),(10,20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1, cv2.LINE_AA) # ウィンドウに表示 cv2.namedWindow("opencv") cv2.moveWindow("opencv",200,100) # Window表示位置指定 cv2.imshow('opencv', frame) cv2.imwrite("./haar_facedetect_image.png",frame) # ESCキーで終了 k = cv2.waitKey(10) & 0xff if k == 27: break # Do a bit of cleanup print("\n Exit Program") cap.release() cv2.destroyAllWindows()
Dlib(HOG特徴量+SVM識別器)による顔検出
・顔検出時間:約0.16sec ・フレームレート:約4fps
■ 顔検出イメージ(Dlib)
■ ソースコード(Dlib)
#!/usr/bin/env python # -*- coding: utf-8 -*- import dlib import cv2 import time import copy # Dlib detector = dlib.get_frontal_face_detector() #カメラデバイスオープン cap = cv2.VideoCapture(0) # カメラフレームサイズをVGAに変更する cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) # カメラ画像の横幅を640に設定 cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) # カメラ画像の縦幅を480に設定 # フレームサイズ取得 frame_width= cap.get(cv2.CAP_PROP_FRAME_WIDTH) frame_height= cap.get(cv2.CAP_PROP_FRAME_HEIGHT) while(cap.isOpened() == True): #FPS算出のため、事前に時間を取得 tick = cv2.getTickCount() # カメラキャプチャ ret, frame = cap.read() # frame = cv2.imread("./face_image.png") # frame = cv2.resize(frame,dsize=(int(frame_width),int(frame_height))) # 画像をコピー face_frame = copy.deepcopy(frame) t1 = time.time() # Dlibの顔検出 dets = detector(frame, 1) t2 = time.time() face_cnt = 0 # Dlibの顔検出箇所の矩型描画ループ for k, d in enumerate(dets): # 顔のトリミング face_image = face_frame[d.top():d.bottom(), d.left():d.right()] # Dlib名を書き込み cv2.putText(frame, "Dlib", (int(d.left()), int(d.top())-4), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0),1, cv2.LINE_AA) # 顔箇所を四角で描画 cv2.rectangle(frame, (int(d.left()), int(d.top())),(int(d.right()), int(d.bottom())), (0, 255, 0), 2) # 顔だけをファイルに保存 cv2.imwrite('dlib_face_image'+ str(face_cnt)+'.png',face_image) face_cnt = face_cnt + 1 # FPS算出と表示用テキスト作成 fps = cv2.getTickFrequency() / (cv2.getTickCount() - tick) # 検出時間を算出 detect_time = t2 - t1 # FPS表示 cv2.putText(frame, "FPS:{}".format(int(fps)),(10,450), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1, cv2.LINE_AA) # 顔検出時間表示 cv2.putText(frame, "DetectTime:{:.2f}".format(detect_time),(10,465), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1, cv2.LINE_AA) # フレームサイズ表示 cv2.putText(frame, str(int(frame_width))+"*"+str(int(frame_height)),(10,20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1, cv2.LINE_AA) # ウィンドウに表示 cv2.namedWindow("DLIB") cv2.moveWindow("DLIB",200,100) # Window表示位置指定 cv2.imshow('DLIB', frame) cv2.imwrite("./dlib_facedetect_image.png",frame) # ESCキーで終了 k = cv2.waitKey(10) & 0xff if k == 27: break # Do a bit of cleanup print("\n Exit Program") cap.release() cv2.destroyAllWindows()
CNN(mmod_human_face_detector.dat.bz2)による顔検出
・顔検出時間:約2.43sec ・フレームレート:約0fps
■ 顔検出イメージ(CNN)
■ ソースコード(CNN)
#!/usr/bin/env python # -*- coding: utf-8 -*- import dlib import cv2 import time import copy # CNN cnn_fn= "./mmod_human_face_detector.dat" cnn_face_detector = dlib.cnn_face_detection_model_v1(cnn_fn) #カメラデバイスオープン cap = cv2.VideoCapture(0) # カメラフレームサイズをVGAに変更する cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) # カメラ画像の横幅を640に設定 cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) # カメラ画像の縦幅を480に設定 # フレームサイズ取得 frame_width= cap.get(cv2.CAP_PROP_FRAME_WIDTH) frame_height= cap.get(cv2.CAP_PROP_FRAME_HEIGHT) while(cap.isOpened() == True): #FPS算出のため、事前に時間を取得 tick = cv2.getTickCount() # カメラキャプチャ ret, frame = cap.read() # frame = cv2.imread("./face_image.png") # frame = cv2.resize(frame,dsize=(int(frame_width),int(frame_height))) # 画像をコピー face_frame = copy.deepcopy(frame) t1 = time.time() # CNNの顔検出 cnn_dets = cnn_face_detector(frame, 1) t2 = time.time() face_cnt = 0 # CNNの顔検出箇所の矩型描画ループ for face in cnn_dets: # 顔のトリミング face_image = face_frame[face.rect.top():face.rect.bottom(), face.rect.left():face.rect.right()] # 顔箇所にCNN文字を表示 cv2.putText(frame, "CNN", (int(face.rect.left()), int(face.rect.top())-4), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0),1, cv2.LINE_AA) # 顔箇所を四角で描画 cv2.rectangle(frame, (face.rect.left(),face.rect.top()), (face.rect.right(),face.rect.bottom()), (0,255,0), 2) # 顔だけをファイルに保存 cv2.imwrite('cnn_face_image'+ str(face_cnt)+'.png',face_image) face_cnt = face_cnt + 1 # FPS算出と表示用テキスト作成 fps = cv2.getTickFrequency() / (cv2.getTickCount() - tick) # 検出時間を算出 detect_time = t2 - t1 # FPS表示 cv2.putText(frame, "FPS:{}".format(int(fps)),(10,450), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1, cv2.LINE_AA) # 顔検出時間表示 cv2.putText(frame, "DetectTime:{:.2f}".format(detect_time),(10,465), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1, cv2.LINE_AA) # フレームサイズ表示 cv2.putText(frame, str(int(frame_width))+"*"+str(int(frame_height)),(10,20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1, cv2.LINE_AA) # ウィンドウに表示 cv2.namedWindow("CNN") cv2.moveWindow("CNN",200,100) # Window表示位置指定 cv2.imshow('CNN', frame) cv2.imwrite("./cnn_facedetect_image.png",frame) # ESCキーで終了 k = cv2.waitKey(10) & 0xff if k == 27: break # Do a bit of cleanup print("\n Exit Program") cap.release() cv2.destroyAllWindows()
MTCNN(Multi-task Cascaded Convolutional Neural Networks for Face Detection)による顔検出
・顔検出時間:約0.14sec ・フレームレート:約4fps
■ 顔検出イメージ(MTCNN)
ついでに...顔のランドマーク(目、鼻、口)も検出箇所に赤色のサークルを表示
■ ソースコード(MTCNN)(2020/05/03)MTCNNを追加
import cv2 from mtcnn import MTCNN import copy import time detector = MTCNN() #カメラデバイスオープン cap = cv2.VideoCapture(0) # カメラフレームサイズをVGAに変更する cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) # カメラ画像の横幅を640に設定 cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) # カメラ画像の縦幅を480に設定 # フレームサイズ取得 frame_width = cap.get(cv2.CAP_PROP_FRAME_WIDTH) frame_height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT) while(cap.isOpened() == True): #FPS算出のため、事前に時間を取得 tick = cv2.getTickCount() # カメラキャプチャ ret, frame = cap.read() # frame = cv2.imread("./face_image.png") # frame = cv2.resize(frame,dsize=(int(frame_width),int(frame_height))) # 画像をコピー face_frame = copy.deepcopy(frame) # BGRからRGBに変換 image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) t1 = time.time() # MTCNN顔検出 mtcnn_dets = detector.detect_faces(image) t2 = time.time() face_cnt = 0 # MTCNNの顔検出箇所の矩型描画ループ for face in mtcnn_dets: # 座標と高幅を取得 box_x, box_y, box_w, box_h = face['box'] # 顔のトリミング face_image = face_frame[box_y:box_y+box_h, box_x:box_x+box_w] # 顔画像の保存 cv2.imwrite('face image'+ str(face_cnt)+'.png',face_image) face_cnt = face_cnt + 1 # 顔箇所にMTCNN文字を表示 cv2.putText(face_frame, "MTCNN", (box_x,box_y-4),cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1, cv2.LINE_AA) # 顔箇所を四角で描画 cv2.rectangle(face_frame, (box_x,box_y), (box_x+box_w,box_y+box_h), (0,255,0), 2) for key, value in face['keypoints'].items(): # 顔のランドマーク箇所に小さい赤色サークルを描画(目、鼻、口) cv2.circle(face_frame,value, 1, (0,0,255), -1) # FPS算出と表示用テキスト作成 fps = cv2.getTickFrequency() / (cv2.getTickCount() - tick) # 検出時間を算出 detect_time = t2 - t1 # FPS表示 cv2.putText(face_frame, "FPS:{}".format(int(fps)),(10,450), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1, cv2.LINE_AA) # 顔検出時間表示 cv2.putText(face_frame, "DetectTime:{:.2f}".format(detect_time),(10,465), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1, cv2.LINE_AA) # フレームサイズ表示 cv2.putText(face_frame, str(int(frame_width))+"*"+str(int(frame_height)),(10,20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1, cv2.LINE_AA) # ウィンドウに表示 cv2.namedWindow("MTCNN") cv2.moveWindow("MTCNN",200,100) # Window表示位置指定 cv2.imshow('MTCNN', face_frame) cv2.imwrite("./mtcnn_facedetect_image.png",face_frame) # ESCキーで終了 k = cv2.waitKey(10) & 0xff if k == 27: break # Do a bit of cleanup print("\n Exit Program") cap.release() cv2.destroyAllWindows()
顔検出時間
・ 検出精度は考えずに処理時間を早くしたい場合は、Haar+Cascade。Haar+Cascadeは顔の角度がつくと検出できないため、画像をRotateしながら顔検出が必要になります。(今後検証します)
・CNNは遅い。ハイスペックのCPUではないと使えない感じです。
・DlibはCNNに比べ160msで検出は速いですが、カメラの性能を考えると遅いです。動体検知で顔検出と顔認証する場合は、10fps程度は必要でありDlibのコンパイルオプションで高速化が可能っぽい。
アルゴリズム | 顔検出時間 | 全体フレームレート |
---|---|---|
Haar+Cascade | 0.01sec | 12fps |
Dlib | 0.16sec | 4fps |
CNN | 2.43sec | 0fps |
MTCNN | 0.14sec | 4fps |
顔検出イメージ(Haar/Dlib/CNN/MTCNNを一緒に検出)
青-CNN/赤-Harr/緑-DLib/黄-MTCNN。アルゴリズムによって顔検出のサイズが異なるなることがわかります。なんか大きさが様々。
■ ソースコード(Haar/Dlib/CNN/MTCNNを一緒に検出)
#!/usr/bin/env python # -*- coding: utf-8 -*- import dlib import cv2 import time import copy from mtcnn import MTCNN # CNN cnn_fn= "./mmod_human_face_detector.dat" cnn_face_detector = dlib.cnn_face_detection_model_v1(cnn_fn) # Dlib detector = dlib.get_frontal_face_detector() # HaarCascade cascade_fn = "./haarcascade_frontalface_alt.xml" cascade = cv2.CascadeClassifier(cascade_fn) # MTCNN mtcnn_detector = MTCNN() #カメラデバイスオープン cap = cv2.VideoCapture(0) # カメラフレームサイズをVGAに変更する cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) # カメラ画像の横幅を640に設定 cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) # カメラ画像の縦幅を480に設定 # フレームサイズ取得 frame_width= cap.get(cv2.CAP_PROP_FRAME_WIDTH) frame_height= cap.get(cv2.CAP_PROP_FRAME_HEIGHT) while(cap.isOpened() == True): #FPS算出のため、事前に時間を取得 tick = cv2.getTickCount() # カメラキャプチャ ret, frame = cap.read() frame = cv2.imread("./face_image.png") frame = cv2.resize(frame,dsize=(int(frame_width),int(frame_height))) # 画像をコピー face_frame = copy.deepcopy(frame) # 画像のグレースケールと平滑化 gray_image = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) gray_image = cv2.equalizeHist(gray_image) # BGRからRGBに変換 rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # CNN顔検出 cnn_dets = cnn_face_detector(frame, 1) # Dlib顔検出 dlib_dets = detector(frame, 1) # HaarCascade顔検出 haar_dets = cascade.detectMultiScale(gray_image, scaleFactor=1.3, minNeighbors=3, minSize=(30, 30), flags=cv2.CASCADE_SCALE_IMAGE) # MTCNN顔検出 mtcnn_dets = mtcnn_detector.detect_faces(rgb_image) # CNNの顔検出箇所の矩型描画ループ for cnn_face in cnn_dets: cv2.putText(frame, "CNN", (int(cnn_face.rect.left()), int(cnn_face.rect.top())-4), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0),1, cv2.LINE_AA) # 顔箇所を四角で描画 cv2.rectangle(frame, (cnn_face.rect.left(),cnn_face.rect.top()), (cnn_face.rect.right(),cnn_face.rect.bottom()), (255,0,0), 2) # Dlibの顔検出箇所の矩型描画ループ for k, d in enumerate(dlib_dets): cv2.putText(frame, "Dlib", (int(d.left()), int(d.top())-4), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0),1, cv2.LINE_AA) # 顔箇所を四角で描画 cv2.rectangle(frame, (int(d.left()), int(d.top())),(int(d.right()), int(d.bottom())), (0, 255, 0), 2) # OpenCVの顔検出箇所の矩型描画ループ for(x,y,w,h) in haar_dets: cv2.putText(frame, "Haar", (x,y-4),cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1, cv2.LINE_AA) # 顔箇所を四角で描画 cv2.rectangle(frame, (x,y), (x+w,y+h), (0,0,255), 2) # MTCNNの顔検出箇所の矩型描画ループ for face in mtcnn_dets: # 座標と高幅を取得 box_x, box_y, box_w, box_h = face['box'] cv2.putText(frame, "MTCNN", (box_x,box_y-4),cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 255), 1, cv2.LINE_AA) # 顔箇所を四角で描画 cv2.rectangle(frame, (box_x,box_y), (box_x+box_w,box_y+box_h), (0,255,255), 2) # ウィンドウに表示 cv2.namedWindow("FaceDetect") cv2.moveWindow("FaceDetect",200,100) # Window表示位置指定 cv2.imshow('FaceDetect', frame) cv2.imwrite("./all_facedetect_image.png",frame) # ESCキーで終了 k = cv2.waitKey(10) & 0xff if k == 27: break # Do a bit of cleanup print("\n Exit Program") cap.release() cv2.destroyAllWindows()