YOLOv8による2次元姿勢推定

【概要】YOLOv8-poseを使用してリアルタイム姿勢推定を実行。人体17箇所のキーポイント検出技術を学習し、5種類のモデルサイズによる精度と速度の比較実験が可能。Windows環境での実行手順、プログラムコード、実験アイデアを含む。

目次

概要

技術名: YOLOv8-pose(You Only Look Once version 8 - Pose Estimation)

論文: "YOLOv8: A New Era of Object Detection and Image Segmentation" (arXiv:2305.09972, 2023)

新規性・特徴: YOLOv8-poseは、単一のニューラルネットワークで人体の17箇所のキーポイント(関節位置)をリアルタイムで検出する技術である。従来手法と比較して高速性と精度を両立し、CPU環境でもリアルタイム処理が可能である。

アプリケーション例: スポーツ動作解析、リハビリテーション支援、フィットネスアプリ、モーションキャプチャ、姿勢矯正システム

体験価値: カメラの前で様々なポーズを取ることで、AI が人体の関節位置を瞬時に認識する過程を視覚的に体験できる。異なるモデルサイズによる精度と速度のトレードオフを実験的に比較し、AI の性能特性を理解することが可能である。

事前準備

Python, Windsurfをインストールしていない場合の手順(インストール済みの場合は実行不要)。

  1. 管理者権限でコマンドプロンプトを起動する(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)。
  2. 以下のコマンドをそれぞれ実行する(winget コマンドは1つずつ実行)。
REM Python をシステム領域にインストール
winget install --scope machine --id Python.Python.3.12 -e --silent
REM Windsurf をシステム領域にインストール
winget install --scope machine --id Codeium.Windsurf -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
REM Windsurf のパス設定
set "WINDSURF_PATH=C:\Program Files\Windsurf"
if exist "%WINDSURF_PATH%" (
    echo "%PATH%" | find /i "%WINDSURF_PATH%" >nul
    if errorlevel 1 setx PATH "%PATH%;%WINDSURF_PATH%" /M >nul
)

必要なパッケージのインストール

管理者権限でコマンドプロンプトを起動し、以下のコマンドを実行する:


pip install -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126
pip install ultralytics opencv-python numpy

プログラムコード


# プログラム名: YOLOv8リアルタイム姿勢推定
# 特徴技術名: YOLOv8(You Only Look Once version 8)
# 出典: Jocher, G., Chaurasia, A., & Qiu, J. (2023). YOLO by Ultralytics. GitHub. https://github.com/ultralytics/ultralytics
# 特徴機能: リアルタイム姿勢推定 - 単一のニューラルネットワークで人体の17個のキーポイント(鼻、目、耳、肩、肘、手首、腰、膝、足首)を同時検出し、30FPS以上の高速処理を実現
# 学習済みモデル: YOLOv8姿勢推定モデル(yolov8n-pose.pt)- COCOデータセットで事前学習済み、人体姿勢の17キーポイントを高精度で検出、初回実行時に自動ダウンロード
# 方式設計:
#   - 関連利用技術: OpenCV(動画処理・表示)、NumPy(数値計算)、tkinter(ファイル選択ダイアログ)
#   - 入力と出力: 入力: 動画(ユーザは「0:動画ファイル,1:カメラ,2:サンプル動画」のメニューで選択.0:動画ファイルの場合はtkinterでファイル選択.1の場合はOpenCVでカメラが開く.2の場合はhttps://github.com/opencv/opencv/blob/master/samples/data/vtest.aviを使用)、出力: 処理結果が画像化できる場合にはOpenCV画面でリアルタイムに表示.OpenCV画面内に処理結果をテキストで表示.さらに,1秒間隔で,print()で処理結果を表示.プログラム終了時にprint()で表示した処理結果をresult.txtファイルに保存し,「result.txtに保存」したことをprint()で表示.プログラム開始時に,プログラムの概要,ユーザが行う必要がある操作(もしあれば)をprint()で表示.
#   - 処理手順: 1.動画フレーム取得、2.YOLOv8モデルで推論実行、3.17個のキーポイント座標と信頼度取得、4.キーポイントを可視化、5.結果表示
#   - 前処理、後処理: 前処理:フレームバッファクリア(最新フレーム取得)、後処理:信頼度閾値によるフィルタリング(0.5以上のキーポイントのみ表示)
#   - 追加処理: キーポイント名称の日本語表示(nose→鼻等)による可読性向上
#   - 調整を必要とする設定値: CONF_THRESHOLD(信頼度閾値、デフォルト0.5)- キーポイント検出の信頼度閾値、低くすると検出数増加但しノイズも増加
# 将来方策: 動的な信頼度閾値調整機能 - 検出されたキーポイント数に基づいて閾値を自動調整し、最適な検出精度を実現
# その他の重要事項: Windows環境での動作確認済み、初回実行時にモデルファイルが自動ダウンロードされる
# 前準備: pip install ultralytics opencv-python numpy

import cv2
import numpy as np
from ultralytics import YOLO
import tkinter as tk
from tkinter import filedialog
import os
import time
import urllib.request
import torch

# システム設定
CAMERA_INDEX = 0  # 使用するカメラ番号
SAMPLE_VIDEO_URL = 'https://github.com/opencv/opencv/blob/master/samples/data/vtest.avi?raw=true'
SAMPLE_VIDEO_FILENAME = 'vtest.avi'
RESULT_FILENAME = 'result.txt'
PRINT_INTERVAL = 1.0  # 結果表示間隔(秒)

# YOLOv8設定
MODEL_NAME = 'yolov8n-pose.pt'  # 使用するモデル(n/s/m/l/xから選択可能)
CONF_THRESHOLD = 0.5  # キーポイント検出の信頼度閾値(0.0-1.0)
INFERENCE_BATCH_SIZE = 1  # 推論時のバッチサイズ

# 表示設定
FONT_SCALE = 0.4
FONT_THICKNESS = 1
TEXT_COLOR = (255, 255, 255)
WINDOW_NAME = 'YOLOv8 姿勢推定'

# その他
RANDOM_SEED = 42  # 再現性のための乱数シード

KEYPOINT_NAMES = [
    '鼻', '左目', '右目', '左耳', '右耳',
    '左肩', '右肩', '左肘', '右肘',
    '左手首', '右手首', '左腰', '右腰',
    '左膝', '右膝', '左足首', '右足首'
]

# プログラム開始メッセージ
print('='*60)
print('YOLOv8リアルタイム姿勢推定プログラム')
print('='*60)
print('このプログラムは動画から人体の17個のキーポイントを検出します。')
print('検出されたキーポイントは画面上にリアルタイムで表示されます。')
print()
print('操作方法:')
print('- qキー: プログラムを終了')
print('- 検出結果は1秒ごとにコンソールに表示されます')
print('- 終了時に全ての検出結果がresult.txtに保存されます')
print('='*60)
print()

# 乱数シード設定
np.random.seed(RANDOM_SEED)
torch.manual_seed(RANDOM_SEED)

# 結果保存用リスト
detection_results = []
last_print_time = time.time()


def video_processing(frame, model):
    """動画フレームの処理"""
    global last_print_time, detection_results

    # 推論実行(最適化パラメータ付き)
    results = model(frame, verbose=False, conf=CONF_THRESHOLD, batch=INFERENCE_BATCH_SIZE)
    annotated_frame = results[0].plot()

    current_time = time.time()
    detected_keypoints = []

    for result in results:
        if hasattr(result, 'keypoints') and result.keypoints is not None:
            keypoints = result.keypoints

            if hasattr(keypoints.data, 'cpu'):
                keypoints_data = keypoints.data.cpu().numpy()
            else:
                keypoints_data = keypoints.data

            if len(keypoints_data.shape) == 3 and keypoints_data.shape[0] > 0 and keypoints_data.shape[1] == 17:
                for person_idx in range(keypoints_data.shape[0]):
                    person_keypoints = []
                    for i in range(17):
                        keypoint = keypoints_data[person_idx, i]
                        if len(keypoint) >= 3:
                            x, y, conf = keypoint[:3]
                            if conf > CONF_THRESHOLD:
                                text = f'{KEYPOINT_NAMES[i]}({x:.0f},{y:.0f})'
                                cv2.putText(annotated_frame, text, (int(x), int(y)),
                                           cv2.FONT_HERSHEY_SIMPLEX, FONT_SCALE, TEXT_COLOR, FONT_THICKNESS)
                                person_keypoints.append(f'{KEYPOINT_NAMES[i]}: ({x:.0f}, {y:.0f}) [信頼度: {conf:.2f}]')

                    if person_keypoints:
                        detected_keypoints.append(person_keypoints)

    # 指定間隔で結果を表示
    if current_time - last_print_time >= PRINT_INTERVAL and detected_keypoints:
        timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
        print(f'\n[{timestamp}] 検出結果:')
        for person_idx, person_keypoints in enumerate(detected_keypoints):
            print(f'  人物{person_idx + 1}:')
            for kp in person_keypoints:
                print(f'    - {kp}')

        # 結果を保存
        detection_results.append({
            'timestamp': timestamp,
            'detections': detected_keypoints
        })
        last_print_time = current_time

    return annotated_frame


print('0: 動画ファイル')
print('1: カメラ')
print('2: サンプル動画')

choice = input('選択: ')
temp_file = None

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(CAMERA_INDEX, cv2.CAP_DSHOW)
    cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
elif choice == '2':
    # サンプル動画ダウンロード・処理
    try:
        print('サンプル動画をダウンロード中...')
        urllib.request.urlretrieve(SAMPLE_VIDEO_URL, SAMPLE_VIDEO_FILENAME)
        temp_file = SAMPLE_VIDEO_FILENAME
        cap = cv2.VideoCapture(SAMPLE_VIDEO_FILENAME)
    except Exception as e:
        print(f'動画のダウンロードに失敗しました: {SAMPLE_VIDEO_URL}')
        print(f'エラー: {e}')
        exit()
else:
    print('無効な選択です')
    exit()

# GPU/CPU自動選択(利用可能ならGPUを使用)
device = 'cuda' if torch.cuda.is_available() else 'cpu'
print(f'使用デバイス: {device}')

# モデルロード
print('YOLOv8モデルをロード中...')
model = YOLO(MODEL_NAME)
print(f'モデル {MODEL_NAME} を正常にロードしました')

# メイン処理
try:
    while True:
        cap.grab()
        ret, frame = cap.retrieve()
        if not ret:
            break

        processed_frame = video_processing(frame, model)
        cv2.imshow(WINDOW_NAME, processed_frame)

        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
finally:
    cap.release()
    cv2.destroyAllWindows()

    # 結果をファイルに保存
    if detection_results:
        with open(RESULT_FILENAME, 'w', encoding='utf-8') as f:
            f.write('YOLOv8姿勢推定 検出結果\n')
            f.write('='*60 + '\n\n')
            for result in detection_results:
                f.write(f'[{result["timestamp"]}]\n')
                for person_idx, person_keypoints in enumerate(result['detections']):
                    f.write(f'  人物{person_idx + 1}:\n')
                    for kp in person_keypoints:
                        f.write(f'    - {kp}\n')
                f.write('\n')
        print(f'\n検出結果を{RESULT_FILENAME}に保存しました')

    if temp_file and os.path.exists(temp_file):
        os.remove(temp_file)

使用方法

  1. プログラムを実行すると、カメラが起動し、リアルタイムで姿勢推定が開始される
  2. カメラに向かって様々なポーズを取ると、17箇所のキーポイントが検出・表示される
  3. 各キーポイントには番号、名称、座標(ピクセル単位)が表示される
  4. 'q'キーを押すとプログラムが終了する

実験・探求のアイデア

AIモデル選択実験

プログラム冒頭のMODEL_NAMEを変更することで、異なるモデルを比較できる:

検出閾値調整実験

CONF_THRESHOLDの値(0.0-1.0)を変更することで、検出感度を調整できる:

体験・実験・探求のアイデア

精度と速度の比較実験: 異なるモデル(n, s, m, l, x)で同じ動作を行い、検出精度と処理速度を比較する

ポーズ難易度実験:

距離による検出性能: カメラからの距離を変えて、キーポイント検出の限界距離を測定する

複数人同時検出: 複数人がカメラに映った場合の検出性能を確認する

照明条件の影響: 明るさを変えて検出精度への影響を観察する