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の深層学習モデルを用いて、各フレームから物体を識別し、バウンディングボックスとクラスラベルを描画する。検出結果は画面表示とともにテキストファイルに記録される。
主要技術
YOLOv10 (You Only Look Once version 10)
Wang, A.らが2024年に発表した物体検出アルゴリズム[1]。従来のYOLOシリーズと異なり、Non-Maximum Suppression (NMS)を不要とする新しいアーキテクチャを採用している。この設計により、後処理段階での計算コストを削減し、エンドツーエンドの推論を実現する。
NMSフリーアーキテクチャ
YOLOv10の中核となる技術革新である。従来の物体検出では、重複する検出結果を除去するためにNMS処理が必要であったが、YOLOv10では一対多の学習戦略と一貫性のある二重割り当て機構により、推論時にNMSを不要とする[1]。これにより、推論速度の向上と処理の簡素化を実現している。
技術的特徴
モデルサイズの選択性
YOLOv10は、nano (n)からextra large (x)まで5段階のモデルサイズを提供する。各モデルは精度と処理速度のトレードオフが異なり、使用環境に応じて選択可能である。nanoモデルは最小のパラメータ数で軽量な処理を、extra largeモデルは最高精度での検出を実現する。
COCO 80クラス検出
Microsoft COCOデータセット[2]で事前学習されたモデルを使用し、人物、車両、動物、日用品など80種類の物体クラスを検出する。各検出結果には信頼度スコアが付与され、閾値(デフォルト0.5)によるフィルタリングが可能である。
実装の特色
デバイス自動選択機構
PyTorchのCUDA対応を活用し、GPU環境では自動的にCUDAデバイスを選択する。GPU未搭載環境ではCPUモードで動作し、環境に依存しない実行を保証する。
日本語対応インターフェース
Windows環境のMeiryoフォントを使用し、検出結果のラベルを日本語で表示する。PillowライブラリとOpenCVを組み合わせることで、日本語テキストの描画を実現している。
フレーム単位の詳細記録
各フレームの検出結果を構造化された形式で記録する。検出数、クラス名、信頼度、座標情報を含む詳細データをリアルタイムで出力し、処理終了時にresult.txtファイルに保存する。カメラ入力時はタイムスタンプ付きで、動画ファイル入力時はフレーム番号付きで記録される。
処理フロー
- 入力ソースの選択(動画ファイル、カメラ、サンプル動画)
- YOLOv10モデルによる推論実行
- 信頼度閾値による検出結果のフィルタリング
- バウンディングボックスとラベルの描画
- 検出結果の構造化出力とファイル保存
参考文献
[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. https://arxiv.org/abs/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. https://cocodataset.org/
ソースコード
# プログラム名: 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:2405.14458.
# 特徴機能: NMSフリー検出機能。Non-Maximum Suppressionを不要とする設計により、後処理なしで物体検出を実現。本プログラムではCOCO 80クラス全体を検出
# 学習済みモデル: YOLOv10 COCO事前学習済みモデル(80クラス全体)
# モデルサイズ選択可能(デフォルト: 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 - 最大
# 方式設計:
# - 関連利用技術:
# - PyTorch: 深層学習フレームワーク、CUDA対応
# - OpenCV: 画像処理、カメラ制御、描画処理、動画入出力管理
# - 入力と出力:
# 入力: 動画(0:動画ファイル,1:カメラ,2:サンプル動画)
# 出力: 各フレームごとに全検出一覧をprint表示し、終了時にresult.txtに保存
# - 処理手順: 1.フレーム取得、2.YOLOv10推論、3.COCO 80クラス検出、4.信頼度閾値で選別、5.バウンディングボックス描画
# - 前処理/後処理: 前処理はYOLO内部(例: 640x640リサイズ、正規化)。後処理は信頼度閾値によるフィルタリング
# - 追加処理: デバイス自動選択(GPU/CPU)
# - 調整可能値: CONF_THRESH(検出信頼度閾値、デフォルト0.5)
# 環境: Windows想定(PillowでMeiryoフォントを使用)
# 前準備:
# - pip install -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126
# - pip install ultralytics opencv-python numpy pillow
import os
import time
import urllib.request
import tkinter as tk
from tkinter import filedialog
from datetime import datetime
import cv2
import numpy as np
import torch
from ultralytics import YOLO
from PIL import Image, ImageDraw, ImageFont
# ===== ガイダンス =====
print('=== YOLOv10物体検出プログラム ===')
print('概要: フレームごとに物体を検出し、矩形とラベルを描画して表示します')
print('機能: YOLOv10によるCOCO 80クラス検出(NMSフリー設計)')
print('操作: qキーで終了')
print('出力: 各フレームの全検出一覧をprint表示し、終了時にresult.txtに保存')
print('注意: YOLOv10モデルファイル(yolov10n.pt等)が必要です')
print()
# ===== 設定 =====
CONF_THRESH = 0.5
# フォント設定(Pillow、日本語表示)
FONT_PATH = 'C:/Windows/Fonts/meiryo.ttc'
FONT_MAIN = ImageFont.truetype(FONT_PATH, 20)
FONT_SMALL = ImageFont.truetype(FONT_PATH, 14)
# GPU/CPU自動選択
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f'デバイス: {str(device)}')
# ===== モデル選択 =====
print('使用するYOLOv10モデルを選択してください:')
print('n: Nano')
print('s: Small')
print('m: Medium')
print('l: Large')
print('x: Extra Large')
print()
MODEL_SIZE = input('選択 (n/s/m/l/x) [デフォルト: n]: ').strip().lower()
if MODEL_SIZE not in ['n', 's', 'm', 'l', 'x']:
MODEL_SIZE = 'n'
print('デフォルト(n)を使用します')
MODEL_NAME = f'yolov10{MODEL_SIZE}.pt'
print(f'選択されたモデル: {MODEL_NAME}')
# ===== モデル初期化 =====
try:
print(f'モデル初期化中...')
model = YOLO(MODEL_NAME)
model.to(device)
print('モデル初期化が完了しました')
except Exception as e:
print('モデルの初期化に失敗しました')
print(f'エラー: {e}')
print('注意: モデルファイルの存在とパスを確認してください')
exit()
# クラス名
CLASS_NAMES = model.names # id -> name
# クラス色生成(HSV→BGR)
def generate_colors(num_classes):
colors = []
for i in range(num_classes):
hue = int(180.0 * i / max(1, num_classes))
hsv = np.uint8([[[hue, 255, 255]]])
bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)[0][0]
colors.append((int(bgr[0]), int(bgr[1]), int(bgr[2])))
return colors
CLASS_COLORS = generate_colors(len(CLASS_NAMES))
frame_count = 0
results_log = []
def bgr_to_rgb(color_bgr):
return (int(color_bgr[2]), int(color_bgr[1]), int(color_bgr[0]))
# ===== フレーム処理関数 =====
def video_frame_processing(frame):
global frame_count
current_time = time.time()
frame_count += 1
# 推論実行
results = model.predict(frame, conf=CONF_THRESH, verbose=False, device=str(device))
res = results[0]
# 検出の取り出し
dets = []
if len(res.boxes) > 0:
boxes = res.boxes.xyxy.cpu().numpy().astype(int)
confs = res.boxes.conf.cpu().numpy()
clses = res.boxes.cls.cpu().numpy().astype(int)
# 信頼度降順
order = np.argsort(confs)[::-1]
boxes = boxes[order]
confs = confs[order]
clses = clses[order]
for (x1, y1, x2, y2), conf, cid in zip(boxes, confs, clses):
name = CLASS_NAMES.get(int(cid), str(int(cid)))
dets.append((x1, y1, x2, y2, name, float(conf)))
# 描画(矩形はOpenCV、テキストはPillow)
for x1, y1, x2, y2, name, conf in dets:
color = CLASS_COLORS[hash(name) % len(CLASS_COLORS)]
cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
# テキスト描画
img_pil = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(img_pil)
for x1, y1, x2, y2, name, conf in dets:
color = CLASS_COLORS[hash(name) % len(CLASS_COLORS)]
draw.text((x1, max(0, y1 - 22)), f'{name}', font=FONT_MAIN, fill=bgr_to_rgb(color))
draw.text((x1, y2 + 4), f'Conf:{conf:.2f}', font=FONT_SMALL, fill=(255, 255, 255))
# 画面左上に情報
draw.text((10, 10), f'YOLOv10 ({device.type}) | Frame: {frame_count} | Dets: {len(dets)}', font=FONT_MAIN, fill=(255, 255, 255))
draw.text((10, 36), '操作: q=終了', font=FONT_SMALL, fill=(255, 255, 0))
processed = cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR)
# 出力用の整形(全検出一覧)
parts = [f'n={len(dets)}']
for x1, y1, x2, y2, name, conf in dets:
parts.append(f'{name}:{conf:.2f}@{x1},{y1},{x2},{y2}')
result = ' | '.join(parts)
return processed, 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物体検出"
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に保存しました')