手のジェスチャーで3D空間のメディアを操作する
カメラの前で手を動かすだけで、3D空間の画像や動画を操作できるシステムを作った。
MediaPipeで手のランドマークを検出。グー、チョキ、パーのジェスチャーで、メディアを再生したり切り替えたりできる。拡張現実的なインターフェース。
作ったもの
Webカメラで手を映すと、AIがリアルタイムで検出する。
グーで動画を再生。パーで画像を表示。チョキで一時停止や切り替え。3D空間に配置されたメディアを、手のポーズだけでコントロールできる仕組み。
MediaPipeのHands APIで、手の21個のランドマークを検出。それをThree.jsの3D空間と統合した。
なぜ作ったか
マウスやキーボードを使わない操作を試したかった。
写真展示で使うことを考えていた。来場者がマウスに触れずに操作できたら、衛生的。手を振るだけで写真が切り替わる。これは面白いと思った。
拡張現実のインターフェースとして、ジェスチャー認識は理想的。試してみることにした。
MediaPipeによる手の検出
MediaPipeを使って、手のランドマークを検出する。
const hands = new Hands({
locateFile: (file) => {
return `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`;
}
});
hands.setOptions({
maxNumHands: 2,
modelComplexity: 1,
minDetectionConfidence: 0.5,
minTrackingConfidence: 0.5
});
検出した手のランドマークは、3D座標(x, y, z)で取得できる。
最初はminDetectionConfidence: 0.7に設定していた。でも、検出が途切れることが多かった。手を少し動かすだけで、検出が止まる。
0.5に下げたら安定した。検出の継続性が上がって、スムーズに操作できるようになった。
ジェスチャーの判定ロジック
手の形から、ジェスチャーを判定する。
function recognizeGesture(landmarks) {
const wrist = landmarks[0];
const thumbTip = landmarks[4];
const indexTip = landmarks[8];
const middleTip = landmarks[12];
const ringTip = landmarks[16];
const pinkyTip = landmarks[20];
// 各指が伸びているか判定
const isIndexStraight = indexTip.y < indexMcp.y - 0.03;
const isMiddleStraight = middleTip.y < middleMcp.y - 0.03;
// ... 他の指も同様
// グー、チョキ、パーの判定
if (isPaperGesture) return "パー";
if (isRockGesture) return "グー";
if (isScissorsGesture) return "チョキ";
return null;
}
指先と付け根のy座標の差で、指が伸びているか判定。閾値は0.03。
この単純な方法だけ。でも、意外と高精度。明るい環境なら、90%以上の認識率になった。
3D空間との統合
検出した手の位置を、3D空間に反映させる。
hands.onResults((results) => {
if (results.multiHandLandmarks) {
for (const landmarks of results.multiHandLandmarks) {
const gesture = recognizeGesture(landmarks);
if (gesture) {
handleGesture(gesture, landmarks);
}
}
}
});
グーを認識したら、動画をThree.jsのVideoTextureとして3D空間に表示。パーなら画像を表示。チョキなら一時停止や切り替え。
手の動きと3Dオブジェクトの動きが連動する。これが拡張現実的なインターフェース。
パフォーマンスの最適化
最初、処理が重かった。
MediaPipeの推論とThree.jsのレンダリングが同時に走る。CPUが100%になる。フレームレートが30fps以下に落ちた。
targetFPSを調整した。60fps→30fpsに下げた。MediaPipeの処理間隔も調整。これで、CPU使用率が70%くらいに下がった。
でも、まだ負荷が高かった。PC推奨にせざるを得なかった。モバイルでは動かない。
操作の実装
グーで動画再生。動画はVideoTextureとして3D空間の平面に表示される。
function handleRockGesture(landmarks) {
if (!window.gestureVideo) {
const video = document.createElement('video');
video.src = 'images/sample.mp4';
video.loop = true;
const videoTexture = new THREE.VideoTexture(video);
const geometry = new THREE.PlaneGeometry(160, 90);
const material = new THREE.MeshBasicMaterial({ map: videoTexture });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
video.play();
}
}
パーで画像表示も同様。テクスチャを差し替えるだけ。
手のポーズだけで、3D空間のコンテンツを切り替えられる。キーボードもマウスも不要。
試してみた
実際に手を動かして操作してみた。
グーで動画が再生される。パーで画像が表示される。チョキで一時停止。思った通りに動く。
でも、照明条件で精度が変わる。暗い部屋だと認識率が落ちる。明るい部屋なら問題ない。
手の位置も重要。カメラに対して正面から見せないと、認識されにくい。斜めだとランドマークがズレる。
システム全体の流れ
使ってみて
手のジェスチャーで3D空間のメディアを操作するシステムとして、基本機能は動作した。
ポイントは以下の3つ:
- MediaPipe Handsで手のランドマークを検出(minDetectionConfidence: 0.5)
- グー、チョキ、パーのジェスチャーで動画・画像を操作
- Three.jsのVideoTextureで3D空間にメディアを表示
処理が重いため、PC推奨。でも、手を振るだけで操作できる新しいインターフェースとして、可能性を感じた。
まとめ
今回は、手のジェスチャー認識で3D空間のメディアを操作するシステムを作りました。
ポイントは以下の3つ:
- MediaPipeとThree.jsを統合
- ジェスチャーでVideoTextureを制御
- targetFPS: 30で処理負荷を軽減
拡張現実的なインターフェースとして面白い。写真展示での利用を想定していたが、照明条件や処理の重さが課題として残った。
さらに深く学ぶには
この記事で興味を持った方におすすめのリンク:
自分の関連記事:
最後まで読んでくださり、ありがとうございました。