Atom's tech blog

4種類の顔検出を動かしてみた [ Haar+Cascade/ HOG+SVM/ CNN/ MTCNN ]

概要

幾つかの顔検出方法がありますが、今回は4種類の顔検出をトライ。

コメント

  • ソースコードはカメラからキャプチャしたイメージにしています。自画像を載せたくないためフリー画像データの読み込みで。(画像読み出しの箇所はコメント文にして残しています)
  • 画像は6人の顔が写っているものを使用し、顔検出した一人ずつの顔をcv2.imwriteでファイル保存(このイメージ画像は掲載しません)します。
  • アルゴリズムで検出した顔サイズが異なる事に注意してください。最後あたりにサイズが違うイメージを入れています。

環境条件

Haar 特徴量+Cascade 識別器 による顔検出

  • 顔検出時間:約0.01sec
  • フレームレート:約12fps
■ 顔検出イメージ(Haar 特徴量+Cascade 識別器)

f:id:iAtom:20201008163039p:plain

ソースコード(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)

f:id:iAtom:20201008163119p:plain

ソースコード(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)

f:id:iAtom:20201008162318p:plain

ソースコード(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)

f:id:iAtom:20201008163207p:plain ついでに...顔のランドマーク(目、鼻、口)も検出箇所に赤色のサークルを表示

ソースコード(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。アルゴリズムによって顔検出のサイズが異なるなることがわかります。なんか大きさが様々。

f:id:iAtom:20201008163237p:plain

ソースコード(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()