2
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?

GeminiのCanvasで動く!カメラを使ったジェスチャー認識HTML

Last updated at Posted at 2025-07-28

Geminiと一緒にコードを書くとき、「Canvas」機能がとても便利ですよね。
実はこのCanvas、カメラを使ったWebアプリもそのまま動きます。しかも、MediaPipeを使ったハンドジェスチャー認識もOK!

ただし、動かすにはちょっとしたコツがあったので、この記事で紹介します。

できあがるアプリのイメージ

  • カメラに手をかざすと、MediaPipeがジェスチャーを認識
    • Victory: ピースサイン ✌️
    • Thumb_Up: サムズアップ 👍
    • Thumb_Down: サムズダウン 👎
    • Pointing_Up: 指差し(上) ☝️
    • Closed_Fist: 握りこぶし ✊
    • Open_Palm: 手のひらを開く 🖐️
    • ILoveYou: "I Love You" のサイン 🤟
  • オプションで手の骨格を描画する機能あり
  • GeminiのCanvasでもそのまま動作!

Geminiに「この記事のコードをベースに改造して」とお願いすれば、手戻りなくスムーズに開発が始められます。

スターターコード

以下のHTMLを index.html として保存し、VSCodeのLive Serverなどで開くだけで動きます。

👇 Gemini Canvas上でも動作確認済みです!

ソースコード
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>MediaPipeジェスチャー認識 スターターキット</title>
    <style>
        body { font-family: sans-serif; display: flex; flex-direction: column; align-items: center; margin: 0; padding: 1rem; background-color: #f0f0f0; }
        h1 { margin-top: 0; }
        #output { font-size: 1.5rem; font-weight: bold; margin: 0.5rem; color: #007bff; min-height: 2.5rem; }
        .video-container { position: relative; width: 640px; height: 480px; }
        video, canvas {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            border-radius: 8px;
        }
        video {
            border: 2px solid #ccc;
            transform: scaleX(-1); /* ユーザーが鏡を見るように映像を左右反転 */
        }
        .controls { margin-top: 1rem; display: flex; align-items: center; gap: 1rem; }
        button {
            padding: 0.5rem 1rem;
            font-size: 1rem;
            cursor: pointer;
            border-radius: 5px;
            border: 1px solid #ccc;
        }
        button:disabled { cursor: not-allowed; background-color: #e0e0e0; }
    </style>
</head>
<body>
    <h1>MediaPipe ジェスチャー認識</h1>
    <p>下のボタンを押してカメラを開始してください</p>
    <div id="output">準備中...</div>
    <div class="video-container">
        <video id="webcam" autoplay playsinline></video>
        <canvas id="skeleton_canvas"></canvas>
    </div>
    <div class="controls">
        <button id="camera_button" disabled>カメラを開始</button>
        <label>
            <input type="checkbox" id="skeleton_checkbox">
            骨格を表示
        </label>
    </div>

    <script type="module">
        // MediaPipeのAI機能をブラウザで使うためのライブラリを読み込みます
        // ライブラリーのバージョンをtasks-vision@0.10.3のように指定すると動作しません。
        import {
            GestureRecognizer,
            FilesetResolver,
            DrawingUtils
        } from "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision/vision_bundle.js";

        // HTMLから要素を取得し、後でJavaScriptから操作できるようにします
        const video = document.getElementById("webcam");
        const outputDiv = document.getElementById("output");
        const cameraButton = document.getElementById("camera_button");
        const skeletonCheckbox = document.getElementById("skeleton_checkbox");
        const canvasElement = document.getElementById("skeleton_canvas");
        const canvasCtx = canvasElement.getContext("2d");

        // アプリケーション全体で使う変数を準備します
        let gestureRecognizer; // ジェスチャー認識を行うAIモデルのインスタンス
        let webcamRunning = false; // カメラが作動中か管理するフラグ
        let drawingUtils; // 骨格などを描画するための補助ツール

        // MediaPipeのAIモデルを非同期で初期化するメイン処理
        async function initializeMediaPipe() {
            // AIモデルが必要とする追加ファイルを読み込む準備
            // ライブラリーのバージョンをtasks-vision@0.10.3のように指定すると動作しません。
            const vision = await FilesetResolver.forVisionTasks(
                "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision/wasm"
            );
            // ジェスチャー認識モデル本体をダウンロードし、設定を行います
            gestureRecognizer = await GestureRecognizer.createFromOptions(vision, {
                baseOptions: {
                    modelAssetPath: "https://storage.googleapis.com/mediapipe-models/gesture_recognizer/gesture_recognizer/float16/1/gesture_recognizer.task",
                    delegate: "GPU" // 可能であればGPUを使って処理を高速化します
                },
                runningMode: "VIDEO", // ビデオ映像をリアルタイムで処理するモード
                numHands: 2 // 同時に認識する手の最大数
            });
            // 初期化が完了したらUIを更新します
            outputDiv.innerText = "準備完了";
            cameraButton.disabled = false;
            drawingUtils = new DrawingUtils(canvasCtx);
        }
        // ページが読み込まれたら、AIモデルの初期化を開始します
        initializeMediaPipe();

        // カメラの開始/停止ボタンが押されたときの処理
        async function toggleWebcam() {
            if (!gestureRecognizer) return; // モデルが準備できていなければ何もしない

            if (webcamRunning) {
                // カメラを停止する処理
                webcamRunning = false;
                cameraButton.innerText = "カメラを開始";
                video.srcObject.getTracks().forEach(track => track.stop());
                video.srcObject = null;
                outputDiv.innerText = "停止中";
                canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
            } else {
                // カメラを開始する処理
                webcamRunning = true;
                cameraButton.innerText = "カメラを停止";
                outputDiv.innerText = "カメラを起動中...";
                const stream = await navigator.mediaDevices.getUserMedia({ video: true });
                video.srcObject = stream;
                // ビデオのデータが読み込まれたら、予測ループを開始します
                video.addEventListener("loadeddata", predictWebcam);
            }
        }

        // 毎フレーム実行される、映像の解析と描画を行うメインループ
        async function predictWebcam() {
            if (!webcamRunning) return; // カメラが停止していたら処理を中断

            // 骨格を描画するキャンバスのサイズを、カメラ映像の実際のサイズに合わせます
            canvasElement.width = video.videoWidth;
            canvasElement.height = video.videoHeight;

            // 現在のビデオフレームをAIモデルに渡して、ジェスチャーを認識させます
            const results = gestureRecognizer.recognizeForVideo(video, Date.now());
            
            // 前のフレームに描画した骨格などを消去します
            canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);

            // 「骨格を表示」がオンの場合、検出した手の骨格を描画します
            if (skeletonCheckbox.checked && results.landmarks) {
                // 描画設定を一時的に保存します(この中の変形処理が他の描画に影響しないようにするため)
                canvasCtx.save();
                // CSSで映像を左右反転させているため、骨格の描画も同じように反転させます
                canvasCtx.scale(-1, 1);
                canvasCtx.translate(-canvasElement.width, 0);
                
                // 検出された全ての手の骨格を描画します
                for (const landmarks of results.landmarks) {
                    drawingUtils.drawConnectors(landmarks, GestureRecognizer.HAND_CONNECTIONS, { color: "#FFFFFF", lineWidth: 5 });
                    drawingUtils.drawLandmarks(landmarks, { color: "#007bff", lineWidth: 2 });
                }
                // 描画設定を元に戻します
                canvasCtx.restore();
            }

            // ジェスチャーが認識されていれば、その名前と信頼度を表示します
            if (results.gestures.length > 0) {
                const categoryName = results.gestures[0][0].categoryName;
                const score = parseFloat(results.gestures[0][0].score * 100).toFixed(2);
                outputDiv.innerText = `ジェスチャー: ${categoryName} (${score}%)`;
            } else {
                outputDiv.innerText = "ジェスチャーなし";
            }
            
            // ブラウザの描画タイミングに合わせて、この関数を繰り返し呼び出します
            window.requestAnimationFrame(predictWebcam);
        }

        // ボタンにクリックイベントを登録します
        cameraButton.addEventListener("click", toggleWebcam);

    </script>
</body>
</html>

ポイント

  • CDNでMediaPipeのモデルを読み込むだけでOK
  • カメラの映像に骨格を描画する処理も含まれています
  • ボタンを押すとカメラが起動し、手のジェスチャーを自動で認識

Geminiへの指示例

このQiita記事のURLをGeminiに渡して、こんなふうに頼んでみてください:

📝 指示例:

(上記のソースコードを貼り付けて)この記事のコードをベースに、ピースサイン(Victory)を認識したら、画面に紙吹雪が舞うアニメーションを追加してください。

この方法なら、環境の違いで動かないなんて心配もなし。AIとの共同作業がぐっとスムーズになります。

MediaPipeでできること(ほんの一部)

MediaPipeは、Googleが提供するAIビジョンライブラリです。ブラウザでも動くのが最大の特徴。

例えば、こんなことができます:

  • 手のジェスチャー認識(今回使用)
  • 顔検出・表情認識
  • ポーズ検出(全身の骨格推定)
  • 物体検出・トラッキング
  • 顔のメッシュ(ARエフェクトなどに活用)

アイデア次第で、ゲーム、UI操作、教育アプリなど幅広く活用できます!

おわりに

GeminiのCanvas × MediaPipeの組み合わせは、「見えるAI体験」 をすぐに試せる最高の組み合わせです。
この記事のコードをベースに、あなたのアイデアで次の一歩を作ってみてください!

2
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
2
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?