MediaPipe Face Landmarker(新API)によるリアルタイムEAR(Eye Aspect Ratio)算出(ソースコードと説明と利用ガイド)
【概要】
MediaPipe新APIでリアルタイムEAR算出を行う。目の開閉状態を数値化するEAR(Eye Aspect Ratio)を計算し、EAR値と閾値の比較により瞬きを判定する。
特徴:
- 顔478点検出(顔表面468点+虹彩10点)→ 目12点からEAR算出
- 時系列グラフで可視化(過去1000フレーム)
- カメラ入力はスレッド化フレーム取得により表示遅延を抑制
- 動画ファイル/カメラ対応
用途: 瞬き頻度の測定、目の開閉状態の記録
【目次】
プログラム利用ガイド
1. このプログラムの利用シーン
このプログラムは、動画ファイルやカメラ映像から目の開閉状態を数値として測定し、瞬きを判定するソフトウェアである。瞬き頻度の測定や、視線分析の前処理として利用できる。
2. 主な機能
- リアルタイムEAR計算: 右目と左目のEAR値を独立して計算し、画面に表示する。
- 瞬き判定: EAR値が閾値(デフォルト0.2)を下回ると「瞬き」と判定する。
- 時系列グラフ表示: 過去1000フレーム分のEAR推移をグラフで可視化する。閾値ラインも表示される。
- ランドマーク可視化: 目の周囲12点(右目6点、左目6点)を映像上にプロットする。
- 処理結果の保存: 全フレームのEAR値をresult.txtファイルに保存する。
- パフォーマンス監視: カメラモードでは10秒ごとに実測fps、処理フレーム数、平均処理時間を表示する。
3. 基本的な使い方
- 起動と入力の選択:
キーボードで0(動画ファイル)、1(カメラ)、2(サンプル動画)のいずれかを入力し、Enterキーを押す。サンプル動画(vtest.avi)は顔が写っていない歩行者の映像であり、動作確認用である。EAR値を得るには、顔が写った動画ファイルかカメラを選ぶ。
- 処理の実行:
映像が表示され、EAR値と瞬き判定がフレームごとに更新される。右側に時系列グラフが表示される。
- 終了方法:
映像が表示されている画面を選択した状態で、キーボードのqキーまたはESCキーを押す。
4. 便利な機能
- 複数の入力ソース: 動画ファイル、カメラ、サンプル動画から選択できる。
- 自動モデルダウンロード: 初回実行時に学習済みモデルを自動的にダウンロードする。
- 詳細なコンソール出力: カメラモードではタイムスタンプ付き、動画モードではフレーム番号付きでEAR値を出力する。
- 結果ファイルの自動保存: プログラム終了時に全フレームの処理結果をresult.txtに保存する。
Python開発環境,ライブラリ類
ここでは、前準備について説明する。機械学習や深層学習を行う場合は、NVIDIA CUDA、Visual Studio、Cursorなどを追加でインストールすると便利である。これらについては別ページ https://www.kkaneko.jp/cc/dev/aiassist.htmlで詳しく解説しているので、必要に応じて参照してください。
Build Tools for Visual Studio 2026(ビルドツール)のインストール
Build Tools for Visual Studio 2026(ビルドツール)のインストールを行い、C/C++ コードのビルド環境を整える。
Build Tools for Visual Studio は,Visual Studio の IDE を含まない C/C++ コンパイラ,ライブラリ,ビルドツール等のコマンドライン向け開発ツールセットである。インストール済みの場合,この手順は不要である。 以下のコマンドは、Build Tools が未インストールの場合は winget で新規インストールし、インストール済みの場合は 【インストールコマンドの実行方法】 管理者権限でコマンドプロンプトを起動する(手順:Windows キーまたはスタートメニュー → 上記のコマンドでは、Build Tools 本体と Visual C++ 再頒布可能パッケージをインストールし、続いて以下のコンポーネントを追加している。 上記以外の追加のコンポーネントが必要になった場合は Visual Studio Installer で個別にインストールできる。 インストール完了の確認 Visual Studio を必要とするとき Visual Studio の機能を必要とする場合は,追加インストールできる。[Build Tools for Visual Studio 2026(ビルドツール)のインストール手順を見るには、ここをクリック]
Windows での Build Tools for Visual Studio 2026 のインストール
setup.exe modify でコンポーネントを追加する(バージョンは変更しない)。cmd と入力 → 右クリック → 「管理者として実行」)。そして、コマンド全体をコマンドプロンプトにコピー&ペーストする。REM VC++ ランタイム
winget install --scope machine --id Microsoft.VCRedist.2015+.x64 -e --silent --disable-interactivity --force --accept-source-agreements --accept-package-agreements --override "/quiet /norestart"
REM ============================================================
REM Visual Studio Build Tools + Desktop development with C++
REM (VCTools、MSBuildTools、CMake連携、Clang、Windows 11 SDK)
REM ============================================================
REM 進行中のインストーラーを停止(ロック競合回避)
taskkill /F /IM vs_setup.exe /T >nul 2>&1
taskkill /F /IM vs_installer.exe /T >nul 2>&1
taskkill /F /IM vs_installerservice.exe /T >nul 2>&1
REM 未インストール時: winget で新規インストール
REM インストール済み時: setup.exe modify でコンポーネント追加(バージョンは変更しない)
winget list --id Microsoft.VisualStudio.BuildTools 2>nul | findstr /i "BuildTools" >nul 2>&1
if %ERRORLEVEL% EQU 0 (
for /f "usebackq delims=" %P in (`"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -products Microsoft.VisualStudio.Product.BuildTools -property installationPath`) do start /wait "" "C:\Program Files (x86)\Microsoft Visual Studio\Installer\setup.exe" modify --installPath "%P" --add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Workload.MSBuildTools --add Microsoft.VisualStudio.Component.VC.CMake.Project --add Microsoft.VisualStudio.Component.VC.Llvm.Clang --add Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset --add Microsoft.VisualStudio.Component.Windows11SDK.26100 --includeRecommended --quiet --norestart --nocache
) else (
winget install --scope machine --id Microsoft.VisualStudio.BuildTools -e --silent --disable-interactivity --force --accept-source-agreements --accept-package-agreements --override "--quiet --wait --norestart --nocache --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended --add Microsoft.VisualStudio.Workload.MSBuildTools --add Microsoft.VisualStudio.Component.VC.CMake.Project --add Microsoft.VisualStudio.Component.VC.Llvm.Clang --add Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset --add Microsoft.VisualStudio.Component.Windows11SDK.26100"
)
REM 破損時の修復(任意、動作がおかしくなった場合)
REM "C:\Program Files (x86)\Microsoft Visual Studio\Installer\setup.exe" repair --installPath "C:\Program Files (x86)\Microsoft Visual Studio\18\BuildTools" --quiet --norestart
REM 導入確認(インストールパスが表示されれば正常)
"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -products * -requires Microsoft.VisualStudio.Workload.VCTools -property installationPath
--includeRecommended により、MSVC コンパイラ、C++ AddressSanitizer、vcpkg、CMake ツール、Windows 11 SDK 等の推奨コンポーネントが含まれる)winget list Microsoft.VisualStudio.BuildTools
Python 3.12 のインストール
Pythonのインストールを行い、Pythonのプログラムを実行する環境を整える。扱う環境は、Windows搭載パソコンである。金子研究室では、Python 3.12.10を推奨する。
[Windows での Python 3.12 のインストール手順を見るには、ここをクリック]
Windows での Python 3.12 のインストール
以下のいずれかの方法でPython 3.12をインストールする。Pythonがインストール済みの場合、この手順は不要である。
方法 1:winget によるインストール
【インストールコマンドの実行方法】
管理者権限でコマンドプロンプトを起動する(手順:Windowsキーまたはスタートメニュー → cmd と入力 → 右クリック → 「管理者として実行」)。そして、コマンド全体をコマンドプロンプトにコピー&ペーストする。
--scope machine を指定することで、システム全体(全ユーザー向け)にインストールされる。このオプションの実行には管理者権限が必要である。インストール完了後、コマンドプロンプトを再起動するとPATHが反映される。
REM Python 3.12 をシステム領域にインストール
winget install --id Python.Python.3.12 -e --scope machine --silent --accept-source-agreements --accept-package-agreements --override "/quiet InstallAllUsers=1 PrependPath=1 Include_test=0 Include_pip=1 Include_launcher=1 InstallLauncherAllUsers=1 TargetDir=\"C:\Program Files\Python312\""
REM Python と Scripts を PATH 先頭に追加
powershell -NoProfile -Command "$p='C:\Program Files\Python312'; $s=\"$p\Scripts\"; $c=[Environment]::GetEnvironmentVariable('Path','Machine'); if((Test-Path $p) -and (';'+$c+';' -notlike \"*;$p;*\") -and (';'+$c+';' -notlike \"*;$s;*\")){[Environment]::SetEnvironmentVariable('Path',\"$p;$s;$c\",'Machine')}"
方法 2:インストーラーによるインストール
- Python公式サイト(https://www.python.org/downloads/)にアクセスし、「Download Python 3.x.x」ボタンからWindows用インストーラーをダウンロードする。
- ダウンロードしたインストーラーを実行する。
- 初期画面の下部に表示される「Add python.exe to PATH」にチェックを入れてから「Customize installation」を選択する。このチェックを入れ忘れると、コマンドプロンプトから
pythonコマンドを実行できない。 - 「Install Python 3.xx for all users」にチェックを入れ、「Install」をクリックする。
インストールの確認
コマンドプロンプトで以下を実行する。
python --version
バージョン番号(例:Python 3.12.x)が表示されればインストール成功である。「'python' は、内部コマンドまたは外部コマンドとして認識されていません。」と表示される場合は、インストールが正常に完了していない。
Python の開発環境 Visual Studio Code のインストールと Python 用の設定
Python の開発環境Visual Studio Code(プログラムを編集するソフトウェア。以下、VS Code)を整える。
[Windows での Visual Studio Code のインストールと Python 用の設定手順を見るには、ここをクリック]
Windows での Visual Studio Code のインストールと Python 用の設定手順
1. VS Code と拡張機能のインストール
以下のコマンドにより,既存の VS Code を削除し,全ユーザー共有の設定で再インストールしたうえで,拡張機能(VS Code に機能を追加するソフトウェア)をまとめて導入する.
【インストールコマンドの実行方法】
管理者権限でコマンドプロンプトを起動する(手順:Windows キーまたはスタートメニュー → cmd と入力 → 右クリック → 「管理者として実行」)。そして,コマンド全体をコマンドプロンプトにコピー&ペーストする。
インストールコマンド
REM ============================================================
REM Microsoft Visual Studio Code
REM ============================================================
winget uninstall -e --id Microsoft.VisualStudioCode --silent --disable-interactivity --accept-source-agreements
rmdir /s /q C:\ProgramData\vscode-extensions 2>nul
rmdir /s /q "%APPDATA%\Code" 2>nul
rmdir /s /q "%USERPROFILE%\.vscode" 2>nul
rmdir /s /q "%LOCALAPPDATA%\Microsoft\vscode-update" 2>nul
REM VS Code をシステム領域に新規インストール
winget install --scope machine --id Microsoft.VisualStudioCode -e --silent --accept-source-agreements --accept-package-agreements
REM 全ユーザー共有の拡張機能フォルダ
mkdir C:\ProgramData\vscode-extensions 2>nul
icacls "C:\ProgramData\vscode-extensions" /grant "Everyone:(OI)(CI)M" /T
REM スタートメニューのショートカットを --extensions-dir 付きで再作成
rmdir /s /q "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code" 2>nul
del "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code.lnk" 2>nul
powershell -NoProfile -Command "$s=New-Object -ComObject WScript.Shell; $lnk=$s.CreateShortcut('C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code.lnk'); $lnk.TargetPath='C:\Program Files\Microsoft VS Code\Code.exe'; $lnk.Arguments='--extensions-dir \"C:\ProgramData\vscode-extensions\"'; $lnk.Save()"
REM ショートカットの検証
powershell -NoProfile -Command "$s=New-Object -ComObject WScript.Shell; $lnk=$s.CreateShortcut('C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code.lnk'); Write-Host 'TargetPath:' $lnk.TargetPath; Write-Host 'Arguments:' $lnk.Arguments"
REM ファイル / フォルダ右クリックの「Code で開く」を登録
reg add "HKLM\SOFTWARE\Classes\*\shell\VSCode\command" /ve /d "\"C:\Program Files\Microsoft VS Code\Code.exe\" --extensions-dir \"C:\ProgramData\vscode-extensions\" \"%1\"" /f
reg add "HKLM\SOFTWARE\Classes\Directory\shell\VSCode\command" /ve /d "\"C:\Program Files\Microsoft VS Code\Code.exe\" --extensions-dir \"C:\ProgramData\vscode-extensions\" \"%1\"" /f
reg add "HKLM\SOFTWARE\Classes\Directory\Background\shell\VSCode\command" /ve /d "\"C:\Program Files\Microsoft VS Code\Code.exe\" --extensions-dir \"C:\ProgramData\vscode-extensions\" \"%V\"" /f
REM --extensions-dir 付きで起動する code.cmd ラッパを作成
REM (%* を echo で書くと対話的 cmd で失われるため、PowerShell で [char]37+'*' を書き出す)
powershell -NoProfile -Command "$pct=[char]37; $q=[char]34; $c='@echo off'+[char]13+[char]10+$q+'C:\Program Files\Microsoft VS Code\bin\code.cmd'+$q+' --extensions-dir '+$q+'C:\ProgramData\vscode-extensions'+$q+' '+$pct+'*'+[char]13+[char]10; [IO.File]::WriteAllText('C:\ProgramData\vscode-extensions\vscode.cmd',$c,[Text.Encoding]::ASCII)"
REM 拡張機能のインストール
set "CODE=C:\Program Files\Microsoft VS Code\bin\code.cmd"
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --uninstall-extension GitHub.copilot
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --uninstall-extension GitHub.copilot-chat
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension ms-python.python
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension ms-python.vscode-pylance
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension ms-python.debugpy
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension MS-CEINTL.vscode-language-pack-ja
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension saoudrizwan.claude-dev
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension rust-lang.rust-analyzer
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension tamasfe.even-better-toml
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension anthropic.claude-code
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension almenon.arepl
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --list-extensions --show-versions
echo === セットアップ完了 ===
2. Python インタプリタの選択
同一マシンに複数の Python がインストールされている場合,VS Code で使用する Python 本体(インタプリタ:Python プログラムを解釈・実行するソフトウェア)を選択する必要がある.
- コマンドパレット(コマンド名で機能を呼び出す VS Code の入力欄)を開く(
Ctrl+Shift+P) Python: Select Interpreterと入力する
- 表示される一覧から,使用する Python(例:
C:\Program Files\Python312\python.exe)を選択する.
必要なライブラリをシステム領域にインストール
管理者権限でコマンドプロンプトを起動する
(手順:Windowsキーまたはスタートメニュー → cmd と入力 → 右クリック → 「管理者として実行」)。
以下のコマンドを実行する。
pip install -U --no-user mediapipe opencv-python numpy
MediaPipe Face Landmarker(新API)によるリアルタイムEAR(Eye Aspect Ratio)算出プログラム
概要
このプログラムは、MediaPipe Face Landmarker[1][3]を用いて動画またはカメラ映像から顔のランドマーク478点を検出し、目の周囲12点の座標からEAR(Eye Aspect Ratio)を計算する。EAR値は目の開閉状態を数値化する指標であり[2]、フレームごとに計算・表示することで瞬き判定を行う。
主要技術
MediaPipe Face Landmarker
Googleが公開している顔ランドマーク検出タスクである[1][3]。機械学習モデルにより顔の478点(顔表面468点と虹彩10点)の特徴点を検出する。本プログラムでは新API(mediapipe.tasks.python.vision)を使用し、RunningMode.VIDEOモードで動画処理を行う。モデルはfloat16版のモデルバンドル(face_landmarker.task、約3.7MB)を用いる。
EAR(Eye Aspect Ratio)
SoukupováとČechが2016年に提案した、目の開閉状態を数値化する計算手法である[2]。目の周囲6点の座標から、垂直方向の距離と水平方向の距離の比率を計算する。計算式はEAR = (||p2-p6|| + ||p3-p5||) / (2 × ||p1-p4||)である。EAR値が閾値を下回るフレームを瞬きと判定する。
技術的特徴
- 連続フレーム間のトラッキング
RunningMode.VIDEOモードでは、前フレームの検出結果を次フレームの処理に利用する。フレームごとに独立に検出するIMAGEモードと比べ、ランドマーク位置のフレーム間の揺らぎが小さくなる。
- float16モデルの利用
float16精度のモデルバンドル(約3.7MB)を使用する。処理速度はPCの性能や入力解像度に依存するため、実測fpsをプログラム内で表示して確認する。
- 左右独立したEAR計算
右目(インデックス33, 160, 158, 133, 153, 144)と左目(インデックス362, 385, 387, 263, 373, 380)のランドマークを別々に処理する。これにより、片目のみの瞬きや左右の目の開閉状態の違いを数値として得られる。なお、ここでの「右目」「左目」はMediaPipeのモデル定義上のラベルであり、カメラ映像を鏡像表示した場合は画面上の左右と逆に見える。
- 時系列グラフによる可視化
過去1000フレーム分のEAR値をdequeで管理し、右目と左目のグラフを別々に表示する。瞬き判定の閾値ラインも同時に表示する。
実装の特色
- カメラ入力の非同期フレーム取得
ThreadedVideoCaptureクラスにより、バックグラウンドスレッドで常に最新フレームを保持する。カメラ入力時に処理が遅延した場合のフレーム蓄積を防ぐ。動画ファイルの場合は全フレームを順に処理する必要があるため、通常のcv2.VideoCaptureを使用する。
- タイムスタンプ管理機構
MediaPipe APIはRunningMode.VIDEOモードで単調増加するタイムスタンプ(ミリ秒)を要求する。本プログラムは動画またはカメラのfpsからタイムスタンプ増分を計算し、各フレームに付与する。
- 拡張キャンバスによる統合表示
入力映像の右側にグラフ領域を追加した拡張キャンバスを生成する。映像、ランドマークプロット、EAR値、瞬き判定、時系列グラフを1つの画面に表示する。
参考文献
[1] Lugaresi, C., Tang, J., Nash, H., McClanahan, C., Uboweja, E., Hays, M., Zhang, F., Chang, C. L., Yong, M. G., Lee, J., Chang, W. T., Hua, W., Georg, M., & Grundmann, M. (2019). MediaPipe: A Framework for Building Perception Pipelines. arXiv preprint arXiv:1906.08172. https://arxiv.org/abs/1906.08172
[2] Soukupová, T., & Čech, J. (2016). Real-Time Eye Blink Detection using Facial Landmarks. 21st Computer Vision Winter Workshop, Rimske Toplice, Slovenia. https://vision.fe.uni-lj.si/cvww2016/proceedings/papers/05.pdf
[3] Google. MediaPipe Face Landmarker(Google AI Edge ドキュメント). https://ai.google.dev/edge/mediapipe/solutions/vision/face_landmarker
[4] Bentivoglio, A. R., Bressman, S. B., Cassetta, E., Carretta, D., Tonali, P., & Albanese, A. (1997). Analysis of blink rate patterns in normal subjects. Movement Disorders, 12(6), 1028-1034.
ソースコード
# MediaPipe Face Landmarker(新API)によるリアルタイムEAR(Eye Aspect Ratio)算出プログラム
#
# - プログラム名: MediaPipe Face Landmarker EAR計算システム
#
# - 特徴技術名: MediaPipe Face Landmarker(新API)
#
# - 出典:
# Lugaresi, C., Tang, J., Nash, H., McClanahan, C., Uboweja, E., Hays, M., ... & Grundmann, M. (2019).
# MediaPipe: A Framework for Building Perception Pipelines.
# arXiv preprint arXiv:1906.08172.
# URL: https://ai.google.dev/edge/mediapipe/solutions/vision/face_landmarker
#
# EAR計算手法:
# Soukupová, T., & Čech, J. (2016).
# Real-Time Eye Blink Detection using Facial Landmarks.
# 21st Computer Vision Winter Workshop, Rimske Toplice, Slovenia.
#
# - 特徴機能: 顔ランドマーク検出(478点=顔表面468点+虹彩10点)
# MediaPipe Face Landmarkerは、顔の特徴点478点を検出する。ビデオストリーム用の推論モード
# (RunningMode.VIDEO)では前フレームの結果を利用するため、フレーム間のランドマーク位置の
# 揺らぎが小さくなる。本プログラムでは、目の周囲の特徴点(左目6点、右目6点)を利用して
# EAR(Eye Aspect Ratio)を算出し、閾値との比較で瞬きを判定する。
#
# - 学習済みモデル:
# 名称: face_landmarker.task(float16版のモデルバンドル)
# 概要: MediaPipe公式が提供する顔ランドマーク検出用モデル
# 特徴: float16精度、ファイルサイズは約3.7MB
# URL: https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task
#
# - 特徴技術および学習済モデルの利用制限:
# Apache License 2.0(MediaPipe)。学術研究および商用利用が可能。
# 詳細: https://github.com/google-ai-edge/mediapipe/blob/master/LICENSE
#
# - 方式設計:
# - 関連利用技術:
# 1. OpenCV(opencv-python): 動画入力、画像処理、画面表示を担当
# 2. NumPy: ランドマーク座標の数値計算、EAR計算に使用
# 3. tkinter: 動画ファイル選択用のGUIダイアログ
# 4. threading: カメラ入力の非同期処理による最新フレーム取得
# 5. urllib: 学習済みモデルとサンプル動画のダウンロード
#
# - 入力と出力:
# 入力: 動画(ユーザは「0:動画ファイル,1:カメラ,2:サンプル動画」のメニューで選択.
# 0:動画ファイルの場合はtkinterでファイル選択.1の場合はOpenCVでカメラが開く.
# 2の場合はhttps://raw.githubusercontent.com/opencv/opencv/master/samples/data/vtest.aviを使用.
# このサンプル動画には顔が写っていないため、EAR値を得るには0または1を選ぶ)
# 出力: OpenCV画面に処理結果を表示(右目・左目のEAR値、瞬き判定、
# 目の特徴点12点のプロット、EAR推移グラフ(右目・左目別々に表示)).
# print()で各フレームのEAR値を出力(カメラモードではタイムスタンプ付き、
# 動画モードではフレーム番号付き).プログラム終了時にresult.txtファイルに
# 全フレームの処理結果を保存し、「result.txtに保存」したことをprint()で表示.
# プログラム開始時に、プログラムの概要と操作方法(qキーまたはESCキーで終了)をprint()で表示
#
# - 処理手順:
# 1. MediaPipe Face Landmarkerで顔の478点のランドマークを検出
# 2. 目の周囲の特徴点を抽出(右目: インデックス33,160,158,133,153,144、
# 左目: インデックス362,385,387,263,373,380。左右はMediaPipeのモデル定義上のラベル)
# 3. 各目について6点の座標からEARを計算: EAR = (||p2-p6|| + ||p3-p5||) / (2 × ||p1-p4||)
# 4. EAR値を閾値(デフォルト0.2)と比較して瞬き判定
# 5. 処理結果を画面表示、コンソール出力、ファイル保存
#
# - 前処理、後処理:
# 前処理: 入力フレームをRGB形式に変換してMediaPipe Image形式に変換(MediaPipe APIの要件)
# 後処理: 検出されたランドマーク座標を正規化座標からピクセル座標に変換
# (x座標 × 画像幅、y座標 × 画像高さ)
#
# - 追加処理:
# 1. スレッド化されたビデオキャプチャ(ThreadedVideoCapture): カメラモードで
# 常に最新フレームを保持し、処理遅延によるフレーム蓄積を防止する。
# 動画ファイルは全フレームを順に処理するため通常のcv2.VideoCaptureを使用
# 2. EAR履歴管理(deque): 過去1000フレーム分のEAR値を保持し、時系列グラフとして表示
# 3. タイムスタンプ管理: RunningMode.VIDEOが要求する単調増加ミリ秒タイムスタンプを付与
#
# - 調整を必要とする設定値:
# 1. EAR_THRESH(デフォルト: 0.2): 瞬き判定の閾値。この値より小さいEAR値を
# 「瞬き」と判定する。目の形状やまぶたの開き具合の個人差により適切な値が
# 異なるため、実測したEAR値を見て調整する。値を小さくすると瞬きの見逃しが増え、
# 大きくすると誤検出が増える
# 2. CONF_THRESH(デフォルト: 0.5): 顔検出・追跡の信頼度閾値。照明条件や顔の向きにより
# 検出が不安定な場合に調整
#
# - 将来方策:
# EAR_THRESHの個人別設定機能の例: プログラム開始時に数秒間のキャリブレーション期間を設け、
# 通常の開眼状態を維持してもらってEAR平均値を測定する。測定した平均値に一定の係数
# (例: 0.7)を掛けた値を閾値として設定する。係数の妥当性は実測により確認する
#
# - その他の重要事項:
# 1. MediaPipe Face Landmarkerの新API(mediapipe.tasks.python.vision)は、
# 旧API(mediapipe.solutions.face_mesh)とは呼び出し方法が異なる
# 2. RunningMode.VIDEOモードでは、各フレームに単調増加するタイムスタンプ(ミリ秒)の
# 指定が必須である
# 3. カメラモードでは、実測fpsと平均処理時間を10秒ごとに表示する
# 4. cv2.CAP_DSHOW(DirectShow)の指定はWindows環境向けである
#
# - 前準備: pip install -U --no-user mediapipe opencv-python numpy
import cv2
import tkinter as tk
from tkinter import filedialog
import os
import numpy as np
import urllib.request
import time
from datetime import datetime
from collections import deque
import threading
# MediaPipe新API
import mediapipe as mp
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
# ===== 設定 =====
# ウィンドウ名
WINDOW_NAME = 'EAR Detection'
# モデル情報
MODEL_URL = 'https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task'
MODEL_PATH = 'face_landmarker.task'
# MediaPipe設定
CONF_THRESH = 0.5
# 目のランドマークインデックス(EAR計算用、MediaPipeのモデル定義上の左右)
RIGHT_EYE_INDICES = [33, 160, 158, 133, 153, 144]
LEFT_EYE_INDICES = [362, 385, 387, 263, 373, 380]
# 色定数
RIGHT_EYE_COLOR = (0, 0, 255) # 赤
LEFT_EYE_COLOR = (0, 255, 0) # 緑
# EAR閾値(瞬き判定用)
EAR_THRESH = 0.2
# グラフ設定
GRAPH_WIDTH = 400
GRAPH_HEIGHT = 200
GRAPH_MARGIN = 20
EAR_HISTORY_SIZE = 1000
# EAR履歴管理
right_ear_history = deque(maxlen=EAR_HISTORY_SIZE)
left_ear_history = deque(maxlen=EAR_HISTORY_SIZE)
# カメラ要求フレームレート
CAMERA_FPS_REQUEST = 60
class ThreadedVideoCapture:
"""カメラ入力用:バックグラウンドスレッドで常に最新フレームを保持する"""
def __init__(self, src):
self.cap = cv2.VideoCapture(src, cv2.CAP_DSHOW) # DirectShow(Windows)
fourcc = cv2.VideoWriter_fourcc('M', 'J', 'P', 'G')
self.cap.set(cv2.CAP_PROP_FOURCC, fourcc)
self.cap.set(cv2.CAP_PROP_FPS, CAMERA_FPS_REQUEST)
self.grabbed, self.frame = self.cap.read()
self.stopped = False
self.lock = threading.Lock()
self.thread = threading.Thread(target=self.update, args=())
self.thread.daemon = True
self.thread.start()
def update(self):
"""バックグラウンドでフレームを取得し続ける"""
while not self.stopped:
grabbed, frame = self.cap.read()
with self.lock:
self.grabbed = grabbed
if grabbed:
self.frame = frame
def read(self):
"""最新フレームを返す"""
with self.lock:
return self.grabbed, self.frame.copy() if self.grabbed else None
def isOpened(self):
return self.cap.isOpened()
def get(self, prop):
return self.cap.get(prop)
def release(self):
self.stopped = True
self.thread.join()
self.cap.release()
print('=== MediaPipe EAR算出プログラム(新API版) ===')
print('機能: 目の開き具合(EAR)を計算・表示し、閾値との比較で瞬きを判定')
print()
# モデルダウンロード
if not os.path.exists(MODEL_PATH):
print('モデルをダウンロード中...')
try:
urllib.request.urlretrieve(MODEL_URL, MODEL_PATH)
print('ダウンロード完了')
except Exception as e:
print(f'ダウンロード失敗: {e}')
raise SystemExit(1)
# MediaPipe Face Landmarker初期化(新API)
base_options = python.BaseOptions(model_asset_path=MODEL_PATH)
options = vision.FaceLandmarkerOptions(
base_options=base_options,
running_mode=vision.RunningMode.VIDEO,
num_faces=1,
min_face_detection_confidence=CONF_THRESH,
min_face_presence_confidence=CONF_THRESH,
min_tracking_confidence=CONF_THRESH,
output_face_blendshapes=False,
output_facial_transformation_matrixes=False
)
face_landmarker = vision.FaceLandmarker.create_from_options(options)
print('MediaPipe Face Landmarker初期化完了(新API, ランドマーク数: 478点)')
print()
def calculate_ear(eye_landmarks):
"""Eye Aspect Ratio (EAR)を計算
EAR = (||p2-p6|| + ||p3-p5||) / (2 * ||p1-p4||)
eye_landmarks: 目の6点 [p1, p2, p3, p4, p5, p6]
"""
v1 = np.linalg.norm(eye_landmarks[1] - eye_landmarks[5])
v2 = np.linalg.norm(eye_landmarks[2] - eye_landmarks[4])
h = np.linalg.norm(eye_landmarks[0] - eye_landmarks[3])
ear = (v1 + v2) / (2.0 * h) if h > 0 else 0
return ear
def is_blink(ear):
"""EAR値から瞬き判定を行う"""
return "瞬き" if ear < EAR_THRESH else "開眼"
def draw_single_ear_graph(frame, history, graph_x, graph_y, color, label):
"""単一のEARグラフを描画"""
# グラフ背景
cv2.rectangle(frame, (graph_x, graph_y),
(graph_x + GRAPH_WIDTH, graph_y + GRAPH_HEIGHT),
(50, 50, 50), -1)
# グリッド線(横線)
for i in range(5):
y = graph_y + int(i * GRAPH_HEIGHT / 4)
cv2.line(frame, (graph_x, y), (graph_x + GRAPH_WIDTH, y), (100, 100, 100), 1)
# Y軸ラベル
y_min, y_max = 0.0, 0.4
cv2.putText(frame, f'{y_max:.1f}', (graph_x + 5, graph_y + 15),
cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 255), 1)
cv2.putText(frame, f'{y_min:.1f}', (graph_x + 5, graph_y + GRAPH_HEIGHT - 5),
cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 255), 1)
# グラフタイトル
cv2.putText(frame, label, (graph_x + GRAPH_WIDTH // 2 - 60, graph_y + 20),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2)
# 現在のEAR値を大きく表示
if len(history) > 0:
current_ear = history[-1]
cv2.putText(frame, f'{current_ear:.3f}', (graph_x + 10, graph_y + 60),
cv2.FONT_HERSHEY_SIMPLEX, 1.5, color, 3)
# 閾値ライン
thresh_y = graph_y + GRAPH_HEIGHT - int((EAR_THRESH - y_min) / (y_max - y_min) * GRAPH_HEIGHT)
cv2.line(frame, (graph_x, thresh_y), (graph_x + GRAPH_WIDTH, thresh_y), (255, 255, 0), 1)
# データプロット
if len(history) < 2:
return
points = []
for i, value in enumerate(history):
x = graph_x + int(i * GRAPH_WIDTH / EAR_HISTORY_SIZE)
y = graph_y + GRAPH_HEIGHT - int((value - y_min) / (y_max - y_min) * GRAPH_HEIGHT)
y = max(graph_y, min(graph_y + GRAPH_HEIGHT, y))
points.append((x, y))
for i in range(1, len(points)):
cv2.line(frame, points[i-1], points[i], color, 2)
def process_video_frame(frame, timestamp_ms, is_camera):
"""動画フレーム処理"""
# グラフ領域を含む拡張キャンバス作成
graph_area_height = GRAPH_HEIGHT * 2 + GRAPH_MARGIN * 3
extended_height = max(frame.shape[0], graph_area_height)
extended_width = frame.shape[1] + GRAPH_WIDTH + GRAPH_MARGIN * 2
extended_frame = np.zeros((extended_height, extended_width, 3), dtype=np.uint8)
extended_frame[:frame.shape[0], :frame.shape[1]] = frame
# MediaPipe Image形式に変換
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
# 新API: detect_for_video を使用
detection_result = face_landmarker.detect_for_video(mp_image, timestamp_ms)
result = ""
if detection_result.face_landmarks:
height, width = frame.shape[:2]
for face_idx, face_landmarks in enumerate(detection_result.face_landmarks):
# ランドマーク数の検証(最大インデックス387を使用するため)
if len(face_landmarks) < 388:
continue
# ランドマーク配列作成
landmarks_array = np.array([
(lm.x * width, lm.y * height)
for lm in face_landmarks
])
# 目のランドマーク取得
left_eye_points = landmarks_array[LEFT_EYE_INDICES]
right_eye_points = landmarks_array[RIGHT_EYE_INDICES]
# EAR計算
left_ear = calculate_ear(left_eye_points)
right_ear = calculate_ear(right_eye_points)
# 最初の顔のEARのみ履歴に追加
if face_idx == 0:
right_ear_history.append(right_ear)
left_ear_history.append(left_ear)
# EAR算出ポイント(12点)をプロット
for point in right_eye_points:
cv2.circle(extended_frame, (int(point[0]), int(point[1])), 3, RIGHT_EYE_COLOR, -1)
for point in left_eye_points:
cv2.circle(extended_frame, (int(point[0]), int(point[1])), 3, LEFT_EYE_COLOR, -1)
# 瞬き判定
right_blink = is_blink(right_ear)
left_blink = is_blink(left_ear)
# 画面表示
y_pos = 30 + face_idx * 50
cv2.putText(extended_frame, f'Face {face_idx+1}:', (10, y_pos),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
cv2.putText(extended_frame, f'Right EAR: {right_ear:.3f} ({right_blink})', (10, y_pos+20),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, RIGHT_EYE_COLOR, 1)
cv2.putText(extended_frame, f'Left EAR: {left_ear:.3f} ({left_blink})', (10, y_pos+40),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, LEFT_EYE_COLOR, 1)
# 結果文字列作成
result = f'{right_ear:.6f}, {left_ear:.6f}'
# グラフ描画(右目・左目)
graph_x = extended_frame.shape[1] - GRAPH_WIDTH - GRAPH_MARGIN
# 右目グラフ(上段)
right_graph_y = GRAPH_MARGIN
draw_single_ear_graph(extended_frame, right_ear_history, graph_x, right_graph_y,
RIGHT_EYE_COLOR, "Right Eye EAR")
# 左目グラフ(下段)
left_graph_y = GRAPH_MARGIN * 2 + GRAPH_HEIGHT
draw_single_ear_graph(extended_frame, left_ear_history, graph_x, left_graph_y,
LEFT_EYE_COLOR, "Left Eye EAR")
return extended_frame, result
def video_frame_processing(frame, timestamp_ms, is_camera):
"""動画フレーム処理(標準形式)"""
global frame_count
current_time = time.time()
frame_count += 1
processed_frame, result = process_video_frame(frame, timestamp_ms, is_camera)
return processed_frame, result, current_time
# 入力選択
print('0: 動画ファイル')
print('1: カメラ')
print('2: サンプル動画(顔が写っていない動作確認用の動画)')
choice = input('選択: ')
is_camera = (choice == '1')
if choice == '0':
root = tk.Tk()
root.withdraw()
path = filedialog.askopenfilename()
if not path:
raise SystemExit(1)
cap = cv2.VideoCapture(path)
elif choice == '1':
cap = ThreadedVideoCapture(0)
else:
SAMPLE_URL = 'https://raw.githubusercontent.com/opencv/opencv/master/samples/data/vtest.avi'
SAMPLE_FILE = 'vtest.avi'
print('サンプル動画をダウンロード中...')
urllib.request.urlretrieve(SAMPLE_URL, SAMPLE_FILE)
print('このサンプル動画には顔が写っていないため、EAR値は出力されない')
cap = cv2.VideoCapture(SAMPLE_FILE)
if not cap.isOpened():
print('動画ファイル・カメラを開けませんでした')
raise SystemExit(1)
# fps情報表示とタイムスタンプ増分計算
if is_camera:
actual_fps = cap.get(cv2.CAP_PROP_FPS)
print(f'カメラのfps設定値: {actual_fps}')
timestamp_increment = int(1000 / actual_fps) if actual_fps > 0 else 33
else:
video_fps = cap.get(cv2.CAP_PROP_FPS)
print(f'動画のfps: {video_fps}')
timestamp_increment = int(1000 / video_fps) if video_fps > 0 else 33
print()
print('=== 動画処理開始 ===')
print('操作方法:')
print(' q キーまたはESCキー: プログラム終了')
print()
frame_count = 0
results_log = []
start_time = time.time()
last_info_time = start_time
info_interval = 10.0
timestamp_ms = 0
total_processing_time = 0.0
try:
while True:
ret, frame = cap.read()
if not ret:
break
timestamp_ms += timestamp_increment
processing_start = time.time()
processed_frame, result, current_time = video_frame_processing(frame, timestamp_ms, is_camera)
processing_time = time.time() - processing_start
total_processing_time += processing_time
cv2.imshow(WINDOW_NAME, processed_frame)
if result:
if is_camera:
timestamp = datetime.fromtimestamp(current_time).strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
print(f'{timestamp}, {result}')
else:
print(f'Frame {frame_count}: {result}')
results_log.append(result)
# 情報提供(カメラモードのみ、10秒ごと)
if is_camera:
elapsed = current_time - last_info_time
if elapsed >= info_interval:
total_elapsed = current_time - start_time
actual_fps = frame_count / total_elapsed if total_elapsed > 0 else 0
avg_processing_time = (total_processing_time / frame_count * 1000) if frame_count > 0 else 0
print(f'[情報] 経過時間: {total_elapsed:.1f}秒, 処理フレーム数: {frame_count}, 実測fps: {actual_fps:.1f}, 平均処理時間: {avg_processing_time:.1f}ms')
last_info_time = current_time
key = cv2.waitKey(1) & 0xFF
if key == ord('q') or key == 27: # 27はESCキー
break
finally:
print('\n=== プログラム終了 ===')
cap.release()
cv2.destroyAllWindows()
face_landmarker.close()
if results_log:
with open('result.txt', 'w', encoding='utf-8') as f:
f.write('=== EAR計算結果(新API版) ===\n')
f.write(f'処理フレーム数: {frame_count}\n')
if is_camera:
f.write('形式: 右EAR, 左EAR(コンソール出力にはタイムスタンプ付き)\n')
else:
f.write('形式: 右EAR, 左EAR(コンソール出力にはフレーム番号付き)\n')
f.write('\n')
f.write('\n'.join(results_log))
print('\n処理結果をresult.txtに保存しました')
カメラ入力を高いフレームレートで扱うための実装技術
本プログラムでは、カメラ入力の遅延を抑え、可能な範囲で高いフレームレートを得るための実装を行っている。以下、ソースコードを引用しながら各技術について解説する。実際に得られるフレームレートはカメラの仕様、USB帯域、PCの処理性能に依存する。
1. DirectShowバックエンドの明示的指定
self.cap = cv2.VideoCapture(src, cv2.CAP_DSHOW)
Windows環境でカメラを開く際にcv2.CAP_DSHOWを明示的に指定している。OpenCVはバックエンドを自動選択するが、DirectShowを直接指定することで初期化に要する時間を短縮できる。DirectShowはWindowsのマルチメディアフレームワークであり、この指定はWindows環境専用である。
2. Motion JPEGコーデックの設定
fourcc = cv2.VideoWriter_fourcc('M', 'J', 'P', 'G')
self.cap.set(cv2.CAP_PROP_FOURCC, fourcc)
Motion JPEG(MJPEG)コーデックを設定している。多くのWebカメラはMJPEG形式での転送に対応しており、非圧縮形式(YUY2など)と比較してUSBバス帯域幅の消費が小さい。同じ解像度でも高いフレームレートを選択できる場合がある。
3. フレームレートの明示的要求
self.cap.set(cv2.CAP_PROP_FPS, CAMERA_FPS_REQUEST)
カメラに対して60fpsのフレームレートを要求している。要求が受け入れられるかはカメラの対応する解像度とコーデックの組み合わせに依存する。設定後の値はcap.get(cv2.CAP_PROP_FPS)で確認できる。
4. スレッド化されたフレーム取得
class ThreadedVideoCapture:
def __init__(self, src):
# ... 初期化処理 ...
self.thread = threading.Thread(target=self.update, args=())
self.thread.daemon = True
self.thread.start()
def update(self):
"""バックグラウンドでフレームを取得し続ける"""
while not self.stopped:
grabbed, frame = self.cap.read()
with self.lock:
self.grabbed = grabbed
if grabbed:
self.frame = frame
ThreadedVideoCaptureクラスは、フレーム取得を独立したスレッドで実行する。メインスレッドが画像処理や描画を行っている間も、バックグラウンドスレッドはカメラから最新フレームを取得し続ける。取得したフレームは上書き保存されるため、古いフレームがバッファに蓄積されない。
この方式は最新フレームだけを使うカメラ入力に適する。動画ファイルでは全フレームを順に処理する必要があるため、本プログラムでは通常のcv2.VideoCaptureを使用している。
スレッド間の排他制御にはthreading.Lockを使用している。
self.lock = threading.Lock()
update()メソッドとread()メソッドの両方でwith self.lock:ブロックを使用することで、フレームデータへの同時アクセスによるデータ競合を回避している。
5. デーモンスレッドの活用
self.thread.daemon = True
取得スレッドをデーモンスレッドとして設定している。これによりメインプログラムが異常終了した場合でもプロセス全体が終了できる。通常終了時のリソース解放のためにrelease()メソッドも実装している。
def release(self):
self.stopped = True
self.thread.join()
self.cap.release()
6. 最新フレームの優先取得
def read(self):
"""最新フレームを返す"""
with self.lock:
return self.grabbed, self.frame.copy() if self.grabbed else None
read()メソッドは保持している最新のフレームを返す。同期的な実装では、処理が遅延すると内部バッファに古いフレームが蓄積され、表示が実時間から遅れる。本実装では常に最新フレームのみを保持することで表示遅延を抑えている。その代わり、処理速度がカメラのフレームレートに追いつかない場合はフレームがスキップされる。
7. フレームのコピーによるデータ保護
return self.grabbed, self.frame.copy() if self.grabbed else None
read()メソッドはフレームのコピーを返す。これによりメインスレッドがフレームを処理している間にバックグラウンドスレッドが同じメモリ領域を上書きすることを防止する。copy()のオーバーヘッドは発生するが、データの整合性を保証するために必要である。
8. 実測fpsの監視機能
if is_camera:
actual_fps = cap.get(cv2.CAP_PROP_FPS)
print(f'カメラのfps設定値: {actual_fps}')
カメラのfps設定値を取得して表示し、60fpsの要求が受け入れられたかを確認する。
さらに実行中の実測fpsも定期的に計算・表示している。
if is_camera:
elapsed = current_time - last_info_time
if elapsed >= info_interval:
total_elapsed = current_time - start_time
actual_fps = frame_count / total_elapsed if total_elapsed > 0 else 0
avg_processing_time = (total_processing_time / frame_count * 1000) if frame_count > 0 else 0
print(f'[情報] 経過時間: {total_elapsed:.1f}秒, 処理フレーム数: {frame_count}, 実測fps: {actual_fps:.1f}, 平均処理時間: {avg_processing_time:.1f}ms')
10秒ごとに実測fps、処理フレーム数、平均処理時間を表示することで、設定値と実測値の差を確認できる。
まとめ
本プログラムは、DirectShowバックエンドの指定、MJPEGコーデックの設定、fpsの明示的要求、スレッド化されたフレーム取得、最新フレーム優先の読み出しを組み合わせている。到達できるフレームレートはカメラとPCの性能で決まるため、実測値を表示して確認する設計としている。
実験・研究スキルの基礎:Windowsで学ぶ瞬き検出実験
1. 実験・研究のスキル構成要素
実験や研究を行うには、以下の5つの構成要素を理解する必要がある。
1.1 実験用データ
このプログラムでは、動画ファイルまたはカメラ映像が実験用データである。顔が映っている動画であれば、目の開閉状態を数値化して分析できる。
1.2 実験計画
何を明らかにするために実験を行うのかを定める。
計画例:
- EAR閾値が瞬き検出精度に与える影響を調べる
- 顔検出信頼度閾値が検出安定性に与える影響を調べる
- 照明条件の変化が検出精度に与える影響を調べる
- 顔の向きや角度が検出精度に与える影響を調べる
- 誤検出(瞬きではないのに瞬きと判定)を最小化するパラメータ設定を調べる
- 見逃し(実際の瞬きを検出できない)を減らすパラメータ設定を調べる
1.3 プログラム
実験を実施するためのツールである。このプログラムはMediaPipe Face Landmarkerを使用して顔の478点のランドマークを検出し、目の周囲12点の座標からEAR(Eye Aspect Ratio)を計算する。
- プログラムの機能を理解して活用することが基本である
- 基本となるプログラムを出発点として、将来、様々な機能を自分で追加することができる
1.4 プログラムの機能
このプログラムは2つのパラメータで瞬き検出を制御する。
入力パラメータ:
- EAR閾値(EAR_THRESH): 瞬き判定の基準値(デフォルト0.2)。この値より小さいEAR値を「瞬き」と判定する
- 顔検出信頼度閾値(CONF_THRESH): 顔検出・追跡の最低信頼度(デフォルト0.5)
出力情報:
- 入力映像に目のランドマーク12点(右目6点、左目6点)を描画して表示
- 右目と左目のEAR値を数値表示
- 瞬き判定結果(「瞬き」または「開眼」)を表示
- 右目と左目のEAR推移グラフを別々に表示
- コンソールに各フレームのEAR値を出力
- プログラム終了時にresult.txtファイルに全フレームの処理結果を保存
EAR(Eye Aspect Ratio)の計算原理:
- 目の周囲6点の座標から、垂直方向の距離と水平方向の距離の比率を求める
- 計算式: EAR = (||p2-p6|| + ||p3-p5||) / (2 × ||p1-p4||)
- 目が開いているときはEAR値が大きく、閉じているときは小さくなる
- 比率であるため、顔とカメラの距離が変わっても値はほぼ変わらない。一方、顔の向きが正面から外れると見かけの縦横比が変わるため値が変化する
1.5 検証(結果の確認と考察)
プログラムの実行結果を観察し、パラメータの影響を考察する。
基本認識:
- パラメータを変えると結果が変わる。その変化を観察することが実験である
- 「良い結果」「悪い結果」は目的によって異なる
- EAR値には個人差がある。目の形状やまぶたの開き具合により適切な閾値が異なる
観察のポイント:
- EAR値は瞬き時にどの程度まで下がるか
- 通常の開眼状態でのEAR値はどの程度か
- 誤検出(瞬きではないのに瞬きと判定される)は発生しているか
- 見逃し(実際の瞬きが検出されない)は発生しているか
- 左右の目でEAR値に差があるか
- 顔の向きを変えたときに検出精度は変化するか
- フレームレートが低い場合、短時間の瞬きが記録されないことがある
2. 間違いの原因と対処方法
2.1 プログラムのミス(人為的エラー)
プログラムがエラーで停止する場合
- 原因: 必要なライブラリがインストールされていない、またはPythonバージョンの不整合
- 対処方法: 「pip install -U --no-user mediapipe opencv-python numpy」を実行してライブラリをインストールする
モデルのダウンロードに失敗する場合
- 原因: ネットワーク接続の問題、またはファイアウォールによるブロック
- 対処方法: ネットワーク接続を確認する。モデルファイル(face_landmarker.task)を手動でダウンロードし、プログラムと同じフォルダに配置する
カメラが開けない場合
- 原因: カメラが他のアプリケーションで使用中、またはカメラドライバの問題
- 対処方法: 他のアプリケーション(ビデオ会議ソフトなど)を終了する。デバイスマネージャでカメラドライバを確認する
動画ファイルが開けない場合
- 原因: ファイルパスに日本語が含まれている、またはOpenCVがデコードできない動画形式
- 対処方法: ファイルを英数字のみのパスに移動する。MP4(H.264)やAVI形式の動画を使用する
2.2 期待と異なる結果が出る場合
顔が検出されない場合
- 原因: 照明が暗すぎる、映像中の顔が小さすぎる、または顔が大きく傾いている
- 対処方法: 照明を明るくする。カメラに顔を近づける。顔を正面に向ける
瞬きが検出されない場合
- 原因: EAR閾値が低すぎる
- 対処方法: EAR_THRESHの値を0.25や0.3に上げて確認する。自分の通常のEAR値をグラフで観察してから閾値を調整する
常に「瞬き」と判定される場合
- 原因: EAR閾値が高すぎる、または目の形状により通常のEAR値が低い
- 対処方法: EAR_THRESHの値を0.15や0.1に下げて確認する。グラフで自分の通常のEAR値を確認する
左右の目で判定結果が異なる場合
- 原因: 顔が斜めを向いている、または左右の目の形状に差がある
- 対処方法: 顔を正面に向けて再測定する。それでも差が残る場合は、左右それぞれのEAR値を観察し、左右別々の閾値を設定することを検討する
処理速度が遅い場合
- 原因: PCの性能不足、または他のアプリケーションがリソースを消費している
- 対処方法: 不要なアプリケーションを終了する。動画ファイルを使用する場合は解像度の低い動画を使用する
3. 実験レポートのサンプル
EAR閾値の調整による瞬き検出精度の変化
実験目的:
自分の目の形状に適したEAR閾値を見つけ、誤検出と見逃しを少なくする。
実験計画:
顔検出信頼度閾値(CONF_THRESH)をxxxxに固定し、EAR閾値(EAR_THRESH)を変化させて適切な値を探す。
実験方法:
カメラを使用して計測を行う。以下の手順で評価する。
- まず通常の開眼状態でのEAR値を10秒間観察し、平均値を記録する
- 意図的に瞬きを10回行い、瞬き時のEAR最小値を記録する
- 異なるEAR閾値を設定し、30秒間の瞬き検出結果を評価する
評価基準:
- 正検出数: 実際の瞬きが正しく検出された回数
- 誤検出数: 瞬きではないのに瞬きと判定された回数
- 見逃し数: 実際の瞬きが検出されなかった回数
実験結果:
| EAR閾値 | 正検出数 | 誤検出数 | 見逃し数 | 総合評価 |
|---|---|---|---|---|
| xxxx | x | x | x | x |
| xxxx | x | x | x | x |
| xxxx | x | x | x | x |
| xxxx | x | x | x | x |
考察:
- (例文)通常の開眼状態でのEAR平均値はxxxxであり、瞬き時のEAR最小値はxxxxであった。この差を考慮して閾値を設定する必要がある
- (例文)EAR閾値xxxxでは誤検出が多かった。目を細めたり、下を向いたりした場合に瞬きと判定された
- (例文)EAR閾値xxxxでは見逃しが増加した。素早い瞬きや浅い瞬きが検出されなかった
- (例文)EAR閾値を上げると誤検出は減るが、同時に見逃しも増えるというトレードオフの関係が確認できた
結論:
(例文)本実験の条件では、自分の目に適したEAR閾値はxxxxであった。この値は通常の開眼状態のEAR平均値のxxxx%に相当する。個人差があるため、使用者ごとに開眼状態のEAR値を測定し、その値に基づいて閾値を設定することが望ましい。
作業内容と瞬き頻度の関係
実験目的:
作業内容によって瞬き頻度が変わるかを観察する。既存研究では、安静時の平均瞬き回数は約17回/分、会話時は約26回/分、読書時は約4.5回/分と報告されている[4]。この傾向が自分の測定でも確認できるかを調べる。
実験計画:
EAR閾値(EAR_THRESH)をxxxxに固定し、条件ごとの瞬き回数を計測して比較する。
実験方法:
カメラを使用して各条件で60秒間の計測を行う。以下の条件で瞬き頻度を記録する。
- 安静条件: 特に課題を行わず、正面を向いて計測する
- 読書条件: 画面上または紙の文章を読みながら計測する
- 会話条件: 他者と会話しながら計測する
- 計算条件: 暗算などの課題を行いながら計測する
各条件は同じ照明、同じカメラ位置で行い、条件の順序による影響を減らすため順序を入れ替えて複数回計測する。
評価基準:
- 瞬き頻度: 1分あたりの瞬き回数
- 平均瞬き間隔: 瞬きと瞬きの間の平均時間(秒)
- EAR最小値: 瞬き時のEAR最小値の平均
実験結果:
| 条件 | 瞬き頻度(回/分) | 平均瞬き間隔(秒) | EAR最小値平均 |
|---|---|---|---|
| 安静 | x | x | x |
| 読書 | x | x | x |
| 会話 | x | x | x |
| 計算 | x | x | x |
考察:
- (例文)安静条件での瞬き頻度はxxxx回/分であり、これを基準として他の条件と比較した
- (例文)読書条件では瞬き頻度がxxxx回/分に減少し、既存研究[4]で報告されている傾向と同じ方向の変化が観察された
- (例文)会話条件では瞬き頻度がxxxx回/分となった
- (例文)読書条件では顔が下を向くため、顔の向きの変化がEAR値に影響した可能性がある。この点は測定上の制約である
結論:
(例文)本実験の条件では、作業内容によって瞬き頻度が変化することが観察された。ただし測定回数が少なく、顔の向きや照明の影響を分離できていないため、傾向の確認にとどまる。条件を統制した上で試行回数を増やすことが次の課題である。