ゲーム画面でのアイテム認識と戦略決定
【概要】 物体検出AI「YOLO11」を用いたゲームAI実装を体験する。YOLO11を活用し、ゲーム画面内の物体をリアルタイムで検出・分析するシステムを構築する。プレイヤー、敵、アイテムの認識から戦略決定まで、AIがゲーム状況を理解する過程を実際のコードで学習する。Windows環境での実行手順、プログラムコード、実験アイデアを含む。


目次
1. 概要
主要技術:YOLO11(You Only Look Once version 11)
論文:「YOLO11: An Overview of the Key Architectural Enhancements」(2024年)
新規性・特徴:C3k2ブロックによる高速推論と高精度を実現し、リアルタイム物体検出において従来モデルを上回る性能を達成。C3k2ブロックは、畳み込み層とクロスステージ部分接続を組み合わせたアーキテクチャで、計算量を削減しながら特徴抽出能力を維持する。ゲームAI、監視システム、自動運転などに応用可能。
リアルタイムゲームでは高速な状況認識が必要であり、YOLO11の高速推論能力がゲームAIの意思決定に適している。
体験価値:実際のゲーム画面を模擬した環境でYOLO11の物体検出能力を体験し、AIがどのようにゲーム状況を認識・分析するかを学習できる。
利用可能なYOLO11モデル
YOLO11には以下のモデルが利用可能である:
- yolo11n.pt:Nano版(最軽量、約2.6MB、最速)
- yolo11s.pt:Small版(軽量、高速)
- yolo11m.pt:Medium版(バランス型)
- yolo11l.pt:Large版(高精度)
- yolo11x.pt:Extra Large版(最高精度、重い)
2. 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/
必要なPythonライブラリのインストール
以下のコマンドを実行する(管理者権限のコマンドプロンプトで実行)。
pip install ultralytics opencv-python numpy matplotlib japanize-matplotlib
YOLO11ゲームAI Visionデモ
3. プログラムコード
YOLO11は実世界の物体を検出するため、ゲーム要素を既存の検出可能クラスにマッピングする。personクラスは位置で役割を区別し、日用品クラスをアイテムとして活用する。
# YOLO11ゲームAI Visionデモ
# 特徴技術名: YOLO11 (You Only Look Once version 11)
# 出典: Ultralytics YOLO11 Documentation, https://docs.ultralytics.com/models/yolo11/ (2024)
# 特徴機能: C3k2ブロックによる高速リアルタイム物体検出。単一推論で複数物体の位置とクラスを同時検出
# 学習済みモデル: yolo11n.pt (Nano版、約2.6MB、COCOデータセット80クラス対応、https://github.com/ultralytics/assets/releases/)
# 方式設計:
# - 関連利用技術: OpenCV (画像処理・表示)、NumPy (数値計算)、Matplotlib (結果可視化)、japanize-matplotlib (日本語表示)
# - 入力と出力: 入力: プログラム内で生成されるゲーム風シミュレーション画像、出力: 物体検出結果の可視化画像とテキスト情報
# - 処理手順: 1.ゲームシーン生成 2.YOLO11による物体検出 3.検出結果の解析 4.ゲーム戦略決定 5.結果可視化
# - 前処理、後処理: 前処理: なし(RGB画像をそのまま入力)、後処理: 検出結果のフィルタリング(信頼度による)
# - 追加処理: ゲームロジックによる物体間距離計算と戦略決定アルゴリズム
# - 調整を必要とする設定値: DANGER_DISTANCE (危険判定距離、デフォルト100ピクセル)
# 将来方策: 検出物体間の最適距離を自動学習するため、複数回の実行結果から統計的に最適値を算出する機能
# その他の重要事項: デモ用のシミュレーション環境のため、実際のゲーム画面での動作には調整が必要
# 前準備:
# - pip install ultralytics opencv-python numpy matplotlib japanize-matplotlib
import cv2
import numpy as np
import time
import os
try:
from ultralytics import YOLO
except ImportError:
print('Error: ultralyticsライブラリがインストールされていません')
print('pip install ultralytics を実行してください')
exit()
try:
import matplotlib.pyplot as plt
import japanize_matplotlib
except ImportError:
print('Error: matplotlib または japanize-matplotlib がインストールされていません')
print('pip install matplotlib japanize-matplotlib を実行してください')
exit()
# 定数定義
RANDOM_SEED = 42
PLAYER_COLOR = (0, 255, 0)
ENEMY_COLOR = (0, 0, 255)
ITEM_COLOR = (255, 255, 0)
VEHICLE_COLOR = (128, 128, 128)
TEXT_COLOR = (255, 255, 255)
DANGER_DISTANCE = 100 # 調整可能な設定値:危険判定距離(ピクセル)
ATTACK_DISTANCE = 200 # 攻撃判定距離(ピクセル)
WIN_SCORE = 50 # 勝利スコア
WIN_SURVIVAL_FRAMES = 5 # 勝利に必要な生存フレーム数
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)
VEHICLE_POS = (500, 380) # 車両の位置
VEHICLE_SIZE = (80, 40) # 車両のサイズ(幅、高さ)
# 表示設定
FONT_SCALE = 0.5 # 小さいテキスト用
FONT_SCALE_LARGE = 0.8 # 大きいテキスト用
FONT_SCALE_MEDIUM = 0.6 # 中サイズテキスト用
FONT_THICKNESS = 1 # 通常のテキスト
FONT_THICKNESS_BOLD = 2 # 太字テキスト
FRAME_DELAY = 30 # フレーム表示待機時間(ミリ秒)
# 乱数シード設定
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)
class GameAIVision:
def __init__(self):
print('YOLO11モデルを初期化中...')
try:
self.model = YOLO('yolo11n.pt')
print('YOLO11モデルの初期化完了')
except Exception as e:
print(f'Error: YOLO11モデルの初期化に失敗しました: {e}')
exit()
self.frame_count = 0
self.score = 0
self.survival_frames = 0
self.danger_count = 0
self.results_log = []
self.last_print_time = time.time()
self.start_time = time.time()
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]
cv2.rectangle(scene, (x1, y1), (x2, y2), PLAYER_COLOR, -1)
draw_text(scene, 'Player', (x1 - 10, y1 - 10))
# 敵キャラクター(時間ベースの動き)
enemy_x = ENEMY_BASE_POS[0] + int(elapsed_time * ENEMY_MOVE_SPEED) % 100
enemy_y = ENEMY_BASE_POS[1]
cv2.rectangle(scene, (enemy_x, enemy_y),
(enemy_x + ENEMY_SIZE[0], enemy_y + ENEMY_SIZE[1]),
ENEMY_COLOR, -1)
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)
cv2.circle(scene, (item_x, item_y), ITEM_RADIUS, ITEM_COLOR, -1)
draw_text(scene, 'Item', (item_x - 20, item_y - 20))
# 車両
x, y = VEHICLE_POS
pts = np.array([[x, y], [x + VEHICLE_SIZE[0], y],
[x + VEHICLE_SIZE[0], y + VEHICLE_SIZE[1]],
[x, y + VEHICLE_SIZE[1]]], np.int32)
cv2.fillPoly(scene, [pts], VEHICLE_COLOR)
draw_text(scene, 'Vehicle', (x - 10, y - 10))
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': None,
'enemies': [],
'items': [],
'vehicles': [],
'strategy': '',
'game_status': 'PLAYING'
}
# 検出結果の分類
for det in detections:
center = det['center']
cls = det['class']
if cls == 'person':
if center[0] < 200: # 左側をプレイヤーと仮定
game_state['player_pos'] = center
else:
game_state['enemies'].append(center)
elif cls in ['bottle', 'cup', 'bowl']:
game_state['items'].append(center)
elif cls in ['car', 'truck', 'bus']:
game_state['vehicles'].append(center)
# 戦略決定とスコア計算
if game_state['player_pos'] and game_state['enemies']:
enemy_pos = game_state['enemies'][0]
distance = np.sqrt((enemy_pos[0] - game_state['player_pos'][0])**2 +
(enemy_pos[1] - game_state['player_pos'][1])**2)
if distance < DANGER_DISTANCE:
self.danger_count += 1
game_state['strategy'] = 'DANGER: 敵が近すぎます'
if self.danger_count >= DANGER_COUNT_LIMIT:
game_state['game_status'] = 'ゲームオーバー'
elif distance < ATTACK_DISTANCE:
game_state['strategy'] = 'ATTACK: 敵が接近中'
self.danger_count = 0
else:
game_state['strategy'] = 'EXPLORE: アイテム探索'
self.danger_count = 0
self.survival_frames += 1 # 安全な距離の時のみカウント
elif game_state['items']:
game_state['strategy'] = 'COLLECT: アイテム発見'
self.score += 10
# アイテム収集時は生存フレーム数を増加させない
else:
game_state['strategy'] = 'PATROL: 脅威なし'
self.survival_frames += 1 # 脅威がない時もカウント
# 勝利判定
if self.score >= WIN_SCORE or self.survival_frames >= WIN_SURVIVAL_FRAMES:
game_state['game_status'] = '勝利'
return game_state
def visualize_results(self, frame, detections, game_state):
result_frame = frame.copy()
# 検出枠を描画
for det in detections:
x1, y1, x2, y2 = det['bbox']
color = PLAYER_COLOR if det['class'] == 'person' and det['center'][0] < 200 else ENEMY_COLOR
cv2.rectangle(result_frame, (x1, y1), (x2, y2), color, 2)
label = f"{det['class']} {det['confidence']:.2f}"
draw_text(result_frame, label, (x1, y1-10), FONT_SCALE, color, FONT_THICKNESS_BOLD)
# ゲーム情報を表示
draw_text(result_frame, f"Strategy: {game_state['strategy']}",
(10, 30), FONT_SCALE_LARGE, TEXT_COLOR, FONT_THICKNESS_BOLD)
draw_text(result_frame, f"Score: {self.score} | Survival: {self.survival_frames}",
(10, 55), FONT_SCALE_MEDIUM, (0, 255, 255), FONT_THICKNESS_BOLD)
draw_text(result_frame, f"Status: {game_state['game_status']}",
(10, 80), FONT_SCALE_MEDIUM, (255, 255, 0), FONT_THICKNESS_BOLD)
# 統計情報
info_text = f"Frame: {self.frame_count} | Objects: {len(detections)}"
draw_text(result_frame, info_text, (10, 460), FONT_SCALE_MEDIUM, TEXT_COLOR)
return result_frame
def save_results(self):
try:
with open('result.txt', 'w', encoding='utf-8') as f:
f.write('YOLO11リアルタイム物体検出デモ - 実行結果\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')
print('result.txtに保存しました')
except Exception as e:
print(f'Error: 結果の保存に失敗しました: {e}')
def run_demo(self, num_frames=30):
print('YOLO11を使用したゲームAI Visionデモを開始します...')
print('操作方法: qキーで終了')
print('=' * 50)
# プログラムの概要表示
print('\n【プログラム概要】')
print('YOLO11による物体検出を使用したゲーム画面解析デモ')
print('シミュレーション環境でゲームAIの戦略決定を実演します')
print('\n【ゲームルール】')
print('- プレイヤー(緑)は敵(赤)を避けながらアイテム(黄)を収集')
print(f'- {WIN_SCORE}ポイント獲得または{WIN_SURVIVAL_FRAMES}フレーム生存で勝利')
print(f'- 敵との距離が{DANGER_DISTANCE}ピクセル未満になると危険状態')
print('=' * 50 + '\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)
# 1秒間隔でprint出力
current_time = time.time()
if current_time - self.last_print_time >= 1.0:
log_text = f'フレーム {i}: 推論時間 {inference_time:.2f}ms, スコア {self.score}点, 戦略: {game_state["strategy"]}'
print(log_text)
self.results_log.append(log_text)
self.last_print_time = current_time
# 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 game_state['game_status'] in ['勝利', 'ゲームオーバー']:
print(f'\n*** {game_state["game_status"]} ***')
print(f'最終スコア: {self.score}点')
self.results_log.append(f'\n*** {game_state["game_status"]} ***')
self.results_log.append(f'最終スコア: {self.score}点')
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}')
axes[idx].axis('off')
# 未使用のサブプロットを非表示
for j in range(len(saved_frames), 6):
axes[j].axis('off')
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('- 緑枠:プレイヤー、赤枠:敵または検出物')
print('- OpenCVウィンドウでリアルタイム表示')
print('- result.txtに詳細ログを保存')
plt.show()
# メイン処理
ai = GameAIVision()
ai.run_demo()
4. 使用方法と実行結果の理解
- 上記のプログラムを実行する
- 初回実行時はYOLO11モデル(yolo11n.pt)が自動的にダウンロードされる。実行結果として、5フレームのゲーム画面解析結果が表示され、game_ai_yolo11_results.pngとして保存される。
推論時間はAIが1フレームを処理する時間で、ゲームでは33ミリ秒以下(30FPS)が理想的である。これより遅いとゲームの反応性が低下する。
5. 実験・探求のアイデア
実験要素
- ゲームパラメータの調整:DANGER_DISTANCE、ATTACK_DISTANCE、WIN_SCOREなどの定数を変更し、ゲームの難易度とAI戦略の変化を観察
- 物体配置の変更:create_game_scene()メソッド内の座標を変更し、異なるゲームレイアウトでの検出精度を確認
- 検出クラスの拡張:'bottle', 'cup', 'bowl'以外のクラスをアイテムとして認識させ、検出の多様性を確認
体験・実験・探求のアイデア
- 動的シーン対応:np.roll()の値を大きくして高速移動をシミュレートし、物体追跡の限界を探る
- マルチオブジェクト戦略:複数の敵やアイテムを配置し、優先順位付けアルゴリズムを実装。最適な意思決定戦略を探求
- カスタムクラスの活用:YOLO11が検出可能な80クラスから、ゲームに適した新しい要素(動物、食べ物、道具など)を追加し、より複雑なゲームシナリオを構築