本記事はクソアプリアドベントカレンダー2024の記事です!
めでたい10周年ということで参加してみましょう。🎉
ということで、
瞬き耐久アプリつくった
瞬き、していますか・・・?
あまりにも無意識にしているので、いざ言われてみるとどうでしょう。
まぶたの重み、目の乾燥、感じませんか・・・?
意識しているかどうかで視界は変わるのです。
そう、瞬きを止めてみましょう!
できたもの
(急な顔面失礼します)
ポイント
- 瞬きの判定が正確ではない
構成の解説記事を書こうと思っていたが、生成AIベースで中身を理解することなく実装されてしまったため、生成物から紐解いていく。
流れ
-
v0.devにて叩きを作る
-
tailwind
ベースでのザックリレイアウトが完成 -
Webcam
の実装方法をゲット -
tensorflow
を用いた目と瞬き判定方法をゲット
-
- 瞬きの判定がされないなどの動作を修正(with GPT4o/GPT4o1-mini)
構成要素
- webcam:
react-webcam
- ユーザーの顔と瞬きを検出
- 機械学習: TensorFlow.js, MediaPipe FaceMesh
- 瞬き判定
- 目の開閉状態の評価指標。一定の閾値を設定することで判定。
瞬き判定: Eye Aspect Ratio
目の複数のキーポイント間の距離から計算
const calculateEAR = (eyePoints) => {
// EAR = (|p2 - p6| + |p3 - p5|) / (2 * |p1 - p4|)
const A = distance(eyePoints[1], eyePoints[5]);
const B = distance(eyePoints[2], eyePoints[4]);
const C = distance(eyePoints[0], eyePoints[3]);
return (A + B) / (2.0 * C);
};
- distance関数: 2点間のユークリッド距離を計算。
- しきい値 (blinkThreshold): EARがこの値以下になると瞬きと判定(通常0.2~0.25)。
const distance = (p1: faceLandmarksDetection.Keypoint, p2: faceLandmarksDetection.Keypoint) => {
return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
};
連続フレーム数 (requiredBlinkFrames): 瞬きを安定して検出するために、連続してEARがしきい値以下であるフレーム数を必要とする。
👉️ こちらは生成AIは瞬き検出の精度向上の為に2フレームを何度も求めてきたが、そうなると20ms目を閉じたときだけしか判定されなかったので、1フレーム(10ms)で仮設定。
アニメーションフレーム管理
こちらはブラウザの再描画に合わせて瞬きの継続的検出を用いているとのことだが、このやり方でなくてもよいとは思っています。
- ループの作成: detectBlink関数内でrequestAnimationFrameを再帰的に呼び出すことで、リアルタイムに瞬き検出を行います。
- キャンセル: コンポーネントのアンマウント時やチャレンジの終了時にcancelAnimationFrameを呼び出し、ループを停止します。
useEffect(() => {
if (model) {
requestRef.current = requestAnimationFrame(detectBlink);
}
return () => {
if (requestRef.current) {
cancelAnimationFrame(requestRef.current);
}
};
}, [model]);
実装構成ファイルはこちら。
さいごに
所要時間は約2時間で、レンダリングエラーを修正することに時間を当て込まれる軍配となりました。
Webcamを使った実装も、TensorFlowを使った実装も経験がなかったため、学びが深いです。
意外と簡単に実装ができると思いつつ、生成AIがなかったら瞬き判定で時間が溶けそうだなという所感でした。
ということで、クソアプリを作るつもりがなんだかそこまでクソでもなく、学ばせて頂いた件でした〜!