手の特徴点を検出して線で結んで可視化した
まずは基礎。手の21点を検出して、線で骨格を描きます。モデル設定と線の見やすさを詰めて、PCでおよそ59fps。次の応用の土台に。
MediaPipe Handsで手のランドマーク(21点)を検出して、線で結んで表示するシステムを作った。
リアルタイムで手の動きを追跡して、映像に重ねて表示。手の形がハッキリ分かる。
やりたかったこと
手の動き検出システムの基礎を作りたかった。
まずは、手のランドマークを検出して、それを可視化する。線で結んで、手の形を表示する。シンプルだけど、これが基本。
複雑な機能は後で追加すればいい。まずは基礎から。
MediaPipe Handsの仕組み
MediaPipe Handsは、21個のランドマークを検出する。
const hands = new Hands({
locateFile: (file) => {
return `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`;
}
});
hands.setOptions({
maxNumHands: 2, // 最大2手
modelComplexity: 1, // モデルの複雑さ(0, 1, 2)
minDetectionConfidence: 0.7, // 検出の信頼度閾値
minTrackingConfidence: 0.7 // 追跡の信頼度閾値
});
hands.onResults((results) => {
if (results.multiHandLandmarks) {
const landmarks = results.multiHandLandmarks[0];
drawHandLandmarks(landmarks);
}
});
modelComplexityは、モデルの複雑さ。0が最速で精度低い、2が最遅で精度高い。
最初は2で試した。精度は高かったけど、処理が重かった。約30msかかった。
0にしたら、速かったけど、精度が低かった。手の形が正しく検出されないことがあった。
1がちょうどいい。約15msで、精度も十分。
ランドマークを線で結ぶ
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.7)';
canvasCtx.lineWidth = 3;
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) => {
canvasCtx.fillStyle = 'green';
canvasCtx.beginPath();
canvasCtx.arc(
landmark.x * canvasWidth,
landmark.y * canvasHeight,
5,
0,
2 * Math.PI
);
canvasCtx.fill();
});
}
線の太さは3px。
最初は1pxで試した。細すぎて見えにくかった。
5pxにしたら、太すぎた。手の形が分かりにくくなった。
3pxがちょうどいい。
信頼度の閾値
検出と追跡の信頼度閾値を調整した。
minDetectionConfidence: 0.7, // 検出の信頼度
minTrackingConfidence: 0.7 // 追跡の信頼度
最初は0.5にしてた。検出率は高かったけど、誤検出も多かった。手じゃない物体が検出されることがあった。
0.9にしたら、誤検出は減ったけど、手が検出されないことが増えた。
0.7がバランス良かった。誤検出は少ないし、検出率も十分。
カメラ映像の取得
WEBカメラから映像を取得する。
const videoElement = document.createElement('video');
videoElement.width = 1280;
videoElement.height = 720;
const stream = await navigator.mediaDevices.getUserMedia({
video: {
width: { ideal: 1280 },
height: { ideal: 720 }
}
});
videoElement.srcObject = stream;
await videoElement.play();
解像度は1280×720。
最初は640×480で試した。解像度が低すぎて、手の細かい部分が検出できなかった。
1920×1080にしたら、処理が重くなった。fpsが30以下に落ちた。
1280×720が、品質とパフォーマンスのバランスが良かった。
ハマったところ
ランドマークの座標系
MediaPipeのランドマークは、正規化座標(0〜1の範囲)。
画面に描画するには、ピクセル座標に変換する必要がある。
// 正規化座標(0〜1)
landmark.x = 0.5;
landmark.y = 0.3;
// ピクセル座標に変換
const pixelX = landmark.x * canvasWidth;
const pixelY = landmark.y * canvasHeight;
変換を失念。すべてのランドマークが画面の左上隅に描画された。
変換を追加。正しい位置に描画されるようになる。
Canvas APIの座標系
Canvas APIのY座標は、上が0、下が正の値。
MediaPipeのY座標も同じ。だから、そのまま使える。
最初は、Y座標を反転させる必要があると思ってた。でも、不要だった。
パフォーマンス
PCでの動作を測定した。
| 処理 | 時間 |
|------|------|
| MediaPipe Hands推論 | 約15ms |
| ランドマーク描画 | 約2ms |
| 合計 | 約17ms |
約59fpsで動作する。ほぼ60fps。
スマホだと、MediaPipe推論が30ms以上かかった。約33fps。まだ実用的。
結果
手のランドマークを検出して、線で結んで可視化できた。
- MediaPipe Handsで21個のランドマーク検出
- 線で結んで手の骨格を描画
- リアルタイムで手の動きを追跡(約59fps)
- 信頼度0.7で誤検出を抑制
モデルの複雑さと信頼度の調整が重要だった。1と0.7が最適。
この基礎システムをベースに、ジェスチャー認識や深度推定を追加していく。
21個のランドマークのインデックス:
手の骨格を体験:
パフォーマンスの詳細:
全体の処理フロー:
使ってみて
実際に体験してみてください:
デモを起動する(全画面表示)
ポイントは以下の3つ:
- modelComplexity=1、信頼度0.7で約59fpsの安定動作
- 線の太さ3pxが見やすい(1pxは細すぎ、5pxは太すぎ)
- 解像度1280×720がバランス良(品質とfpsの両立)
まとめ
今回は、手のランドマーク検出と可視化の基礎システムを実装しました。
ポイントは以下の3つ:
- MediaPipe Hands(モデルの複雑さ1、約15ms)
- 21個のランドマークを線で結んで描画(線の太さ3px)
- 信頼度0.7で誤検出を抑制、約59fpsで動作
モデルパラメータの調整で、精度とパフォーマンスのバランスを取った。
手の動き検出の基礎を学びたい方の参考になれば嬉しいです。
さらに深く学ぶには
この記事で興味を持った方におすすめのリンク:
自分の関連記事:
最後まで読んでくださり、ありがとうございました。