GestureRecognizerで手の形とジェスチャーを調べる
手の位置が分かるだけでなく、「開いた手」「握った手」などの名前も欲しいときはGestureRecognizerを使います。AI生成の手の画像で点と分類を一緒に表示し、手があるのにNoneになるケースも理解しましょう。
この記事でできるようになること
手のランドマークと分類結果を同じ順番で読める。手の未検出と、検出後のNoneを区別できる。
HTMLとJavaScriptの変数・関数・イベントの基本が分かる方を対象に、MediaPipeの学習済みモデルを使うところから進めます。自分でモデルを学習させる準備は不要です。
1. 何をしたいときに使うか・モデルを選ぶ
サイトの手の線画には、関節の座標があれば描画できます。ジェスチャーの名前は必須ではありません。反対に、手を開いたら操作する、親指を上げたら合図を表示するといった機能には、座標に加えて分類が必要です。GestureRecognizerは手の座標も返すため、分類が画面のどの手に対応するかを同時に見せられます。
既定のジェスチャー候補は、None、Closed_Fist、Open_Palm、Pointing_Up、Thumb_Down、Thumb_Up、Victory、ILoveYouです。任意の手話をすべて理解するモデルではありません。初期画像は、既存の手の線画ツールの作例にも使ったAI生成の架空の手です。指が離れた入力で、点と分類を見比べます。AI生成人物の集合写真にも切り替えられます。飲み物を持つ手や、小さく写る手は、既定の形と一致しない場合があります。その結果をそのまま表示することで、モデルが何を返したか確認します。
「手の形」という言葉を使っても、モデルが解釈する分類カテゴリと、利用者が意図した動作は同じとは限りません。日常の手の動き、物を持つ状態、指の一部だけが見える写真では分類が安定しないことがあります。最初からアプリの操作に接続せず、点・線・候補名を表示するテスト画面で条件を調べることが出発点です。
本サンプルはGesture Recognizer float16のモデルバンドルを使います。バンドルは、手を探す処理と、座標やジェスチャーを求める処理をまとめたファイルです。手の点だけならHandLandmarker、既定ジェスチャーまで必要ならGestureRecognizer、自分の分類を追加したいならモデルのカスタマイズ、と必要な出力から選びます。公式のジェスチャー認識の概要で対象とモデルを確認できます。
2. まず完成したサンプルを動かす
次のデモを開き、サンプル画像を選んで「AIを読み込んで実行する」を押します。画像を選ぶだけではAIを実行しません。最初のボタン操作でSDK・WASM・モデルを取得し、次からは初期化済みのタスクを再利用します。

今回の実行で観察したこと
既存のAI生成の手画像から1つの手が検出され、Open_Palmがスコア0.683で返りました。条件はPCのChrome、SDK 0.10.34、Gesture Recognizer float16版1、numHands: 4です。指が離れた入力の1例であり、日常の手の動きや、AI人物の集合写真でも同じ分類が出るという評価ではありません。手の点とジェスチャーの候補名を一緒に表示して、モデルがどの手を扱ったか確認します。
デモに同梱する人物はAI生成画像、猫は運営者の愛猫の写真です。任意のJPEG・PNG・WebPも読み込めますが、入力画像を推論用サーバーへ送信する処理はありません。SDKとモデルの取得は外部への通信を行います。時刻欄は初期化を除く推論・結果加工・描画の計測で、速度を保証する数字ではありません。
3. ZIPを展開して自分のPCで動かす
VS Codeでフォルダーを開く方法や、app.jsの編集・保存が初めてなら、導入記事のエディター準備と「保存 → ブラウザ更新 → 実行」の手順を先に確認してください。下では、このサンプルの起動に必要な操作を示します。
この教材はHTMLとJavaScriptだけで動く独立サンプルです。サイト全体のAstroプロジェクトをインストールする必要はありません。SDKをCDNから取得するため、起動時のインターネット接続は必要です。画像は推論用サーバーへ送信せず、ブラウザ内で処理します。
- 上のZIPをダウンロードします。WindowsではZIPを右クリックして「すべて展開」を選びます。
- 展開したフォルダーを開き、
index.htmlとapp.js、person.jpg、cat.jpg、hand.jpg、README.txtが並んでいることを確認します。さらに同じ名前のフォルダーがある場合は、その中へ進みます。 - エクスプローラーのアドレス欄へ
powershellと入力してEnterを押します。このフォルダーを作業場所にした端末が開きます。 - 次のコマンドでNode.jsとnpmがあるか確認します。
node --version
npm.cmd --version バージョンの数字が出れば準備できます。「認識されません」と出る場合はNode.js公式のダウンロードページからLTS版を導入し、端末を開き直します。Node.jsはこの例ではAIを実行するためではなく、HTTPサーバーを起動するために使います。
npx.cmd --yes http-server . -p 8080 初回はHTTPサーバーのツールを取得することがあります。端末にアドレスが表示され、そのまま入力を待つ状態になればサーバーが動いています。ブラウザでhttp://localhost:8080を開きます。画像の選択画面が出たら成功です。端末は実行中のまま残します。
Mac・Linuxはnpm --version、npx --yes http-server . -p 8080に読み替えます。Windowsの.cmdは、PowerShellのスクリプト実行制限に当たりにくい呼び出し方です。HTMLをダブルクリックしてfile://で開くとモジュールの読み込みが制限されることがあるため、HTTPで開きます。
8080が使用中ならコマンドのポートを8081へ変え、開くURLもhttp://localhost:8081に変えます。終了するときは端末でCtrl+Cを押します。コードを修正したら保存し、ブラウザを再読み込みしてから実行します。古い結果が残る場合は、開発者ツールのNetworkでキャッシュを無効にして再読み込みします。
4. AIの出力を自分のJavaScriptへつなぐ
recognize(inputCanvas)の結果で、landmarksに手の点、gesturesに分類候補、handednessに左右の候補が入ります。これらは手ごとの配列です。最初の手はlandmarks[0]とgestures[0]を組み合わせます。手の検出順が、左から右へ固定されると決めつけないようにします。
ランドマークのxとyは、画像の幅・高さに対して正規化した値です。画像左上が基準で、Canvasに描くにはxへ横幅、yへ高さを掛けます。線をつなぐ点の番号はSDKのHAND_CONNECTIONSを使います。すべての点を順番に結ぶと、本来の関節構造にならないため、接続情報が必要です。
配列が空なら、手を検出できていません。手の点はあるがNoneなら、手は見つかったものの、既定のジェスチャーに該当しないなどの結果です。この2つを同じ「失敗」メッセージへまとめると、どこを改善すればよいか分からなくなります。サンプルでは手の候補数とジェスチャー名を別々に確認できます。
左右は画像のどちら側にあるかという意味ではありません。人の左手・右手の候補を表します。カメラプレビューをCSSで左右反転する場合、表示を反転しただけなのか、入力画像も反転したのかを区別します。入力と結果Canvasに同じ変換を使い、画面の反転を分類の左右と混ぜないことが大切です。
次は処理の中心だけを抜き出した例です。inputCanvasやタスクの変数は、すでに初期化済みとします。この数行だけをHTMLへ貼ると動くという意味ではありません。実際に動かすコードは次の完全版を使い、中心となるAPIをこの短い例で確認します。
const result = recognizer.recognize(inputCanvas);
result.landmarks.forEach((points, handIndex) => {
const gesture = result.gestures[handIndex]?.[0];
console.log(gesture?.categoryName, gesture?.score);
points.forEach((point) => {
const x = point.x * inputCanvas.width;
const y = point.y * inputCanvas.height;
// この座標に点を描く
});
}); 5. 完全なコードを確認する
index.htmlは操作画面とCanvasを用意し、app.jsは画像選択・モデル初期化・推論・描画を担当します。画像を読み込むawait image.decode()の後にCanvasへ描き、読み込み前の画像を推論へ渡さないようにしています。GPUの初期化が失敗した場合はCPUで再試行し、両方失敗した場合は理由を画面とConsoleへ表示します。
index.htmlの全文を開く
<!doctype html><html lang="ja"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="robots" content="noindex,follow"><title>GestureRecognizerで手の形とジェスチャーを調べる | MediaPipe実行サンプル</title><style>
:root { color-scheme: light; font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; color: #253235; background: #f3f7f6; line-height: 1.8; }
* { box-sizing: border-box; }
body { margin: 0; }
main { width: min(100% - 32px, 1080px); margin: 32px auto 56px; }
h1 { font-size: clamp(1.45rem, 4vw, 2.2rem); line-height: 1.5; margin: 8px 0 16px; }
h2 { font-size: 1.15rem; margin: 0 0 12px; }
p { margin: 8px 0; }
a { color: #006453; text-underline-offset: 3px; }
.eyebrow { color: #006453; font-size: .85rem; font-weight: 700; letter-spacing: .07em; }
.intro { max-width: 780px; }
.panel { margin-top: 24px; padding: 24px; border: 1px solid #d2e1dc; border-radius: 16px; background: white; }
fieldset { border: 0; padding: 0; margin: 0 0 18px; }
legend { padding: 0; margin-bottom: 8px; font-weight: 700; }
.choices { display: flex; flex-wrap: wrap; gap: 12px; }
.choice { display: flex; align-items: center; gap: 8px; border: 1px solid #bdcec7; border-radius: 8px; padding: 10px 16px; cursor: pointer; }
.choice:has(input:checked) { background: #e8f7ee; border-color: #00715b; }
input[type="radio"] { width: 18px; height: 18px; accent-color: #00715b; }
input[type="file"] { display: block; max-width: 100%; margin-top: 8px; font: inherit; font-size: .9rem; }
label[for="image-file"] { font-weight: 700; }
button { font: inherit; padding: 12px 22px; border-radius: 9px; border: 0; background: #006b55; color: white; font-weight: 700; cursor: pointer; min-height: 48px; }
button:disabled { opacity: .5; cursor: wait; }
:focus-visible { outline: 3px solid #e3aa28; outline-offset: 3px; }
.actions { margin-top: 20px; }
.note { font-size: .88rem; color: #566561; }
#status { min-height: 1.8em; margin-top: 16px; font-weight: 600; overflow-wrap: anywhere; }
#status[data-error="true"] { color: #a42b24; }
.images { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; }
figure { margin: 0; min-width: 0; }
figcaption { font-weight: 700; margin-bottom: 8px; }
.canvas-wrap { display: grid; place-items: center; min-height: 180px; overflow: hidden; border: 1px solid #d2e1dc; border-radius: 10px; background: #f0f3f1; }
canvas { display: block; width: 100%; height: auto; }
dl { display: flex; flex-wrap: wrap; gap: 12px 36px; margin: 20px 0 0; }
dl > div { min-width: 130px; }
dt { font-size: .85rem; color: #566561; }
dd { margin: 0; font-weight: 700; }
pre { margin: 0; padding: 18px; background: #f0f4f2; border: 1px solid #d2e1dc; border-radius: 8px; max-height: 420px; overflow: auto; font-size: .85rem; line-height: 1.7; }
details summary { cursor: pointer; font-weight: 700; padding: 4px 0; }
details pre { margin-top: 12px; }
footer { font-size: .85rem; color: #566561; margin-top: 24px; }
@media (max-width: 640px) { main { margin-top: 20px; } .panel { padding: 18px; } .images { grid-template-columns: 1fr; } button { width: 100%; } dl { gap: 12px 24px; } }
#summary{white-space:pre-line;overflow-wrap:anywhere}select{font:inherit;max-width:100%;padding:8px;margin:10px}input[type="range"]{display:block;width:100%;max-width:400px}#input-canvas{touch-action:manipulation}</style></head><body><main><header><p class="eyebrow">MEDIAPIPE · JAVASCRIPT · IMAGE</p><h1>GestureRecognizerで手の形とジェスチャーを調べる</h1><p>AI生成の手で、21点の座標と学習済みのジェスチャー名を確認します。手を検出できても、既定の形に当てはまらない場合があります。猫の足は人の手を対象とするこのモデルの対象外です。</p><p class="note">人物はプロジェクト内のAI生成画像、猫は運営者の愛猫です。画像はブラウザ内で処理し、推論用サーバーへ送信しません。初回は外部CDNとGoogleからSDK・モデルを取得します。</p></header><section class="panel"><h2>1. 画像を選んで実行する</h2><fieldset><legend>サンプル画像</legend><div class="choices"><label class="choice"><input type="radio" name="sample" value="hand" checked>AI生成の手</label><label class="choice"><input type="radio" name="sample" value="person">AI作成の人物</label><label class="choice"><input type="radio" name="sample" value="cat" >愛猫の写真</label></div></fieldset><label for="image-file">自分の画像でも試す(任意)</label><input id="image-file" type="file" accept="image/jpeg,image/png,image/webp"><p class="note">JPEG・PNG・WebP、20 MBまで。長辺960 pxへ縮小します。</p><div class="actions"><button id="run-button" disabled>AIを読み込んで実行する</button></div><p id="status" role="status" aria-live="polite" aria-atomic="true">画像を読み込んでいます。</p><noscript>JavaScriptを有効にしてください。</noscript></section><section class="panel"><h2>2. 実行結果を確認する</h2><div class="images"><figure><figcaption>入力画像</figcaption><div class="canvas-wrap"><canvas id="input-canvas" width="480" height="320" role="img" aria-label="入力画像"></canvas></div></figure><figure><figcaption>手の点・線・分類候補</figcaption><div class="canvas-wrap"><canvas id="result-canvas" width="480" height="320" role="img" aria-label="未実行"></canvas></div></figure></div><dl><div><dt>候補数</dt><dd id="count">未実行</dd></div><div><dt>入力 → 推論サイズ</dt><dd id="dimensions">準備中</dd></div><div><dt>推論・結果処理の時間</dt><dd id="elapsed">未実行</dd></div><div><dt>実行先</dt><dd id="delegate">未初期化</dd></div></dl><p class="note">時間はモデル初期化を除き、推論と結果の加工・描画を含みます。端末によって変わり、性能保証ではありません。</p><p id="summary">まだ実行していません。</p></section><section class="panel"><h2>3. 出力JSONを読む</h2><details open><summary>結果のデータ(小数は表示用に丸めています)</summary><pre id="json-output">まだ実行していません。</pre></details></section><footer><p>SDK:@mediapipe/tasks-vision 0.10.34 / モデル:Gesture Recognizer float16</p><p><a href="./README.txt">起動方法・画像出典</a></p></footer></main><script type="module" src="./app.js"></script></body></html>app.jsの全文を開く
// SDK・WASMは同じ固定版にそろえます。モデル版は別に管理します。
const CONFIG = {
"slug": "mediapipe-gesture-recognition",
"title": "GestureRecognizerで手の形とジェスチャーを調べる",
"task": "GestureRecognizer",
"version": "0.10.34",
"model": "https://storage.googleapis.com/mediapipe-models/gesture_recognizer/gesture_recognizer/float16/1/gesture_recognizer.task",
"modelName": "Gesture Recognizer float16",
"type": "gesture",
"method": "recognize",
"options": {
"runningMode": "IMAGE",
"numHands": 4
},
"default": "hand",
"result": "手の点・線・分類候補",
"intro": "手の21点の座標と、学習済みのジェスチャー名を確認します。手を検出できても、既定の形に当てはまらない場合があります。猫の足は人の手を対象とするこのモデルの対象外です。"
};
const $ = selector => document.querySelector(selector);
const input = $('#input-canvas');
const output = $('#result-canvas');
const ctx = output.getContext('2d');
const inputCtx = input.getContext('2d');
const runButton = $('#run-button');
let task = null,
sdk = null,
delegate = '',
imageReady = false,
busy = false,
selection = 0;
const status = (message, error = false) => {
$('#status').textContent = message;
$('#status').dataset.error = String(error);
};
function controls() {
runButton.disabled = busy || !imageReady;
for (const element of document.querySelectorAll('input, select')) element.disabled = busy;
runButton.textContent = busy ? '処理しています…' : 'AIを読み込んで実行する';
}
async function loadImage(source, name) {
const request = ++selection;
imageReady = false;
controls();
$('#count').textContent = '未実行';
$('#elapsed').textContent = '未実行';
$('#summary').textContent = 'まだ実行していません。';
$('#json-output').textContent = 'まだ実行していません。';
const url = source instanceof File ? URL.createObjectURL(source) : null;
try {
const image = new Image();
image.src = url ?? source;
await image.decode();
if (request !== selection) return;
const scale = Math.min(1, 960 / Math.max(image.naturalWidth, image.naturalHeight));
input.width = output.width = Math.max(1, Math.round(image.naturalWidth * scale));
input.height = output.height = Math.max(1, Math.round(image.naturalHeight * scale));
inputCtx.drawImage(image, 0, 0, input.width, input.height);
ctx.drawImage(input, 0, 0);
$('#dimensions').textContent = image.naturalWidth + ' × ' + image.naturalHeight + ' → ' + input.width + ' × ' + input.height + ' px';
input.setAttribute('aria-label', name + 'の入力画像');
imageReady = true;
status(name + 'を選びました。実行ボタンを押してください。');
} catch (error) {
console.error(error);
status('画像を読み込めませんでした。JPEG・PNG・WebPを選び直してください。', true);
} finally {
if (url) URL.revokeObjectURL(url);
if (request === selection) controls();
}
}
async function initialize() {
if (task) return task;
status('初回の準備中:SDK・WASM・モデルをインターネットから取得しています。');
const base = 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@' + CONFIG.version;
sdk = await import(base + '/vision_bundle.mjs');
const vision = await sdk.FilesetResolver.forVisionTasks(base + '/wasm');
const create = hardware => sdk[CONFIG.task].createFromOptions(vision, {
...CONFIG.options,
baseOptions: {
modelAssetPath: CONFIG.model,
delegate: hardware
}
});
try {
task = await create('GPU');
delegate = 'GPU';
} catch (error) {
console.warn('GPUの初期化失敗。CPUで再試行します。', error);
status('GPUを初期化できませんでした。CPUで再試行しています。');
task = await create('CPU');
delegate = 'CPU';
}
$('#delegate').textContent = delegate;
return task;
}
function drawLandmarks(points, connections, color, filterVisibility = false) {
ctx.strokeStyle = color;
ctx.fillStyle = color;
ctx.lineWidth = Math.max(2, output.width / 300);
for (const connection of connections ?? []) {
const a = points[connection.start],
b = points[connection.end];
if (!a || !b || filterVisibility && ((a.visibility ?? 1) < .5 || (b.visibility ?? 1) < .5)) continue;
ctx.beginPath();
ctx.moveTo(a.x * output.width, a.y * output.height);
ctx.lineTo(b.x * output.width, b.y * output.height);
ctx.stroke();
}
for (const p of points) {
if (filterVisibility && (p.visibility ?? 1) < .5) continue;
ctx.beginPath();
ctx.arc(p.x * output.width, p.y * output.height, Math.max(2, output.width / 300), 0, Math.PI * 2);
ctx.fill();
}
}
function caption(text) {
const size = Math.max(14, Math.round(output.width / 36));
ctx.font = 'bold ' + size + 'px system-ui';
ctx.fillStyle = '#063b35';
const lines = text.split('\n');
ctx.fillRect(0, output.height - lines.length * (size + 12), output.width, lines.length * (size + 12));
ctx.fillStyle = '#fff';
lines.forEach((line, i) => ctx.fillText(line, 10, output.height - (lines.length - i - 1) * (size + 12) - 8));
}
async function processImage() {
const engine = await initialize();
ctx.drawImage(input, 0, 0);
let result, count, summary;
const started = performance.now();
result = engine[CONFIG.method](input);
count = result.landmarks.length;
result.landmarks.forEach(points => drawLandmarks(points, sdk.GestureRecognizer.HAND_CONNECTIONS, '#00ffd1'));
summary = count ? result.gestures.map((categories, i) => '手' + (i + 1) + ':' + (categories[0]?.categoryName ?? '候補なし') + ' / ' + (categories[0]?.score?.toFixed(3) ?? '—')).join('\n') : '人の手は検出されませんでした。処理は完了しています。';
caption(summary);
$('#elapsed').textContent = (performance.now() - started).toFixed(1) + ' ms';
$('#count').textContent = String(count);
$('#summary').textContent = summary;
$('#json-output').textContent = JSON.stringify(result, (_key, value) => ArrayBuffer.isView(value) ? Array.from(value) : typeof value === 'number' ? Number(value.toFixed(5)) : value, 2);
output.setAttribute('aria-label', CONFIG.result + '。' + summary);
status('実行が完了しました。画像・数値・JSONを確認してください。');
}
runButton.addEventListener('click', async () => {
if (busy || !imageReady) return;
busy = true;
controls();
try {
await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
await processImage();
} catch (error) {
console.error(error);
status('実行に失敗しました:' + error.message + '。通信とConsoleを確認し、SDK・WASM・モデルの取得状況を調べてください。', true);
} finally {
busy = false;
controls();
}
});
for (const radio of document.querySelectorAll('input[name="sample"]')) radio.addEventListener('change', () => {
$('#image-file').value = '';
void loadImage('./' + radio.value + '.jpg', radio.value === 'cat' ? '愛猫の写真' : radio.value === 'hand' ? 'AI生成の手' : 'AI作成の人物');
});
$('#image-file').addEventListener('change', () => {
const file = $('#image-file').files[0];
if (!file) return;
if (file.size > 20 * 1024 * 1024 || !['image/jpeg', 'image/png', 'image/webp'].includes(file.type)) {
status('JPEG・PNG・WebP、20 MB以下を選んでください。', true);
return;
}
for (const radio of document.querySelectorAll('input[name="sample"]')) radio.checked = false;
void loadImage(file, file.name);
});
addEventListener('pagehide', () => {
task?.close();
task = null;
});
void loadImage('./' + CONFIG.default + '.jpg', CONFIG.default === 'cat' ? '愛猫の写真' : CONFIG.default === 'hand' ? 'AI生成の手' : 'AI作成の人物');
最初は全文を暗記せず、CONFIGでモデルと設定を確認し、initialize()でタスクを作り、processImage()で結果を読む順番で追います。UIの状態管理は、AIの推論と別の役割です。実行中に画像が変わらないようにする処理も、公開するアプリには必要になります。
6. 1つずつ条件を変えて理解する
- 初期状態のAI生成の手で実行し、手の点と分類を確認します。次にAI人物へ切り替え、手が検出された数と分類を比べます。Noneがあれば、手の点があるかも確認します。
- 猫へ切り替え、処理が完了して候補がない場合と、読み込みエラーを区別します。猫の足は人の手モデルの対象ではありません。
- 任意の画像として、指先まで写る人の手のJPEG・PNG・WebPを読み込みます。写真は自分で用意する場合もブラウザ内で処理されます。
numHands: 4を1にして再読み込みし、候補数の上限と、モデルが手を見つけられるかを分けて観察します。
操作への応用では、1フレームだけの分類で決定せず、同じ候補が続いた時間を見たり、操作後に待ち時間を設けたりします。この記事は静止画なのでその制御を省いていますが、誤った分類が即座に重要な操作へつながらない作りにすると試しやすくなります。
7. このサイトのツールへどうつながるか
サイトの手のポーズ線画と手形スタンプはHandLandmarkerの座標を利用します。本サンプルのGestureRecognizerとはタスクが違いますが、点をCanvasへ描く考え方は共通です。まず線画で座標の扱いを理解し、その後に名前付きの分類を追加すると処理を追いやすくなります。
線画を選ぶUIで「開いた手の候補」を案内するなどの応用は考えられます。ただし、既存サイトにジェスチャー操作が実装されていると説明するものではありません。分類結果は学習用デモとして表示し、利用者の意図を確定する根拠にはしません。
出力を使う前に確認する限界
手が小さい、指が重なる、物を持つ、強い影があると、検出と分類の両方に影響します。AI生成人物には指の形が不自然な部分がある場合もあります。モデルの限界と画像そのものの条件を分けて読みます。
8. 動かない・結果が違うときの調べ方
| 状態 | 先に確認すること |
|---|---|
| 手はあるのにNoneになる | landmarksがあるか確認します。手を見つけたことと、既定ジェスチャーに分類できたことは別です。 |
| 手の点が0件 | 指先まで見えるか、手が小さすぎないかを確認します。人の手向けのモデルなので猫の前足は対象外です。 |
| 左右が思った表示と違う | handednessは人の手の左右の候補です。プレビューだけの反転と、推論に渡す画像の反転を分けて確認します。 |
| 複数の手が全部返らない | numHandsは上限です。上限に達していない場合も、隠れ方や大きさによって未検出になります。 |
| 画面・入力画像を開けない | HTTPサーバーのフォルダーとURLを確認し、Networkで同梱画像が404になっていないか見ます。 |
| SDK・モデルを読み込めない | SDK・WASM・モデルの固定URLをNetworkで確認します。GPU初期化失敗時はCPUで再試行し、両方失敗した場合はConsoleを読みます。 |
ChromeやEdgeではF12で開発者ツールを開けます。Consoleに出た最初のエラーと、Networkの失敗したURLを確認します。モデルの読み込み失敗なのにスコアの閾値を変更しても直りません。画像読み込み、モデル初期化、推論、描画の順に分けて調べます。
サンプルでは実行中のボタンと画像変更を無効にし、同時に処理が走らないようにしています。画像ファイルのObject URLは読み込み後に解放し、ページから離れるときはタスクのclose()を呼びます。スマホの画面幅では結果を縦に並べます。PCの表示幅を狭める確認は実機検証の代わりにはならないので、端末・OS・ブラウザを変える際は別途試します。
9. サイトの採用版と最新のMediaPipe事情
現在の猫の手ツールのTasks Visionは0.10.34です。SDKは推論のAPIと実行環境、モデルは学習済みのデータなので、バージョンを別々に管理します。この記事のモデルは固定版1で、コードに/latest/は使っていません。公式の例にlatestがあっても、教材を再現する場合は確認したURLを固定します。
このサンプルは、サイトの採用版に合わせて0.10.34を固定しています。2026年10月12日に確認した最新安定版は1.1.0です。最新版を試す場合も、SDKとWASMを同じ版にし、サンプルを再実行して結果と互換性を確認します。
SDKのファイルとWASMだけを別の版にすると、APIが読み込めても実行時に問題が出る場合があります。また、旧MediaPipe SolutionsとTasksでは使うパッケージ・クラス・設定が異なります。検索で見つけたコードをコピーする前に、記事がどの版のどのタスクを扱うか確認します。
参考資料
確認日:2026年10月12日。本文は公式API・プロジェクトの実装と、本教材のデモに基づいて説明しています。モデルの推定結果を人の判断の代わりに確定した情報として扱わず、画像とJSONを見比べて検証します。