1
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で動く!カメラとMediaPipeを使ったポーズ検出HTML

Posted at

こんにちは!GeminiとMediaPipeを使ったWebアプリ開発、楽しんでいますか?

前回の記事では、手の形を認識する方法を紹介しました。

今回はその続編として、全身の骨格をリアルタイムに検出する「ポーズ検出」に挑戦します。もちろん、このコードもGeminiのCanvasでそのまま動きます!

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

  • カメラに映った人物の骨格(ポーズ)をリアルタイムに検出します。
  • 検出した骨格を、カメラ映像の上に線で描画します。
  • この基本コードを応用して、体を動かして遊ぶゲームなどを作れます。

スターターコード

まずは基本となる、骨格を検出して表示するだけのシンプルなコードです。
以下のHTMLを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>
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://fonts.googleapis.com/css2?family=Inter&display=swap" rel="stylesheet">
    <style>
        body {
            font-family: 'Inter', sans-serif;
        }
        #vision-container {
            position: relative;
            width: 100%;
            max-width: 640px;
            aspect-ratio: 4 / 3;
            overflow: hidden;
            border-radius: 0.5rem;
            box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
        }
        #webcam, #skeletonCanvas {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            object-fit: cover;
        }
        #webcam {
            transform: scaleX(-1);
            z-index: 0;
        }
        #skeletonCanvas {
            z-index: 1;
        }
    </style>
</head>
<body class="bg-gray-100 flex flex-col items-center justify-center min-h-screen p-4">

    <div class="w-full max-w-2xl text-center">
        <h1 class="text-2xl md:text-3xl font-bold text-gray-800 mb-2">姿勢検知サンプル</h1>
        <p class="text-gray-600 mb-4">MediaPipe PoseLandmarkerを使用して、Webカメラからリアルタイムに骨格を検出します。</p>
    </div>

    <div id="vision-container" class="bg-black">
        <video id="webcam" autoplay playsinline></video>
        <canvas id="skeletonCanvas"></canvas>
    </div>

    <div class="mt-4 text-center">
        <button id="startCamButton" class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded-lg transition-transform transform hover:scale-105 disabled:bg-gray-400 disabled:cursor-not-allowed">
            カメラを有効にする
        </button>
        <p id="loadingText" class="mt-2 text-gray-500 h-5"></p>
    </div>

    <script type="module">
        import * as vision from "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision/vision_bundle.js";
        const { PoseLandmarker, FilesetResolver, DrawingUtils } = vision;

        const video = document.getElementById('webcam');
        const skeletonCanvas = document.getElementById('skeletonCanvas');
        const skeletonCtx = skeletonCanvas.getContext('2d');
        const startCamButton = document.getElementById('startCamButton');
        const loadingText = document.getElementById('loadingText');

        let poseLandmarker;
        let drawingUtils;
        let lastVideoTime = -1;

        document.addEventListener('DOMContentLoaded', () => {
            drawingUtils = new DrawingUtils(skeletonCtx);
        });

        startCamButton.addEventListener('click', setupCameraAndPose);

        async function setupCameraAndPose() {
            startCamButton.disabled = true;
            loadingText.textContent = "AIモデルを読み込んでいます...";

            try {
                const visionResolver = await FilesetResolver.forVisionTasks(
                    "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision/wasm"
                );
                poseLandmarker = await PoseLandmarker.createFromOptions(visionResolver, {
                    baseOptions: {
                        modelAssetPath: `https://storage.googleapis.com/mediapipe-models/pose_landmarker/pose_landmarker_lite/float16/1/pose_landmarker_lite.task`,
                        delegate: "GPU"
                    },
                    runningMode: "VIDEO"
                });

                loadingText.textContent = "カメラへのアクセスを許可してください...";
                const stream = await navigator.mediaDevices.getUserMedia({ video: true });
                video.srcObject = stream;
                video.addEventListener("loadeddata", () => {
                    loadingText.textContent = "準備完了です!";
                    startCamButton.classList.add('hidden');
                    predictWebcam();
                });

            } catch (error) {
                console.error("初期化に失敗しました:", error);
                loadingText.textContent = `エラー: ${error.message}`;
                startCamButton.disabled = false;
            }
        }

        async function predictWebcam() {
            if (video.currentTime !== lastVideoTime && poseLandmarker) {
                lastVideoTime = video.currentTime;
                const poseResult = poseLandmarker.detectForVideo(video, performance.now());
                drawPoseResults(poseResult);
            }
            requestAnimationFrame(predictWebcam);
        }

        function drawPoseResults(results) {
            skeletonCanvas.width = video.videoWidth;
            skeletonCanvas.height = video.videoHeight;
            skeletonCtx.save();
            skeletonCtx.clearRect(0, 0, skeletonCanvas.width, skeletonCanvas.height);
            skeletonCtx.scale(-1, 1);
            skeletonCtx.translate(-skeletonCanvas.width, 0);

            if (results.landmarks && results.landmarks.length > 0) {
                const landmarks = results.landmarks[0];
                drawingUtils.drawConnectors(landmarks, PoseLandmarker.POSE_CONNECTIONS, { color: '#00FF00', lineWidth: 3 });
                drawingUtils.drawLandmarks(landmarks, { radius: 4, color: '#FF0000' });
            }
            skeletonCtx.restore();
        }
    </script>
</body>
</html>

ポイント

  • 前回使ったGestureRecognizerの代わりにPoseLandmarkerを使います。
  • 基本的な初期化の流れはハンドジェスチャー認識とほとんど同じです。
  • DrawingUtils が、検出したランドマーク(関節点)とコネクタ(骨格線)の描画をよしなにやってくれて便利です。

応用例:体で遊ぶ!恐竜ジャンプゲーム

このポーズ検出技術を使って、「自分がジャンプすると、ゲーム内のキャラクターもジャンプする」ゲームを作ってみました。

👇 こちらのURLで実際に遊べます!

どうやって動いているの?

このゲームの裏側はとてもシンプルです。

  1. スターターコードと同じように、リアルタイムであなたの骨格を検出します。
  2. 両肩のY座標(垂直位置)を常に監視します。
  3. 肩の位置が急に上に動いたら、「ユーザーがジャンプした!」と判断します。
  4. その瞬間に、ゲーム内の恐竜をジャンプさせる命令を送ります。

たったこれだけで、自分の体をコントローラーにした新しいゲーム体験が生まれます。

Geminiへの指示例

このポーズ検出のスターターコードをベースに、Geminiにこんなふうにお願いしてみましょう。

指示例:

(上記のスターターコードを貼り付けて)このコードをベースに、ユーザーが腕を上げたら(両手首のy座標が両肩のy座標より小さくなったら)、画面に「ばんざい!」と表示する機能を追加してください。

このように、検出した骨格の座標を使えば、特定のポーズを判定するロジックを簡単に追加できます。

おわりに

MediaPipeを使えば、特別な機材なしに、Webカメラだけで身体の動きを捉えることができます。GeminiのCanvasと組み合わせることで、アイデアをすぐに形にして試せるのが本当に面白いところです。

フィットネスアプリ、ジェスチャーによるUI操作、新しい形のゲームなど、可能性は無限大です。ぜひ、このスターターコードを改造して、あなただけの「体感型Webアプリ」を作ってみてください!

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