手の動きで画像と動画を表示するARインターフェース
MediaPipe Handsで手のジェスチャーを認識して、画像や動画を表示するシステムを作った。
ピースサインで画像、ロックサインで動画。midori269からの改良版で、シンプルで使いやすくした。
やりたかったこと
拡張現実(AR)のインターフェースを模索してた。
マウスやタッチじゃなくて、手の動きでコンテンツを操作したい。ジェスチャーだけで、画像や動画を出したり消したり。
最初は色々なパターンを実装しようと思ってたけど、まずは基本の2つから。ピースサインとロックサイン。
基本的な仕組み
処理は4ステップ。
1. WEBカメラ映像取得: リアルタイムで取得
2. MediaPipe Hands推論: 21個のランドマーク検出
3. ジェスチャー判定: 指の伸び具合からパターン認識
4. コンテンツ表示: 画像または動画を表示
ジェスチャー判定
MediaPipeで検出した21個のランドマークから、指の伸び具合を計算する。
function detectHandPose(landmarks) {
// 人差し指が伸びているか
const indexExtended = landmarks[8].y < landmarks[6].y;
// 中指が伸びているか
const middleExtended = landmarks[12].y < landmarks[10].y;
// 親指が伸びているか
const thumbExtended = landmarks[4].x > landmarks[3].x + 0.05;
// 小指が伸びているか
const pinkyExtended = landmarks[20].y < landmarks[18].y;
// ピースサイン(人差し指+中指)
if (indexExtended && middleExtended && !thumbExtended) {
return "ピースサイン";
}
// ロックサイン(親指+小指)
if (thumbExtended && pinkyExtended && !indexExtended && !middleExtended) {
return "ロックサイン";
}
return null;
}
Y座標で判定してる。指が上向き(y座標が小さい)なら伸びてる、下向き(y座標が大きい)なら曲がってる。
最初は、距離で判定してた。
// NG: 距離で判定
const distance = Math.hypot(
landmarks[8].x - landmarks[6].x,
landmarks[8].y - landmarks[6].y
);
if (distance > 0.1) { /* 伸びてる */ }
これだと、手の角度によって距離が変わる。横向きの手だと、距離が短くなって、判定が狂った。
Y座標の比較に変えたら、角度に依存しなくなった。シンプルで正確。
画像と動画の表示
ピースサインで画像、ロックサインで動画を表示する。
if (detectedPose === "ピースサイン") {
poseImageElement.style.display = 'block';
poseImageElement.style.left = (avgX * canvasWidth) + 'px';
poseImageElement.style.top = (avgY * canvasHeight - 100) + 'px';
} else {
poseImageElement.style.display = 'none';
}
if (detectedPose === "ロックサイン") {
rockVideoElement.style.display = 'block';
rockVideoElement.play();
rockVideoElement.style.left = (avgX * canvasWidth) + 'px';
rockVideoElement.style.top = (avgY * canvasHeight - 100) + 'px';
} else {
rockVideoElement.pause();
rockVideoElement.style.display = 'none';
}
手の位置(ランドマークの平均)に画像や動画を表示する。
Y座標から100pxオフセットしてるのは、手と重ならないようにするため。
最初はオフセットなしで試した。画像が手に隠れて見えなかった。
-100pxにしたら、手の上に表示されるようになった。見やすい。
ランドマークの描画
手のランドマーク(21点)を線で結んで表示する。
function drawHandLandmarks(canvasCtx, landmarks, canvasWidth, canvasHeight) {
// 接続する線のリスト
const connections = [
[0, 1], [1, 2], [2, 3], [3, 4], // 親指
[0, 5], [5, 6], [6, 7], [7, 8], // 人差し指
[0, 9], [9, 10], [10, 11], [11, 12], // 中指
[0, 13], [13, 14], [14, 15], [15, 16], // 薬指
[0, 17], [17, 18], [18, 19], [19, 20] // 小指
];
// 線を描画
canvasCtx.strokeStyle = 'rgba(0, 255, 0, 0.5)';
canvasCtx.lineWidth = 2;
connections.forEach(([start, end]) => {
canvasCtx.beginPath();
canvasCtx.moveTo(landmarks[start].x * canvasWidth, landmarks[start].y * canvasHeight);
canvasCtx.lineTo(landmarks[end].x * canvasWidth, landmarks[end].y * canvasHeight);
canvasCtx.stroke();
});
// ランドマークを描画
landmarks.forEach((landmark, index) => {
canvasCtx.fillStyle = index === 8 ? 'red' : 'green';
canvasCtx.beginPath();
canvasCtx.arc(
landmark.x * canvasWidth,
landmark.y * canvasHeight,
5,
0,
2 * Math.PI
);
canvasCtx.fill();
});
}
人差し指の先(ランドマーク8)だけ赤色にしてる。他は緑色。
最初は全部同じ色だった。どの指がどれか分からなかった。
先端だけ色を変えたら、分かりやすくなった。
ハマったところ
カメラの権限取得
WEBカメラの権限取得に失敗することがある。
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: { width: 1280, height: 720 }
});
videoElement.srcObject = stream;
} catch (error) {
console.error('カメラアクセスエラー:', error);
alert('カメラへのアクセスが許可されていません');
}
最初は、エラーハンドリングなしで実装してた。権限がないと、画面が真っ白になった。
try-catchを追加して、エラーメッセージを表示するようにした。
ジェスチャーの誤検出
ピースサインとロックサインが、たまに誤検出される。
手を動かしてる途中で、一瞬だけピースサインになることがある。
連続検出の仕組みを入れたかったけど、今回は省略した。midori271で実装することにした。
動画の再生制御
ロックサインを検出したら動画を再生、検出しなくなったら停止。
if (detectedPose === "ロックサイン") {
rockVideoElement.play();
} else {
rockVideoElement.pause();
}
最初は、autoplayだけで試した。ロックサインを離しても、動画が止まらなかった。
pause()を追加したら、ちゃんと止まるようになった。
パフォーマンス
PCでの動作を測定した。
| 処理 | 時間 |
|------|------|
| MediaPipe Hands推論 | 約15ms |
| ジェスチャー判定 | 約0.5ms |
| 描画処理 | 約3ms |
| 合計 | 約18.5ms |
約54fpsで動作する。60fpsに近い。
スマホだと、MediaPipe推論が40ms以上かかった。約25fps。まだ使える。
画像と動画の表示は、CSSで制御してるから軽い。
結果
手の動きで画像と動画を表示できるARインターフェースを実装できた。
- MediaPipe Handsで21個のランドマーク検出
- ピースサインで画像、ロックサインで動画
- Y座標比較でシンプルに判定
- 手の位置に追従して表示
ジェスチャー判定の方法を色々試した。距離計算より、Y座標比較の方が精度が高かった。
次回(midori271)では、ジェスチャーのパターンを増やして、安定化の仕組みも入れる予定。
手の動きを体験してみてください:
全体の処理フロー:
まとめ
今回は、手のジェスチャーで画像と動画を表示するARインターフェースの基礎版を実装しました。
ポイントは以下の3つ:
- MediaPipe Handsで21個のランドマーク検出(約15ms)
- Y座標比較でシンプルに指の伸び具合を判定
- ピースサインで画像、ロックサインで動画を表示
判定方法を距離計算からY座標比較に変えたことで、精度が大幅に向上した。
ARインターフェースの実験を考えている方の参考になれば嬉しいです。
さらに深く学ぶには
この記事で興味を持った方におすすめのリンク:
自分の関連記事:
最後まで読んでくださり、ありがとうございました。