MediaPipeによる人物検出(ソースコードと実行結果)


Python開発環境,ライブラリ類
ここでは、最低限の事前準備について説明する。機械学習や深層学習を行う場合は、NVIDIA CUDA、Visual Studio、Cursorなどを追加でインストールすると便利である。これらについては別ページ https://www.kkaneko.jp/cc/dev/aiassist.htmlで詳しく解説しているので、必要に応じて参照してください。
Python 3.12 のインストール
インストール済みの場合は実行不要。
管理者権限でコマンドプロンプトを起動(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する。管理者権限は、wingetの--scope machineオプションでシステム全体にソフトウェアをインストールするために必要である。
REM Python をシステム領域にインストール
winget install --scope machine --id Python.Python.3.12 -e --silent
REM Python のパス設定
set "PYTHON_PATH=C:\Program Files\Python312"
set "PYTHON_SCRIPTS_PATH=C:\Program Files\Python312\Scripts"
echo "%PATH%" | find /i "%PYTHON_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_PATH%" /M >nul
echo "%PATH%" | find /i "%PYTHON_SCRIPTS_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_SCRIPTS_PATH%" /M >nul
【関連する外部ページ】
Python の公式ページ: https://www.python.org/
AI エディタ Windsurf のインストール
Pythonプログラムの編集・実行には、AI エディタの利用を推奨する。ここでは,Windsurfのインストールを説明する。
管理者権限でコマンドプロンプトを起動(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行して、Windsurfをシステム全体にインストールする。管理者権限は、wingetの--scope machineオプションでシステム全体にソフトウェアをインストールするために必要となる。
winget install --scope machine Codeium.Windsurf -e --silent
【関連する外部ページ】
Windsurf の公式ページ: https://windsurf.com/
必要なライブラリのインストール
コマンドプロンプトを管理者として実行(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する
pip install mediapipe opencv-python numpy pillow
MediaPipe人物検出プログラム
概要
このプログラムは、MediaPipe Object Detectorを活用して動画内の人物をリアルタイムで検出する。COCOデータセット[1]で学習済みのEfficientDet-Lite0モデル[2]を使用し、各フレームから人物(personクラス)を抽出してバウンディングボックスで可視化する。入力源として動画ファイル、カメラ、サンプル動画の3種類に対応し、検出結果をコンソール出力とファイル保存の両方で提供する。
主要技術
MediaPipe Object Detector
Googleが開発したMediaPipe Tasks Vision API[3]のオブジェクト検出機能である。TensorFlow Liteモデルを基盤とし、モバイルデバイスやエッジデバイスでの推論に最適化されている。本プログラムでは、動画モード(VisionRunningMode.VIDEO)で動作し、タイムスタンプベースのフレーム処理を実行する。
EfficientDet-Lite0
EfficientDet[2]のモバイル向け軽量版である。複合スケーリング手法により、精度と計算効率のバランスを実現している。INT8量子化により、モデルサイズは約4MBに圧縮され、推論速度が向上している。COCOデータセット[1]の80クラスに対応し、本プログラムではpersonクラスのみを抽出して使用する。
技術的特徴
信頼度ベースのフィルタリング
検出結果に対して信頼度閾値(CONF_THRESH=0.5)を適用し、誤検出を抑制する。閾値は調整可能で、値を上げると誤検出が減少するが検出漏れが増加する特性がある。検出された人物は信頼度の降順でソートされ、重要度の高い検出結果から優先的に表示される。
3段階の存在判定
最大信頼度に基づく独自の判定ロジックを実装している。信頼度0.7以上で「人の存在確認」、0.7未満で「人影の可能性」、検出なしで「気配なし」と判定する。この段階的な判定により、検出の確実性を直感的に把握できる。
実装の特色
マルチモデル対応
EfficientDet-Lite0(バランス型)、EfficientDet-Lite2(高精度)、SSD MobileNetV2(軽量)の3種類のモデルから選択可能である。用途や処理環境に応じて最適なモデルを選択できる設計となっている。
Windows環境への最適化
DirectShowバックエンド(CAP_DSHOW)の優先使用、Windows標準フォント(メイリオ)による日本語表示、tkinterによるファイル選択ダイアログなど、Windows環境での動作を考慮した実装となっている。Pillowライブラリ[4]を使用した日本語テキストのオーバーレイ表示により、検出結果を日本語で直接確認できる。
リアルタイム処理と結果記録
各フレームの処理結果を即座に表示し、同時にメモリ内のリストに蓄積する。プログラム終了時には、全処理結果をresult.txtファイルに保存する。カメラ入力時はタイムスタンプ付きで、動画ファイル入力時はフレーム番号付きで結果を記録する。
参考文献
[1] Lin, T. Y., et al. (2014). Microsoft COCO: Common Objects in Context. European Conference on Computer Vision (ECCV), 740-755. https://arxiv.org/abs/1405.0312
[2] Tan, M., Pang, R., & Le, Q. V. (2020). EfficientDet: Scalable and Efficient Object Detection. IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR), 10781-10790. https://arxiv.org/abs/1911.09070
[3] Google. (2023). MediaPipe Object Detection Task Guide. https://developers.google.com/mediapipe/solutions/vision/object_detector
[4] Clark, A., et al. (2024). Pillow (PIL Fork) Documentation. https://pillow.readthedocs.io/
ソースコード
# プログラム名: MediaPipe人物検出プログラム
# 特徴技術名: MediaPipe
# 出典: MediaPipe Tasks - Google
# 特徴機能: MediaPipe Tasks Object Detectorによる2Dオブジェクト検出。リアルタイムで動作するオブジェクト検出で人物検出に特化
# 学習済みモデル: EfficientDet-Lite0事前学習済みモデル(COCO 80クラスからPersonクラスのみ抽出)
# 方式設計:
# - 関連利用技術:
# - MediaPipe: Googleが開発したマルチプラットフォーム機械学習ソリューション
# - OpenCV: 画像処理、カメラ制御、描画処理、動画入出力管理
# - 入力と出力: 入力: 動画(ユーザは「0:動画ファイル,1:カメラ,2:サンプル動画」のメニューで選択.0:動画ファイルの場合はtkinterでファイル選択.1の場合はOpenCVでカメラが開く.2の場合はhttps://raw.githubusercontent.com/opencv/opencv/master/samples/data/vtest.aviを使用)、出力: OpenCV画面でリアルタイム表示(検出した人物をバウンディングボックスで表示)、各フレームごとにprint()による処理結果表示、プログラム終了時にresult.txtファイルに保存
# - 処理手順: 1.フレーム取得、2.MediaPipe推論実行、3.Personクラスのフィルタリング、4.信頼度閾値による選別、5.バウンディングボックス描画
# - 前処理、後処理: 前処理:MediaPipe内部で自動実行。後処理:信頼度による閾値フィルタリングのみ実施
# - 追加処理: 検出結果の信頼度降順ソートにより重要な検出を優先表示
# - 調整を必要とする設定値: CONF_THRESH(人物検出信頼度閾値、デフォルト0.5)- 値を上げると誤検出が減少するが検出漏れが増加
# 将来方策: CONF_THRESHの動的調整機能。フレーム毎の検出数を監視し、検出数が閾値を超えた場合は信頼度を上げ、検出数が少ない場合は下げる適応的制御の実装
# その他の重要事項: Windows環境専用設計、初回実行時は学習済みモデルの自動ダウンロード
# 前準備:
# - pip install mediapipe opencv-python numpy pillow
import cv2
import tkinter as tk
from tkinter import filedialog
import os
import numpy as np
import warnings
import time
import urllib.request
from PIL import Image, ImageDraw, ImageFont
import mediapipe as mp
from datetime import datetime
warnings.filterwarnings('ignore')
# ===== 設定・定数管理 =====
# MediaPipe設定
BaseOptions = mp.tasks.BaseOptions
ObjectDetector = mp.tasks.vision.ObjectDetector
ObjectDetectorOptions = mp.tasks.vision.ObjectDetectorOptions
VisionRunningMode = mp.tasks.vision.RunningMode
# モデル選択(0, 2, ssdから選択可能)
MODEL_SIZE = '0' # 使用するモデルサイズ(0=EfficientDet-Lite0, 2=EfficientDet-Lite2, ssd=SSD MobileNetV2)
# モデル情報
MODEL_INFO = {
'0': {
'name': 'EfficientDet-Lite0',
'desc': 'バランス型(推奨)',
'url': 'https://storage.googleapis.com/mediapipe-models/object_detector/efficientdet_lite0/int8/1/efficientdet_lite0.tflite',
'file': 'efficientdet_lite0.tflite'
},
'2': {
'name': 'EfficientDet-Lite2',
'desc': '高精度',
'url': 'https://storage.googleapis.com/mediapipe-models/object_detector/efficientdet_lite2/int8/1/efficientdet_lite2.tflite',
'file': 'efficientdet_lite2.tflite'
},
'ssd': {
'name': 'SSD MobileNetV2',
'desc': '最軽量',
'url': 'https://storage.googleapis.com/mediapipe-models/object_detector/ssd_mobilenet_v2/float32/1/ssd_mobilenet_v2.tflite',
'file': 'ssd_mobilenet_v2.tflite'
}
}
MODEL_URL = MODEL_INFO[MODEL_SIZE]['url']
MODEL_PATH = MODEL_INFO[MODEL_SIZE]['file']
# 検出パラメータ(調整可能)
CONF_THRESH = 0.5 # 人物検出信頼度閾値(0.0-1.0)
PERSON_CLASS_NAME = 'person' # COCOデータセットのクラス名
# 人物の表示色(BGR形式)
PERSON_COLOR = (0, 255, 0) # 緑(バウンディングボックス用)
# フォント設定(Windows環境)
FONT_PATH = 'C:/Windows/Fonts/meiryo.ttc'
FONT_SIZE = 30
# プログラム概要表示(ガイダンス)
print('=== MediaPipe人物検出プログラム ===')
print('概要: リアルタイムで人物を検出し、バウンディングボックスで表示します')
print('機能: MediaPipeによる人物検出(COCOデータセットのPersonクラス)')
print('操作: qキーで終了')
print('注意: Windows環境向け。初回はモデルを自動ダウンロードするためインターネット接続が必要')
print('出力: 各フレームごとに処理結果をprint表示、終了時にresult.txtへ保存')
print()
# システム初期化
print('システム初期化中...')
# モデルダウンロード
if not os.path.exists(MODEL_PATH):
print(f'{MODEL_INFO[MODEL_SIZE]["name"]}モデルをダウンロード中...')
urllib.request.urlretrieve(MODEL_URL, MODEL_PATH)
print('モデルのダウンロードが完了しました')
# MediaPipeモデル初期化
print(f'MediaPipe Object Detector {MODEL_INFO[MODEL_SIZE]["name"]}モデルを初期化中...')
options = ObjectDetectorOptions(
base_options=BaseOptions(model_asset_path=MODEL_PATH),
running_mode=VisionRunningMode.VIDEO,
score_threshold=CONF_THRESH,
max_results=100 # 全件取得に近い十分大きな値
)
detector = ObjectDetector.create_from_options(options)
print(f'MediaPipe Object Detector {MODEL_INFO[MODEL_SIZE]["name"]}モデルの初期化が完了しました')
print(f'モデル: {MODEL_INFO[MODEL_SIZE]["name"]} ({MODEL_INFO[MODEL_SIZE]["desc"]})')
print('初期化完了')
print()
# グローバル変数
frame_count = 0
results_log = []
def video_frame_processing(frame):
"""
フレーム処理メイン関数。
- 入力: OpenCV取得のBGRフレーム(numpy.ndarray)
- 処理: BGR→RGB変換後にMediaPipeで人物検出。personクラスを抽出し、最大confidenceを用いた存在判定を行う
- 出力: 描画済みのBGRフレーム(numpy.ndarray)、処理結果文字列、現在時刻
"""
global frame_count
current_time = time.time()
frame_count += 1
h, w = frame.shape[:2]
# MediaPipeで検出実行(RGB前提)
rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=rgb_frame)
# タイムスタンプを実際の経過時間から計算(ミリ秒)
timestamp_ms = int(current_time * 1000)
detection_result = detector.detect_for_video(mp_image, timestamp_ms)
persons = []
max_confidence = 0.0
if detection_result.detections:
for detection in detection_result.detections:
# カテゴリーからpersonクラスを抽出
for category in detection.categories:
if category.category_name == PERSON_CLASS_NAME:
bbox = detection.bounding_box
x1 = int(bbox.origin_x)
y1 = int(bbox.origin_y)
x2 = int(bbox.origin_x + bbox.width)
y2 = int(bbox.origin_y + bbox.height)
# 画像境界にクリップ
x1 = max(0, min(x1, w - 1))
x2 = max(0, min(x2, w - 1))
y1 = max(0, min(y1, h - 1))
y2 = max(0, min(y2, h - 1))
conf = float(category.score)
persons.append({'box': (x1, y1, x2, y2), 'detection_conf': conf})
if conf > max_confidence:
max_confidence = conf
break # 該当クラスが見つかったら次の検出へ
# 信頼度でソート(降順)
persons.sort(key=lambda x: x['detection_conf'], reverse=True)
# 人の存在判定(最大confidenceを使用)
person_count = len(persons)
if person_count == 0:
presence_level = '気配なし'
elif max_confidence < 0.7:
presence_level = '人影の可能性'
else:
presence_level = '人の存在確認'
# 結果文字列の作成
result = f'{person_count}人検出'
if person_count > 0:
result += f' | 最大信頼度 {max_confidence:.0%}'
for i, p in enumerate(persons[:3]): # 上位3人まで表示
result += f' | 人物{i+1}: 信頼度{p["detection_conf"]:.0%}'
result += f' | 判定: {presence_level}'
# 描画処理(バウンディングボックスとラベル)
for i, p in enumerate(persons):
x1, y1, x2, y2 = p['box']
color = PERSON_COLOR
cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
label1 = f'Person {i+1}'
label2 = f'Conf:{p["detection_conf"]:.1%}'
cv2.putText(frame, label1, (x1, max(0, y1 - 10)), cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, 2)
cv2.putText(frame, label2, (x1, min(h - 1, y2 + 15)), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 255), 1)
# 画面情報(英語はOpenCV、和文はPillowで表示)
cv2.putText(frame, f'MediaPipe | Frame: {frame_count}', (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
cv2.putText(frame, 'Press: q=Quit', (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 0), 1)
# 日本語表示(Pillow)
font = ImageFont.truetype(FONT_PATH, FONT_SIZE)
img_pil = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(img_pil)
draw.text((10, 90), presence_level, font=font, fill=(0, 255, 0))
draw.text((10, 130), f'人数: {person_count}', font=font, fill=(255, 0, 0))
frame = cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR)
return frame, result, current_time
# 入力選択
print('0: 動画ファイル')
print('1: カメラ')
print('2: サンプル動画')
choice = input('選択: ')
if choice == '0':
root = tk.Tk()
root.withdraw()
path = filedialog.askopenfilename()
if not path:
exit()
cap = cv2.VideoCapture(path)
elif choice == '1':
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
if not cap.isOpened():
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
else:
# サンプル動画ダウンロード・処理
SAMPLE_URL = 'https://raw.githubusercontent.com/opencv/opencv/master/samples/data/vtest.avi'
SAMPLE_FILE = 'vtest.avi'
urllib.request.urlretrieve(SAMPLE_URL, SAMPLE_FILE)
cap = cv2.VideoCapture(SAMPLE_FILE)
if not cap.isOpened():
print('動画ファイル・カメラを開けませんでした')
exit()
# メイン処理
print('\n=== 動画処理開始 ===')
print('操作方法:')
print(' q キー: プログラム終了')
try:
while True:
ret, frame = cap.read()
if not ret:
break
MAIN_FUNC_DESC = "MediaPipe人物検出"
processed_frame, result, current_time = video_frame_processing(frame)
cv2.imshow(MAIN_FUNC_DESC, processed_frame)
if choice == '1': # カメラの場合
print(datetime.fromtimestamp(current_time).strftime("%Y-%m-%d %H:%M:%S.%f")[:-3], result)
else: # 動画ファイルの場合
print(frame_count, result)
results_log.append(result)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
finally:
print('\n=== プログラム終了 ===')
cap.release()
cv2.destroyAllWindows()
if results_log:
with open('result.txt', 'w', encoding='utf-8') as f:
f.write('=== 結果 ===\n')
f.write(f'処理フレーム数: {frame_count}\n')
f.write(f'使用モデル: {MODEL_INFO[MODEL_SIZE]["name"]}\n')
f.write('\n')
f.write('\n'.join(results_log))
print(f'\n処理結果をresult.txtに保存しました')