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


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 -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126
pip install ultralytics opencv-python numpy
YOLOv10人物検出プログラム
概要
このプログラムは、動画フレームから人物を検出し、その位置をバウンディングボックスで特定する。
主要技術
YOLOv10
単一のニューラルネットワークで画像全体から物体を検出する手法である。Non-Maximum Suppressionを不要とするアーキテクチャを採用している[1]。COCOデータセット
80種類の物体カテゴリを含む物体検出用データセットである[2]。本プログラムでは事前学習済みモデルから人物クラスのみを使用する。
参考文献
- [1] Wang, A., Chen, H., Liu, L., Chen, K., Lin, Z., Han, J., & Ding, G. (2024). YOLOv10: Real-Time End-to-End Object Detection. arXiv preprint arXiv:2405.14458.
- [2] Lin, T. Y., Maire, M., Belongie, S., Hays, J., Perona, P., Ramanan, D., ... & Zitnick, C. L. (2014). Microsoft COCO: Common objects in context. In European conference on computer vision (pp. 740-755). Springer.
ソースコード
# プログラム名: YOLOv10人物検出プログラム
# 特徴技術名: YOLOv10
# 出典: Wang, A., Chen, H., Liu, L., Chen, K., Lin, Z., Han, J., & Ding, G. (2024). YOLOv10: Real-Time End-to-End Object Detection. arXiv preprint arXiv:2405.14458.
# 特徴機能: NMSフリー検出機能。Non-Maximum Suppressionを不要とする新アーキテクチャにより、後処理なしで物体検出を実現。本プログラムでは人物検出に特化
# 学習済みモデル: YOLOv10 COCO事前学習済みモデル(80クラスからPersonクラスのみ抽出)
# モデルサイズ選択可能(デフォルト:n):
# n (nano): yolov10n.pt - 最軽量
# s (small): yolov10s.pt - 軽量
# m (medium): yolov10m.pt - バランス型
# b (balanced): yolov10b.pt - バランス型
# l (large): yolov10l.pt - 高精度
# x (extra large): yolov10x.pt - 最高精度
import cv2
import tkinter as tk
from tkinter import filedialog
import urllib
import urllib.request
import os
import time
from datetime import datetime
import torch
import numpy as np
from ultralytics import YOLO
import warnings
from PIL import Image, ImageDraw, ImageFont
warnings.filterwarnings('ignore')
# ===== 設定・定数管理 =====
# YOLOv10モデル設定(デフォルト:n、変更可能:n, s, m, b, l, x)
MODEL_SIZE = 'n' # 使用するモデルサイズ(n=nano, s=small, m=medium, b=balanced, l=large, x=extra large)
MODEL_NAME = f'yolov10{MODEL_SIZE}.pt'
# モデル情報
MODEL_INFO = {
'n': {'name': 'nano', 'desc': '最軽量'},
's': {'name': 'small', 'desc': '軽量'},
'm': {'name': 'medium', 'desc': 'バランス型'},
'b': {'name': 'balanced', 'desc': 'バランス型'},
'l': {'name': 'large', 'desc': '高精度'},
'x': {'name': 'extra large', 'desc': '最高精度'}
}
# 検出パラメータ(調整可能)
CONF_THRESH = 0.5 # 人物検出信頼度閾値(0.0-1.0)
PERSON_CLASS_NAME = 'person' # 動的解決用のクラス名
# 表示設定
FONT_PATH = 'C:/Windows/Fonts/meiryo.ttc' # 日本語フォントパス
# 人物の表示色(BGR形式)
PERSON_COLOR = (0, 255, 0) # 緑(バウンディングボックス用)
# プログラム概要表示
print('=== YOLOv10人物検出プログラム ===')
print('概要: リアルタイムで人物を検出し、バウンディングボックスで表示します')
print('機能: YOLOv10による人物検出(COCOデータセットのPersonクラス)')
print('操作: 0=動画ファイル, 1=カメラ, 2=サンプル動画 / 画面表示中は q キーで終了')
print('出力: フレーム毎の処理結果表示、終了時にresult.txt保存')
print()
# システム初期化
print('システム初期化中...')
# GPU/CPU自動選択
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f'デバイス: {str(device)}')
# YOLOv10モデル初期化
print(f'YOLOv10{MODEL_SIZE}モデルを初期化中...')
person_model = YOLO(MODEL_NAME)
person_model.to(device)
# クラスIDの動的解決
PERSON_CLASS_ID = 0 # デフォルト値
names = person_model.names
if names and isinstance(names, dict):
for class_id, class_name in names.items():
if class_name.lower() == PERSON_CLASS_NAME:
PERSON_CLASS_ID = class_id
print(f'クラスIDを動的決定: "{PERSON_CLASS_NAME}" -> {PERSON_CLASS_ID}')
break
print(f'YOLOv10{MODEL_SIZE}モデルの初期化が完了しました')
print(f'モデルサイズ: {MODEL_SIZE} ({MODEL_INFO[MODEL_SIZE]["name"]}={MODEL_INFO[MODEL_SIZE]["desc"]})')
print(f'{str(device).upper()}使用モード')
print('初期化完了')
print()
frame_count = 0
results_log = []
def video_frame_processing(frame):
global frame_count
current_time = time.time()
frame_count += 1
# 推論実行
results = person_model(frame, device=device, verbose=False)
persons = []
presence_level = '気配なし'
max_person_conf_all = 0.0
if results and len(results) > 0 and results[0].boxes is not None:
boxes = results[0].boxes.xyxy.cpu().numpy()
confs = results[0].boxes.conf.cpu().numpy()
classes = results[0].boxes.cls.cpu().numpy()
# Personクラスのみ抽出
person_mask = (classes == PERSON_CLASS_ID)
if np.any(person_mask):
person_boxes = boxes[person_mask]
person_confs = confs[person_mask]
# しきい値適用前の最大信頼度を記録
max_person_conf_all = float(np.max(person_confs))
# 信頼度で降順ソート
sorted_indices = np.argsort(person_confs)[::-1]
person_boxes = person_boxes[sorted_indices]
person_confs = person_confs[sorted_indices]
# しきい値を超えたもののみpersonsリストに追加
for box, conf in zip(person_boxes, person_confs):
if conf >= CONF_THRESH:
x1, y1, x2, y2 = map(int, box)
persons.append({'box': (x1, y1, x2, y2), 'detection_conf': float(conf)})
# 存在判定(しきい値適用前の最大信頼度で判定)
if max_person_conf_all >= CONF_THRESH:
presence_level = '人の存在確認'
elif max_person_conf_all > 0.0:
presence_level = '人影の可能性'
# 詳細出力
person_count = len(persons)
result = f'フレーム {frame_count}: {person_count}人検出'
for i, p in enumerate(persons):
result += f' | 人物{i+1}: 信頼度{p["detection_conf"]:.0%}'
result += f' | 判定: {presence_level}'
# 可視化(矩形描画)
for p in persons:
x1, y1, x2, y2 = p['box']
cv2.rectangle(frame, (x1, y1), (x2, y2), PERSON_COLOR, 2)
# テキスト描画
if os.path.exists(FONT_PATH):
# Pillowでテキスト描画(日本語フォント)
img_pil = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(img_pil)
font_main = ImageFont.truetype(FONT_PATH, 30)
font_small = ImageFont.truetype(FONT_PATH, 18)
# 各人物のラベル
for i, p in enumerate(persons):
x1, y1, x2, y2 = p['box']
draw.text((x1, max(0, y1 - 24)), f'Person {i+1}', font=font_small, fill=(0, 255, 0))
draw.text((x1, y2 + 4), f'Conf: {p["detection_conf"]:.1%}', font=font_small, fill=(255, 255, 255))
# システム情報と判定結果
info1 = f'YOLOv10 ({str(device).upper()}) | Frame: {frame_count}'
info2 = '操作: q=終了'
draw.text((10, 10), info1, font=font_small, fill=(255, 255, 255))
draw.text((10, 34), info2, font=font_small, fill=(255, 255, 0))
draw.text((10, 58), presence_level, font=font_main, fill=(0, 255, 0))
draw.text((10, 96), f'人数: {person_count}', font=font_main, fill=(255, 0, 0))
frame = cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR)
else:
# OpenCVでの描画
cv2.putText(frame, presence_level, (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
cv2.putText(frame, f'Count: {person_count}', (10, 100), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 2)
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 = "YOLOv10 Person Detection"
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'使用デバイス: {str(device).upper()}\n')
if device.type == 'cuda':
f.write(f'GPU: {torch.cuda.get_device_name(0)}\n')
f.write('\n')
f.write('\n'.join(results_log))
print(f'\n処理結果をresult.txtに保存しました')