【Python・OpenCV】顔検出ってどうやるの? 7種類のモデル、アルゴリズムで検証!

※当サイトではアフィリエイト広告を利用しています

Python プログラミング 画像処理

【Python・OpenCV】顔検出ってどうやるの? 7種類のモデル、アルゴリズムで検証!

はじめに

顔検出はセキュリティシステム、スマホのカメラアプリ、監視カメラなど、身の回りの様々なところで活用されています。
顔検出とは、デジタル画像や動画から人間の顔の領域を自動的に検出・認識する技術のことを指します。この技術は、コンピュータビジョンやパターン認識の分野で活発に研究が行われており、近年ではディープラーニングの発展により大幅に性能が向上しています。
既存の学習モデルを利用することで、比較的簡単に顔検出を実装することができるので、サンプルコードを含め紹介します。
また、dlibなどOpenCV以外の顔検出モデルについても取り上げ、それらの特徴についても確認します。

(広告) OpenCV関連書籍をAmazonで探す

顔検出と顔認識

顔認識(face recognition)と顔検出(face detection)は似た概念ですが、異なる処理を行っています。

顔検出(Face Detection)
顔検出とは、入力された画像または動画から人間の顔の有無や位置を検出する処理のことです。つまり、画像内に顔があるかどうか、そしてあった場合はその位置(座標)を特定することが目的です。
検出アルゴリズムは特徴量(エッジ、輝度勾配など)を用いて顔らしい部分を見つけ出します。代表的な従来手法であるHaar-likeはOpenCVでは組み込まれています。近年では深層学習を使った高精度な顔検出手法も多数提案されています。
顔検出は認証や監視システム、写真アプリなど様々な用途で活用されています。

顔認識(Face Recognition)
一方、顔認識は検出された顔から、誰の顔なのかを同定する処理を指します。つまり、個人の識別が目的です。
顔認識では、まず顔検出によって顔の位置が特定され、次に検出された顔画像から特徴量が抽出されます。そして、事前に登録された既知の顔画像の特徴量とのマッチングを行うことで、個人を識別します。
特徴量の抽出には、主成分分析(PCA)や線形判別分析(LDA)などの手法が従来から利用されてきましたが、近年は深層学習による高次元特徴量がよく使われるようになりました(FaceNet、VGGFace2など)。
顔認識はセキュリティゲート、入退室管理システムなどでの本人確認に加え、デジタルアルバムの自動顔タグ付けなどの用途があります。

つまり、顔検出は「顔の有無と位置検出」、顔認識は「個人の同定」が主な目的です。顔認識を行うには、事前に顔検出が必要不可欠な処理となります。両者は密接に関係しながらも、別個の処理概念となります。​​​​​​​​​​​​​​​

さまざまな顔検出アルゴリズム

確かに、それぞれのアルゴリズムには特徴があり、使い分ける必要があります。詳しく解説しましょう。

OpenCV(Haar Cascade)

  • Haar Cascade手法を用いた従来の手法に基づく顔検出器が使用可能
  • 軽量なアルゴリズムのため、処理速度は比較的速い
  • 精度は他の深層学習ベースの手法に比べると低め

OpenCV(Yu Net)

  • 畳み込みニューラルネットワーク (CNN)をベースとした、軽量で高速な顔検出アルゴリズム
  • 顔検出だけでなく、目、鼻、口などのランドマーク検出に対応
  • リアルタイム性と高精度のバランスが優れている
  • モデルがOpenCVに含まれていないが、OpenCV 4.8.0以降に組み込まれている

OpenCV(Yu Net)

  • 畳み込みニューラルネットワーク (CNN)をベースとした、軽量で高速な顔検出アルゴリズム
  • 顔検出だけでなく、目、鼻、口などのランドマーク検出に対応
  • リアルタイム性と高精度のバランスが優れている
  • モデルがOpenCVに含まれていないが、OpenCV 4.8.0以降に組み込まれている

dlib

  • C++で実装された高性能な機械学習ライブラリ
  • HOG (Histogram of Oriented Gradients) 特徴量と線形分類器を使った顔検出器と畳み込みニューラルネットワーク (CNN)をベースのモデルが利用可能
  • 68点のフェイスランドマーク検出も可能で、高精度
  • ディープラーニングベースではないが、従来手法の中では最も精度が高い
  • CPU実行なので比較的高速だが、GPU対応はしていない

MTCNN (Multi-task Cascaded Convolutional Networks)

  • ディープラーニングベースの顔検出/ランドマーク検出アルゴリズム
  • 3つのネットワークが直列につながったカスケード構造を採用
  • 顔検出の精度が非常に高く、角度や照明の変化にも頑健
  • フェイスランドマーク検出も高精度に行える
  • ただし、ディープラーニングモデルなので処理速度は遅め
  • GPU使用時の高速化が可能

face_recognition

  • dlib の顔検出/顔認識エンジンをPythonラッパーでパッケージ化
  • 顔検出/エンコーディング/クラスタリングなどの機能を提供
  • 顔認識の精度は非常に高い
  • 検出モデルとして、”hog”と”cnn”の2つが使用可能。”cnn”はGPU/CUDAを使用することで高速化に対応。
  • アップサンプリングに対応しており、精度向上が期待できる

総じて、リアルタイム性が求められる場合はOpenCVやdlibが、高精度が求められる場合はMTCNNや深層学習ベースの手法が適しています。処理対象や用途に合わせて、精度と速度のトレードオフを考慮する必要があります。GPUの利用可否も重要なファクターになります。​​​​​​​​​​​​​​​​

顔検出アルゴリズムの比較

前章で紹介したライブラリの実装例として、動画ファイルを読み込んで顔検出結果のマーキングを描画して保存するサンプルコードを紹介します。
入力する動画として、この動画を使用しています。画角1920x1080、再生時間 約17秒、総フレーム数は423フレームの動画です。

処理時間を計測しており、実行環境は次になります。

ハードウェア :13inch M1 Macbook Pro
Python :Ver 3.11.1
OpenCV :Ver 4.9.0
dlib :Ver 19.24.4
MTCNN :Ver 0.1.1
face_recognition :Ver 1.3.1
face-recognition-models :Ver 0.3.0

OpenCVの顔検出を実行するためには、contribeのインストールが必要となります。
下記を実行してインストールをしてください。

pip install opencv-contrib-python

顔検出のサンプルコードは、以下の手順で処理を行うものを作成しました。
使用するライブラリによって、多少異なる箇所かあるかもしれませんが、本投稿で紹介するサンプルコードはこの手順に準じたコードになっているため、各ライブラリの使い方の違いが確認できます。

  1. 処理時間計測の準備:
    TickMeterオブジェクトの作成し、計測を開始
  2. 顔顔検出器を初期化:
    顔検出モデルを読み込む
  3. 入力動画ファイルを開く:
    動画ファイルを読み込み、動画ファイルのプロパティを取得
  4. 出力動画の準備:
    3で取得した動画ファイルのプロパティを使用
  5. 入力動画から1フレーム分の画像を取得:
    必要に応じて前処理(カラー画像のグレースケール化など)
  6. 顔検出の実行:
    実行結果の内容は使用するライブラリによって異なる
  7. 検出結果を描画:
    検出した顔の領域を矩形で囲う、フェースランドマークの描画
  8. 出力動画にフレームを書き込む:
    検出結果を描画した画像の書き込み
  9. フレーム毎に処理を実行:
    5〜8をすべての入力動画のフレームに対して実行
  10. リソースの開放:
    動画ファイルの読み書きにリソースに対して実行
  11. 処理時間計測を終了:
    時間計測を終了して、計測結果を表示

※ 以下に示す処理時間の結果は1回のみ計測結果となります。実行環境によって処理時間は変わるため、各顔検出ライブラリの相対的な処理時間の違いを確認する参考値程度と捉えてください。

処理時間計測については、この記事で解説しています。

OpenCV(Haar Cascade)

OpenCVに実装されている、Haar Cascade類器を使って画像から顔を検出し、赤い四角で囲んでいます。
顔検出だけを行うものではなく、顔検出を学習させたモデルを指定することで、顔検出が可能になります。他のオブジェクトの検出を学習したモデルファイルを準備することでf、様々なオブジェクトの検出が可能になります。

モデルはOpenCVをインストール時に一緒にインストールされます。
下記サンプルコードの9行目のcv2.data.haarcascadesで、顔検出のモデルファイル haarcascade_frontalface_default.xmlのパスを取得することができます。

参考リンク:OpenCV Cascade Classifier

使い方

cv2.CascadeClassifierクラスのインスタンスを生成します。

cv2.CascadeClassifier(filename)

引数
名称説明
filename(必須)モデルファイルのパス
戻り値

cv2.CascadeClassifierクラスのインスタンス

顔検出の実行は次の関数です。

cv2.CascadeClassifier.detectMultiScale(image, scaleFactor, minNeighbors, flags, minSize, maxSize)

引数
名称説明
image(必須)入力画像
scaleFactor(オプション)画像サイズをどれだけ縮小するかを指定します。
minNeighbors(オプション各候補矩形を保持するために必要な隣接数を指定します。
flag(オプション古いcascadeの場合,cv2.HaarDetectObjects関数と同じ意味を持つパラメータ。
新しいcascadeでは利用されません。
minSize(オプション検出対象の最小サイズを指定します。これより小さいサイズは無視されます。
maxSize(オプション検出対象の最大サイズを指定します。これより大きいサイズは無視されます。
maxSize == minSize の場合、モデルはシングル・スケールで評価されます。
戻り値

検出対象を示す矩形のリスト

サンプルコード

import cv2

# TickMeterオブジェクトを作成
tm = cv2.TickMeter()
# 計測開始
tm.start()

# 顔検出器を初期化
face_detector = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml")

# 動画ファイルを開く
cap = cv2.VideoCapture("video.mp4")

# 動画ファイルの各種プロパティーを取得
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))  # フレームの幅
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))  # フレームの高さ
fps = float(cap.get(cv2.CAP_PROP_FPS))  # FPS

# VideoWriter を作成する。
output_file = "cascad_output.mp4"  # 保存する動画ファイル名
fourcc = cv2.VideoWriter_fourcc(*'mp4v')  # 動画のコーデックを指定
out = cv2.VideoWriter(output_file, fourcc, fps, (frame_width, frame_height), True)

while True:
    # フレームを取得
    ret, frame = cap.read()

    if not ret:
        break
    
    # グレースケール変換
    gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    
    # 顔検出
    faces = face_detector.detectMultiScale(gray_frame, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))
    
    # 検出した顔に四角形を描画
    for (x, y, w, h) in faces:
        cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 0, 255), 2)
        
    # 動画にフレームを書き込む
    out.write(frame)

# リソースを開放
cap.release()
out.release()

# 計測終了
tm.stop()

# 計測結果を表示
elapsed_time = tm.getTimeSec()
print("Elapsed time 1:", elapsed_time, "seconds")

合計処理時間は、

Elapsed time 1: 49.22435875 seconds

となり、複数のフレームで誤検出と検出できていない顔が確認できます。

OpenCV(Yu Net)

YuNetは、軽量で高速、高精度な顔検出アルゴリズムです。

モデルはOpenCVに含まれていませんが、下記より入手できます。
https://github.com/opencv/opencv_zoo/tree/main/models/face_detection_yunet

顔検出とフェイスランドマークに対応しています。

参考文献:YuNet: A Tiny Millisecond-level Face Detector

使い方

cv2.FaceDetectorYNクラスのインスタンスを生成します。

cv2.FaceDetectorYN.create(model, config, input_size, score_threshold,nms_threshold, top_k, backend_id, target_id)

引数
名称説明
model(必須)モデルファイルのパス
config(オプション)コンフィグファイルのパス。ONNXモデルの場合は不要
input_size(オプション入力画像のサイズ
score_threshold(オプション指定した値よりも小さいスコアのバウンディングボックスを除外するための閾値
nms_threshold(オプション指定した値より大きいIoUのバウンディングボックスを除外するための閾値
top_k(オプションNMSの前に上位K個のbboxを保持
backend_id(オプションバックエンドのID
target_id(オプションターゲットデバイスの ID
戻り値

cv2.FaceDetectorYNクラスのインスタンス

顔検出の実行は次の関数です。

detect(image, faces)

引数
名称説明
image(必須)モデルファイルのパス
faces(オプション指定された場合、検出結果がこの変数に格納されます。指定されない場合は、新しい配列が作成されて検出結果が格納され戻り値として取得できます。
戻り値

検出結果。
戻り値のリストは次のような構成になっています。

  • 0-1: 顔領域の左上のx, y座標
  • 2-3: 顔領域の幅、高さ
  • 4-5: 右目のx, y座標
  • 6-7: 左目のx, y座標
  • 8-9: 鼻のx, y座標
  • 10-11: 口の右端のx, y座標
  • 12-13: 口の左端のx, y座標
  • 14: 顔検出のスコア

サンプルコード

import cv2
import numpy as np

# TickMeterオブジェクトを作成
tm = cv2.TickMeter()
# 計測開始
tm.start()

# 動画ファイルを開く
cap = cv2.VideoCapture("video.mp4")

# 動画ファイルの各種プロパティーを取得
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))  # フレームの幅
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))  # フレームの高さ
fps = float(cap.get(cv2.CAP_PROP_FPS))  # FPS

# YuNet顔検出モデルの読み込み
# https://github.com/opencv/opencv_zoo/tree/main/models/face_detection_yunet
yunet = cv2.FaceDetectorYN.create("face_detection_yunet_2023mar_int8.onnx", "", (frame_width, frame_height))

# VideoWriter を作成する。
output_file = "yu_net_output.mp4"  # 保存する動画ファイル名
fourcc = cv2.VideoWriter_fourcc(*'mp4v')  # 動画のコーデックを指定
out = cv2.VideoWriter(output_file, fourcc, fps, (frame_width, frame_height), True)

while True:
    # フレームを取得
    ret, frame = cap.read()

    if not ret:
        break
    
    # 顔検出
    faces = yunet.detect(frame)
    
    # 顔の輪郭と目、鼻、口の位置を描画
    if faces[1] is not None:
        for idx, face in enumerate(faces[1]):
            coords = face[:-1].astype(np.int32)
            cv2.rectangle(frame, (coords[0], coords[1]), (coords[0]+coords[2], coords[1]+coords[3]), (0, 0, 255), 2)
            cv2.circle(frame, (coords[4], coords[5]), 2, (0, 255, 0), 2)
            cv2.circle(frame, (coords[6], coords[7]), 2, (0, 255, 0), 2)
            cv2.circle(frame, (coords[8], coords[9]), 2, (0, 255, 0), 2)
            cv2.circle(frame, (coords[10], coords[11]), 2, (0, 255, 0), 2)
            cv2.circle(frame, (coords[12], coords[13]), 2, (0, 255, 0), 2)
        
    # 動画にフレームを書き込む
    out.write(frame)

# リソースを開放
cap.release()
out.release()

# 計測終了
tm.stop()

# 計測結果を表示
elapsed_time = tm.getTimeSec()
print("Elapsed time 1:", elapsed_time, "seconds")

合計処理時間は、

Elapsed time 1: 32.548149041 seconds

となり、誤検出がなく高い精度で顔を検出し、処理時間も高速です。

OpenCV(ResNet-10モデル)

OpenCVに同梱されている事前学習済みの深層学習ベースの顔検出モデルです。正式名称は「res10_300x300_ssd_iter_140000.caffemodel」で、SSDアーキテクチャを採用したリアルタイム顔検出器です。

モデルと設定ファイル prototxtが必要です。

モデルとprototxtは下記より入手できます。
https://github.com/opencv/opencv_3rdparty/raw/dnn_samples_face_detector_20170830/res10_300x300_ssd_iter_140000.caffemodel
https://github.com/opencv/opencv/blob/master/samples/dnn/face_detector/deploy.prototxt

使い方

モデルファイルを読み込み、Netオブジェクトを生成します。

cv2.dnn.readNetFromCaffe(prototxt, caffeModel)

引数
名称説明
prototxt(必須)ネットワーク アーキテクチャのテキスト記述を含む .prototxt ファイルへのパス
caffeModel(オプション学習したネットワークを含む .caffemodel ファイルへのパス
戻り値

Netオブジェクト

次に、画像データをニューラルネットワークに入力する際に必要な前処理を行います。

cv2.dnn.blobFromImage(image, scalefactor, size, mean, swapRB, crop, ddepth)

引数
名称説明
image(必須)入力画像(1、3、または4チャンネル)
scalefactor(オプション画像のピクセル値を変換するための係数。通常は1/255.0を指定して、ピクセル値を0-1の範囲に正規化します。(デフォルト:1.0)
size(オプション出力Blobのサイズ。ネットワークの入力層のサイズに合わせる必要があります。(デフォルト:(0, 0))
mean(オプション各チャンネルの平均値。この値が画像のピクセル値から差し引かれます。(デフォルト:(0, 0, 0))
swapRB(オプションRGBチャンネルの順序を変更するかどうかを指定します。trueの場合、RGBからBGRの順序に変更されます。(デフォルト:True
crop(オプション画像を中心で切り取るかどうかを指定します。Trueの場合、画像の縦横比を維持したまま、短い辺に合わせて中心から切り取られます。(デフォルト:True
ddepth(オプション出力Blobの深さ(depth)を指定します。(デフォルト:cv2.CV_32F(32ビット浮動小数点数))
戻り値

Blobをnumpy.ndarrayオブジェクトとして返します。
前処理された画像データが格納されています。Blobオブジェクトは、ネットワークの入力層に渡すことができます。
戻り値は、[batch_size, channels, height, width]となっています。

次に、cv2.dnn.blobFromImage関数の戻り値を下記の関数で処理します。

Net.setInput(blob, name, scalefactor, mean)

引数
名称説明
blob(必須)新しいblob。cv2.CV_32Fまたはcv2.CV_8Uである必要があります。
name(オプション入力レイヤーの名前。(デフォルト:""(空の文字列))
scalefactorオプション出力Blobのサイズ。ネットワークの入力層のサイズに合わせる必要があります。(デフォルト:1.0)
mean(オプション各チャンネルの平均値。この値が画像のピクセル値から差し引かれます。(デフォルト:(0, 0, 0))
戻り値

戻り値はありません。

最後に、顔検出を実行します。
Net,forward関数はOpenCVのDNNモジュールにおいて、事前に読み込まれたディープニューラルネットワークモデルに入力データを与え、出力を取得する関数です。

Net.forward(outputName)

引数
名称説明
outputName(オプション出力を取得する必要があるレイヤーの名前。(デフォルト:""(空の文字列))
戻り値

指定されたレイヤーの最初の出力の blob。
形状は[1, 1, N, 7]となります。

  • 1 x 1 は、バッチサイズとダミーの次元です。
  • N は検出された顔ボックスの数を表します。
  • 7 は、各顔ボックスを表す7つの値(x1, y1, x2, y2, conf, class_id, other)からなります。

つまり、各検出された顔ボックスは以下のように表現されます。

[obj_score, obj_class, obj_left, obj_top, obj_right, obj_bottom, other]

  • obj_score: その顔ボックスの確信度スコア
  • obj_class: オブジェクトクラスID (顔検出の場合は通常0)
  • obj_left: 顔ボックスの左上x座標
  • obj_top: 顔ボックスの左上y座標
  • obj_right: 顔ボックスの右下x座標
  • obj_bottom: 顔ボックスの右下y座標
  • other: 予備値(通常0)

サンプルコード

import cv2

# TickMeterオブジェクトを作成
tm = cv2.TickMeter()
# 計測開始
tm.start()

# 顔検出器のモデルを読み込む
# https://github.com/opencv/opencv_3rdparty/raw/dnn_samples_face_detector_20170830/res10_300x300_ssd_iter_140000.caffemodel
# https://github.com/opencv/opencv/blob/master/samples/dnn/face_detector/deploy.prototxt
model_path = "res10_300x300_ssd_iter_140000.caffemodel"
config_path = "deploy.prototxt"

net = cv2.dnn.readNetFromCaffe(config_path, model_path)

# 動画ファイルを開く
cap = cv2.VideoCapture("video.mp4")

# 動画ファイルの各種プロパティーを取得
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))  # フレームの幅
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))  # フレームの高さ
fps = float(cap.get(cv2.CAP_PROP_FPS))  # FPS

# VideoWriter を作成する。
output_file = "res10_output.mp4"  # 保存する動画ファイル名
fourcc = cv2.VideoWriter_fourcc(*'mp4v')  # 動画のコーデックを指定
out = cv2.VideoWriter(output_file, fourcc, fps, (frame_width, frame_height), True)

while True:
    # フレームを取得
    ret, frame = cap.read()

    if not ret:
        break

    blob = cv2.dnn.blobFromImage(frame)
 
	# pass the blob through the network and obtain the detections and
	# predictions
    net.setInput(blob)

    # 顔検出の実行
    detections = net.forward()

    # 検出された顔に四角形を描画
    for i in range(detections.shape[2]):
        confidence = detections[0, 0, i, 2]
        if confidence > 0.5:
            box = detections[0, 0, i, 3:7] * [frame.shape[1], frame.shape[0], frame.shape[1], frame.shape[0]]
            (x1, y1, x2, y2) = box.astype("int")
            cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 0, 255), 2)
       
    # 動画にフレームを書き込む
    out.write(frame)

# リソースを開放
cap.release()
out.release()

# 計測終了
tm.stop()

# 計測結果を表示
elapsed_time = tm.getTimeSec()
print("Elapsed time 1:", elapsed_time, "seconds")

合計処理時間は、

Elapsed time 1: 94.426068458 seconds

となり、誤検出がなく高い精度で顔を検出しています。

dlib

dlibの顔検出器は、高精度な相関フィルタベースのアルゴリズムを採用しています。
フェースランドマークとして検出するポイント数が多いのが特徴です

import cv2
import dlib

# TickMeterオブジェクトを作成
tm = cv2.TickMeter()
# 計測開始
tm.start()

# 顔検出器とランドマーク検出器を初期化
# http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")

# 動画ファイルを開く
cap = cv2.VideoCapture("video.mp4")

# 動画ファイルの各種プロパティーを取得
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))  # フレームの幅
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))  # フレームの高さ
fps = float(cap.get(cv2.CAP_PROP_FPS))  # FPS

# VideoWriter を作成する。
output_file = "dlib_output.mp4"  # 保存する動画ファイル名
fourcc = cv2.VideoWriter_fourcc(*'mp4v')  # 動画のコーデックを指定
out = cv2.VideoWriter(output_file, fourcc, fps, (frame_width, frame_height), True)

while True:
    # フレームを取得
    ret, frame = cap.read()
    
    if not ret:
        break
    
    # 画像をBGRからRGBに変換
    gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    
    # 顔を検出
    dets = detector(gray_frame, 1)
    
    for face in dets:
        # 顔の輪郭とランドマークを検出
        shape = predictor(gray_frame, face)
        
        # ランドマークの座標を取得
        landmarks = [(shape.part(i).x, shape.part(i).y) for i in range(68)]
        
        # 顔の輪郭と目、鼻、口の位置を描画
        for point in landmarks:
            cv2.circle(frame, point, 2, (0, 255, 0), -1)
        cv2.rectangle(frame, (face.left(), face.top()), (face.right(), face.bottom()), (0, 0, 255), 2)
        
    # 動画にフレームを書き込む
    out.write(frame)
        
# リソースを開放
cap.release()
out.release()

# 計測終了
tm.stop()

# 計測結果を表示
elapsed_time = tm.getTimeSec()
print("Elapsed time 1:", elapsed_time, "seconds")

合計処理時間は、

Elapsed time 1: 257.192458292 seconds

となり、処理時間が遅く、検出できていない顔が確認できます。
検出も若干不安定の様です。

dlib(cnn)

CNN (Convolutional Neural Network) をベースとした高精度な顔検出モデルです。
フェースランドマークの検出はサポートしていません。
このモデルは前述の従来手法に比べて高い精度を持ち、顔の角度や照明条件の変化にも頑健です。
ただし、ディープラーニングベースのモデルであるため、CPUのみでは処理速度が遅くなる可能性があり、GPUの利用が推奨されています。

import cv2
import dlib

# TickMeterオブジェクトを作成
tm = cv2.TickMeter()
# 計測開始
tm.start()

# 顔検出器とランドマーク検出器を初期化
# http://dlib.net/files/mmod_human_face_detector.dat.bz2
cnn_face_detector = dlib.cnn_face_detection_model_v1("mmod_human_face_detector.dat")

# 動画ファイルを開く
cap = cv2.VideoCapture("video.mp4")

# 動画ファイルの各種プロパティーを取得
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))  # フレームの幅
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))  # フレームの高さ
fps = float(cap.get(cv2.CAP_PROP_FPS))  # FPS

# VideoWriter を作成する。
output_file = "dlib_cnn_output.mp4"  # 保存する動画ファイル名
fourcc = cv2.VideoWriter_fourcc(*'mp4v')  # 動画のコーデックを指定
out = cv2.VideoWriter(output_file, fourcc, fps, (frame_width, frame_height), True)

while True:
    # フレームを取得
    ret, frame = cap.read()
    
    if not ret:
        break
    
    # 画像をBGRからRGBに変換
    rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    
    # 顔を検出
    # 2爪の引数の"1"は画像に対するアップサンプリングの回数
    dets = cnn_face_detector(rgb_frame, 1)
    
    # 検出した顔に四角形を描画
    for face in dets:
        cv2.rectangle(frame, (face.rect.left(), face.rect.top()), (face.rect.right(), face.rect.bottom()), (0, 0, 255), 2)
        
    # 動画にフレームを書き込む
    out.write(frame)
        
# リソースを開放
cap.release()
out.release()

# 計測終了
tm.stop()

# 計測結果を表示
elapsed_time = tm.getTimeSec()
print("Elapsed time 1:", elapsed_time, "seconds")

合計処理時間は、

Elapsed time 1: 3285.173822375 seconds

となりました。CPUでは1時間弱ととても処理時間が遅く、全く実用的ではありませんでした。
また、検出率はそれほど良くない結果でした。
GPU環境を所有していないので、検証ができないのが残念です。

MTCNN (Multi-task Cascaded Convolutional Networks)

MTCNNは、高精度な顔検出とランドマーク検出を行うための深層学習ベースのアルゴリズムです。
他の手法に比べて比較的高い精度を持ち、顔の角度や照明条件の変化にも頑健な処理ができることが大きな特徴です。

事前準備として、MTCNNのPythonライブラリをインストールします。
ターミナル等で、次のコマンドを実行してください。

% pip install mtcnn

使い方

クラスとして実装されており、はじめにMTCNNのインスタンスを生成します。

MTCNN(weights_file, min_face_size, steps_threshold, scale_factor)

引数
名称説明
weights_file(オプション)MTCNN の P, R, O ネットワークの重みが格納された uri ファイル。デフォルトではパッケージに同梱されているものを読み込みます。(デフォルト:None)
min_face_size(オプション)検出する顔の最小サイズ(デフォルト:20)
steps_threshold(オプション)ステップのしきい値(デフォルト:None)
scale_factor(オプション)スケールファクタ(デフォルト:0.709)
戻り値

MTCNNクラスのインスタンス

次に、detect_faces関数で画像からバウンディングボックスを検出します。

MTCNN.detect_faces(img)

引数
名称説明
img(必須)入力画像データ
戻り値

検出されたすべてのバウンディングボックスとそのキーポイントを含むリスト。

サンプルコード

サンプルコードは下のになります。

from mtcnn.mtcnn import MTCNN
import cv2

# TickMeterオブジェクトを作成
tm = cv2.TickMeter()
# 計測開始
tm.start()

# MTCNNモデルを初期化
# https://github.com/ipazc/mtcnn
detector = MTCNN()

# 動画ファイルを開く
cap = cv2.VideoCapture("video.mp4")

# 動画ファイルの各種プロパティーを取得
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))  # フレームの幅
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))  # フレームの高さ
fps = float(cap.get(cv2.CAP_PROP_FPS))  # FPS

# VideoWriter を作成する。
output_file = "mtcnn_output.mp4"  # 保存する動画ファイル名
fourcc = cv2.VideoWriter_fourcc(*'mp4v')  # 動画のコーデックを指定
out = cv2.VideoWriter(output_file, fourcc, fps, (frame_width, frame_height), True)

while True:
    # フレームを取得
    ret, frame = cap.read()
    
    if not ret:
        break
    
    # 顔を検出
    faces = detector.detect_faces(frame)
    
    # 検出した顔に四角形とランドマークを描画
    for face in faces:
        x, y, width, height = face['box']
        keypoints = face['keypoints']
        
        cv2.rectangle(frame, (x, y), (x + width, y + height), (0, 0, 255), 2)
        
        for point in keypoints.values():
            cv2.circle(frame, tuple(point), 2, (0, 255, 0), -1) 
    
    # 動画にフレームを書き込む
    out.write(frame)# 結果を表示
        
# リソースを開放
cap.release()
out.release()

# 計測終了
tm.stop()

# 計測結果を表示
elapsed_time = tm.getTimeSec()
print("Elapsed time 1:", elapsed_time, "seconds")

次のように、合計処理時間は長いですが、若干の誤検出が見られるものの良好な検出率となっています。
画像中の左から3番目の顔を検出できたのは本記事中でMTCNNだけでした。

Elapsed time 1: 334.253225 seconds

face_recognition

face_recognitionは顔認識も可能で高機能なライブラリですが、本記事では顔検出について検証します。
face_recognitionには"hog"と”cnn"の2つのモデルが搭載されています。

事前準備として、face_recognitionのPythonライブラリをインストールします。
ターミナル等で、次のコマンドを実行してください。

% pip install face-recognition

face_recognition.face_locations関数

顔検出にはface_locations関数を使用します。

face_recognition.face_locations(img, number_of_times_to_upsample, model)

引数

名称説明
img(必須)入力画像データ。numpy.ndarrayオブジェクト。
number_of_times_to_upsample(オプション画像をアップサンプリングする回数。数値が大きいほど、より小さな顔が見つかります。(デフォルト:1)
model(オプションどの顔検出モデルを使用するか。 "hog"は精度は低くなりますが、CPU では高速です。 "cnn"は、GPU/CUDA で高速化された、より正確なディープラーニング モデルです(可能な場合)。(デフォルト:hog)

戻り値

見つかった顔の位置情報のタプル。(上、右、下、左) 順番のリスト

サンプルコード

下記のコードではhogモデルになりますが、37行目のface_recognition.face_locations関数の引数でモデルの変更ができます。

import face_recognition
import cv2
import numpy as np

# TickMeterオブジェクトを作成
tm = cv2.TickMeter()
# 計測開始
tm.start()

# 動画ファイルを開く
cap = cv2.VideoCapture("video.mp4")

# 動画ファイルの各種プロパティーを取得
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))  # フレームの幅
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))  # フレームの高さ
fps = float(cap.get(cv2.CAP_PROP_FPS))  # FPS

# VideoWriter を作成する。
output_file = "face_recognition_output.mp4"  # 保存する動画ファイル名
fourcc = cv2.VideoWriter_fourcc(*'mp4v')  # 動画のコーデックを指定
out = cv2.VideoWriter(output_file, fourcc, fps, (frame_width, frame_height), True)

count = 1
while True:
    # フレームを取得
    ret, frame = cap.read()

    if not ret:
        break

    print('frame: ' + str(count))

    # グレースケール変換
    rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    
    # 画像から顔を検出
    face_locations = face_recognition.face_locations(rgb_frame, number_of_times_to_upsample=1, model="hog")
    face_landmarks_list = face_recognition.face_landmarks(rgb_frame, face_locations=face_locations)
    for face_location in face_locations:
        # 検出した顔の領域を取得
        top, right, bottom, left = face_location
        
        # 顔の領域を四角形で囲む
        cv2.rectangle(frame, (left, top), (right, bottom), (0, 0, 255), 2)

        # 検出したランドマークを描画
        for face_landmarks in face_landmarks_list:
            for feature_name, points in face_landmarks.items():
                points = np.array(points)
                for point in points:
                    cv2.circle(frame, tuple(point), 1, (0, 255, 0), -1)

    # 動画にフレームを書き込む
    out.write(frame)
    count = count + 1

# リソースを開放
cap.release()
out.release()

# 計測終了
tm.stop()

# 計測結果を表示
elapsed_time = tm.getTimeSec()
print("Elapsed time 1:", elapsed_time, "seconds")

下は"hog"モデル、アップサンプリング 1回の結果です。

number_of_times_to_upsample=1, model="hog" の合計処理時間

Elapsed time 1: 261.720306375 seconds

下は"hog"モデル、アップサンプリング 2回の結果です。

number_of_times_to_upsample=2, model="hog" の合計処理時間

Elapsed time 1: 1019.819796416 seconds

最後は"cnn"モデル、アップサンプリング 1回の結果です。

合計処理時間は、

Elapsed time 1: 3413.467849041 seconds

いずれも条件でもCPUでは処理時間が長いため、GPUの利用は必須に思えます。
顔検出率も優れています。
また、アップサンプリング回数を増やすことで検出率の向上が期待できます。

フェースランドマークの取得や今回は取り上げませんでしたが、顔認識も搭載しているので、GPU環境がある場合は検討の価値は高いと思います。

おわりに

顔検出の技術は現在では特別なものではなくなりました。
様々な技術が提案されており、本記事で紹介した様にそれらを簡単に利用することができます。

本記事の検証ではライブラリやモデルの違いによって顔の検出率は大きく異なることがわかりました。また、処理時間にも大きな違いがあり利用者のハードウェア環境によって選択肢が増えることもわかりました。

今回取り上げたなかで、OpenCV(Yu Net)は顔検出率、処理時間のいずれも優れていました。
GPUが不要で、CPUで使えることろ考慮すると有力な選択肢の一つではないかと思いました

ご質問や取り上げて欲しい内容などがありましたら、コメントをお願いします。
最後までご覧いただきありがとうございました。

参考リンク

■(広告)OpenCVの参考書としてどうぞ!■

-Python, プログラミング, 画像処理
-