InsightFaceによる顔検出

【概要】 InsightFaceフレームワークのSCRFD技術を用いた顔検出プログラムの実装と実験を行う。SCRFDはサンプル分布を再配分することを特徴とする顔検出技術である。Webカメラから顔と5点キーポイントを検出する。Windows環境での実行手順、プログラムコード、実験アイデアを含む。

目次

1. はじめに

SCRFD(Sample and Computation Redistribution for Efficient Face Detection)技術

InsightFaceは顔認識のための統合フレームワークであり、SCRFDはその中で使用される顔検出アルゴリズムの一つである。顔検出は画像から顔の位置を特定する技術であり、顔認識(個人識別)の前段階として使用される。

技術名: SCRFD(Sample and Computation Redistribution for Efficient Face Detection)
出典: Guo, J., Deng, J., An, X., & Yu, J. (2021). Sample and Computation Redistribution for Efficient Face Detection. arXiv:2105.04714.

SCRFDはサンプル分布を効率的に再配分すること特徴とする顔検出技術である。Webカメラからの映像をリアルタイムで処理し、顔検出と5点キーポイント(両目、鼻、口の両端)の検出を実行する。

2. Python開発環境,ライブラリ類

Python 3.12 のインストール

インストール済みの場合は実行不要。

管理者権限でコマンドプロンプトを起動(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する。管理者権限は、wingetの--scope machineオプションでシステム全体にソフトウェアをインストールするために必要である。

REM Python をシステム領域にインストール
winget install --scope machine --id Python.Python.3.12 -e --silent
REM Python のパス設定
set "PYTHON_PATH=C:\Program Files\Python312"
set "PYTHON_SCRIPTS_PATH=C:\Program Files\Python312\Scripts"
echo "%PATH%" | find /i "%PYTHON_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_PATH%" /M >nul
echo "%PATH%" | find /i "%PYTHON_SCRIPTS_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_SCRIPTS_PATH%" /M >nul

関連する外部ページ

Python の公式ページ: https://www.python.org/

AI エディタ Windsurf のインストール

Pythonプログラムの編集・実行には、AI エディタの利用を推奨する。ここでは,Windsurfのインストールを説明する。

管理者権限でコマンドプロンプトを起動(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行して、Windsurfをシステム全体にインストールする。管理者権限は、wingetの--scope machineオプションでシステム全体にソフトウェアをインストールするために必要となる。

winget install --scope machine Codeium.Windsurf -e --silent

関連する外部ページ

Windsurf の公式ページ: https://windsurf.com/

必要なパッケージのインストール

コマンドプロンプトを管理者として実行(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する。


pip install insightface opencv-python onnxruntime numpy

3. プログラムコード


# プログラム名: InsightFace顔検出プログラム
# 特徴技術名: SCRFD (Sample and Computation Redistribution for Efficient Face Detection)
# 出典: Guo, J., et al. (2022). Sample and Computation Redistribution for Efficient Face Detection. ICLR 2022. https://github.com/deepinsight/insightface
# 特徴機能: サンプルと計算量の再分配による高効率顔検出。画像ピラミッドの各スケールでサンプルと計算量を適応的に再分配し、検出精度を維持しながら計算効率を向上
# 学習済みモデル: buffalo_sc - InsightFaceの軽量モデルパッケージ。SCRFD検出器を含む統合モデル。初回実行時に自動ダウンロード(~300MB)
# 方式設計:
#   - 関連利用技術:
#     - InsightFace: SCRFDアルゴリズムによる高効率顔検出、CNN基盤の深層学習手法
#     - OpenCV: 画像処理、カメラ制御、描画処理
#     - ONNX Runtime: モデル推論エンジン、GPU/CPU自動選択機能
#   - 入力と出力: 入力: 動画(ユーザは「0:動画ファイル,1:カメラ,2:サンプル動画」のメニューで選択.0:動画ファイルの場合はtkinterでファイル選択.1の場合はOpenCVでカメラが開く.2の場合はhttps://raw.githubusercontent.com/opencv/opencv/master/samples/data/vtest.aviを使用)、出力: OpenCV画面でリアルタイム表示(検出した顔をバウンディングボックスと5点キーポイントで表示)、1秒間隔でprint()による処理結果表示、プログラム終了時にresult.txtファイルに保存
#   - 処理手順: 1.InsightFace(SCRFD)で顔検出、2.信頼度閾値によるフィルタリング、3.5点キーポイント抽出、4.検出結果の表示
#   - 前処理、後処理: 前処理:バッファクリア処理(最新フレーム取得のためgrab()とretrieve()を分離)、後処理:信頼度閾値によるフィルタリング(DET_SCORE_THRESHOLD以上の検出結果のみ採用)
#   - 追加処理: 5点キーポイント(右目、左目、鼻、右口角、左口角)の座標表示、DirectML対応によるWindows環境でのGPU利用拡大
#   - 調整を必要とする設定値: DET_SCORE_THRESHOLD(検出信頼度閾値、デフォルト0.5)- プログラム先頭で定義、値を上げると誤検出が減少するが検出漏れが増加
# 将来方策: DET_SCORE_THRESHOLDの動的調整機能。現在は固定値だが、シーンに応じて自動調整する機能(例:検出数が多すぎる場合は閾値を上げ、少なすぎる場合は下げる)
# その他の重要事項: Windows環境専用、初回実行時は学習済みモデルのダウンロードに時間がかかる、GPU自動検出機能(CUDA/DirectML/CPU)
# 前準備:
#   - pip install insightface opencv-python onnxruntime numpy
#   - GPU使用の場合: pip install onnxruntime-gpu

import cv2
import tkinter as tk
from tkinter import filedialog
import os
import numpy as np
from insightface.app import FaceAnalysis
import warnings
import time
import urllib.request

warnings.filterwarnings('ignore')

# ===== 調整可能な設定値 =====
DET_SCORE_THRESHOLD = 0.5  # 検出信頼度閾値(0.0-1.0):値を上げると誤検出が減少するが検出漏れが増加

# ===== 設定・定数管理 =====
SAMPLE_VIDEO_URL = 'https://raw.githubusercontent.com/opencv/opencv/master/samples/data/vtest.avi'
SAMPLE_VIDEO_NAME = 'vtest.avi'
RESULT_FILE = 'result.txt'

# カメラ設定
WINDOW_WIDTH = 1280  # カメラ解像度幅
WINDOW_HEIGHT = 720  # カメラ解像度高さ
FPS = 30  # フレームレート

# 検出パラメータ
DET_SIZE = (640, 640)  # 検出時の画像サイズ

# 表示設定
PRINT_INTERVAL = 1.0  # 結果出力間隔(秒)

# 顔とキーポイントの表示色(BGR形式)
FACE_COLOR = (0, 255, 0)  # 緑(バウンディングボックス用)
KPS_COLOR = (0, 0, 255)  # 赤(キーポイント用)
KPS_RADIUS = 3  # キーポイントの円の半径

# キーポイント名
KPS_NAMES = ['右目', '左目', '鼻', '右口角', '左口角']


def detect_gpu():
    """GPU使用可能性を判定"""
    try:
        import onnxruntime as ort
        providers = ort.get_available_providers()

        if 'CUDAExecutionProvider' in providers:
            # CUDAが利用可能か実際に確認
            try:
                sess = ort.InferenceSession(
                    None,
                    providers=['CUDAExecutionProvider']
                )
                print('GPU(CUDA)検出 - GPU使用モードで実行')
                return 0, ['CUDAExecutionProvider', 'CPUExecutionProvider']
            except:
                pass

        if 'DirectMLExecutionProvider' in providers:
            print('GPU(DirectML)検出 - DirectML使用モードで実行')
            return -1, ['DirectMLExecutionProvider', 'CPUExecutionProvider']

        print('GPU未検出 - CPU使用モードで実行')
        return -1, ['CPUExecutionProvider']
    except Exception as e:
        print(f'GPU判定エラー: {e}')
        return -1, ['CPUExecutionProvider']


def download_insightface_model():
    """SCRFDモデルダウンロード処理"""
    model_dir = os.path.join(os.path.expanduser('~'), '.insightface', 'models', 'buffalo_sc')

    if not os.path.exists(model_dir):
        print('SCRFD buffalo_scモデルの初回ダウンロードを行います...')
        print('モデル: buffalo_sc(軽量モデルパッケージ、~300MB)')
        print('ダウンロードには数分かかる場合があります')
    else:
        print('SCRFD buffalo_scモデルが既に存在します')


def extract_face_info(face, face_id):
    """顔情報の抽出"""
    bbox = face.bbox.astype(int)

    face_info = {
        'id': face_id,
        'box': (bbox[0], bbox[1], bbox[2], bbox[3]),
        'keypoints': []
    }

    # キーポイント情報の抽出
    if hasattr(face, 'kps') and face.kps is not None:
        for i, kp in enumerate(face.kps):
            if i < len(KPS_NAMES):
                face_info['keypoints'].append({
                    'name': KPS_NAMES[i],
                    'x': int(kp[0]),
                    'y': int(kp[1])
                })

    # 信頼度情報
    if hasattr(face, 'det_score'):
        face_info['detection_conf'] = float(face.det_score)

    return face_info


def log_detection_results(frame_count, faces_data, results_log):
    """検出結果のログ出力"""
    output = f'フレーム {frame_count}: {len(faces_data)}顔検出'
    print(output)

    for face_info in faces_data:
        detail = f"顔{face_info['id']}: "
        if face_info['keypoints']:
            kps_info = []
            for kp in face_info['keypoints']:
                kps_info.append(f"{kp['name']}({kp['x']},{kp['y']})")
            detail += ' '.join(kps_info)
        if 'detection_conf' in face_info:
            detail += f" [信頼度: {face_info['detection_conf']:.2f}]"
        print(f'  {detail}')
        results_log.append(f'{output} | {detail}')


def video_processing(frame):
    """フレーム処理メイン関数"""
    global frame_count, last_print_time, results_log

    frame_count += 1

    # 顔検出実行
    faces = app.get(frame)

    # 信頼度でフィルタリング
    faces = [face for face in faces if face.det_score >= DET_SCORE_THRESHOLD]

    faces_data = []

    # 各顔の処理
    for i, face in enumerate(faces):
        face_info = extract_face_info(face, i + 1)
        faces_data.append(face_info)

    # 1秒間隔での出力
    current_time = time.time()
    if current_time - last_print_time >= PRINT_INTERVAL and faces_data:
        log_detection_results(frame_count, faces_data, results_log)
        last_print_time = current_time

    # 描画処理
    for face_info in faces_data:
        x1, y1, x2, y2 = face_info['box']

        # バウンディングボックス
        cv2.rectangle(frame, (x1, y1), (x2, y2), FACE_COLOR, 2)

        # キーポイント描画
        for kp in face_info['keypoints']:
            cv2.circle(frame, (kp['x'], kp['y']), KPS_RADIUS, KPS_COLOR, -1)

        # ラベル表示
        label1 = f"Face {face_info['id']}"
        if 'detection_conf' in face_info:
            label2 = f"Conf:{face_info['detection_conf']:.1%}"
            cv2.putText(frame, label2, (x1, y2+15), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 255), 1)

        cv2.putText(frame, label1, (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, FACE_COLOR, 2)

    # システム情報表示
    gpu_status = 'GPU' if CTX_ID >= 0 else 'CPU'
    info1 = f'InsightFace ({gpu_status}) | Frame: {frame_count} | Faces: {len(faces_data)}'
    info2 = f'Press: q=Quit | Threshold: {DET_SCORE_THRESHOLD}'

    cv2.putText(frame, info1, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
    cv2.putText(frame, info2, (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 0), 1)

    return frame


# GPU判定
CTX_ID, PROVIDERS = detect_gpu()

# プログラム概要表示
print('=== InsightFace顔検出プログラム ===')
print('概要: リアルタイムで顔と5点キーポイントを検出し表示します')
print('機能: SCRFD(InsightFace)による顔検出と顔特徴点検出')
print('操作: qキーで終了')
print('出力: 1秒間隔での処理結果表示、終了時にresult.txt保存')
print(f'設定: 検出信頼度閾値 = {DET_SCORE_THRESHOLD}')
print()

# システム初期化
print('システム初期化中...')
start_time = time.time()

# SCRFDモデルダウンロード確認
download_insightface_model()

# 顔検出用アプリケーション初期化
try:
    print('SCRFD buffalo_scモデルを初期化中...')
    app = FaceAnalysis(name='buffalo_sc', providers=PROVIDERS)
    app.prepare(ctx_id=CTX_ID, det_size=DET_SIZE)
    print('SCRFDモデルをロードしました')
except Exception as e:
    print('SCRFD buffalo_scモデルの初期化に失敗しました')
    print(f'エラー: {e}')
    exit()

print('初期化完了')
print()

# グローバル変数
frame_count = 0
last_print_time = time.time()
results_log = []

# 入力選択
print('0: 動画ファイル')
print('1: カメラ')
print('2: サンプル動画')

choice = input('選択: ')
temp_file = None

if choice == '0':
    root = tk.Tk()
    root.withdraw()
    path = filedialog.askopenfilename()
    if not path:
        exit()
    cap = cv2.VideoCapture(path)
elif choice == '1':
    cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
    cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
    cap.set(cv2.CAP_PROP_FRAME_WIDTH, WINDOW_WIDTH)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, WINDOW_HEIGHT)
    cap.set(cv2.CAP_PROP_FPS, FPS)
elif choice == '2':
    # サンプル動画ダウンロード・処理
    try:
        urllib.request.urlretrieve(SAMPLE_VIDEO_URL, SAMPLE_VIDEO_NAME)
        temp_file = SAMPLE_VIDEO_NAME
        cap = cv2.VideoCapture(SAMPLE_VIDEO_NAME)
        print('サンプル動画のダウンロードが完了しました')
    except Exception as e:
        print(f'動画のダウンロードに失敗しました: {SAMPLE_VIDEO_URL}')
        print(f'エラー: {e}')
        exit()
else:
    print('無効な選択です')
    exit()

# 動画処理開始メッセージ
print('\n=== 動画処理開始 ===')
print('操作方法:')
print('  q キー: プログラム終了')
print()

# メイン処理
try:
    while True:
        cap.grab()
        ret, frame = cap.retrieve()
        if not ret:
            break

        processed_frame = video_processing(frame)
        cv2.imshow('InsightFace Detection', processed_frame)

        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
finally:
    cap.release()
    cv2.destroyAllWindows()

    # 結果保存
    if results_log:
        with open(RESULT_FILE, 'w', encoding='utf-8') as f:
            f.write('=== InsightFace顔検出結果 ===\n')
            f.write(f'処理フレーム数: {frame_count}\n')
            f.write(f'検出信頼度閾値: {DET_SCORE_THRESHOLD}\n\n')
            f.write('\n'.join(results_log))

        print(f'\n処理結果を{RESULT_FILE}に保存しました')

    if temp_file and os.path.exists(temp_file):
        os.remove(temp_file)

print('\n=== プログラム終了 ===')

4. 使用方法

  1. 上記のプログラムを実行する
  2. 検出された顔に赤色の境界ボックスと緑色のキーポイントが表示される。
  3. 各顔の下部に検出信頼度(0.000~1.000)が表示される。検出信頼度は0.0から1.0の値で、1.0に近いほど顔である確信度が高い。0.5以上を閾値として使用することが多い。
  4. 'q'キーを押すとプログラムが終了する。

5. 実験・探求のアイデア

様々な条件での検出性能テスト

キーポイント活用の探求

検出された5点キーポイントの座標を利用して、顔の向きや表情の簡易推定を実行する。両目の座標差から顔の傾きを計算したり、口の両端の位置から表情の変化を確認する。