8
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

クソアプリAdvent Calendar 2024

Day 5

【クソアプリ】瞬き耐久チャレンジを作った

Last updated at Posted at 2024-12-06

本記事はクソアプリアドベントカレンダー2024の記事です!

めでたい10周年ということで参加してみましょう。🎉

ということで、

瞬き耐久アプリつくった

瞬き、していますか・・・?
あまりにも無意識にしているので、いざ言われてみるとどうでしょう。
まぶたの重み、目の乾燥、感じませんか・・・?

意識しているかどうかで視界は変わるのです。

そう、瞬きを止めてみましょう!

できたもの

(急な顔面失礼します)

CleanShot 2024-12-06 at 16 27 29

ポイント

  • 瞬きの判定が正確ではない

構成の解説記事を書こうと思っていたが、生成AIベースで中身を理解することなく実装されてしまったため、生成物から紐解いていく。

流れ

  1. v0.devにて叩きを作る
    • tailwindベースでのザックリレイアウトが完成
    • Webcamの実装方法をゲット
    • tensorflowを用いた目と瞬き判定方法をゲット
  2. 瞬きの判定がされないなどの動作を修正(with GPT4o/GPT4o1-mini)

構成要素

  • webcam: react-webcam
    • ユーザーの顔と瞬きを検出
  • 機械学習: TensorFlow.js, MediaPipe FaceMesh
    • TensorFlow.js

      • ブラウザ上での機械学習モデル実行のライブラリ
      • 顔認識と瞬き検出
    • MediaPipe FaceMesh12

      • 顔のキーポイントを検出する、高度な顔ランドマーク検出モデル
      • 目のキーポイントを使用して、瞬きを検出
      • Eye Aspect Ratio: 瞬き判定のために目の開閉状態を計算
  • 瞬き判定
    • 目の開閉状態の評価指標。一定の閾値を設定することで判定。

瞬き判定: 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がなかったら瞬き判定で時間が溶けそうだなという所感でした。

ということで、クソアプリを作るつもりがなんだかそこまでクソでもなく、学ばせて頂いた件でした〜!

  1. https://github.com/google-ai-edge/mediapipe/blob/master/docs/solutions/face_mesh.md

  2. https://qiita.com/nemutas/items/6321aeca27492baeeb92

8
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?