Colab を用いたAI実行体験:MediaPipe による15種類のAIタスク実行(カメラ入力対応)
【概要】
- MediaPipe Tasks API(Googleが提供する事前学習済みAIタスクを数行のコードで呼び出せるライブラリ)を用いて,物体検出,画像セグメンテーション(画像を領域ごとに分割する処理),姿勢・手・顔・ジェスチャー認識,音声分類,テキスト処理等の15種類のAIタスクを実行する
- Pythonプログラムは,処理対象ファイル(画像・音声)の自動ダウンロード,モデル自動ダウンロード,AIによる推論,結果表示の手順で統一し,理解と改変を容易にする。映像を扱うタスクでは,カメラ入力によるリアルタイム処理を行う
- Google Colab 上での実行に対応する。繰り返し検証等への発展に適する
- 期待した精度・性能が得られない場合,設定の変更や学習済みモデルの選択を行う.データの前処理,他の技術の利用,追加学習等の検討も考えられる
【説明資料】
[PDF], [パワーポイント] (同じ内容,クリックしてダウンロード), [動画]
【目次】
- Python の実行環境を整える
- MediaPipe の概要、インストール、動作確認
- プログラム実行手順
- Pythonプログラムの共通構造
- 下記の15プログラムの実行時の留意事項
- タスク1:物体検出(Object Detector)
- タスク2:画像分類(Image Classifier)
- タスク3:画像セグメンテーション(Image Segmenter)
- タスク4:姿勢推定(Pose Landmarker)
- タスク5:手のランドマーク検出(Hand Landmarker)
- タスク6:ジェスチャー認識(Gesture Recognizer)
- タスク7:顔ランドマーク検出(Face Landmarker)
- タスク8:顔検出(Face Detector)
- タスク9:画像埋め込み(Image Embedder)
- タスク10:音声分類(Audio Classifier)
- タスク11:テキスト分類(Text Classifier,画像不要)
- タスク12:言語検出(Language Detector,画像不要)
- タスク13:ホリスティック検出(Holistic Landmarker)
- タスク14:手の3D可視化(Hand Landmarker world landmarks)
- タスク15:姿勢の3D可視化(Pose Landmarker world landmarks)
- MediaPipe の用途例
【サイト内の関連ページ】
プログラム実行手順
本章では,各タスクのプログラムを Colab で実行する手順を示す.Colab は,ブラウザ上で Python を実行できる Google のサービスである(詳細はGoogle Colaboratoryとはを参照).
Colab でのプログラム実行
- 新しいセルを追加し,本資料の該当タスクのコードを貼り付けて実行する
- 静止画を処理するタスク(タスク9から12,タスク14,タスク15)では,
/content直下にダウンロードされたファイルの一覧が番号付きで表示されるので,処理対象ファイルの番号を入力して選択する。テキストを処理するタスク(タスク11,タスク12)では,input()による標準入力で文字列を入力する - カメラ入力を用いるタスク(タスク1から8,タスク13)では,初回実行時にブラウザがカメラの使用許可を求めるので,これを許可する。カメラ映像に推論結果を重ねた画像がセルの直下に連続表示される。処理を終了するには,セルの停止ボタンを押す
- 結果はセルの直下に表示される(画像はインライン表示,テキスト出力は
print関数の出力としてセル出力欄に表示される)
Pythonプログラムの共通構造
すべてのPythonプログラムの構造は,以下のとおり共通化している.
- 処理対象ファイル(画像・音声)の自動ダウンロード(存在チェックを含む).保存先は
/content直下である。これらのファイルを使用できる - 学習済みモデル(AIモデルファイル)の自動ダウンロード(存在チェックを含む).保存先は
/content直下である - 入力:映像を扱うタスクでは,ブラウザのカメラから取得した映像フレームを入力とする。静止画を扱うタスクでは,
/content直下のファイルを番号入力で選択する。テキスト系タスクではinput()による標準入力を用いる - 結果表示:映像を扱うタスクでは,推論結果を重ねた画像をブラウザに連続表示する。静止画を扱うタスクおよびテキストを扱うタスクでは
printによる出力を用い,3D可視化タスク(タスク14,タスク15)では matplotlib による表示を用いる
処理対象ファイルおよびモデルは,初回実行時にコード内で自動ダウンロードする.静止画を扱うタスクでは,番号入力により,ダウンロード済みファイル以外の任意の /content 直下のファイルも処理対象とできる.なお,MediaPipe 等のライブラリ導入についてはパッケージのインストールを参照のこと.
カメラ入力を用いるタスクでは,各スクリプトの冒頭でブラウザのカメラを起動するJavaScriptを実行し,フレームを1枚ずつ取得して推論・描画し,結果をブラウザに表示する処理を繰り返す。
下記の15プログラムの実行時の留意事項
- 初回実行時はインターネット接続が必要である(モデルおよびサンプル画像のダウンロードのため)
- 初回実行時は処理開始までに数十秒から数分程度かかる場合がある(モデルおよびサンプル画像のダウンロードのため)
- 学習済みモデルおよび処理対象ファイルの保存先は
/content直下である(2回目以降の実行ではダウンロードを省略する) - カメラ入力を用いるタスクでは,初回実行時にブラウザがカメラの使用許可を求めるので,これを許可する
- カメラ入力を用いるタスクは
while Trueによる連続処理であり,処理を終了するにはセルの停止ボタンを押す - タスク14およびタスク15ではmatplotlibを使用する(Colab には標準でインストール済みである)
- Colab のセッションが切れると
/content配下のファイルは消去されるため,再実行時には再度ダウンロードが行われる(セッション切断のリスクを参照。保存が必要な場合はGoogle Driveへの定期保存を参照)
【MediaPipe版】タスク1:物体検出(Object Detector)
概要::画像内の物体をバウンディングボックス(物体を囲む矩形領域)で検出し,COCOデータセット(Microsoftが公開した画像認識用の代表的データセット)の80クラス(人,車,犬,椅子等)から分類する.EfficientDet-Lite2(448×448入力,BiFPN特徴ネットワーク採用の物体検出モデル)をベースモデル(推論の基盤となる学習済みモデル)とする.防犯カメラの物体カウント,写真整理の自動タグ付け,在庫管理等に利用される.本タスクでは,カメラ映像の各フレームに対して推論を行い,結果を重ねて連続表示する.
- 制約事項:COCO 80クラスのみが検出対象であり,クラス外(食品,医療器具,工業部品等)は検出されない.小物体や遮蔽物体の検出精度はモデルの表現能力に制約される.
- 設定での精度向上:score_threshold(検出結果として採用する最低スコアの閾値)を下げると見逃しが減り,上げると誤検出が減る.category_allowlist(検出対象とするクラスの限定リスト)で対象クラスを絞り込むと誤検出を抑制でき,max_results(出力する検出結果の最大件数)で検出数の上限を制御できる.
# task_object_detector
!pip install -q -U mediapipe
import urllib.request
from pathlib import Path
import numpy as np, cv2
import mediapipe as mp
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
from IPython.display import display, Javascript, Image, update_display
from google.colab.output import eval_js
from base64 import b64decode
# User-Agent設定(一部サーバのアクセス制限回避のため)
opener = urllib.request.build_opener()
opener.addheaders = [("User-Agent", "Mozilla/5.0")]
urllib.request.install_opener(opener)
# モデルダウンロード(/content 直下に保存)
MODEL_URL = "https://storage.googleapis.com/mediapipe-models/object_detector/efficientdet_lite2/float16/latest/efficientdet_lite2.tflite"
model = Path("/content") / Path(MODEL_URL).name
if not model.exists():
urllib.request.urlretrieve(MODEL_URL, model)
# 推論器の作成
options = vision.ObjectDetectorOptions(
base_options=python.BaseOptions(model_asset_path=str(model)),
score_threshold=0.5)
detector = vision.ObjectDetector.create_from_options(options)
# カメラ起動(getUserMedia)
display(Javascript('''
async function startCamera() {
try {
window._video = document.createElement('video');
window._video.srcObject = await navigator.mediaDevices.getUserMedia({video: true});
await window._video.play();
} catch (e) {
window._video = null;
}
}
function takeFrame() {
if (!window._video || !window._video.videoWidth || !window._video.videoHeight) return null;
const c = document.createElement('canvas');
c.width = window._video.videoWidth;
c.height = window._video.videoHeight;
c.getContext('2d').drawImage(window._video, 0, 0);
return c.toDataURL('image/jpeg', 0.8);
}
'''))
eval_js('startCamera()')
# カメラループ
display(Image(data=b''), display_id='cam')
while True:
data = eval_js('takeFrame()')
if data is None:
continue
img = cv2.imdecode(np.frombuffer(b64decode(data.split(',')[1]), np.uint8), cv2.IMREAD_COLOR)
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
result = detector.detect(mp_image)
for det in result.detections:
bb = det.bounding_box
cat = det.categories[0]
cv2.rectangle(img, (bb.origin_x, bb.origin_y),
(bb.origin_x + bb.width, bb.origin_y + bb.height), (0, 255, 0), 2)
cv2.putText(img, f"{cat.category_name} {cat.score:.2f}",
(bb.origin_x, bb.origin_y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
_, buf = cv2.imencode('.jpg', img)
update_display(Image(data=buf.tobytes()), display_id='cam')
【MediaPipe版】タスク2:画像分類(Image Classifier)
概要:画像全体に対して単一の分類ラベルを推定する.EfficientNet-Lite2(EfficientNetアーキテクチャによる画像分類モデル)をベースモデルとし,ImageNet(約120万枚・1000クラスの画像分類用大規模データセット)の1000クラスに対応する.画像検索の自動タグ付け,コンテンツモデレーション(不適切コンテンツの選別),アルバム整理等に利用される.本タスクでは,カメラ映像の各フレームに対して推論を行い,上位の分類結果を重ねて連続表示する.
- 制約事項:ImageNet 1000クラスに限定され,画像全体に対する単一ラベル推定のため複数物体の同時分類はできない.被写体が小さい画像や複数被写体の画像では精度が頭打ちとなる.
- 設定での精度向上:max_resultsを増やすと上位候補の観察により判断材料が増える.score_thresholdで低信頼度候補を除外し,category_allowlist/category_denylist(除外対象クラスのリスト)で対象クラスを限定すれば誤分類が減少する.
# task_image_classifier
!pip install -q -U mediapipe
import urllib.request
from pathlib import Path
import numpy as np, cv2
import mediapipe as mp
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
from IPython.display import display, Javascript, Image, update_display
from google.colab.output import eval_js
from base64 import b64decode
# User-Agent設定
opener = urllib.request.build_opener()
opener.addheaders = [("User-Agent", "Mozilla/5.0")]
urllib.request.install_opener(opener)
# モデルダウンロード(/content 直下に保存)
MODEL_URL = "https://storage.googleapis.com/mediapipe-models/image_classifier/efficientnet_lite2/float32/latest/efficientnet_lite2.tflite"
model = Path("/content") / Path(MODEL_URL).name
if not model.exists():
urllib.request.urlretrieve(MODEL_URL, model)
# 推論器の作成
options = vision.ImageClassifierOptions(
base_options=python.BaseOptions(model_asset_path=str(model)), max_results=5)
classifier = vision.ImageClassifier.create_from_options(options)
# カメラ起動(getUserMedia)
display(Javascript('''
async function startCamera() {
try {
window._video = document.createElement('video');
window._video.srcObject = await navigator.mediaDevices.getUserMedia({video: true});
await window._video.play();
} catch (e) {
window._video = null;
}
}
function takeFrame() {
if (!window._video || !window._video.videoWidth || !window._video.videoHeight) return null;
const c = document.createElement('canvas');
c.width = window._video.videoWidth;
c.height = window._video.videoHeight;
c.getContext('2d').drawImage(window._video, 0, 0);
return c.toDataURL('image/jpeg', 0.8);
}
'''))
eval_js('startCamera()')
# カメラループ
display(Image(data=b''), display_id='cam')
while True:
data = eval_js('takeFrame()')
if data is None:
continue
img = cv2.imdecode(np.frombuffer(b64decode(data.split(',')[1]), np.uint8), cv2.IMREAD_COLOR)
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
result = classifier.classify(mp_image)
y_offset = 30
for cat in result.classifications[0].categories:
cv2.putText(img, f"{cat.category_name}: {cat.score:.2f}",
(10, y_offset), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
y_offset += 25
_, buf = cv2.imencode('.jpg', img)
update_display(Image(data=buf.tobytes()), display_id='cam')
【MediaPipe版】タスク3:画像セグメンテーション(Image Segmenter)
概要:画像を画素単位で領域分割する(各画素がどのクラスに属するかを判定する処理).DeepLab V3(Googleが開発したセマンティックセグメンテーション用モデル)をベースモデルとし,Pascal VOC 2012(物体認識評価のための代表的データセット)の21クラス(背景,人,車,犬,猫,自転車,椅子,植木鉢等)に対応する.背景除去,写真合成,自動運転の領域認識等に利用される.本タスクでは,カメラ映像の各フレームに対してセグメンテーションを行い,領域を色分けして重ねて連続表示する.
- 制約事項:Pascal VOC 2012の21クラスのみに対応し,果物,料理,建築物等は背景として扱われる.細部の輪郭精度はDeepLab V3の出力解像度に依存し,細い物体や複雑な形状は崩れやすい.
- 設定での精度向上:output_confidence_masks(各クラスの確信度マスクを出力するオプション)の確信度閾値を調整すれば境界の不確実領域を除外できる.対象物が画像中で十分に大きく写るよう前処理(リサイズ,トリミング)を行うと境界精度が向上する.
# task_image_segmenter
!pip install -q -U mediapipe
import urllib.request
from pathlib import Path
import numpy as np, cv2
import mediapipe as mp
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
from IPython.display import display, Javascript, Image, update_display
from google.colab.output import eval_js
from base64 import b64decode
# User-Agent設定
opener = urllib.request.build_opener()
opener.addheaders = [("User-Agent", "Mozilla/5.0")]
urllib.request.install_opener(opener)
# モデルダウンロード(/content 直下に保存)
MODEL_URL = "https://storage.googleapis.com/mediapipe-models/image_segmenter/deeplab_v3/float32/latest/deeplab_v3.tflite"
model = Path("/content") / Path(MODEL_URL).name
if not model.exists():
urllib.request.urlretrieve(MODEL_URL, model)
# 推論器の作成(カテゴリマスクと確信度マスクの両方を出力)
options = vision.ImageSegmenterOptions(
base_options=python.BaseOptions(model_asset_path=str(model)),
output_category_mask=True,
output_confidence_masks=True)
segmenter = vision.ImageSegmenter.create_from_options(options)
# カメラ起動(getUserMedia)
display(Javascript('''
async function startCamera() {
try {
window._video = document.createElement('video');
window._video.srcObject = await navigator.mediaDevices.getUserMedia({video: true});
await window._video.play();
} catch (e) {
window._video = null;
}
}
function takeFrame() {
if (!window._video || !window._video.videoWidth || !window._video.videoHeight) return null;
const c = document.createElement('canvas');
c.width = window._video.videoWidth;
c.height = window._video.videoHeight;
c.getContext('2d').drawImage(window._video, 0, 0);
return c.toDataURL('image/jpeg', 0.8);
}
'''))
eval_js('startCamera()')
# カメラループ
confidence_threshold = 0.5
display(Image(data=b''), display_id='cam')
while True:
data = eval_js('takeFrame()')
if data is None:
continue
img = cv2.imdecode(np.frombuffer(b64decode(data.split(',')[1]), np.uint8), cv2.IMREAD_COLOR)
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
result = segmenter.segment(mp_image)
mask = result.category_mask.numpy_view()
# 各クラスの確信度マスクから低確信度領域を背景化
refined = mask.copy()
for cls in np.unique(mask):
if cls == 0:
continue
conf = result.confidence_masks[cls].numpy_view()
refined[(mask == cls) & (conf < confidence_threshold)] = 0
colored = cv2.applyColorMap((refined * 12 % 255).astype(np.uint8), cv2.COLORMAP_JET)
img = cv2.resize(img, (colored.shape[1], colored.shape[0]))
overlay = cv2.addWeighted(img, 0.5, colored, 0.5, 0)
_, buf = cv2.imencode('.jpg', overlay)
update_display(Image(data=buf.tobytes()), display_id='cam')
【MediaPipe版】タスク4:姿勢推定(Pose Landmarker)
概要:人体の33個の3Dランドマーク(特徴点の3次元座標.鼻,肩,肘,手首,腰,膝,足首等)を検出する.BlazePose GHUM Heavy(Googleの高精度姿勢推定モデル)をベースモデルとする.フィットネス分析,モーションキャプチャ(身体動作のデジタル記録),リハビリ動作評価等に利用される.本タスクでは,カメラ映像の各フレームに対してx, y座標による2D描画を行い,代表5点(鼻,両肩,両手首)のx, y, z値を画面に重ねて連続表示することで,正規化座標としての3D性を直接確認できる構成とする.
- 制約事項:標準モデルは単一人物向けであり,2名以上の同時検出はできない.遮蔽,後ろ向き,激しい動作時は誤検出や欠落が生じる.
- 設定での精度向上:min_pose_detection_confidence(姿勢検出の最小信頼度)の引き上げで誤検出を抑制できる.本コードでは0.7を設定している.min_pose_presence_confidence(姿勢の存在確率の最小値)とmin_tracking_confidence(追跡の最小信頼度)で検出の厳しさを調整できる.
- z値について:本タスクで表示されるz値は,腰の中央を原点とした相対深度の正規化値である(メートル単位の3D座標はpose_world_landmarksで取得可能であり,これはタスク15で可視化する).
# task_pose_landmarker
!pip install -q -U mediapipe
import urllib.request
from pathlib import Path
import numpy as np, cv2
import mediapipe as mp
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
from IPython.display import display, Javascript, Image, update_display
from google.colab.output import eval_js
from base64 import b64decode
# User-Agent設定
opener = urllib.request.build_opener()
opener.addheaders = [("User-Agent", "Mozilla/5.0")]
urllib.request.install_opener(opener)
# モデルダウンロード(/content 直下に保存)
MODEL_URL = "https://storage.googleapis.com/mediapipe-models/pose_landmarker/pose_landmarker_heavy/float16/latest/pose_landmarker_heavy.task"
model = Path("/content") / Path(MODEL_URL).name
if not model.exists():
urllib.request.urlretrieve(MODEL_URL, model)
# BlazePoseの33点接続関係(公式定義)
POSE_CONNECTIONS = [
(0, 1), (1, 2), (2, 3), (3, 7), (0, 4), (4, 5), (5, 6), (6, 8),
(9, 10), (11, 12), (11, 13), (13, 15), (15, 17), (15, 19), (15, 21),
(17, 19), (12, 14), (14, 16), (16, 18), (16, 20), (16, 22), (18, 20),
(11, 23), (12, 24), (23, 24), (23, 25), (24, 26), (25, 27), (26, 28),
(27, 29), (28, 30), (29, 31), (30, 32), (27, 31), (28, 32)]
# 代表5点(インデックス, 表示名)
POSE_KEY_POINTS = [(0, "Nose"), (11, "L-Shoulder"), (12, "R-Shoulder"),
(15, "L-Wrist"), (16, "R-Wrist")]
# 推論器の作成
options = vision.PoseLandmarkerOptions(
base_options=python.BaseOptions(model_asset_path=str(model)),
min_pose_detection_confidence=0.7,
min_pose_presence_confidence=0.7,
min_tracking_confidence=0.7)
landmarker = vision.PoseLandmarker.create_from_options(options)
# カメラ起動(getUserMedia)
display(Javascript('''
async function startCamera() {
try {
window._video = document.createElement('video');
window._video.srcObject = await navigator.mediaDevices.getUserMedia({video: true});
await window._video.play();
} catch (e) {
window._video = null;
}
}
function takeFrame() {
if (!window._video || !window._video.videoWidth || !window._video.videoHeight) return null;
const c = document.createElement('canvas');
c.width = window._video.videoWidth;
c.height = window._video.videoHeight;
c.getContext('2d').drawImage(window._video, 0, 0);
return c.toDataURL('image/jpeg', 0.8);
}
'''))
eval_js('startCamera()')
# カメラループ
display(Image(data=b''), display_id='cam')
while True:
data = eval_js('takeFrame()')
if data is None:
continue
img = cv2.imdecode(np.frombuffer(b64decode(data.split(',')[1]), np.uint8), cv2.IMREAD_COLOR)
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
result = landmarker.detect(mp_image)
h, w = img.shape[:2]
for pose_landmarks in result.pose_landmarks:
points = [(int(lm.x * w), int(lm.y * h)) for lm in pose_landmarks]
for s, e in POSE_CONNECTIONS:
cv2.line(img, points[s], points[e], (0, 255, 0), 2)
for p in points:
cv2.circle(img, p, 4, (0, 0, 255), -1)
# 代表点のx, y, z値を画面に表示
y_offset = 25
for idx, name in POSE_KEY_POINTS:
lm = pose_landmarks[idx]
text = f"{name}: x={lm.x:.2f} y={lm.y:.2f} z={lm.z:.2f}"
cv2.putText(img, text, (10, y_offset), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 3)
cv2.putText(img, text, (10, y_offset), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
y_offset += 22
_, buf = cv2.imencode('.jpg', img)
update_display(Image(data=buf.tobytes()), display_id='cam')
【MediaPipe版】タスク5:手のランドマーク検出(Hand Landmarker)
概要:両手それぞれについて21個のランドマーク(手首,各指の関節,指先)を検出し,左右の判定(handedness:検出した手が左右いずれかの識別)も同時に行う.手話認識,VR/ARのジェスチャー入力,リモコン代替等に利用される.本タスクでは,カメラ映像の各フレームに対してx, y座標による2D描画を行い,5指先(親指・人差し指・中指・薬指・小指)のx, y, z値を画面に重ねて連続表示することで,正規化座標としての3D性を直接確認できる構成とする.
- 制約事項:手が小さく写る場合や強い遮蔽がある場合は検出されにくく,左右判定も誤る場合がある.指の重なりや高速な動作時の座標精度はモデルの表現能力に制約される.
- 設定での精度向上:num_hands(検出する手の最大数)を実際の手の数に合わせる.本コードではmin_hand_detection_confidenceおよびmin_hand_presence_confidenceに0.7を設定し,誤検出を抑制している.min_tracking_confidence(追跡の最小信頼度)で検出・追跡の厳しさを調整できる.
- z値について:本タスクで表示されるz値は,手首を原点とした相対深度の正規化値である(メートル単位の3D座標はhand_world_landmarksで取得可能であり,これはタスク14で可視化する).
# task_hand_landmarker
!pip install -q -U mediapipe
import urllib.request
from pathlib import Path
import numpy as np, cv2
import mediapipe as mp
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
from IPython.display import display, Javascript, Image, update_display
from google.colab.output import eval_js
from base64 import b64decode
# User-Agent設定
opener = urllib.request.build_opener()
opener.addheaders = [("User-Agent", "Mozilla/5.0")]
urllib.request.install_opener(opener)
# モデルダウンロード(/content 直下に保存)
MODEL_URL = "https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/latest/hand_landmarker.task"
model = Path("/content") / Path(MODEL_URL).name
if not model.exists():
urllib.request.urlretrieve(MODEL_URL, model)
# 手の21点接続関係(公式定義:手首・親指・人差指・中指・薬指・小指の各骨格)
HAND_CONNECTIONS = [
(0, 1), (1, 2), (2, 3), (3, 4),
(0, 5), (5, 6), (6, 7), (7, 8),
(5, 9), (9, 10), (10, 11), (11, 12),
(9, 13), (13, 14), (14, 15), (15, 16),
(13, 17), (0, 17), (17, 18), (18, 19), (19, 20)]
# 5指先(インデックス, 表示名)
HAND_KEY_POINTS = [(4, "Thumb"), (8, "Index"), (12, "Middle"),
(16, "Ring"), (20, "Pinky")]
# 推論器の作成
options = vision.HandLandmarkerOptions(
base_options=python.BaseOptions(model_asset_path=str(model)),
num_hands=2,
min_hand_detection_confidence=0.7,
min_hand_presence_confidence=0.7,
min_tracking_confidence=0.7)
landmarker = vision.HandLandmarker.create_from_options(options)
# カメラ起動(getUserMedia)
display(Javascript('''
async function startCamera() {
try {
window._video = document.createElement('video');
window._video.srcObject = await navigator.mediaDevices.getUserMedia({video: true});
await window._video.play();
} catch (e) {
window._video = null;
}
}
function takeFrame() {
if (!window._video || !window._video.videoWidth || !window._video.videoHeight) return null;
const c = document.createElement('canvas');
c.width = window._video.videoWidth;
c.height = window._video.videoHeight;
c.getContext('2d').drawImage(window._video, 0, 0);
return c.toDataURL('image/jpeg', 0.8);
}
'''))
eval_js('startCamera()')
# カメラループ
display(Image(data=b''), display_id='cam')
while True:
data = eval_js('takeFrame()')
if data is None:
continue
img = cv2.imdecode(np.frombuffer(b64decode(data.split(',')[1]), np.uint8), cv2.IMREAD_COLOR)
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
result = landmarker.detect(mp_image)
h, w = img.shape[:2]
y_offset = 25
for hand_landmarks, handedness in zip(result.hand_landmarks, result.handedness):
points = [(int(lm.x * w), int(lm.y * h)) for lm in hand_landmarks]
for s, e in HAND_CONNECTIONS:
cv2.line(img, points[s], points[e], (0, 255, 0), 2)
for p in points:
cv2.circle(img, p, 4, (0, 0, 255), -1)
label = handedness[0].category_name
# 5指先のx, y, z値を画面に表示
for idx, name in HAND_KEY_POINTS:
lm = hand_landmarks[idx]
text = f"{label[0]}-{name}: x={lm.x:.2f} y={lm.y:.2f} z={lm.z:.2f}"
cv2.putText(img, text, (10, y_offset), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 3)
cv2.putText(img, text, (10, y_offset), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
y_offset += 22
_, buf = cv2.imencode('.jpg', img)
update_display(Image(data=buf.tobytes()), display_id='cam')
【MediaPipe版】タスク6:ジェスチャー認識(Gesture Recognizer)
概要:Hand Landmarkerを基盤として手の形状を分類するタスクである.標準7種類のジェスチャー(Closed_Fist:グー,Open_Palm:パー,Pointing_Up:指差し,Thumb_Down:サムズダウン,Thumb_Up:サムズアップ,Victory:ピース,ILoveYou:アイラブユー)に対応する.非接触UI(手を触れずに操作するインタフェース),プレゼンテーション制御,アクセシビリティ用途(身体的制約のあるユーザの操作支援)等に利用される.本タスクでは,カメラ映像の各フレームに対して認識を行い,結果を重ねて連続表示する.
- 制約事項:標準7種類のみが認識対象であり,それ以外の独自ジェスチャーは認識できない.追加には別モデルの利用あるいはカスタム学習(独自データでの追加学習)が必要となる.
- 設定での精度向上:本コードではmin_hand_detection_confidenceに0.7,canned_gesture_classifier_options(組み込みジェスチャー分類器のオプション)のscore_thresholdに0.5を設定し,低信頼度の検出・分類を抑制している.同オプションのcategory_allowlist/category_denylistで対象ジェスチャーを絞り込み,num_handsで検出数を制御できる.
# task_gesture_recognizer
!pip install -q -U mediapipe
import urllib.request
from pathlib import Path
import numpy as np, cv2
import mediapipe as mp
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
from mediapipe.tasks.python.components import processors
from IPython.display import display, Javascript, Image, update_display
from google.colab.output import eval_js
from base64 import b64decode
# User-Agent設定
opener = urllib.request.build_opener()
opener.addheaders = [("User-Agent", "Mozilla/5.0")]
urllib.request.install_opener(opener)
# モデルダウンロード(/content 直下に保存)
MODEL_URL = "https://storage.googleapis.com/mediapipe-models/gesture_recognizer/gesture_recognizer/float16/latest/gesture_recognizer.task"
model = Path("/content") / Path(MODEL_URL).name
if not model.exists():
urllib.request.urlretrieve(MODEL_URL, model)
# 推論器の作成
options = vision.GestureRecognizerOptions(
base_options=python.BaseOptions(model_asset_path=str(model)),
num_hands=2,
min_hand_detection_confidence=0.7,
canned_gesture_classifier_options=processors.ClassifierOptions(score_threshold=0.5))
recognizer = vision.GestureRecognizer.create_from_options(options)
# カメラ起動(getUserMedia)
display(Javascript('''
async function startCamera() {
try {
window._video = document.createElement('video');
window._video.srcObject = await navigator.mediaDevices.getUserMedia({video: true});
await window._video.play();
} catch (e) {
window._video = null;
}
}
function takeFrame() {
if (!window._video || !window._video.videoWidth || !window._video.videoHeight) return null;
const c = document.createElement('canvas');
c.width = window._video.videoWidth;
c.height = window._video.videoHeight;
c.getContext('2d').drawImage(window._video, 0, 0);
return c.toDataURL('image/jpeg', 0.8);
}
'''))
eval_js('startCamera()')
# カメラループ
display(Image(data=b''), display_id='cam')
while True:
data = eval_js('takeFrame()')
if data is None:
continue
img = cv2.imdecode(np.frombuffer(b64decode(data.split(',')[1]), np.uint8), cv2.IMREAD_COLOR)
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
result = recognizer.recognize(mp_image)
for i, gestures in enumerate(result.gestures):
gesture = gestures[0]
handedness = result.handedness[i][0].category_name
text = f"{handedness}: {gesture.category_name} ({gesture.score:.2f})"
cv2.putText(img, text, (10, 30 + i * 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
_, buf = cv2.imencode('.jpg', img)
update_display(Image(data=buf.tobytes()), display_id='cam')
【MediaPipe版】タスク7:顔ランドマーク検出(Face Landmarker)
概要:顔の478個の3Dランドマーク(顔メッシュ:顔表面を細かい多角形で覆う点群)を検出する.加えて52種類の表情ブレンドシェイプ係数(blendshape:表情の基本要素ごとの強さを0〜1で表す値.笑顔,目を閉じる,眉を上げる等)と頭部姿勢の変換行列(顔の向きを表す4×4行列)を出力する.アバター制御,AR効果,表情解析,運転手の眠気検知等に利用される.本タスクでは,カメラ映像の各フレームに対して検出を行い,478点のランドマークおよびブレンドシェイプ上位を重ねて連続表示する.
- 制約事項:大きな頭部回転,強い遮蔽,小さい顔では478点の位置精度が低下する.微細な表情差や横顔の奥行き推定精度には限界がある.
- 設定での精度向上:num_facesを実際の顔数に合わせる.本コードではmin_face_detection_confidenceおよびmin_face_presence_confidenceに0.7を設定し,誤検出を抑制している.output_face_blendshapesやoutput_facial_transformation_matrixesは必要な場合のみ有効化すると処理負荷が減る.
# task_face_landmarker
!pip install -q -U mediapipe
import urllib.request
from pathlib import Path
import numpy as np, cv2
import mediapipe as mp
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
from IPython.display import display, Javascript, Image, update_display
from google.colab.output import eval_js
from base64 import b64decode
# User-Agent設定
opener = urllib.request.build_opener()
opener.addheaders = [("User-Agent", "Mozilla/5.0")]
urllib.request.install_opener(opener)
# モデルダウンロード(/content 直下に保存)
MODEL_URL = "https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task"
model = Path("/content") / Path(MODEL_URL).name
if not model.exists():
urllib.request.urlretrieve(MODEL_URL, model)
# 推論器の作成
options = vision.FaceLandmarkerOptions(
base_options=python.BaseOptions(model_asset_path=str(model)),
output_face_blendshapes=True,
output_facial_transformation_matrixes=True,
num_faces=1,
min_face_detection_confidence=0.7,
min_face_presence_confidence=0.7,
min_tracking_confidence=0.7)
landmarker = vision.FaceLandmarker.create_from_options(options)
# カメラ起動(getUserMedia)
display(Javascript('''
async function startCamera() {
try {
window._video = document.createElement('video');
window._video.srcObject = await navigator.mediaDevices.getUserMedia({video: true});
await window._video.play();
} catch (e) {
window._video = null;
}
}
function takeFrame() {
if (!window._video || !window._video.videoWidth || !window._video.videoHeight) return null;
const c = document.createElement('canvas');
c.width = window._video.videoWidth;
c.height = window._video.videoHeight;
c.getContext('2d').drawImage(window._video, 0, 0);
return c.toDataURL('image/jpeg', 0.8);
}
'''))
eval_js('startCamera()')
# カメラループ
display(Image(data=b''), display_id='cam')
while True:
data = eval_js('takeFrame()')
if data is None:
continue
img = cv2.imdecode(np.frombuffer(b64decode(data.split(',')[1]), np.uint8), cv2.IMREAD_COLOR)
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
result = landmarker.detect(mp_image)
h, w = img.shape[:2]
for face_landmarks in result.face_landmarks:
for lm in face_landmarks:
cv2.circle(img, (int(lm.x * w), int(lm.y * h)), 1, (0, 255, 0), -1)
if result.face_blendshapes:
y_offset = 25
for bs in sorted(result.face_blendshapes[0], key=lambda x: x.score, reverse=True)[:5]:
text = f"{bs.category_name}: {bs.score:.2f}"
cv2.putText(img, text, (10, y_offset), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 3)
cv2.putText(img, text, (10, y_offset), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
y_offset += 22
_, buf = cv2.imencode('.jpg', img)
update_display(Image(data=buf.tobytes()), display_id='cam')
【MediaPipe版】タスク8:顔検出(Face Detector)
概要:画像内の顔を矩形で検出し,両目・鼻先・口・両耳の耳珠点(じじゅてん:耳の前方にある軟骨突起)の6点キーポイントを出力する.BlazeFace full-range(CenterNet系の構造を採用した遠距離・広範囲向け顔検出モデル)をベースモデルとし,遠距離の被写体や背面カメラ画像にも対応する.プライバシー保護のための顔ぼかし,参加人数カウント等に利用される.本タスクでは,カメラ映像の各フレームに対して検出を行い,矩形とキーポイントを重ねて連続表示する.
- 制約事項:強い横顔や極端な遮蔽下では検出精度が低下する.
- 設定での精度向上:min_detection_confidenceを下げると遠い顔も検出できる一方,誤検出が増える.min_suppression_threshold(重複検出を統合する際の重なり判定の閾値)で重複検出の統合度合いを調整できる.
# task_face_detector
!pip install -q -U mediapipe
import urllib.request
from pathlib import Path
import numpy as np, cv2
import mediapipe as mp
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
from IPython.display import display, Javascript, Image, update_display
from google.colab.output import eval_js
from base64 import b64decode
# User-Agent設定
opener = urllib.request.build_opener()
opener.addheaders = [("User-Agent", "Mozilla/5.0")]
urllib.request.install_opener(opener)
# モデルダウンロード(/content 直下に保存)
MODEL_URL = "https://storage.googleapis.com/mediapipe-models/face_detector/blaze_face_full_range/float16/latest/blaze_face_full_range.tflite"
model = Path("/content") / Path(MODEL_URL).name
if not model.exists():
urllib.request.urlretrieve(MODEL_URL, model)
# 推論器の作成
options = vision.FaceDetectorOptions(base_options=python.BaseOptions(model_asset_path=str(model)))
detector = vision.FaceDetector.create_from_options(options)
# カメラ起動(getUserMedia)
display(Javascript('''
async function startCamera() {
try {
window._video = document.createElement('video');
window._video.srcObject = await navigator.mediaDevices.getUserMedia({video: true});
await window._video.play();
} catch (e) {
window._video = null;
}
}
function takeFrame() {
if (!window._video || !window._video.videoWidth || !window._video.videoHeight) return null;
const c = document.createElement('canvas');
c.width = window._video.videoWidth;
c.height = window._video.videoHeight;
c.getContext('2d').drawImage(window._video, 0, 0);
return c.toDataURL('image/jpeg', 0.8);
}
'''))
eval_js('startCamera()')
# カメラループ
display(Image(data=b''), display_id='cam')
while True:
data = eval_js('takeFrame()')
if data is None:
continue
img = cv2.imdecode(np.frombuffer(b64decode(data.split(',')[1]), np.uint8), cv2.IMREAD_COLOR)
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
result = detector.detect(mp_image)
for det in result.detections:
bb = det.bounding_box
cv2.rectangle(img, (bb.origin_x, bb.origin_y),
(bb.origin_x + bb.width, bb.origin_y + bb.height), (0, 255, 0), 2)
for kp in det.keypoints:
cv2.circle(img, (int(kp.x * img.shape[1]), int(kp.y * img.shape[0])), 3, (0, 0, 255), -1)
_, buf = cv2.imencode('.jpg', img)
update_display(Image(data=buf.tobytes()), display_id='cam')
【MediaPipe版】タスク9:画像埋め込み(Image Embedder)
概要:画像を高次元の特徴ベクトル(埋め込み:画像の内容を数百次元程度の数値の並びで表現したもの)に変換する.MobileNet V3 Large(高表現能力を持つMobileNet V3アーキテクチャ)をベースモデルとする.2画像間のコサイン類似度(2つのベクトルがなす角度に基づく類似度.1に近いほど類似)を計算することで類似画像検索が可能となる.画像検索エンジン,重複画像検出,クラスタリング(類似データを自動的にグループ化する処理)等に利用される.
- 制約事項:MobileNet V3 Largeによる汎用特徴量であり,細かな意味的差異の識別能力には限りがある.
- 設定での精度向上:l2_normalize=True(特徴ベクトルの長さを1に正規化する設定)でコサイン類似度の比較が安定する.quantize(数値精度を落として軽量化する量子化の有無)で量子化を切り替え,速度と精度のバランスを調整できる.
画像を2枚選択するため,/content 直下のファイルの番号入力を2回行う.
# task_image_embedder
!pip install -q -U mediapipe
import urllib.request
from pathlib import Path
import mediapipe as mp
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
def select_file(title, initialdir, filetypes):
# /content 直下のファイルを拡張子で絞り込み番号入力で選択
exts = filetypes[0][1].lower().replace("*", "").split()
candidates = sorted(p for p in Path(initialdir).iterdir()
if p.is_file() and p.suffix.lower() in exts)
print(f"[{title}]")
for i, p in enumerate(candidates):
print(f" {i}: {p.name}")
return str(candidates[int(input(f"番号を入力 (0-{len(candidates)-1}): "))])
# User-Agent設定
opener = urllib.request.build_opener()
opener.addheaders = [("User-Agent", "Mozilla/5.0")]
urllib.request.install_opener(opener)
# 画像ダウンロード
IMG_URLS = [
"https://raw.githubusercontent.com/opencv/opencv/4.x/samples/data/aero1.jpg",
"https://raw.githubusercontent.com/opencv/opencv/4.x/samples/data/aero3.jpg"]
IMG_DIR = Path("/content")
for url in IMG_URLS:
p = IMG_DIR / Path(url).name
if not p.exists():
urllib.request.urlretrieve(url, p)
# モデルダウンロード(/content 直下に保存)
MODEL_URL = "https://storage.googleapis.com/mediapipe-models/image_embedder/mobilenet_v3_large/float32/latest/mobilenet_v3_large.tflite"
model = Path("/content") / Path(MODEL_URL).name
if not model.exists():
urllib.request.urlretrieve(MODEL_URL, model)
# 画像選択(2枚)
path1 = select_file(
title="比較する1枚目の画像を選択", initialdir=IMG_DIR,
filetypes=[("画像", "*.jpg *.jpeg *.png *.bmp"), ("すべて", "*.*")])
path2 = select_file(
title="比較する2枚目の画像を選択", initialdir=IMG_DIR,
filetypes=[("画像", "*.jpg *.jpeg *.png *.bmp"), ("すべて", "*.*")])
# 推論
options = vision.ImageEmbedderOptions(
base_options=python.BaseOptions(model_asset_path=str(model)), l2_normalize=True)
embedder = vision.ImageEmbedder.create_from_options(options)
emb1 = embedder.embed(mp.Image.create_from_file(path1))
emb2 = embedder.embed(mp.Image.create_from_file(path2))
# 表示
similarity = vision.ImageEmbedder.cosine_similarity(emb1.embeddings[0], emb2.embeddings[0])
print(f"画像間のコサイン類似度: {similarity:.4f}")
print("(1.0に近いほど類似)")
embedder.close()
【MediaPipe版】タスク10:音声分類(Audio Classifier)
概要:音声波形を入力として,AudioSet(Googleが公開した約200万件の音響イベントのデータセット)オントロジー(音響イベントの階層的な分類体系)の521種類の音響イベント(音楽,会話,犬の鳴き声,車のクラクション等)を分類する.YAMNet(AudioSetで学習された音響イベント分類モデル)をベースモデルとする.スマートホームの異常音検知,音響シーン解析,自動字幕用の音響タグ付け等に利用される.本タスクではYAMNetの前提に従い,入力WAVは16kHzモノラル16ビットPCM(音声をデジタル化する代表的な符号化方式)であることを前提とする.本タスクではフレームごとの分類結果をクラスごとに平均化することで,入力音声全体の主たる音響イベントを判定する構成とする.
- 制約事項:AudioSet 521クラスに限定され,入力は16kHzモノラルである必要がある.音響イベントの細分類,話者識別等,YAMNetの定義外のタスクには対応できない.
- 設定での精度向上:max_resultsを増やして上位候補を観察し,score_thresholdで低信頼度を除外できる.前処理でノイズ除去や音量正規化(音量を一定範囲に揃える処理)を行うと分類精度が向上する場合がある.
# task_audio_classifier
!pip install -q -U mediapipe
import urllib.request, wave
from collections import defaultdict
from pathlib import Path
import numpy as np
from mediapipe.tasks import python
from mediapipe.tasks.python import audio
from mediapipe.tasks.python.components import containers
def select_file(title, initialdir, filetypes):
# /content 直下のファイルを拡張子で絞り込み番号入力で選択
exts = filetypes[0][1].lower().replace("*", "").split()
candidates = sorted(p for p in Path(initialdir).iterdir()
if p.is_file() and p.suffix.lower() in exts)
print(f"[{title}]")
for i, p in enumerate(candidates):
print(f" {i}: {p.name}")
return str(candidates[int(input(f"番号を入力 (0-{len(candidates)-1}): "))])
# User-Agent設定
opener = urllib.request.build_opener()
opener.addheaders = [("User-Agent", "Mozilla/5.0")]
urllib.request.install_opener(opener)
# 音声ダウンロード
WAV_URL = "https://storage.googleapis.com/audioset/miaow_16k.wav"
WAV_DIR = Path("/content")
wav_path = WAV_DIR / Path(WAV_URL).name
if not wav_path.exists():
urllib.request.urlretrieve(WAV_URL, wav_path)
# モデルダウンロード(/content 直下に保存)
MODEL_URL = "https://storage.googleapis.com/mediapipe-models/audio_classifier/yamnet/float32/latest/yamnet.tflite"
model = Path("/content") / Path(MODEL_URL).name
if not model.exists():
urllib.request.urlretrieve(MODEL_URL, model)
# 音声選択
selected = select_file(
title="WAVファイルを選択", initialdir=WAV_DIR,
filetypes=[("WAV", "*.wav"), ("すべて", "*.*")])
# WAV読み込み(16kHzモノラル16ビットPCM前提)
with wave.open(selected, 'rb') as wf:
n_channels = wf.getnchannels()
sr = wf.getframerate()
frames = wf.readframes(wf.getnframes())
samples = np.frombuffer(frames, dtype=np.int16)
if n_channels > 1:
samples = samples.reshape(-1, n_channels).mean(axis=1)
samples = samples.astype(np.float32) / 32768.0
# 推論
options = audio.AudioClassifierOptions(
base_options=python.BaseOptions(model_asset_path=str(model)), max_results=5)
classifier = audio.AudioClassifier.create_from_options(options)
audio_data = containers.AudioData.create_from_array(samples, sr)
result = classifier.classify(audio_data)
# 全フレームの結果をクラスごとに平均化
sums = defaultdict(float)
counts = defaultdict(int)
for frame_result in result:
for cat in frame_result.classifications[0].categories:
sums[cat.category_name] += cat.score
counts[cat.category_name] += 1
averaged = sorted(((name, sums[name] / counts[name]) for name in sums),
key=lambda x: x[1], reverse=True)
# 表示
print("音声分類結果(全フレーム平均上位5件):")
for name, score in averaged[:5]:
print(f" {name}: {score:.4f}")
classifier.close()
【MediaPipe版】タスク11:テキスト分類(Text Classifier,画像不要)
概要:入力テキストの感情を肯定的・否定的に分類する.BERT-classifier(MobileBERTベースの分類モデル)をベースモデルとし,SST-2(Stanford Sentiment Treebank:映画レビューの感情ラベル付きデータセット)で学習され,英語の感情分析(二値分類)に対応する.レビュー分析,SNS投稿のセンチメント解析(文章から書き手の感情の肯定・否定度を推定する分析),カスタマーサポートのトリアージ(問い合わせの重要度・種別による振り分け)等に利用される.
テキスト入力には標準入力(input())を用いる.本コードでは入力前処理(前後の空白除去,連続空白の単一化)を行う.
- 制約事項:既定モデルは英語の感情二値分類(肯定・否定)のみであり,日本語や多クラス分類には対応しない.ドメイン固有表現(特定分野でのみ用いられる用語),皮肉,反語等はモデルの能力を超える場合がある.
- 設定での精度向上:max_resultsとscore_thresholdで出力候補の範囲を調整できる.入力前処理(記号除去,小文字化,略語展開)で精度が向上する場合がある.
# task_text_classifier
!pip install -q -U mediapipe
import re, urllib.request
from pathlib import Path
from mediapipe.tasks import python
from mediapipe.tasks.python import text
def input_text(title, prompt, initialvalue=""):
print(f"[{title}]")
s = input(f"{prompt} (既定値: {initialvalue}): ")
return s if s else initialvalue
# User-Agent設定
opener = urllib.request.build_opener()
opener.addheaders = [("User-Agent", "Mozilla/5.0")]
urllib.request.install_opener(opener)
# モデルダウンロード(/content 直下に保存)
MODEL_URL = "https://storage.googleapis.com/mediapipe-models/text_classifier/bert_classifier/float32/latest/bert_classifier.tflite"
model = Path("/content") / Path(MODEL_URL).name
if not model.exists():
urllib.request.urlretrieve(MODEL_URL, model)
# テキスト入力
input_str = input_text("テキスト分類", "分類したい英語テキストを入力:",
initialvalue="I love this product!")
# 入力前処理(前後空白除去・連続空白の単一化)
normalized_text = re.sub(r"\s+", " ", input_str).strip()
# 推論
options = text.TextClassifierOptions(base_options=python.BaseOptions(model_asset_path=str(model)))
classifier = text.TextClassifier.create_from_options(options)
result = classifier.classify(normalized_text)
# 表示
print(f"入力: {normalized_text}")
print("分類結果:")
for cat in result.classifications[0].categories:
print(f" {cat.category_name}: {cat.score:.4f}")
classifier.close()
【MediaPipe版】タスク12:言語検出(Language Detector,画像不要)
概要:入力テキストの言語を判定する.110言語に対応する.多言語コンテンツのルーティング(言語ごとに処理経路を振り分けること),自動翻訳の前段処理(翻訳元言語を特定する処理),国際化対応のWebサービス等に利用される.本コードでは入力テキストが20文字未満の場合に注意を表示し,短文での精度低下を利用者に明示する構成とする.
- 制約事項:短文,固有名詞のみの文,複数言語が混在する文では判定精度が低下する.類似言語の識別や混在文の主言語判定精度はモデルの訓練データに依存する.
- 設定での精度向上:max_resultsを増やして上位候補を確認することで誤判定を検知しやすくなる.score_thresholdで低確率の候補を除外し,最上位のみを採用すると出力の安定性が高まる.
# task_language_detector
!pip install -q -U mediapipe
import urllib.request
from pathlib import Path
from mediapipe.tasks import python
from mediapipe.tasks.python import text
def input_text(title, prompt, initialvalue=""):
print(f"[{title}]")
s = input(f"{prompt} (既定値: {initialvalue}): ")
return s if s else initialvalue
# User-Agent設定
opener = urllib.request.build_opener()
opener.addheaders = [("User-Agent", "Mozilla/5.0")]
urllib.request.install_opener(opener)
# モデルダウンロード(/content 直下に保存)
MODEL_URL = "https://storage.googleapis.com/mediapipe-models/language_detector/language_detector/float32/1/language_detector.tflite"
model = Path("/content") / Path(MODEL_URL).name
if not model.exists():
urllib.request.urlretrieve(MODEL_URL, model)
# テキスト入力
input_str = input_text("言語検出", "言語を判定したいテキストを入力:",
initialvalue="今日は良い天気です.")
# 短文での精度低下に関する注意
if len(input_str) < 20:
print(f"注意: 入力が短い({len(input_str)}文字)ため判定精度が低下する場合があります")
# 推論
options = text.LanguageDetectorOptions(base_options=python.BaseOptions(model_asset_path=str(model)))
detector = text.LanguageDetector.create_from_options(options)
result = detector.detect(input_str)
# 表示
print(f"入力: {input_str}")
print("言語検出結果(上位):")
for d in result.detections[:5]:
print(f" {d.language_code}: {d.probability:.4f}")
detector.close()
【MediaPipe版】タスク13:ホリスティック検出(Holistic Landmarker)
概要:単一の人物に対して顔ランドマーク(468点),ポーズランドマーク(33点),左右の手ランドマーク(各21点)を一度の推論で同時検出する.合計543点の人体ランドマークを一括取得できるため,全身ジェスチャー解析,手話認識,スポーツフォーム解析,アバター駆動等の,顔・体幹・手指を統合的に扱う応用に適する.本タスクでは,カメラ映像の各フレームに対して検出を行い,顔・ポーズ・両手のランドマークを重ねて連続表示する.
タスク4・タスク5との関係:
- 同一点:内部の検出原理はPose Landmarker(タスク4)とHand Landmarker(タスク5)と共通であり,ランドマーク番号体系および接続関係(POSE_CONNECTIONS,HAND_CONNECTIONS)も同一である.本タスクでも同じ定義を用いる.
- 違い:タスク4・5は対象を1種類(ポーズのみ,手のみ)に限定するのに対し,本タスクは顔・ポーズ・両手を1つのモデル呼び出しで統合検出する.本タスクは単一人物専用であり,複数人物の同時検出はできない.
- 制約事項:単一人物専用であり,複数人物が写る画像では1人分のみが検出される.全身が写る画像を前提とし,顔のみ・上半身のみの画像ではポーズや手の検出が不安定となる.
- 設定での精度向上:本コードではmin_pose_detection_confidence,min_pose_landmarks_confidence,min_face_detection_confidence,min_face_landmarks_confidence,min_hand_landmarks_confidenceに0.7を設定し,各部位の検出感度を厳密化している.output_face_blendshapesやoutput_segmentation_maskは必要時のみ有効化する.なお,HolisticLandmarkerOptionsの信頼度関連引数はPose Landmarker(タスク4)やFace Landmarkerと命名規則が異なり,ランドマーク追跡側の信頼度は
presenceではなくlandmarksを用いる点に留意する. - インポートに関する注意:HolisticLandmarkerは
mediapipe.tasks.python.visionパッケージから直接公開されていない場合があり,mediapipe.tasks.python.vision.holistic_landmarkerサブモジュールから明示的にインポートする.
# task_holistic_landmarker
!pip install -q -U mediapipe
import urllib.request
from pathlib import Path
import numpy as np, cv2
import mediapipe as mp
from mediapipe.tasks import python
from mediapipe.tasks.python.vision.holistic_landmarker import (
HolisticLandmarker, HolisticLandmarkerOptions)
from IPython.display import display, Javascript, Image, update_display
from google.colab.output import eval_js
from base64 import b64decode
# User-Agent設定
opener = urllib.request.build_opener()
opener.addheaders = [("User-Agent", "Mozilla/5.0")]
urllib.request.install_opener(opener)
# モデルダウンロード(/content 直下に保存)
MODEL_URL = "https://storage.googleapis.com/mediapipe-models/holistic_landmarker/holistic_landmarker/float16/latest/holistic_landmarker.task"
model = Path("/content") / Path(MODEL_URL).name
if not model.exists():
urllib.request.urlretrieve(MODEL_URL, model)
# BlazePoseの33点接続関係(タスク4と同一定義)
POSE_CONNECTIONS = [
(0, 1), (1, 2), (2, 3), (3, 7), (0, 4), (4, 5), (5, 6), (6, 8),
(9, 10), (11, 12), (11, 13), (13, 15), (15, 17), (15, 19), (15, 21),
(17, 19), (12, 14), (14, 16), (16, 18), (16, 20), (16, 22), (18, 20),
(11, 23), (12, 24), (23, 24), (23, 25), (24, 26), (25, 27), (26, 28),
(27, 29), (28, 30), (29, 31), (30, 32), (27, 31), (28, 32)]
# 手の21点接続関係(タスク5と同一定義)
HAND_CONNECTIONS = [
(0, 1), (1, 2), (2, 3), (3, 4),
(0, 5), (5, 6), (6, 7), (7, 8),
(5, 9), (9, 10), (10, 11), (11, 12),
(9, 13), (13, 14), (14, 15), (15, 16),
(13, 17), (0, 17), (17, 18), (18, 19), (19, 20)]
# 推論器の作成
options = HolisticLandmarkerOptions(
base_options=python.BaseOptions(model_asset_path=str(model)),
min_pose_detection_confidence=0.7,
min_pose_landmarks_confidence=0.7,
min_face_detection_confidence=0.7,
min_face_landmarks_confidence=0.7,
min_hand_landmarks_confidence=0.7)
landmarker = HolisticLandmarker.create_from_options(options)
# カメラ起動(getUserMedia)
display(Javascript('''
async function startCamera() {
try {
window._video = document.createElement('video');
window._video.srcObject = await navigator.mediaDevices.getUserMedia({video: true});
await window._video.play();
} catch (e) {
window._video = null;
}
}
function takeFrame() {
if (!window._video || !window._video.videoWidth || !window._video.videoHeight) return null;
const c = document.createElement('canvas');
c.width = window._video.videoWidth;
c.height = window._video.videoHeight;
c.getContext('2d').drawImage(window._video, 0, 0);
return c.toDataURL('image/jpeg', 0.8);
}
'''))
eval_js('startCamera()')
# カメラループ
display(Image(data=b''), display_id='cam')
while True:
data = eval_js('takeFrame()')
if data is None:
continue
img = cv2.imdecode(np.frombuffer(b64decode(data.split(',')[1]), np.uint8), cv2.IMREAD_COLOR)
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
result = landmarker.detect(mp_image)
h, w = img.shape[:2]
# 顔ランドマーク(468点を緑の小点で描画)
if result.face_landmarks:
for lm in result.face_landmarks:
cv2.circle(img, (int(lm.x * w), int(lm.y * h)), 1, (0, 255, 0), -1)
# ポーズランドマーク(タスク4と同一の描画形式)
if result.pose_landmarks:
points = [(int(lm.x * w), int(lm.y * h)) for lm in result.pose_landmarks]
for s, e in POSE_CONNECTIONS:
cv2.line(img, points[s], points[e], (0, 255, 0), 2)
for p in points:
cv2.circle(img, p, 4, (0, 0, 255), -1)
# 両手ランドマーク(タスク5と同一の描画形式)
for hand_landmarks, label in [(result.left_hand_landmarks, "Left"),
(result.right_hand_landmarks, "Right")]:
if hand_landmarks:
points = [(int(lm.x * w), int(lm.y * h)) for lm in hand_landmarks]
for s, e in HAND_CONNECTIONS:
cv2.line(img, points[s], points[e], (255, 0, 0), 2)
for p in points:
cv2.circle(img, p, 4, (0, 0, 255), -1)
_, buf = cv2.imencode('.jpg', img)
update_display(Image(data=buf.tobytes()), display_id='cam')
【MediaPipe版】タスク14:手の3D可視化(Hand Landmarker world landmarks)
概要:Hand Landmarkerが出力するhand_world_landmarks(手首を原点とするメートル単位の3D座標)を,matplotlibの3D散布図および骨格線として可視化する.画面はマウスドラッグで回転でき,手指の立体構造を任意の視点から観察できる.モーション解析,手形状の3D計測,VR/AR用ジェスチャー設計等に利用される.
タスク5との関係:
- 同一点:使用するモデル(hand_landmarker.task),推論API(vision.HandLandmarker),21点の接続関係(HAND_CONNECTIONS),信頼度閾値の設定値(0.7)はすべてタスク5と同一である.推論部分のコードは同一形式とする.
- 違い:タスク5は2D画像座標(x, y)を画像平面に描画し,z値は数値表示のみであった.本タスクはhand_world_landmarksを参照することで,メートル単位の実空間スケールの3D座標を取得し,matplotlibの3D空間にプロットする.これにより,画像平面では確認しにくい奥行き方向の指の曲がり等を立体的に確認できる.
- 制約事項:hand_world_landmarksは手首を原点とした相対座標であり,カメラからの絶対距離は得られない.左右の手が同時に検出された場合,各手の世界座標は独立した原点を持つため,両手の相対位置関係は本座標系では表現されない(相対位置はhand_landmarksの正規化画像座標を参照する).
- 設定での精度向上:信頼度閾値の調整はタスク5と共通.3D表示の見やすさのため,matplotlibのウィンドウサイズは適宜調整する.
# task_hand_landmarker_3d
!pip install -q -U mediapipe
import urllib.request
from pathlib import Path
import matplotlib.pyplot as plt
import mediapipe as mp
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
def select_file(title, initialdir, filetypes):
# /content 直下のファイルを拡張子で絞り込み番号入力で選択
exts = filetypes[0][1].lower().replace("*", "").split()
candidates = sorted(p for p in Path(initialdir).iterdir()
if p.is_file() and p.suffix.lower() in exts)
print(f"[{title}]")
for i, p in enumerate(candidates):
print(f" {i}: {p.name}")
return str(candidates[int(input(f"番号を入力 (0-{len(candidates)-1}): "))])
# User-Agent設定
opener = urllib.request.build_opener()
opener.addheaders = [("User-Agent", "Mozilla/5.0")]
urllib.request.install_opener(opener)
# 画像ダウンロード(タスク5と同一)
IMG_URL = "https://storage.googleapis.com/mediapipe-tasks/hand_landmarker/woman_hands.jpg"
IMG_DIR = Path("/content")
img_path = IMG_DIR / Path(IMG_URL).name
if not img_path.exists():
urllib.request.urlretrieve(IMG_URL, img_path)
# モデルダウンロード(/content 直下に保存.タスク5と同一モデル)
MODEL_URL = "https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/latest/hand_landmarker.task"
model = Path("/content") / Path(MODEL_URL).name
if not model.exists():
urllib.request.urlretrieve(MODEL_URL, model)
# 手の21点接続関係(タスク5と同一定義)
HAND_CONNECTIONS = [
(0, 1), (1, 2), (2, 3), (3, 4),
(0, 5), (5, 6), (6, 7), (7, 8),
(5, 9), (9, 10), (10, 11), (11, 12),
(9, 13), (13, 14), (14, 15), (15, 16),
(13, 17), (0, 17), (17, 18), (18, 19), (19, 20)]
# 画像選択
selected = select_file(
title="手の画像を選択", initialdir=IMG_DIR,
filetypes=[("画像", "*.jpg *.jpeg *.png *.bmp"), ("すべて", "*.*")])
# 推論(タスク5と同一形式)
options = vision.HandLandmarkerOptions(
base_options=python.BaseOptions(model_asset_path=str(model)),
num_hands=2,
min_hand_detection_confidence=0.7,
min_hand_presence_confidence=0.7,
min_tracking_confidence=0.7)
landmarker = vision.HandLandmarker.create_from_options(options)
image = mp.Image.create_from_file(selected)
result = landmarker.detect(image)
# 3D可視化(world landmarks使用)
fig = plt.figure(figsize=(8, 8))
ax = fig.add_subplot(111, projection='3d')
colors = ['r', 'b']
for i, (hand_world, handedness) in enumerate(zip(result.hand_world_landmarks, result.handedness)):
label = handedness[0].category_name
xs = [lm.x for lm in hand_world]
ys = [lm.y for lm in hand_world]
zs = [lm.z for lm in hand_world]
c = colors[i % len(colors)]
ax.scatter(xs, ys, zs, c=c, marker='o', label=label)
for s, e in HAND_CONNECTIONS:
ax.plot([xs[s], xs[e]], [ys[s], ys[e]], [zs[s], zs[e]], c=c)
print(f"{label} hand: 21 world landmarks (meters)")
ax.set_xlabel('X (m)')
ax.set_ylabel('Y (m)')
ax.set_zlabel('Z (m)')
ax.set_title('Hand 3D (hand_world_landmarks)')
ax.legend()
plt.show()
landmarker.close()
【MediaPipe版】タスク15:姿勢の3D可視化(Pose Landmarker world landmarks)
概要:Pose Landmarkerが出力するpose_world_landmarks(腰中央を原点とするメートル単位の3D座標)を,matplotlibの3D散布図および骨格線として可視化する.画面はマウスドラッグで回転でき,全身姿勢の立体構造を任意の視点から観察できる.フィットネスフォーム解析,リハビリ評価,モーションキャプチャの可視化等に利用される.
タスク4との関係:
- 同一点:使用するモデル(pose_landmarker_heavy.task),推論API(vision.PoseLandmarker),33点の接続関係(POSE_CONNECTIONS),信頼度閾値の設定値(0.7)はすべてタスク4と同一である.推論部分のコードも同一形式とする.
- 違い:タスク4は2D画像座標(x, y)を画像平面に描画し,z値は代表5点の数値表示のみであった.本タスクはpose_world_landmarksを参照することで,メートル単位の実空間スケールの3D座標を取得し,matplotlibの3D空間にプロットする.これにより,画像平面では確認しにくい前後方向の体幹の傾き等を立体的に確認できる.
- 制約事項:pose_world_landmarksは腰中央を原点とした相対座標であり,カメラからの絶対距離は得られない.被写体の身長や体格による絶対スケールの個体差はモデルが推定する範囲内に限られる.
- 設定での精度向上:信頼度閾値の調整はタスク4と共通.3D表示の見やすさのため,matplotlibのウィンドウサイズは適宜調整する.
# task_pose_landmarker_3d
!pip install -q -U mediapipe
import urllib.request
from pathlib import Path
import matplotlib.pyplot as plt
import mediapipe as mp
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
def select_file(title, initialdir, filetypes):
# /content 直下のファイルを拡張子で絞り込み番号入力で選択
exts = filetypes[0][1].lower().replace("*", "").split()
candidates = sorted(p for p in Path(initialdir).iterdir()
if p.is_file() and p.suffix.lower() in exts)
print(f"[{title}]")
for i, p in enumerate(candidates):
print(f" {i}: {p.name}")
return str(candidates[int(input(f"番号を入力 (0-{len(candidates)-1}): "))])
# User-Agent設定
opener = urllib.request.build_opener()
opener.addheaders = [("User-Agent", "Mozilla/5.0")]
urllib.request.install_opener(opener)
# 画像ダウンロード(全身が写る単一人物画像)
IMG_URL = "https://cdn.pixabay.com/photo/2019/03/12/20/39/girl-4051811_960_720.jpg"
IMG_DIR = Path("/content")
img_path = IMG_DIR / Path(IMG_URL).name
if not img_path.exists():
urllib.request.urlretrieve(IMG_URL, img_path)
# モデルダウンロード(/content 直下に保存.タスク4と同一モデル)
MODEL_URL = "https://storage.googleapis.com/mediapipe-models/pose_landmarker/pose_landmarker_heavy/float16/latest/pose_landmarker_heavy.task"
model = Path("/content") / Path(MODEL_URL).name
if not model.exists():
urllib.request.urlretrieve(MODEL_URL, model)
# BlazePoseの33点接続関係(タスク4と同一定義)
POSE_CONNECTIONS = [
(0, 1), (1, 2), (2, 3), (3, 7), (0, 4), (4, 5), (5, 6), (6, 8),
(9, 10), (11, 12), (11, 13), (13, 15), (15, 17), (15, 19), (15, 21),
(17, 19), (12, 14), (14, 16), (16, 18), (16, 20), (16, 22), (18, 20),
(11, 23), (12, 24), (23, 24), (23, 25), (24, 26), (25, 27), (26, 28),
(27, 29), (28, 30), (29, 31), (30, 32), (27, 31), (28, 32)]
# 画像選択
selected = select_file(
title="姿勢推定する画像を選択", initialdir=IMG_DIR,
filetypes=[("画像", "*.jpg *.jpeg *.png *.bmp"), ("すべて", "*.*")])
# 推論(タスク4と同一形式)
options = vision.PoseLandmarkerOptions(
base_options=python.BaseOptions(model_asset_path=str(model)),
min_pose_detection_confidence=0.7,
min_pose_presence_confidence=0.7,
min_tracking_confidence=0.7)
landmarker = vision.PoseLandmarker.create_from_options(options)
image = mp.Image.create_from_file(selected)
result = landmarker.detect(image)
# 3D可視化(world landmarks使用)
fig = plt.figure(figsize=(8, 8))
ax = fig.add_subplot(111, projection='3d')
for pose_world in result.pose_world_landmarks:
xs = [lm.x for lm in pose_world]
ys = [lm.y for lm in pose_world]
zs = [lm.z for lm in pose_world]
ax.scatter(xs, ys, zs, c='r', marker='o')
for s, e in POSE_CONNECTIONS:
ax.plot([xs[s], xs[e]], [ys[s], ys[e]], [zs[s], zs[e]], c='g')
print("33 world landmarks 検出 (meters)")
ax.set_xlabel('X (m)')
ax.set_ylabel('Y (m)')
ax.set_zlabel('Z (m)')
ax.set_title('Pose 3D (pose_world_landmarks)')
plt.show()
landmarker.close()