ゲーム画面でのアイテム認識と戦略決定
【概要】
物体検出AI「YOLO11」を用いたゲームAIの実装を体験する。ゲーム画面内の物体をリアルタイムで検出・分析するプログラムを実行し、プレイヤー、敵、アイテムの認識から戦略決定までの流れを確認する。Windows環境での実行手順、プログラムコード、実験のアイデアを含む。
主要技術:YOLO11(You Only Look Once version 11)
論文:Khanam, R., & Hussain, M. (2024). YOLOv11: An Overview of the Key Architectural Enhancements. arXiv:2410.17725(Ultralytics社はYOLO11について公式の論文を発表していない)
新規性・特徴:C3k2ブロック(畳み込み層とクロスステージ部分接続を組み合わせた構造。計算量を抑えながら特徴抽出の能力を保つ)、SPPF、C2PSAブロックを採用し、特徴抽出を強化している。YOLOv8mと比べ、YOLO11mはパラメータ数を22%削減しながらCOCOデータセットでより高いmAP(平均適合率の平均。物体検出の精度を表す指標)を達成する。ゲームAI、監視システム、自動運転などに応用できる。
リアルタイムゲームでは高速な状況認識が必要であり、YOLO11の高速な推論がゲームAIの意思決定に適している。
体験価値:ゲーム画面を模擬した環境でYOLO11の物体検出を体験し、AIがゲーム状況を認識・分析する過程を学べる。
利用可能なYOLO11モデル
YOLO11には以下のモデルが利用可能である。
- yolo11n.pt:Nano版(最軽量、約2.6Mパラメータ、最速)
- yolo11s.pt:Small版(約9.5Mパラメータ、軽量、高速)
- yolo11m.pt:Medium版(約20.1Mパラメータ、バランス型)
- yolo11l.pt:Large版(約25.4Mパラメータ、高精度)
- yolo11x.pt:Extra Large版(約57.0Mパラメータ、最高精度、処理は重い)
【目次】
1. Python開発環境,ライブラリ類
ここでは、最低限の事前準備について説明する。機械学習や深層学習を行う場合は、NVIDIA CUDA、Visual Studio、Cursorなどを追加でインストールすると便利である。これらについては別ページ https://www.kkaneko.jp/cc/dev/aiassist.html で解説しているので、必要に応じて参照すること。
2. Python 3.12 のインストール
Pythonのインストールを行い、Pythonのプログラムを実行する環境を整える。扱う環境は、Windows搭載パソコンである。金子研究室では、Python 3.12.10を推奨する。
[Windows での Python 3.12 のインストール手順を見るには、ここをクリック]
Windows での Python 3.12 のインストール
以下のいずれかの方法でPython 3.12をインストールする。Pythonがインストール済みの場合、この手順は不要である。
方法 1:winget によるインストール
【インストールコマンドの実行方法】
管理者権限でコマンドプロンプトを起動する(手順:Windowsキーまたはスタートメニュー → cmd と入力 → 右クリック → 「管理者として実行」)。そして、コマンド全体をコマンドプロンプトにコピー&ペーストする。
--scope machine を指定することで、システム全体(全ユーザー向け)にインストールされる。このオプションの実行には管理者権限が必要である。インストール完了後、コマンドプロンプトを再起動するとPATHが反映される。
REM Python 3.12 をシステム領域にインストール
winget install --id Python.Python.3.12 -e --scope machine --silent --accept-source-agreements --accept-package-agreements --override "/quiet InstallAllUsers=1 PrependPath=1 Include_test=0 Include_pip=1 Include_launcher=1 InstallLauncherAllUsers=1 TargetDir=\"C:\Program Files\Python312\""
if not "%ERRORLEVEL%"=="0" ( color 0c & echo Python 3.12 のインストールに失敗しました & ping 127.0.0.1 -n 6 >nul & color )
REM Python と Scripts を PATH 先頭に追加
powershell -NoProfile -Command "$p='C:\Program Files\Python312'; $s=\"$p\Scripts\"; if(Test-Path $p){$k=[Microsoft.Win32.Registry]::LocalMachine.OpenSubKey('SYSTEM\CurrentControlSet\Control\Session Manager\Environment',$true); $c=$k.GetValue('Path','',[Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames); $t=$k.GetValueKind('Path'); $new=$c; if((';'+$new+';') -notlike \"*;$p;*\"){$new=$p+';'+$new}; if((';'+$new+';') -notlike \"*;$s;*\"){$new=$s+';'+$new}; if($new -ne $c){$k.SetValue('Path',$new,$t)}; $k.Close()}"
REM 現在のセッションにも反映(システムPATHを再取得して連結)
for /f "usebackq tokens=2,*" %A in (`reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v Path`) do set "PATH=%B"
REM pip / wheel の更新
python -m pip install --no-user -U pip wheel
if not "%ERRORLEVEL%"=="0" ( color 0c & echo pip / wheel の更新に失敗しました & ping 127.0.0.1 -n 6 >nul & color )
方法 2:インストーラーによるインストール
- Python公式サイト(https://www.python.org/downloads/)にアクセスし、「Download Python 3.x.x」ボタンからWindows用インストーラーをダウンロードする。
- ダウンロードしたインストーラーを実行する。
- 初期画面の下部に表示される「Add python.exe to PATH」にチェックを入れてから「Customize installation」を選択する。このチェックを入れ忘れると、コマンドプロンプトから
pythonコマンドを実行できない。 - 「Install Python 3.xx for all users」にチェックを入れ、「Install」をクリックする。
インストールの確認
コマンドプロンプトで以下を実行する。
python --version
バージョン番号(例:Python 3.12.x)が表示されればインストール成功である。「'python' は、内部コマンドまたは外部コマンドとして認識されていません。」と表示される場合は、インストールが正常に完了していない。
3. Python の開発環境 Visual Studio Code のインストールと Python 用の設定
Python の開発環境Visual Studio Code(プログラムを編集するソフトウェア。以下、VS Code)を整える。
[Windows での Visual Studio Code のインストールと Python 用の設定手順を見るには、ここをクリック]
Windows での Visual Studio Code のインストールと Python 用の設定手順
1. VS Code と拡張機能のインストール
以下のコマンドにより,既存の VS Code を削除し,全ユーザー共有の設定で再インストールしたうえで,拡張機能(VS Code に機能を追加するソフトウェア)をまとめて導入する.
【インストールコマンドの実行方法】
管理者権限でコマンドプロンプトを起動する(手順:Windows キーまたはスタートメニュー → cmd と入力 → 右クリック → 「管理者として実行」)。そして,コマンド全体をコマンドプロンプトにコピー&ペーストする。
インストールコマンド
REM ============================================================
REM Microsoft Visual Studio Code
REM ============================================================
REM Build Tools + Desktop development with C++(VCTools)+ 追加コンポーネント(一括)
REM 未インストール時: winget で新規インストール
REM インストール済み時: setup.exe modify でコンポーネント追加(バージョンは変更しない)
winget list --id Microsoft.VisualStudio.BuildTools 2>nul | findstr /i "BuildTools" >nul 2>&1
if %ERRORLEVEL% EQU 0 (
for /f "usebackq delims=" %P in (`"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -products Microsoft.VisualStudio.Product.BuildTools -property installationPath`) do start /wait "" "C:\Program Files (x86)\Microsoft Visual Studio\Installer\setup.exe" modify --installPath "%P" --add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Workload.MSBuildTools --add Microsoft.VisualStudio.Component.VC.CMake.Project --add Microsoft.VisualStudio.Component.VC.Llvm.Clang --add Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset --add Microsoft.VisualStudio.Component.Windows11SDK.26100 --add Microsoft.VisualStudio.Component.VC.v143.x86.x64 --includeRecommended --quiet --norestart --nocache
if not "%ERRORLEVEL%"=="0" ( color 0c & echo Build Tools のコンポーネント追加に失敗しました & ping 127.0.0.1 -n 6 >nul & color )
) else (
winget install --scope machine --id Microsoft.VisualStudio.BuildTools -e --silent --disable-interactivity --force --accept-source-agreements --accept-package-agreements --override "--quiet --wait --norestart --nocache --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended --add Microsoft.VisualStudio.Workload.MSBuildTools --add Microsoft.VisualStudio.Component.VC.CMake.Project --add Microsoft.VisualStudio.Component.VC.Llvm.Clang --add Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset --add Microsoft.VisualStudio.Component.Windows11SDK.26100 --add Microsoft.VisualStudio.Component.VC.v143.x86.x64"
if not "%ERRORLEVEL%"=="0" ( color 0c & echo Build Tools のインストールに失敗しました & ping 127.0.0.1 -n 6 >nul & color )
)
REM 全ユーザー共有の拡張機能フォルダ
if not exist "C:\ProgramData\vscode-extensions" mkdir "C:\ProgramData\vscode-extensions"
icacls "C:\ProgramData\vscode-extensions" /grant "Everyone:(OI)(CI)M" /T
REM スタートメニューのショートカットを --extensions-dir 付きで再作成
if exist "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code" rmdir /s /q "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code"
if exist "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code.lnk" del "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code.lnk"
powershell -NoProfile -Command "$s=New-Object -ComObject WScript.Shell; $lnk=$s.CreateShortcut('C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code.lnk'); $lnk.TargetPath='C:\Program Files\Microsoft VS Code\Code.exe'; $lnk.Arguments='--extensions-dir \"C:\ProgramData\vscode-extensions\"'; $lnk.Save()"
REM ショートカットの検証
powershell -NoProfile -Command "$s=New-Object -ComObject WScript.Shell; $lnk=$s.CreateShortcut('C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code.lnk'); Write-Host 'TargetPath:' $lnk.TargetPath; Write-Host 'Arguments:' $lnk.Arguments"
REM ファイル / フォルダ右クリックの「Code で開く」を登録
reg add "HKLM\SOFTWARE\Classes\*\shell\VSCode\command" /ve /d "\"C:\Program Files\Microsoft VS Code\Code.exe\" --extensions-dir \"C:\ProgramData\vscode-extensions\" \"%1\"" /f
reg add "HKLM\SOFTWARE\Classes\Directory\shell\VSCode\command" /ve /d "\"C:\Program Files\Microsoft VS Code\Code.exe\" --extensions-dir \"C:\ProgramData\vscode-extensions\" \"%1\"" /f
reg add "HKLM\SOFTWARE\Classes\Directory\Background\shell\VSCode\command" /ve /d "\"C:\Program Files\Microsoft VS Code\Code.exe\" --extensions-dir \"C:\ProgramData\vscode-extensions\" \"%V\"" /f
REM --extensions-dir 付きで起動する code.cmd ラッパを作成
REM (%* を echo で書くと対話的 cmd で失われるため、PowerShell で [char]37+'*' を書き出す)
powershell -NoProfile -Command "$pct=[char]37; $q=[char]34; $c='@echo off'+[char]13+[char]10+$q+'C:\Program Files\Microsoft VS Code\bin\code.cmd'+$q+' --extensions-dir '+$q+'C:\ProgramData\vscode-extensions'+$q+' '+$pct+'*'+[char]13+[char]10; [IO.File]::WriteAllText('C:\ProgramData\vscode-extensions\vscode.cmd',$c,[Text.Encoding]::ASCII)"
REM 拡張機能のインストール
set "CODE=C:\Program Files\Microsoft VS Code\bin\code.cmd"
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --uninstall-extension GitHub.copilot
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --uninstall-extension GitHub.copilot-chat
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension ms-python.python
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension ms-python.vscode-pylance
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension ms-python.debugpy
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension MS-CEINTL.vscode-language-pack-ja
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension saoudrizwan.claude-dev
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension rust-lang.rust-analyzer
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension tamasfe.even-better-toml
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension anthropic.claude-code
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension almenon.arepl
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --list-extensions --show-versions
REM settings.json を作成(自動更新オフ、Python、Claude Code 設定)
if not exist "%APPDATA%\Code\User" mkdir "%APPDATA%\Code\User"
python -c "import json,os;data={'update.mode':'none','update.enableWindowsBackgroundUpdates':False,'extensions.autoUpdate':False,'python.defaultInterpreterPath':r'C:\Program Files\Python312\python.exe','claudeCode.environmentVariables':[{'name':'ANTHROPIC_API_KEY','value':'not-needed'},{'name':'ANTHROPIC_AUTH_TOKEN','value':'ollama'},{'name':'ANTHROPIC_BASE_URL','value':'http://localhost:11434'},{'name':'ANTHROPIC_MODEL','value':'glm-4.7-flash'},{'name':'CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC','value':'1'}]};p=os.path.join(os.environ['APPDATA'],'Code','User','settings.json');open(p,'w',encoding='utf-8').write(json.dumps(data,indent=4));print('Done:',p)"
REM 自動更新の抑止ポリシー(settings.json に加えて、レジストリ側でも明示的にオフ)
reg add "HKLM\SOFTWARE\Policies\Microsoft\VSCode" /v "UpdateMode" /t REG_SZ /d "none" /f
echo === セットアップ完了 ===
2. Python インタプリタの選択
同一マシンに複数の Python がインストールされている場合,VS Code で使用する Python 本体(インタプリタ:Python プログラムを解釈・実行するソフトウェア)を選択する必要がある.
- コマンドパレット(コマンド名で機能を呼び出す VS Code の入力欄)を開く(
Ctrl+Shift+P) Python: Select Interpreterと入力する
- 表示される一覧から,使用する Python(例:
C:\Program Files\Python312\python.exe)を選択する.
必要なPythonライブラリのインストール
管理者権限でコマンドプロンプトを起動する
(手順:Windowsキーまたはスタートメニュー → cmd と入力 → 右クリック → 「管理者として実行」)。
次のコマンドを実行する。Python 3.12 の環境に対してインストールする。
REM PyTorch をインストール(GPU対応版)
set "CUDA_TAG=cu128"
set "PYTHON_PATH=C:\Program Files\Python312"
"%PYTHON_PATH%\Scripts\pip" install --no-user -U numpy torch torchvision torchaudio --index-url https://download.pytorch.org/whl/%CUDA_TAG%
"%PYTHON_PATH%\Scripts\pip" install --no-user -U ultralytics opencv-python matplotlib japanize-matplotlib pillow
4. プログラムコード
概要
このプログラムは、YOLO11による物体検出をゲームAIに応用したデモである。プログラム内で生成されるゲームシーンに対してリアルタイム物体検出を実行し、検出結果に基づいてゲーム戦略を自動で決定する。実画像(人物、果物)を組み込んだシミュレーション環境により、物体検出の動作を確認できる。
主要技術
YOLO11 (You Only Look Once version 11)
Ultralyticsが2024年に公開した物体検出モデルである[1]。C3k2ブロック、SPPF、C2PSAブロックを採用し、従来のYOLOシリーズと比較して検出精度と処理速度のバランスが向上している[2]。このプログラムではNano版(yolo11n.pt)を使用し、約2.6MパラメータのモデルでCOCOデータセット80クラスの物体検出を行う。
リアルタイム処理のアーキテクチャ
単一の推論で複数物体の位置とクラスを同時に検出する。バウンディングボックス(物体を囲む矩形)の座標、信頼度スコア、クラス確率を一度に出力することで、リアルタイムのゲーム環境での応用を可能にしている[3]。
技術的特徴
物体検出とゲームロジックの統合
検出された物体の位置情報から物体間のユークリッド距離(2点間の直線距離)を計算し、危険判定距離(250ピクセル)、攻撃判定距離(350ピクセル)、アイテム収集距離(200ピクセル)のしきい値に基づいて戦略を決定する。この距離に基づく判定により、ゲーム内での行動選択を実現している。
実画像を用いたシミュレーション
OpenCVのサンプル画像(basketball2.png、fruits.jpg)を実行時にダウンロードして使用し、実際の画像に対する物体検出の動作を確認できる。
実装の特色
ゲーム状態の管理
フレームごとの危険状態の変化を追跡し、累積の危険カウントによる敗北判定と、生存フレーム数による勝利判定を実装している。プレイヤー、敵、アイテムの位置を更新し、振動運動や直線移動などの動きを組み込んでいる。
可視化と結果記録
OpenCVによるリアルタイム表示とMatplotlibによる結果表示を併用する。日本語フォント(メイリオ)を使用した情報表示により、戦略の状態やスコアを提示する。処理結果はテキストファイル(result.txt)に保存され、推論時間、検出数、戦略決定の履歴を記録する。
参考文献
[1] Ultralytics. Ultralytics YOLO11. Ultralytics Documentation. https://docs.ultralytics.com/models/yolo11/
[2] Khanam, R., & Hussain, M. (2024). YOLOv11: An Overview of the Key Architectural Enhancements. arXiv preprint arXiv:2410.17725. https://arxiv.org/abs/2410.17725
[3] Redmon, J., Divvala, S., Girshick, R., & Farhadi, A. (2016). You Only Look Once: Unified, Real-Time Object Detection. Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition (CVPR), pp. 779-788. https://arxiv.org/abs/1506.02640
ソースコード
# YOLO11ゲームAI Visionデモ
# 特徴技術名: YOLO11 (You Only Look Once version 11)
# 出典: Ultralytics YOLO11 Documentation, https://docs.ultralytics.com/models/yolo11/
# 特徴機能: C3k2ブロックとC2PSAブロックによるリアルタイム物体検出。単一推論で複数物体の位置とクラスを同時検出
# 学習済みモデル: yolo11n.pt (Nano版、約2.6Mパラメータ、ファイルサイズ約5.4MB、COCOデータセット80クラス対応、https://github.com/ultralytics/assets/releases/)
# 方式設計:
# - 関連利用技術: OpenCV (画像処理・表示)、NumPy (数値計算)、Matplotlib (結果可視化)、japanize-matplotlib (日本語表示)、Pillow (日本語文字の描画)
# - 入力と出力: 入力: プログラム内で生成される実画像を含むゲームシーン、出力: 物体検出結果の可視化画像とテキスト情報
# - 処理手順: 1.ゲームシーン生成 2.YOLO11による物体検出 3.検出結果の解析 4.ゲーム戦略決定 5.結果可視化
# - 前処理、後処理: 前処理: なし(生成した画像をそのまま入力)、後処理: 検出結果のクラス名による振り分け
# - 追加処理: ゲームロジックによる物体間距離計算と戦略決定アルゴリズム
# - 調整を必要とする設定値: DANGER_DISTANCE (危険判定距離、デフォルト250ピクセル)
# 将来方策: 検出物体間の最適距離を自動学習するため、複数回の実行結果から統計的に最適値を算出する機能
# その他の重要事項: デモ用のシミュレーション環境。実画像を使用してYOLO検出を機能させる。実行にはインターネット接続が必要
# 前準備:
# - 管理者権限のコマンドプロンプトで次を実行する(Python 3.12 環境が対象)
# set "CUDA_TAG=cu128"
# set "PYTHON_PATH=C:\Program Files\Python312"
# "%PYTHON_PATH%\Scripts\pip" install --no-user -U numpy torch torchvision torchaudio --index-url https://download.pytorch.org/whl/%CUDA_TAG%
# "%PYTHON_PATH%\Scripts\pip" install --no-user -U ultralytics opencv-python matplotlib japanize-matplotlib pillow
import cv2
import numpy as np
import time
import urllib.request
from PIL import Image, ImageDraw, ImageFont
from ultralytics import YOLO
import matplotlib.pyplot as plt
import japanize_matplotlib
# 定数定義
RANDOM_SEED = 42
PLAYER_COLOR = (0, 255, 0)
ENEMY_COLOR = (0, 0, 255)
TEXT_COLOR = (255, 255, 255)
DANGER_DISTANCE = 250 # 危険判定距離(ピクセル)
ATTACK_DISTANCE = 350 # 攻撃判定距離(ピクセル)
ITEM_COLLECT_DISTANCE = 200 # アイテム収集距離(ピクセル)
WIN_SCORE = 50 # 勝利スコア
WIN_SURVIVAL_FRAMES = 30 # 勝利に必要な生存フレーム数
DANGER_COUNT_LIMIT = 3 # 危険状態の許容回数
# ゲームオブジェクトの設定
SCENE_WIDTH = 640 # シーン幅
SCENE_HEIGHT = 480 # シーン高さ
PLAYER_POS = (100, 300) # プレイヤー初期位置
PLAYER_SIZE = (50, 100) # プレイヤーサイズ(幅、高さ)
ENEMY_BASE_POS = (400, 250) # 敵の基準位置
ENEMY_SIZE = (50, 100) # 敵のサイズ(幅、高さ)
ENEMY_MOVE_SPEED = 50 # 敵の移動速度(ピクセル/秒)
ITEM_BASE_POS = (300, 200) # アイテムの基準位置(振動の中心)
ITEM_RADIUS = 20 # アイテムの半径
ITEM_AMPLITUDE = 50 # アイテムの振幅
ITEM_FREQUENCY = 0.5 # アイテムの振動周波数(Hz)
# 表示設定
FONT_SCALE = 0.5 # 小さいテキスト用
FONT_SCALE_MEDIUM = 0.6 # 中サイズテキスト用
FONT_THICKNESS = 1 # 通常のテキスト
FONT_THICKNESS_BOLD = 2 # 太字テキスト
FRAME_DELAY = 30 # フレーム表示待機時間(ミリ秒)
# 日本語フォント設定
FONT_PATH = 'C:/Windows/Fonts/meiryo.ttc'
FONT_SIZE = 20
# 乱数シード設定
np.random.seed(RANDOM_SEED)
def draw_text(img, text, pos, scale=FONT_SCALE, color=TEXT_COLOR, thickness=FONT_THICKNESS):
"""テキスト描画の共通関数(英語用)"""
cv2.putText(img, text, pos, cv2.FONT_HERSHEY_SIMPLEX, scale, color, thickness)
def draw_japanese_text(img, text, pos, font_size=FONT_SIZE, color=(255, 255, 255)):
"""日本語テキスト描画関数"""
font = ImageFont.truetype(FONT_PATH, font_size)
img_pil = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(img_pil)
draw.text(pos, text, font=font, fill=color[::-1]) # BGRからRGBに変換
return cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR)
class GameAIVision:
def __init__(self):
print('========================================')
print('YOLO11ゲームAI Visionデモ')
print('========================================')
print('\n【プログラム概要】')
print('YOLO11による物体検出を使用したゲーム画面解析デモ')
print('実画像を使用してYOLO検出を実現します')
print('\n【操作方法】')
print('- qキー: プログラム終了')
print('- 自動実行: 30フレームまたは勝利/敗北まで継続')
print('\n【ゲームルール】')
print('- プレイヤー(人物画像)は敵(車両画像)を避けながらアイテム(果物画像)を収集')
print(f'- アイテムに{ITEM_COLLECT_DISTANCE}ピクセル以内に接近すると10点獲得')
print(f'- {WIN_SCORE}点獲得または{WIN_SURVIVAL_FRAMES}フレーム生存で勝利')
print(f'- 敵との距離が{DANGER_DISTANCE}ピクセル未満が{DANGER_COUNT_LIMIT}回で敗北')
print('\n【注意事項】')
print('- 実画像を使用してYOLO検出を実現')
print('- インターネット接続が必要(画像ダウンロード用)')
print('========================================\n')
print('YOLO11モデルを初期化中...')
self.model = YOLO('yolo11n.pt')
print('YOLO11モデルの初期化完了')
# 実画像をダウンロード
print('ゲーム用画像を準備中...')
self.load_game_images()
self.frame_count = 0
self.score = 0
self.survival_frames = 0
self.danger_count = 0
self.in_danger_this_frame = False # 現在のフレームで危険状態かどうか
self.was_in_danger = False # 前フレームで危険状態だったか
self.results_log = []
self.start_time = time.time()
self.collected_items = set() # 収集済みアイテムの管理(中心座標ベース)
# ゲームオブジェクトの実際の位置
self.actual_player_center = [PLAYER_POS[0] + PLAYER_SIZE[0]//2,
PLAYER_POS[1] + PLAYER_SIZE[1]//2]
self.actual_item_center = list(ITEM_BASE_POS) # アイテムの中心座標(振動の中心)
def load_game_images(self):
"""ゲーム用の実画像をダウンロード"""
# 人物画像(プレイヤー用) - OpenCVのサンプル画像を使用
person_url = "https://raw.githubusercontent.com/opencv/opencv/master/samples/data/basketball2.png"
resp = urllib.request.urlopen(person_url)
img_array = np.asarray(bytearray(resp.read()), dtype=np.uint8)
self.person_img = cv2.imdecode(img_array, cv2.IMREAD_COLOR)
# 中央部分を切り出し
h, w = self.person_img.shape[:2]
crop_x = w // 4
crop_y = 0
crop_w = w // 2
crop_h = h
self.person_img = self.person_img[crop_y:crop_y+crop_h, crop_x:crop_x+crop_w]
self.person_img = cv2.resize(self.person_img, PLAYER_SIZE)
# 果物画像(アイテム用)
fruit_url = "https://raw.githubusercontent.com/opencv/opencv/master/samples/data/fruits.jpg"
resp = urllib.request.urlopen(fruit_url)
fruits_img = cv2.imdecode(np.asarray(bytearray(resp.read()), dtype=np.uint8), cv2.IMREAD_COLOR)
# 果物画像から一部を切り出し
self.fruit_img = fruits_img[50:150, 50:150]
self.fruit_img = cv2.resize(self.fruit_img, (ITEM_RADIUS*2, ITEM_RADIUS*2))
# 車両画像(敵用)を生成
self.vehicle_img = np.ones((ENEMY_SIZE[1], ENEMY_SIZE[0], 3), dtype=np.uint8) * 50
# 車体
car_body = [(5, ENEMY_SIZE[1]//2),
(ENEMY_SIZE[0]-5, ENEMY_SIZE[1]//2),
(ENEMY_SIZE[0]-5, ENEMY_SIZE[1]-10),
(5, ENEMY_SIZE[1]-10)]
cv2.fillPoly(self.vehicle_img, [np.array(car_body, np.int32)], (100, 100, 150))
# 窓
window_top = ENEMY_SIZE[1]//2 + 5
window_height = 15
cv2.rectangle(self.vehicle_img, (10, window_top), (ENEMY_SIZE[0]-10, window_top+window_height), (150, 200, 255), -1)
# タイヤ
tire_y = ENEMY_SIZE[1] - 5
cv2.circle(self.vehicle_img, (15, tire_y), 5, (30, 30, 30), -1)
cv2.circle(self.vehicle_img, (ENEMY_SIZE[0]-15, tire_y), 5, (30, 30, 30), -1)
print('画像の準備完了')
def create_game_scene(self):
scene = np.ones((SCENE_HEIGHT, SCENE_WIDTH, 3), dtype=np.uint8) * 50
elapsed_time = time.time() - self.start_time
# プレイヤー(人物画像)を配置
x1, y1 = PLAYER_POS
x2, y2 = x1 + PLAYER_SIZE[0], y1 + PLAYER_SIZE[1]
scene[y1:y2, x1:x2] = self.person_img
draw_text(scene, 'Player', (x1 - 10, y1 - 10))
# 敵(車両画像)を配置
enemy_offset = int(elapsed_time * ENEMY_MOVE_SPEED) % 100
enemy_x = ENEMY_BASE_POS[0] + enemy_offset
enemy_y = ENEMY_BASE_POS[1]
self.actual_enemy_center = [enemy_x + ENEMY_SIZE[0]//2, enemy_y + ENEMY_SIZE[1]//2]
scene[enemy_y:enemy_y+ENEMY_SIZE[1], enemy_x:enemy_x+ENEMY_SIZE[0]] = self.vehicle_img
draw_text(scene, 'Enemy', (enemy_x - 10, enemy_y - 10))
# アイテム(果物画像)を配置
item_x = ITEM_BASE_POS[0]
item_y = ITEM_BASE_POS[1] + int(np.sin(elapsed_time * 2 * np.pi * ITEM_FREQUENCY) * ITEM_AMPLITUDE)
self.actual_item_current_pos = [item_x, item_y]
item_y1 = max(0, item_y - ITEM_RADIUS)
item_y2 = min(SCENE_HEIGHT, item_y + ITEM_RADIUS)
item_x1 = max(0, item_x - ITEM_RADIUS)
item_x2 = min(SCENE_WIDTH, item_x + ITEM_RADIUS)
if item_y2 > item_y1 and item_x2 > item_x1:
img_h = item_y2 - item_y1
img_w = item_x2 - item_x1
resized_fruit = cv2.resize(self.fruit_img, (img_w, img_h))
scene[item_y1:item_y2, item_x1:item_x2] = resized_fruit
draw_text(scene, 'Item', (item_x - 20, item_y - 20))
return scene
def detect_objects(self, frame):
results = self.model(frame, verbose=False)
detections = []
for r in results:
boxes = r.boxes
if boxes is not None:
for box in boxes:
x1, y1, x2, y2 = box.xyxy[0].cpu().numpy()
conf = box.conf[0].cpu().numpy()
cls = int(box.cls[0].cpu().numpy())
class_name = self.model.names[cls]
detections.append({
'bbox': [int(x1), int(y1), int(x2), int(y2)],
'confidence': float(conf),
'class': class_name,
'center': [(x1 + x2) / 2, (y1 + y2) / 2]
})
return detections
def analyze_game_state(self, detections):
game_state = {
'player_pos': self.actual_player_center, # 実際の位置を使用
'enemies': [self.actual_enemy_center], # 実際の位置を使用
'items': [self.actual_item_current_pos], # 実際の位置を使用
'vehicles': [],
'strategy': '',
'game_status': 'PLAYING',
'yolo_detected': len(detections) > 0 # YOLO検出の有無を記録
}
# YOLO検出結果を活用
for det in detections:
center = det['center']
cls = det['class']
# YOLOが検出した物体を記録
if cls == 'person':
# 人物検出位置を更新(より正確な位置)
if abs(center[0] - self.actual_player_center[0]) < 100:
game_state['player_pos'] = center
elif cls in ['car', 'truck', 'bus']:
# 車両検出
if abs(center[0] - self.actual_enemy_center[0]) < 100:
game_state['enemies'][0] = center
elif cls in ['apple', 'orange', 'banana']:
# 果物検出
if abs(center[0] - self.actual_item_current_pos[0]) < 100:
game_state['items'][0] = center
# 戦略決定とスコア計算
self.in_danger_this_frame = False # 現在のフレームの危険状態をリセット
if game_state['player_pos']:
# 敵との距離チェック
if game_state['enemies']:
enemy_pos = game_state['enemies'][0]
enemy_distance = np.sqrt((enemy_pos[0] - game_state['player_pos'][0])**2 +
(enemy_pos[1] - game_state['player_pos'][1])**2)
if enemy_distance < DANGER_DISTANCE:
self.in_danger_this_frame = True
# 前フレームで危険状態でなかった場合のみカウントを増やす
if not self.was_in_danger:
self.danger_count += 1
game_state['strategy'] = f'DANGER: 敵が近すぎます (距離: {enemy_distance:.0f}px)'
if self.danger_count >= DANGER_COUNT_LIMIT:
game_state['game_status'] = 'ゲームオーバー'
elif enemy_distance < ATTACK_DISTANCE:
game_state['strategy'] = f'ATTACK: 敵が接近中 (距離: {enemy_distance:.0f}px)'
else:
game_state['strategy'] = f'EXPLORE: アイテム探索 (敵距離: {enemy_distance:.0f}px)'
else:
game_state['strategy'] = 'PATROL: 脅威なし'
# 生存フレームは毎フレームカウント
self.survival_frames += 1
# アイテム収集チェック
if game_state['items']:
for item_pos in game_state['items']:
item_distance = np.sqrt((item_pos[0] - game_state['player_pos'][0])**2 +
(item_pos[1] - game_state['player_pos'][1])**2)
# アイテムIDを振動の中心座標で生成
item_id = f"{ITEM_BASE_POS[0]}_{ITEM_BASE_POS[1]}"
# 収集範囲内かつ未収集のアイテムの場合
if item_distance < ITEM_COLLECT_DISTANCE and item_id not in self.collected_items:
self.score += 10
self.collected_items.add(item_id)
game_state['strategy'] = f'COLLECT: アイテム収集 (+10点, 距離: {item_distance:.0f}px)'
break
# 次フレームのために危険状態を記録
self.was_in_danger = self.in_danger_this_frame
# 勝利判定
if self.score >= WIN_SCORE:
game_state['game_status'] = f'勝利(スコア{self.score}点達成)'
elif self.survival_frames >= WIN_SURVIVAL_FRAMES:
game_state['game_status'] = f'勝利({self.survival_frames}フレーム生存)'
return game_state
def visualize_results(self, frame, detections, game_state):
result_frame = frame.copy()
# YOLO検出枠を描画
for det in detections:
x1, y1, x2, y2 = det['bbox']
color = (255, 0, 255) # 紫色でYOLO検出を表示
cv2.rectangle(result_frame, (x1, y1), (x2, y2), color, 2)
label = f"YOLO: {det['class']} {det['confidence']:.2f}"
draw_text(result_frame, label, (x1, y1-10), FONT_SCALE, color, FONT_THICKNESS_BOLD)
# ゲーム状態の可視化
px, py = int(game_state['player_pos'][0]), int(game_state['player_pos'][1])
cv2.circle(result_frame, (px, py), 5, PLAYER_COLOR, -1)
if game_state['enemies']:
ex, ey = int(game_state['enemies'][0][0]), int(game_state['enemies'][0][1])
cv2.circle(result_frame, (ex, ey), 5, ENEMY_COLOR, -1)
cv2.line(result_frame, (px, py), (ex, ey), (128, 128, 128), 1)
# ゲーム情報を日本語で表示
result_frame = draw_japanese_text(result_frame, f"戦略: {game_state['strategy']}", (10, 30), 24, TEXT_COLOR)
result_frame = draw_japanese_text(result_frame, f"スコア: {self.score} | 生存: {self.survival_frames}/{WIN_SURVIVAL_FRAMES}", (10, 60), 20, (0, 255, 255))
result_frame = draw_japanese_text(result_frame, f"状態: {game_state['game_status']}", (10, 90), 20, (255, 255, 0))
# 統計情報
yolo_status = f"YOLO検出: {len(detections)}個"
info_text = f"Frame: {self.frame_count} | {yolo_status} | Danger: {self.danger_count}/{DANGER_COUNT_LIMIT}"
draw_text(result_frame, info_text, (10, 460), FONT_SCALE_MEDIUM, TEXT_COLOR)
return result_frame
def save_results(self):
with open('result.txt', 'w', encoding='utf-8') as f:
f.write('YOLO11リアルタイム物体検出デモ - 実行結果\n')
f.write('=' * 50 + '\n')
f.write('実画像を使用したYOLO検出デモ\n')
f.write('=' * 50 + '\n')
for log in self.results_log:
f.write(log + '\n')
f.write(f'\n最終スコア: {self.score}点\n')
f.write(f'最終生存フレーム数: {self.survival_frames}フレーム\n')
f.write(f'収集アイテム数: {len(self.collected_items)}個\n')
f.write(f'危険カウント: {self.danger_count}/{DANGER_COUNT_LIMIT}\n')
print('処理結果をresult.txtに保存しました')
def run_demo(self, num_frames=30):
print('\nデモを開始します...\n')
# Matplotlib用の図を準備
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
axes = axes.flatten()
saved_frames = []
for i in range(num_frames):
self.frame_count = i
# ゲームシーンを生成
scene = self.create_game_scene()
# 物体検出
start_time = time.time()
detections = self.detect_objects(scene)
inference_time = (time.time() - start_time) * 1000
# ゲーム状態分析
game_state = self.analyze_game_state(detections)
# 結果表示
result = self.visualize_results(scene, detections, game_state)
# OpenCVウィンドウでリアルタイム表示
cv2.imshow('YOLO11 Game AI Vision Demo', result)
# フレームごとに出力
yolo_info = f"YOLO検出数: {len(detections)}"
log_text = f'フレーム {i}: 推論時間 {inference_time:.2f}ms, スコア {self.score}点, {yolo_info}, 戦略: {game_state["strategy"]}'
print(log_text)
self.results_log.append(log_text)
# Matplotlib用に最初の6フレームを保存
if i < 6:
saved_frames.append((result.copy(), game_state['strategy']))
# キー入力待機
if cv2.waitKey(FRAME_DELAY) & 0xFF == ord('q'):
print('\nユーザーによる中断')
break
# ゲーム終了判定
if '勝利' in game_state['game_status'] or 'ゲームオーバー' in game_state['game_status']:
print(f'\n*** {game_state["game_status"]} ***')
print(f'最終スコア: {self.score}点')
print(f'最終生存フレーム数: {self.survival_frames}フレーム')
print(f'収集アイテム数: {len(self.collected_items)}個')
self.results_log.append(f'\n*** {game_state["game_status"]} ***')
self.results_log.append(f'最終スコア: {self.score}点')
self.results_log.append(f'最終生存フレーム数: {self.survival_frames}フレーム')
break
cv2.destroyAllWindows()
# Matplotlibで結果を表示
for idx, (frame, strategy) in enumerate(saved_frames):
axes[idx].imshow(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
axes[idx].set_title(f'フレーム {idx}: {strategy}', fontsize=10)
axes[idx].axis('off')
# 未使用のサブプロットを非表示
for j in range(len(saved_frames), 6):
axes[j].axis('off')
plt.suptitle('YOLO11 Game AI Vision デモ結果', fontsize=14)
plt.tight_layout()
plt.savefig('game_ai_yolo11_results.png', dpi=150)
print('\n結果を game_ai_yolo11_results.png に保存しました')
# 結果をテキストファイルに保存
self.save_results()
# 結果表示
print('\n【実行結果の見方】')
print('- 実画像を使用してYOLO11が物体を検出')
print('- 紫枠: YOLO11による検出結果')
print('- 緑点: プレイヤー中心、赤点: 敵中心')
print('- 灰色線: プレイヤーと敵の距離')
print('- result.txtに詳細ログを保存')
plt.show()
# メイン処理
if __name__ == "__main__":
ai = GameAIVision()
ai.run_demo()
5. 使用方法と結果の見方
実行手順
- 上記のプログラムを実行する。
- 初回実行時はYOLO11モデル(yolo11n.pt)が自動的にダウンロードされる。
- OpenCVのウィンドウにゲーム画面と検出結果が表示される。
qキーを押すと途中で終了できる。 - 実行終了後、最初の6フレームの解析結果がMatplotlibのウィンドウに表示され、game_ai_yolo11_results.png として保存される。
結果の解釈指針
紫枠はYOLO11による検出結果、緑点はプレイヤーの中心、赤点は敵の中心、灰色線はプレイヤーと敵の距離を表す。推論時間はAIが1フレームを処理する時間で、ゲームでは33ミリ秒以下(30FPS)が目安である。これより遅いとゲームの反応が鈍くなる。
6. 実験・探求のアイデア
実験要素
- ゲームパラメータの調整:DANGER_DISTANCE、ATTACK_DISTANCE、WIN_SCOREなどの定数を変更し、ゲームの難易度とAI戦略の変化を観察する。
- 物体配置の変更:PLAYER_POS、ENEMY_BASE_POS、ITEM_BASE_POSなどの定数を変更し、異なるゲームレイアウトでの検出結果を確認する。
- 検出クラスの拡張:analyze_game_state()メソッド内の 'apple', 'orange', 'banana' 以外のクラスをアイテムとして認識させ、検出の多様性を確認する。
体験・実験・探求のアイデア
- 動的シーンへの対応:ENEMY_MOVE_SPEEDの値を大きくして高速移動を再現し、物体検出が追随できる限界を探る。
- マルチオブジェクト戦略:複数の敵やアイテムを配置し、優先順位を付ける処理を実装して、意思決定の方針を探求する。
- カスタムクラスの活用:YOLO11が検出できる80クラスから、ゲームに適した要素(動物、食べ物、道具など)を追加し、より複雑なゲームシナリオを構築する。