This commit is contained in:
Your Name
2025-03-26 21:16:45 +00:00
parent 55b1caf1f2
commit beb5ba009f
16 changed files with 493 additions and 1338 deletions

View File

@@ -0,0 +1,6 @@
# DNF > 2023-06-06 3:57pm
https://universe.roboflow.com/atomade/dnf-fzffw
Provided by a Roboflow user
License: CC BY 4.0

View File

@@ -0,0 +1,36 @@
DNF - v1 2023-06-06 3:57pm
==============================
This dataset was exported via roboflow.com on June 6, 2023 at 8:01 AM GMT
Roboflow is an end-to-end computer vision platform that helps you
* collaborate with your team on computer vision projects
* collect & organize images
* understand and search unstructured image data
* annotate, and create datasets
* export, train, and deploy computer vision models
* use active learning to improve your dataset over time
For state of the art Computer Vision training notebooks you can use with this dataset,
visit https://github.com/roboflow/notebooks
To find over 100k other datasets and pre-trained models, visit https://universe.roboflow.com
The dataset includes 649 images.
Player-gold-monster-boss are annotated in YOLOv8 format.
The following pre-processing was applied to each image:
* Auto-orientation of pixel data (with EXIF-orientation stripping)
* Resize to 640x640 (Stretch)
The following augmentation was applied to create 3 versions of each source image:
* Randomly crop between 0 and 40 percent of the image
* Random brigthness adjustment of between -30 and +30 percent
* Random exposure adjustment of between -20 and +20 percent
* Random Gaussian blur of between 0 and 1 pixels
The following transformations were applied to the bounding boxes of each image:
* Salt and pepper noise was applied to 5 percent of pixels

View File

@@ -1,16 +1,12 @@
path: /workspace/dnf-auto-cloud/data/training
train: /workspace/dnf-auto-cloud/data/training/images/train
val: /workspace/dnf-auto-cloud/data/training/images/val
test: /workspace/dnf-auto-cloud/data/training/images/test
nc: 10
names:
- monster
- boss
- door
- item
- npc
- player
- hp_bar
- mp_bar
- skill_ready
- cooldown
train: images/train
val: images/val
test: images/test
nc: 15
names: ['door_boss', 'door_boss_activated', 'door_normal', 'door_normal_activated', 'item_diamondcoin_advanced', 'item_gold', 'monster_hebron_archer', 'monster_hebron_boss', 'monster_hebron_bulbhead', 'monster_hebron_robot', 'monster_hebron_stone', 'monster_hebron_tortoise', 'player_spectre', 'window_continue', 'window_result']
roboflow:
workspace: atomade
project: dnf-fzffw
version: 1
license: CC BY 4.0
url: https://universe.roboflow.com/atomade/dnf-fzffw/dataset/1

View File

@@ -1,9 +0,0 @@
DNF自动化客户端
安装说明:
1. 安装Python 3.8或更高版本
2. 安装依赖包:
pip install pillow numpy websockets keyboard mouse pywin32 mss
3. 编辑config.ini设置服务器地址
4. 运行start.bat启动客户端

View File

@@ -1,837 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
DNF自动化客户端负责截取游戏画面并执行服务器返回的操作
优化版 - 增加断线重连、性能优化和增强的游戏状态管理
"""
import os
import sys
import json
import base64
import time
import random
import asyncio
import websockets
import configparser
import ssl
import ctypes
from io import BytesIO
from datetime import datetime
import threading
import logging
import traceback
# 图像处理
from PIL import Image
import numpy as np
# Windows API
import win32gui
import win32con
import win32api
import win32process
# 输入模拟
import keyboard
import mouse
# 尝试导入mss库如果不存在则继续使用PIL
try:
import mss
MSS_AVAILABLE = True
except ImportError:
MSS_AVAILABLE = False
print("警告: mss库未安装将使用PIL进行截图性能较低")
print("请使用 pip install mss 安装以获得更好的性能")
# 配置文件路径 - 使用绝对路径
CONFIG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config.ini")
# 日志设置
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
handlers=[
logging.FileHandler(os.path.join(os.path.dirname(os.path.abspath(__file__)), "client.log"), encoding="utf-8"),
logging.StreamHandler()
]
)
logger = logging.getLogger("DNFAutoClient")
class DNFAutoClient:
"""DNF自动化客户端类"""
def __init__(self):
"""初始化客户端"""
self.config = self.load_config()
self.server_url = self.config.get("Server", "url")
self.client_id = None
self.running = False
self.ws = None
self.capture_interval = float(self.config.get("Capture", "interval"))
self.max_retries = int(self.config.get("Connection", "max_retries", fallback="5"))
self.retry_delay = int(self.config.get("Connection", "retry_delay", fallback="5"))
# 增强的游戏状态
self.game_state = {
"in_battle": False,
"current_map": "",
"hp_percent": 100,
"mp_percent": 100,
"active_buffs": [],
"cooldowns": {},
"inventory_full": False,
"current_quest": None,
"last_operation_time": time.time(),
"session_start_time": time.time()
}
# 连接状态
self.last_heartbeat_time = 0
self.connection_attempts = 0
self.reconnecting = False
def load_config(self):
"""加载配置文件"""
if not os.path.exists(CONFIG_FILE):
self.create_default_config()
config = configparser.ConfigParser()
config.read(CONFIG_FILE, encoding="utf-8")
return config
def create_default_config(self):
"""创建默认配置文件"""
config = configparser.ConfigParser()
config["Server"] = {
"url": "wss://your-server-url:8080/ws",
"verify_ssl": "false"
}
config["Capture"] = {
"interval": "0.5",
"quality": "70"
}
config["Game"] = {
"window_title": "地下城与勇士",
"key_mapping": "default"
}
config["Connection"] = {
"max_retries": "5",
"retry_delay": "5",
"heartbeat_interval": "5"
}
# 保存配置
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
config.write(f)
logger.info(f"已创建默认配置文件: {CONFIG_FILE}")
async def connect(self):
"""连接到服务器"""
logger.info(f"正在连接到服务器: {self.server_url}")
ssl_context = None
if self.server_url.startswith("wss://"):
ssl_context = ssl.create_default_context()
if self.config.get("Server", "verify_ssl").lower() == "false":
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
try:
self.ws = await websockets.connect(
self.server_url,
ssl=ssl_context,
max_size=10 * 1024 * 1024, # 10MB
ping_interval=None, # 禁用自动ping我们将使用自己的心跳
close_timeout=5
)
# 等待认证
await self.authenticate()
logger.info("已连接到服务器")
self.connection_attempts = 0 # 重置连接尝试次数
self.last_heartbeat_time = time.time()
return True
except Exception as e:
logger.error(f"连接服务器失败: {e}")
return False
async def connect_with_retry(self):
"""带重试机制的连接函数"""
if self.reconnecting:
logger.info("已有重连过程在进行中,跳过")
return False
self.reconnecting = True
retry_count = 0
try:
while retry_count < self.max_retries and not self.ws:
try:
logger.info(f"尝试连接服务器 (尝试 {retry_count + 1}/{self.max_retries})...")
success = await self.connect()
if success:
self.reconnecting = False
return True
except Exception as e:
logger.error(f"连接服务器失败: {e}")
retry_count += 1
retry_delay = min(60, self.retry_delay * (2 ** retry_count)) # 指数退避策略
logger.info(f"等待 {retry_delay} 秒后重试...")
await asyncio.sleep(retry_delay)
if not self.ws:
logger.error(f"达到最大重试次数 ({self.max_retries}),连接失败")
self.reconnecting = False
return False
except Exception as e:
logger.error(f"重连过程中发生错误: {e}")
self.reconnecting = False
return False
self.reconnecting = False
return True
async def authenticate(self):
"""客户端认证"""
try:
# 等待认证挑战
challenge_raw = await self.ws.recv()
challenge = json.loads(challenge_raw)
if challenge.get("type") != "auth_challenge":
raise ValueError("无效的认证挑战")
# 收集系统信息
system_info = self.get_system_info()
# 发送认证响应
await self.ws.send(json.dumps({
"type": "auth_response",
"response": f"client_{random.getrandbits(32)}",
"client_info": {
"version": "1.0.1", # 更新版本号
"os": "Windows",
"screen_resolution": self.get_screen_resolution(),
"system_info": system_info
}
}))
# 等待认证结果
result_raw = await self.ws.recv()
result = json.loads(result_raw)
if result.get("type") != "auth_result" or result.get("status") != "success":
raise ValueError("认证失败")
# 保存客户端ID
self.client_id = result.get("client_id")
logger.info(f"认证成功客户端ID: {self.client_id}")
except Exception as e:
logger.error(f"认证失败: {e}")
raise
def get_system_info(self):
"""获取系统信息"""
system_info = {}
try:
system_info["hostname"] = os.environ.get("COMPUTERNAME", "Unknown")
system_info["username"] = os.environ.get("USERNAME", "Unknown")
system_info["processor"] = os.environ.get("PROCESSOR_IDENTIFIER", "Unknown")
# 获取系统内存信息
mem = ctypes.c_ulonglong()
ctypes.windll.kernel32.GetPhysicallyInstalledSystemMemory(ctypes.byref(mem))
system_info["memory_gb"] = round(mem.value / (1024 * 1024), 2)
except Exception as e:
logger.error(f"获取系统信息失败: {e}")
return system_info
def get_screen_resolution(self):
"""获取屏幕分辨率"""
user32 = ctypes.windll.user32
return [user32.GetSystemMetrics(0), user32.GetSystemMetrics(1)]
def get_game_window(self):
"""获取游戏窗口句柄"""
window_title = self.config.get("Game", "window_title")
hwnd = win32gui.FindWindow(None, window_title)
if hwnd == 0:
logger.warning(f"找不到游戏窗口: {window_title}")
return None
return hwnd
def capture_game_screen(self, hwnd=None):
"""截取游戏画面"""
try:
if hwnd is None:
hwnd = self.get_game_window()
if hwnd is None:
return None
# 获取窗口位置和大小
rect = win32gui.GetWindowRect(hwnd)
x, y, width, height = rect[0], rect[1], rect[2] - rect[0], rect[3] - rect[1]
# 检查窗口是否最小化
if width <= 0 or height <= 0:
logger.warning("游戏窗口被最小化或大小无效")
return None
# 使用mss进行截图(如果可用)速度比PIL快5-10倍
if MSS_AVAILABLE:
with mss.mss() as sct:
monitor = {"top": y, "left": x, "width": width, "height": height}
sct_img = sct.grab(monitor)
# 将mss图像转换为PIL图像
img = Image.frombytes("RGB", sct_img.size, sct_img.bgra, "raw", "BGRX")
else:
# 使用PIL进行截图(备选方案)
img = ImageGrab.grab(bbox=(x, y, x + width, y + height))
# 压缩图像
quality = int(self.config.get("Capture", "quality"))
buffer = BytesIO()
img.save(buffer, format="JPEG", quality=quality)
# 转换为Base64
img_base64 = base64.b64encode(buffer.getvalue()).decode("utf-8")
return {
"image": img_base64,
"window_rect": [x, y, width, height],
"timestamp": time.time()
}
except Exception as e:
logger.error(f"截取游戏画面失败: {e}")
logger.error(traceback.format_exc())
return None
async def send_heartbeat(self):
"""发送心跳包"""
if not self.ws or not self.running:
return False
try:
await self.ws.send(json.dumps({
"type": "heartbeat",
"timestamp": time.time(),
"client_id": self.client_id,
"game_state": {
"in_battle": self.game_state["in_battle"],
"current_map": self.game_state["current_map"]
}
}))
# 等待心跳响应
response_raw = await asyncio.wait_for(
self.ws.recv(),
timeout=5.0
)
# 检查响应
response = json.loads(response_raw)
if response.get("type") != "heartbeat_response":
logger.warning(f"收到非心跳响应: {response.get('type')}")
return False
self.last_heartbeat_time = time.time()
return True
except asyncio.TimeoutError:
logger.warning("心跳超时")
return False
except websockets.exceptions.ConnectionClosed:
logger.warning("发送心跳时连接已关闭")
return False
except Exception as e:
logger.error(f"发送心跳失败: {e}")
return False
async def heartbeat_loop(self):
"""心跳循环"""
heartbeat_interval = float(self.config.get("Connection", "heartbeat_interval", fallback="5"))
while self.running:
if self.ws and not self.ws.closed:
success = await self.send_heartbeat()
if not success:
# 心跳失败,检查连接状态
if time.time() - self.last_heartbeat_time > heartbeat_interval * 3:
logger.warning(f"心跳超时超过 {heartbeat_interval * 3} 秒,尝试重新连接")
if self.ws:
await self.ws.close()
self.ws = None
await asyncio.sleep(heartbeat_interval)
async def reconnect_loop(self):
"""重连检查循环"""
while self.running:
if not self.ws or self.ws.closed:
logger.warning("WebSocket连接已断开尝试重连...")
if await self.connect_with_retry():
logger.info("重连成功")
else:
logger.error("重连失败")
# 不要立即停止,继续尝试
await asyncio.sleep(5) # 每5秒检查一次连接状态
async def execute_action(self, action):
"""执行操作"""
try:
action_type = action.get("type")
# 等待指定的延迟时间
if "delay" in action:
await asyncio.sleep(action["delay"])
# 获取游戏窗口
hwnd = self.get_game_window()
if hwnd is None:
return
# 确保窗口处于前台
if win32gui.GetForegroundWindow() != hwnd:
try:
# 尝试多种方法激活窗口
win32gui.ShowWindow(hwnd, win32con.SW_RESTORE) # 恢复窗口(如果最小化)
win32gui.SetForegroundWindow(hwnd) # 尝试置为前台
await asyncio.sleep(0.1)
# 如果窗口仍然不在前台,使用更强的方法
if win32gui.GetForegroundWindow() != hwnd:
# 获取当前激活窗口的线程和进程ID
curr_hwnd = win32gui.GetForegroundWindow()
curr_thread_id = win32process.GetWindowThreadProcessId(curr_hwnd)[0]
# 获取目标窗口的线程和进程ID
target_thread_id = win32process.GetWindowThreadProcessId(hwnd)[0]
# 附加线程输入
win32process.AttachThreadInput(target_thread_id, curr_thread_id, True)
win32gui.SetForegroundWindow(hwnd)
win32gui.BringWindowToTop(hwnd)
win32process.AttachThreadInput(target_thread_id, curr_thread_id, False)
await asyncio.sleep(0.1)
except Exception as e:
logger.error(f"激活窗口失败: {e}")
# 获取窗口位置
rect = win32gui.GetWindowRect(hwnd)
window_x, window_y = rect[0], rect[1]
# 执行不同类型的操作
if action_type == "move_to":
# 移动到指定位置
position = action.get("position", [0, 0])
x, y = position[0] + window_x, position[1] + window_y
# 生成人类化的移动路径
current_pos = win32gui.GetCursorPos()
path = self.generate_movement_path(current_pos, [x, y])
# 执行移动
for point in path:
win32api.SetCursorPos((int(point[0]), int(point[1])))
await asyncio.sleep(0.01) # 10ms延迟
elif action_type == "click":
# 点击指定位置
position = action.get("position", [0, 0])
x, y = position[0] + window_x, position[1] + window_y
# 移动到位置
current_pos = win32gui.GetCursorPos()
path = self.generate_movement_path(current_pos, [x, y])
for point in path:
win32api.SetCursorPos((int(point[0]), int(point[1])))
await asyncio.sleep(0.01)
# 执行点击
mouse.click()
elif action_type == "use_skill":
# 使用技能
key = action.get("key", "1")
keyboard.press(key)
await asyncio.sleep(0.05)
keyboard.release(key)
# 如果有目标位置,移动鼠标并点击
if "target_position" in action:
pos = action["target_position"]
x, y = pos[0] + window_x, pos[1] + window_y
win32api.SetCursorPos((int(x), int(y)))
await asyncio.sleep(0.05)
mouse.click()
elif action_type == "interact":
# 交互
key = action.get("key", "f")
keyboard.press(key)
await asyncio.sleep(0.1)
keyboard.release(key)
elif action_type == "move_random":
# 随机移动
direction = action.get("direction", "right")
duration = action.get("duration", 1.0)
# 方向键映射
dir_keys = {
"up": "w",
"down": "s",
"left": "a",
"right": "d"
}
key = dir_keys.get(direction, "d")
keyboard.press(key)
await asyncio.sleep(duration)
keyboard.release(key)
elif action_type == "use_item":
# 使用物品
key = action.get("key", "f1")
keyboard.press(key)
await asyncio.sleep(0.1)
keyboard.release(key)
elif action_type == "stop":
# 停止所有按键
for key in ["w", "a", "s", "d", "1", "2", "3", "4", "5", "6"]:
if keyboard.is_pressed(key):
keyboard.release(key)
elif action_type == "type_text":
# 输入文本
text = action.get("text", "")
if text:
keyboard.write(text, delay=0.05)
elif action_type == "press_key_combo":
# 按下组合键
keys = action.get("keys", [])
if keys:
for key in keys:
keyboard.press(key)
await asyncio.sleep(0.1)
for key in reversed(keys):
keyboard.release(key)
else:
logger.warning(f"未知操作类型: {action_type}")
# 更新游戏状态
self.game_state["last_operation_time"] = time.time()
except Exception as e:
logger.error(f"执行操作失败: {e}")
logger.error(traceback.format_exc())
def generate_movement_path(self, start_pos, end_pos, steps=None):
"""生成模拟人类的鼠标移动路径"""
# 计算距离
distance = ((end_pos[0] - start_pos[0])**2 + (end_pos[1] - start_pos[1])**2)**0.5
# 如果未指定步数,根据距离计算
if steps is None:
steps = max(int(distance / 20), 5) # 每20像素一个点至少5个点
# 生成基础路径
t = np.linspace(0, 1, steps)
path = []
# 动态调整平滑度
smoothness = float(self.config.get("Game", "movement_smoothness", fallback="0.8"))
for i in range(steps):
# 基础线性插值
x = start_pos[0] + (end_pos[0] - start_pos[0]) * t[i]
y = start_pos[1] + (end_pos[1] - start_pos[1]) * t[i]
# 添加随机偏移(越靠近中间偏移越大)
mid_factor = 4 * t[i] * (1 - t[i]) # 在中间最大
max_offset = distance * 0.05 * mid_factor * (1 - smoothness) # 最大偏移为距离的5%,受平滑度影响
offset_x = random.normalvariate(0, max_offset / 3)
offset_y = random.normalvariate(0, max_offset / 3)
# 添加到路径
path.append([x + offset_x, y + offset_y])
# 确保起点和终点准确
path[0] = start_pos
path[-1] = end_pos
return path
def analyze_image(self, image_data, detection_results):
"""
分析图像数据,更新游戏状态
参数:
image_data (dict): 图像数据
detection_results (list): 检测结果
"""
# 更新战斗状态
monsters_detected = False
for det in detection_results:
if det["class_name"] in ["monster", "boss"]:
monsters_detected = True
break
self.game_state["in_battle"] = monsters_detected
# 分析血条和蓝条
hp_bars = [d for d in detection_results if d["class_name"] == "hp_bar"]
mp_bars = [d for d in detection_results if d["class_name"] == "mp_bar"]
if hp_bars:
# 估算血量百分比
self.game_state["hp_percent"] = self.estimate_bar_percent(hp_bars[0])
if mp_bars:
# 估算蓝量百分比
self.game_state["mp_percent"] = self.estimate_bar_percent(mp_bars[0])
# 检测技能冷却
cooldowns = [d for d in detection_results if d["class_name"] == "cooldown"]
self.game_state["cooldowns"] = {}
for cd in cooldowns:
if "skill_id" in cd:
self.game_state["cooldowns"][cd["skill_id"]] = cd.get("remaining_time", 1.0)
def estimate_bar_percent(self, bar_detection):
"""
估计血条/蓝条的百分比
参数:
bar_detection (dict): 条形检测结果
返回:
float: 百分比值(0-100)
"""
# 这里需要根据实际情况实现
# 临时方案:返回检测结果中的值,如果没有则返回默认值
return bar_detection.get("percent", 100)
async def capture_and_process_loop(self):
"""截图和处理循环"""
consecutive_errors = 0
while self.running:
try:
# 检查WebSocket连接
if not self.ws or self.ws.closed:
await asyncio.sleep(0.5) # 连接断开时等待
continue
# 获取游戏窗口
hwnd = self.get_game_window()
if hwnd is None:
await asyncio.sleep(1.0) # 找不到窗口时等待1秒
continue
# 截取游戏画面
screen_data = self.capture_game_screen(hwnd)
if screen_data is None:
await asyncio.sleep(0.5) # 截图失败时等待0.5秒
continue
# 准备请求数据
request = {
"type": "image",
"request_id": f"req_{random.getrandbits(32)}",
"timestamp": time.time(),
"data": screen_data["image"],
"game_state": self.game_state,
"window_rect": screen_data["window_rect"]
}
# 发送请求
await self.ws.send(json.dumps(request))
# 等待响应
response_raw = await asyncio.wait_for(
self.ws.recv(),
timeout=5.0
)
# 处理响应
response = json.loads(response_raw)
if response.get("type") == "action_response":
# 获取检测结果并更新游戏状态
if "detections" in response:
self.analyze_image(screen_data, response["detections"])
# 执行动作
actions = response.get("actions", [])
# 按优先级排序
actions.sort(key=lambda x: x.get("execution_priority", 1.0))
for action in actions:
await self.execute_action(action)
# 重置错误计数
consecutive_errors = 0
elif response.get("type") == "error":
logger.error(f"服务器返回错误: {response.get('message')}")
consecutive_errors += 1
# 等待指定的间隔时间
await asyncio.sleep(self.capture_interval)
except asyncio.TimeoutError:
logger.warning("等待服务器响应超时")
consecutive_errors += 1
except websockets.exceptions.ConnectionClosed:
logger.error("WebSocket连接已关闭")
break
except Exception as e:
logger.error(f"处理循环出错: {e}")
logger.error(traceback.format_exc())
consecutive_errors += 1
await asyncio.sleep(1.0) # 出错时等待1秒
# 如果连续错误过多,尝试重新连接
if consecutive_errors >= 5:
logger.warning(f"连续出错 {consecutive_errors} 次,尝试重新连接")
if self.ws:
await self.ws.close()
self.ws = None
consecutive_errors = 0
async def run(self):
"""运行客户端"""
self.running = True
# 连接到服务器
if not await self.connect_with_retry():
self.running = False
return
try:
# 创建任务
capture_task = asyncio.create_task(self.capture_and_process_loop())
heartbeat_task = asyncio.create_task(self.heartbeat_loop())
reconnect_task = asyncio.create_task(self.reconnect_loop())
# 等待任务完成
await asyncio.gather(capture_task, heartbeat_task, reconnect_task)
except asyncio.CancelledError:
logger.info("客户端任务已取消")
except Exception as e:
logger.error(f"客户端运行出错: {e}")
logger.error(traceback.format_exc())
finally:
self.running = False
if self.ws:
await self.ws.close()
logger.info("客户端已停止")
def start(self):
"""启动客户端"""
try:
# 启动心跳监控线程(备用方案,以防异步心跳失效)
self._monitor_thread = threading.Thread(target=self._monitor_connection)
self._monitor_thread.daemon = True
self._monitor_thread.start()
# 运行主循环
asyncio.run(self.run())
except KeyboardInterrupt:
logger.info("用户中断,正在退出...")
except Exception as e:
logger.error(f"客户端出错: {e}")
logger.error(traceback.format_exc())
def _monitor_connection(self):
"""监控连接的后台线程"""
while True:
try:
time.sleep(30) # 每30秒检查一次
if not self.running:
break
# 检查心跳时间
if self.last_heartbeat_time > 0 and time.time() - self.last_heartbeat_time > 60:
logger.warning("心跳超时,可能需要重连")
# 不直接重连,留给重连循环处理
except Exception as e:
logger.error(f"连接监控线程出错: {e}")
def stop(self):
"""停止客户端"""
self.running = False
logger.info("正在停止客户端...")
# 创建默认配置文件(如果不存在)
def ensure_config():
if not os.path.exists(CONFIG_FILE):
config = configparser.ConfigParser()
config["Server"] = {
"url": "wss://your-server-url:8080/ws",
"verify_ssl": "false"
}
config["Capture"] = {
"interval": "0.5",
"quality": "70"
}
config["Game"] = {
"window_title": "地下城与勇士",
"key_mapping": "default",
"movement_smoothness": "0.8"
}
config["Connection"] = {
"max_retries": "5",
"retry_delay": "5",
"heartbeat_interval": "5"
}
# 保存配置
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
config.write(f)
print(f"已创建默认配置文件: {CONFIG_FILE}")
print("请编辑配置文件设置正确的服务器地址等信息")
# 启动客户端
if __name__ == "__main__":
# 确保配置文件存在
ensure_config()
try:
client = DNFAutoClient()
client.start()
except KeyboardInterrupt:
logger.info("用户中断,正在退出...")
except Exception as e:
logger.error(f"客户端出错: {e}")
logger.error(traceback.format_exc())

View File

@@ -1,17 +0,0 @@
[Server]
url = wss://your-server-ip:8080/ws
verify_ssl = false
[Capture]
interval = 0.5
quality = 70
[Game]
window_title = 地下城与勇士
key_mapping = default
movement_smoothness = 0.8
[Connection]
max_retries = 5
retry_delay = 5
heartbeat_interval = 5

View File

@@ -1,4 +0,0 @@
@echo off
echo 正在启动DNF自动化客户端...
python client.py
pause

Binary file not shown.

0
models/__init__.py Normal file
View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 KiB

After

Width:  |  Height:  |  Size: 220 KiB

View File

@@ -99,3 +99,100 @@
97, 0.0092242, 0.0074598, 0.0032303, 0.98604, 0.98365, 0.99366, 0.9308, 0.0082706, 0.005929, 0.0022603, 0.000496, 0.000496, 0.000496
98, 0.0088322, 0.007461, 0.0025581, 0.9797, 0.9832, 0.99359, 0.92623, 0.0086755, 0.006093, 0.0023018, 0.000397, 0.000397, 0.000397
99, 0.009008, 0.0076601, 0.0027773, 0.97461, 0.98322, 0.99354, 0.93797, 0.0082771, 0.0060029, 0.0023297, 0.000298, 0.000298, 0.000298
0, 0.10537, 0.076466, 0.071107, 0.0057694, 0.53319, 0.039631, 0.0090939, 0.083053, 0.030588, 0.065088, 0.070833, 0.0032407, 0.0032407
1, 0.084226, 0.072698, 0.061562, 0.39192, 0.37511, 0.099785, 0.024765, 0.078095, 0.027365, 0.054508, 0.040768, 0.006509, 0.006509
2, 0.081015, 0.064917, 0.053284, 0.45499, 0.44536, 0.25051, 0.085181, 0.076592, 0.021748, 0.044283, 0.010637, 0.0097112, 0.0097112
3, 0.074563, 0.061782, 0.048053, 0.73241, 0.43019, 0.47748, 0.18303, 0.062812, 0.018912, 0.037823, 0.009703, 0.009703, 0.009703
4, 0.068283, 0.057336, 0.042833, 0.67085, 0.54173, 0.50967, 0.22036, 0.054194, 0.01771, 0.0334, 0.009703, 0.009703, 0.009703
5, 0.061134, 0.053866, 0.037512, 0.65146, 0.45436, 0.555, 0.26817, 0.052837, 0.016332, 0.028931, 0.009604, 0.009604, 0.009604
6, 0.05909, 0.053523, 0.033439, 0.57689, 0.67196, 0.67603, 0.32589, 0.047249, 0.014892, 0.025372, 0.009505, 0.009505, 0.009505
7, 0.056171, 0.05248, 0.031248, 0.79275, 0.60855, 0.73263, 0.391, 0.045996, 0.015203, 0.021998, 0.009406, 0.009406, 0.009406
8, 0.054112, 0.048681, 0.027633, 0.88722, 0.72512, 0.76046, 0.38817, 0.042352, 0.014402, 0.01973, 0.009307, 0.009307, 0.009307
9, 0.052162, 0.048991, 0.025344, 0.89922, 0.72099, 0.82979, 0.40421, 0.042111, 0.013725, 0.017555, 0.009208, 0.009208, 0.009208
10, 0.0513, 0.048124, 0.024098, 0.9543, 0.73898, 0.87995, 0.53812, 0.035614, 0.013525, 0.015445, 0.009109, 0.009109, 0.009109
11, 0.049729, 0.046288, 0.022249, 0.80022, 0.75284, 0.81464, 0.46789, 0.039512, 0.01382, 0.014261, 0.00901, 0.00901, 0.00901
12, 0.047242, 0.046023, 0.020322, 0.87033, 0.82, 0.87714, 0.53135, 0.036358, 0.013075, 0.013154, 0.008911, 0.008911, 0.008911
13, 0.045947, 0.049008, 0.019099, 0.79432, 0.81413, 0.85648, 0.54684, 0.036657, 0.013149, 0.011747, 0.008812, 0.008812, 0.008812
14, 0.044563, 0.045478, 0.017354, 0.86486, 0.82851, 0.86331, 0.58533, 0.030859, 0.012629, 0.010733, 0.008713, 0.008713, 0.008713
15, 0.044386, 0.045777, 0.016909, 0.87888, 0.84115, 0.88297, 0.5868, 0.030465, 0.012362, 0.010119, 0.008614, 0.008614, 0.008614
16, 0.044421, 0.044363, 0.015621, 0.93144, 0.82596, 0.88851, 0.60501, 0.031073, 0.012311, 0.0097153, 0.008515, 0.008515, 0.008515
17, 0.042487, 0.046041, 0.015011, 0.92876, 0.83356, 0.89565, 0.55739, 0.031302, 0.012821, 0.0089586, 0.008416, 0.008416, 0.008416
18, 0.042481, 0.041832, 0.014583, 0.84688, 0.87297, 0.88565, 0.57654, 0.032206, 0.012529, 0.008568, 0.008317, 0.008317, 0.008317
19, 0.041834, 0.041992, 0.014201, 0.8425, 0.87675, 0.87723, 0.56809, 0.035273, 0.012553, 0.0082376, 0.008218, 0.008218, 0.008218
20, 0.041529, 0.040971, 0.013827, 0.88358, 0.89188, 0.90811, 0.61107, 0.029281, 0.012575, 0.0083256, 0.008119, 0.008119, 0.008119
21, 0.039979, 0.041675, 0.013442, 0.90751, 0.87639, 0.88663, 0.65531, 0.028874, 0.011992, 0.0077705, 0.00802, 0.00802, 0.00802
22, 0.040631, 0.042681, 0.013149, 0.92106, 0.89742, 0.90632, 0.63937, 0.027697, 0.012193, 0.0073721, 0.007921, 0.007921, 0.007921
23, 0.039521, 0.041223, 0.012621, 0.89472, 0.8844, 0.909, 0.59444, 0.029174, 0.012005, 0.0069702, 0.007822, 0.007822, 0.007822
24, 0.039209, 0.040794, 0.012326, 0.94475, 0.8852, 0.91868, 0.64485, 0.029654, 0.012124, 0.0069315, 0.007723, 0.007723, 0.007723
25, 0.038706, 0.042495, 0.012114, 0.94535, 0.88812, 0.91316, 0.66164, 0.025027, 0.011684, 0.0064895, 0.007624, 0.007624, 0.007624
26, 0.038853, 0.039949, 0.011693, 0.93719, 0.88993, 0.91549, 0.64189, 0.031298, 0.011859, 0.0065789, 0.007525, 0.007525, 0.007525
27, 0.037325, 0.038238, 0.011581, 0.94332, 0.8866, 0.91919, 0.69192, 0.024287, 0.011155, 0.0062871, 0.007426, 0.007426, 0.007426
28, 0.037054, 0.039379, 0.010862, 0.94197, 0.88904, 0.91807, 0.67987, 0.027484, 0.011633, 0.0063429, 0.007327, 0.007327, 0.007327
29, 0.037804, 0.039336, 0.010948, 0.94615, 0.88732, 0.92419, 0.71138, 0.024992, 0.011194, 0.0060732, 0.007228, 0.007228, 0.007228
30, 0.035318, 0.038011, 0.010588, 0.93566, 0.90238, 0.9254, 0.69709, 0.025254, 0.01115, 0.0060346, 0.007129, 0.007129, 0.007129
31, 0.035848, 0.040036, 0.010657, 0.95219, 0.87909, 0.91779, 0.68068, 0.02775, 0.011805, 0.0060232, 0.00703, 0.00703, 0.00703
32, 0.036455, 0.03784, 0.010277, 0.9521, 0.89118, 0.91821, 0.69482, 0.02398, 0.011316, 0.006109, 0.006931, 0.006931, 0.006931
33, 0.035511, 0.040274, 0.01034, 0.95571, 0.89172, 0.91894, 0.67729, 0.025415, 0.011374, 0.0058415, 0.006832, 0.006832, 0.006832
34, 0.034818, 0.036288, 0.0098505, 0.96073, 0.90876, 0.92328, 0.73266, 0.022878, 0.010763, 0.0055736, 0.006733, 0.006733, 0.006733
35, 0.034432, 0.035868, 0.0099866, 0.94404, 0.89441, 0.90445, 0.6819, 0.02454, 0.010886, 0.0055561, 0.006634, 0.006634, 0.006634
36, 0.034021, 0.035818, 0.009415, 0.95426, 0.90633, 0.90901, 0.68886, 0.023201, 0.010798, 0.0054224, 0.006535, 0.006535, 0.006535
37, 0.033418, 0.03598, 0.0094498, 0.94152, 0.89777, 0.89953, 0.69698, 0.023835, 0.010837, 0.0053287, 0.006436, 0.006436, 0.006436
38, 0.033272, 0.036502, 0.0095944, 0.94272, 0.90222, 0.92314, 0.70942, 0.023785, 0.011131, 0.0052042, 0.006337, 0.006337, 0.006337
39, 0.033521, 0.035348, 0.0089882, 0.94528, 0.90316, 0.9228, 0.7345, 0.022518, 0.010903, 0.0052435, 0.006238, 0.006238, 0.006238
40, 0.032908, 0.035765, 0.0095038, 0.94537, 0.90443, 0.92117, 0.73349, 0.023005, 0.010825, 0.0052056, 0.006139, 0.006139, 0.006139
41, 0.031944, 0.03523, 0.0090051, 0.94376, 0.89426, 0.92003, 0.72121, 0.021874, 0.010633, 0.0051577, 0.00604, 0.00604, 0.00604
42, 0.032284, 0.034942, 0.0085232, 0.94648, 0.89959, 0.92236, 0.72606, 0.021799, 0.010527, 0.0051405, 0.005941, 0.005941, 0.005941
43, 0.03217, 0.035902, 0.0087585, 0.9491, 0.9019, 0.92087, 0.73303, 0.021869, 0.01047, 0.0051244, 0.005842, 0.005842, 0.005842
44, 0.031443, 0.033035, 0.0085704, 0.94745, 0.90156, 0.92477, 0.7546, 0.020727, 0.01034, 0.0049707, 0.005743, 0.005743, 0.005743
45, 0.031114, 0.036149, 0.0084183, 0.94662, 0.90115, 0.92202, 0.72985, 0.021511, 0.010381, 0.0051055, 0.005644, 0.005644, 0.005644
46, 0.030806, 0.033467, 0.0085668, 0.94773, 0.9066, 0.91584, 0.7378, 0.021743, 0.010383, 0.0049932, 0.005545, 0.005545, 0.005545
47, 0.03045, 0.035342, 0.0088827, 0.94675, 0.89267, 0.91217, 0.72865, 0.021152, 0.010576, 0.0050758, 0.005446, 0.005446, 0.005446
48, 0.030546, 0.034058, 0.0083615, 0.9474, 0.89399, 0.90736, 0.72605, 0.022204, 0.010774, 0.0049421, 0.005347, 0.005347, 0.005347
49, 0.030783, 0.035215, 0.0082594, 0.94012, 0.88191, 0.91291, 0.76995, 0.019367, 0.010101, 0.0049213, 0.005248, 0.005248, 0.005248
50, 0.030272, 0.031735, 0.0081784, 0.94596, 0.89436, 0.91247, 0.72832, 0.022859, 0.010223, 0.0047657, 0.005149, 0.005149, 0.005149
51, 0.030048, 0.033497, 0.0078879, 0.9442, 0.90128, 0.91924, 0.77297, 0.018489, 0.0096083, 0.0046946, 0.00505, 0.00505, 0.00505
52, 0.029478, 0.034815, 0.0079617, 0.94805, 0.90482, 0.91374, 0.73906, 0.021018, 0.010099, 0.0047801, 0.004951, 0.004951, 0.004951
53, 0.029622, 0.033132, 0.0073636, 0.94613, 0.90688, 0.90788, 0.76831, 0.018532, 0.0098147, 0.0046803, 0.004852, 0.004852, 0.004852
54, 0.029849, 0.034944, 0.0074608, 0.95061, 0.90497, 0.90431, 0.73547, 0.020199, 0.010039, 0.0046062, 0.004753, 0.004753, 0.004753
55, 0.028862, 0.030966, 0.0074558, 0.95399, 0.90448, 0.92083, 0.77247, 0.017624, 0.0094819, 0.0045197, 0.004654, 0.004654, 0.004654
56, 0.028374, 0.031805, 0.0075731, 0.94923, 0.9021, 0.91864, 0.7448, 0.021008, 0.0099044, 0.0046206, 0.004555, 0.004555, 0.004555
57, 0.028933, 0.033019, 0.0077383, 0.94261, 0.90025, 0.91444, 0.76969, 0.018055, 0.0092425, 0.0044035, 0.004456, 0.004456, 0.004456
58, 0.02764, 0.031741, 0.0071853, 0.9316, 0.90703, 0.90207, 0.7154, 0.019898, 0.0099125, 0.0043523, 0.004357, 0.004357, 0.004357
59, 0.027372, 0.032124, 0.0071181, 0.93235, 0.90338, 0.91095, 0.78268, 0.017186, 0.0096073, 0.0043241, 0.004258, 0.004258, 0.004258
60, 0.027831, 0.03038, 0.0073338, 0.93878, 0.90209, 0.91096, 0.74039, 0.02007, 0.0099401, 0.0042286, 0.004159, 0.004159, 0.004159
61, 0.028398, 0.031714, 0.0072932, 0.9362, 0.90344, 0.92164, 0.79353, 0.017023, 0.0093412, 0.0041499, 0.00406, 0.00406, 0.00406
62, 0.02799, 0.034212, 0.0075848, 0.92984, 0.90388, 0.90626, 0.74251, 0.01955, 0.0094862, 0.0041463, 0.003961, 0.003961, 0.003961
63, 0.026864, 0.029966, 0.0072165, 0.9445, 0.90611, 0.9103, 0.75938, 0.018653, 0.0093565, 0.0041413, 0.003862, 0.003862, 0.003862
64, 0.026335, 0.029561, 0.0066826, 0.93036, 0.90621, 0.92241, 0.78571, 0.017073, 0.0095363, 0.0040866, 0.003763, 0.003763, 0.003763
65, 0.026865, 0.032126, 0.0069433, 0.93762, 0.8962, 0.91818, 0.76424, 0.019093, 0.0098457, 0.0040423, 0.003664, 0.003664, 0.003664
66, 0.025874, 0.029991, 0.0067089, 0.93369, 0.90285, 0.91089, 0.75601, 0.01744, 0.0095415, 0.0040609, 0.003565, 0.003565, 0.003565
67, 0.025686, 0.029558, 0.0068254, 0.94118, 0.89702, 0.93001, 0.80512, 0.016593, 0.0091715, 0.0040623, 0.003466, 0.003466, 0.003466
68, 0.026146, 0.0306, 0.006672, 0.9435, 0.89793, 0.92328, 0.78872, 0.016968, 0.0094819, 0.0039386, 0.003367, 0.003367, 0.003367
69, 0.02575, 0.03149, 0.0066763, 0.95011, 0.89324, 0.92115, 0.77461, 0.017017, 0.00915, 0.0038871, 0.003268, 0.003268, 0.003268
70, 0.025257, 0.029951, 0.0062569, 0.94152, 0.90354, 0.93186, 0.79316, 0.016541, 0.0089645, 0.0037884, 0.003169, 0.003169, 0.003169
71, 0.025736, 0.031298, 0.0063836, 0.94456, 0.89936, 0.98277, 0.85375, 0.015854, 0.0091391, 0.0037851, 0.00307, 0.00307, 0.00307
72, 0.025147, 0.029945, 0.0065308, 0.94031, 0.90261, 0.98337, 0.80994, 0.016953, 0.0095129, 0.0037837, 0.002971, 0.002971, 0.002971
73, 0.025429, 0.029684, 0.0067952, 0.9484, 0.89445, 0.97873, 0.85639, 0.015808, 0.0090361, 0.0037594, 0.002872, 0.002872, 0.002872
74, 0.0244, 0.02808, 0.006261, 0.9484, 0.89159, 0.96903, 0.8146, 0.01757, 0.0091019, 0.0037436, 0.002773, 0.002773, 0.002773
75, 0.024458, 0.029165, 0.0064301, 0.9414, 0.90268, 0.96465, 0.82351, 0.016609, 0.0093188, 0.0037172, 0.002674, 0.002674, 0.002674
76, 0.024491, 0.028867, 0.0065167, 0.93885, 0.90952, 0.96421, 0.82762, 0.015425, 0.0091639, 0.0037354, 0.002575, 0.002575, 0.002575
77, 0.024234, 0.030084, 0.0061172, 0.92641, 0.92039, 0.97935, 0.84484, 0.015876, 0.0093784, 0.0036685, 0.002476, 0.002476, 0.002476
78, 0.023912, 0.027955, 0.0057952, 0.92526, 0.92039, 0.97048, 0.83617, 0.016671, 0.0094657, 0.0036557, 0.002377, 0.002377, 0.002377
79, 0.023572, 0.028896, 0.0056073, 0.94601, 0.89761, 0.96783, 0.83554, 0.015535, 0.0090661, 0.0036031, 0.002278, 0.002278, 0.002278
80, 0.023433, 0.027542, 0.0060494, 0.95198, 0.89309, 0.96861, 0.83564, 0.01569, 0.0090857, 0.0035759, 0.002179, 0.002179, 0.002179
81, 0.023474, 0.029614, 0.0059355, 0.956, 0.89106, 0.96929, 0.83867, 0.015587, 0.0091519, 0.0035584, 0.00208, 0.00208, 0.00208
82, 0.022964, 0.028318, 0.0058851, 0.95519, 0.89445, 0.97281, 0.84384, 0.01506, 0.0089855, 0.003513, 0.001981, 0.001981, 0.001981
83, 0.023384, 0.028354, 0.0057465, 0.95494, 0.89426, 0.98393, 0.86829, 0.015179, 0.0090079, 0.0034722, 0.001882, 0.001882, 0.001882
84, 0.022817, 0.027814, 0.0059098, 0.95244, 0.89233, 0.97627, 0.84317, 0.015875, 0.0092964, 0.0034103, 0.001783, 0.001783, 0.001783
85, 0.023342, 0.027297, 0.0060606, 0.95319, 0.89174, 0.97141, 0.84056, 0.015359, 0.0091238, 0.0033967, 0.001684, 0.001684, 0.001684
86, 0.022191, 0.027358, 0.005542, 0.9539, 0.88966, 0.97417, 0.85334, 0.015129, 0.0091815, 0.0034193, 0.001585, 0.001585, 0.001585
87, 0.022573, 0.027708, 0.0056441, 0.95191, 0.89295, 0.97479, 0.84976, 0.015716, 0.0091705, 0.0033853, 0.001486, 0.001486, 0.001486
88, 0.022409, 0.027443, 0.0058222, 0.92751, 0.9028, 0.96398, 0.84426, 0.015372, 0.008944, 0.0033563, 0.001387, 0.001387, 0.001387
89, 0.021741, 0.026495, 0.0053773, 0.94905, 0.89763, 0.97, 0.84371, 0.014766, 0.0090199, 0.0033002, 0.001288, 0.001288, 0.001288
90, 0.021996, 0.028446, 0.0058893, 0.93917, 0.88982, 0.96402, 0.8402, 0.01475, 0.0089879, 0.0032734, 0.001189, 0.001189, 0.001189
91, 0.022016, 0.027665, 0.0055442, 0.94716, 0.90265, 0.97066, 0.849, 0.01469, 0.0089841, 0.0033016, 0.00109, 0.00109, 0.00109
92, 0.021633, 0.027603, 0.0055049, 0.9475, 0.90336, 0.96807, 0.84835, 0.014596, 0.0091195, 0.0032879, 0.000991, 0.000991, 0.000991
93, 0.021683, 0.026557, 0.0052333, 0.94842, 0.90211, 0.97055, 0.85609, 0.014893, 0.0090179, 0.0032596, 0.000892, 0.000892, 0.000892
94, 0.021309, 0.025954, 0.0053442, 0.94614, 0.90127, 0.97183, 0.85953, 0.014493, 0.0088654, 0.0032242, 0.000793, 0.000793, 0.000793
95, 0.021511, 0.026043, 0.0054107, 0.94789, 0.90206, 0.96895, 0.86042, 0.014186, 0.008769, 0.003192, 0.000694, 0.000694, 0.000694
96, 0.020815, 0.024904, 0.0052537, 0.94738, 0.90358, 0.9677, 0.84651, 0.01453, 0.0088882, 0.0031793, 0.000595, 0.000595, 0.000595
1 epoch train/box_loss train/obj_loss train/cls_loss metrics/precision metrics/recall metrics/mAP_0.5 metrics/mAP_0.5:0.95 val/box_loss val/obj_loss val/cls_loss x/lr0 x/lr1 x/lr2
99 97 0.0092242 0.0074598 0.0032303 0.98604 0.98365 0.99366 0.9308 0.0082706 0.005929 0.0022603 0.000496 0.000496 0.000496
100 98 0.0088322 0.007461 0.0025581 0.9797 0.9832 0.99359 0.92623 0.0086755 0.006093 0.0023018 0.000397 0.000397 0.000397
101 99 0.009008 0.0076601 0.0027773 0.97461 0.98322 0.99354 0.93797 0.0082771 0.0060029 0.0023297 0.000298 0.000298 0.000298
102 0 0.10537 0.076466 0.071107 0.0057694 0.53319 0.039631 0.0090939 0.083053 0.030588 0.065088 0.070833 0.0032407 0.0032407
103 1 0.084226 0.072698 0.061562 0.39192 0.37511 0.099785 0.024765 0.078095 0.027365 0.054508 0.040768 0.006509 0.006509
104 2 0.081015 0.064917 0.053284 0.45499 0.44536 0.25051 0.085181 0.076592 0.021748 0.044283 0.010637 0.0097112 0.0097112
105 3 0.074563 0.061782 0.048053 0.73241 0.43019 0.47748 0.18303 0.062812 0.018912 0.037823 0.009703 0.009703 0.009703
106 4 0.068283 0.057336 0.042833 0.67085 0.54173 0.50967 0.22036 0.054194 0.01771 0.0334 0.009703 0.009703 0.009703
107 5 0.061134 0.053866 0.037512 0.65146 0.45436 0.555 0.26817 0.052837 0.016332 0.028931 0.009604 0.009604 0.009604
108 6 0.05909 0.053523 0.033439 0.57689 0.67196 0.67603 0.32589 0.047249 0.014892 0.025372 0.009505 0.009505 0.009505
109 7 0.056171 0.05248 0.031248 0.79275 0.60855 0.73263 0.391 0.045996 0.015203 0.021998 0.009406 0.009406 0.009406
110 8 0.054112 0.048681 0.027633 0.88722 0.72512 0.76046 0.38817 0.042352 0.014402 0.01973 0.009307 0.009307 0.009307
111 9 0.052162 0.048991 0.025344 0.89922 0.72099 0.82979 0.40421 0.042111 0.013725 0.017555 0.009208 0.009208 0.009208
112 10 0.0513 0.048124 0.024098 0.9543 0.73898 0.87995 0.53812 0.035614 0.013525 0.015445 0.009109 0.009109 0.009109
113 11 0.049729 0.046288 0.022249 0.80022 0.75284 0.81464 0.46789 0.039512 0.01382 0.014261 0.00901 0.00901 0.00901
114 12 0.047242 0.046023 0.020322 0.87033 0.82 0.87714 0.53135 0.036358 0.013075 0.013154 0.008911 0.008911 0.008911
115 13 0.045947 0.049008 0.019099 0.79432 0.81413 0.85648 0.54684 0.036657 0.013149 0.011747 0.008812 0.008812 0.008812
116 14 0.044563 0.045478 0.017354 0.86486 0.82851 0.86331 0.58533 0.030859 0.012629 0.010733 0.008713 0.008713 0.008713
117 15 0.044386 0.045777 0.016909 0.87888 0.84115 0.88297 0.5868 0.030465 0.012362 0.010119 0.008614 0.008614 0.008614
118 16 0.044421 0.044363 0.015621 0.93144 0.82596 0.88851 0.60501 0.031073 0.012311 0.0097153 0.008515 0.008515 0.008515
119 17 0.042487 0.046041 0.015011 0.92876 0.83356 0.89565 0.55739 0.031302 0.012821 0.0089586 0.008416 0.008416 0.008416
120 18 0.042481 0.041832 0.014583 0.84688 0.87297 0.88565 0.57654 0.032206 0.012529 0.008568 0.008317 0.008317 0.008317
121 19 0.041834 0.041992 0.014201 0.8425 0.87675 0.87723 0.56809 0.035273 0.012553 0.0082376 0.008218 0.008218 0.008218
122 20 0.041529 0.040971 0.013827 0.88358 0.89188 0.90811 0.61107 0.029281 0.012575 0.0083256 0.008119 0.008119 0.008119
123 21 0.039979 0.041675 0.013442 0.90751 0.87639 0.88663 0.65531 0.028874 0.011992 0.0077705 0.00802 0.00802 0.00802
124 22 0.040631 0.042681 0.013149 0.92106 0.89742 0.90632 0.63937 0.027697 0.012193 0.0073721 0.007921 0.007921 0.007921
125 23 0.039521 0.041223 0.012621 0.89472 0.8844 0.909 0.59444 0.029174 0.012005 0.0069702 0.007822 0.007822 0.007822
126 24 0.039209 0.040794 0.012326 0.94475 0.8852 0.91868 0.64485 0.029654 0.012124 0.0069315 0.007723 0.007723 0.007723
127 25 0.038706 0.042495 0.012114 0.94535 0.88812 0.91316 0.66164 0.025027 0.011684 0.0064895 0.007624 0.007624 0.007624
128 26 0.038853 0.039949 0.011693 0.93719 0.88993 0.91549 0.64189 0.031298 0.011859 0.0065789 0.007525 0.007525 0.007525
129 27 0.037325 0.038238 0.011581 0.94332 0.8866 0.91919 0.69192 0.024287 0.011155 0.0062871 0.007426 0.007426 0.007426
130 28 0.037054 0.039379 0.010862 0.94197 0.88904 0.91807 0.67987 0.027484 0.011633 0.0063429 0.007327 0.007327 0.007327
131 29 0.037804 0.039336 0.010948 0.94615 0.88732 0.92419 0.71138 0.024992 0.011194 0.0060732 0.007228 0.007228 0.007228
132 30 0.035318 0.038011 0.010588 0.93566 0.90238 0.9254 0.69709 0.025254 0.01115 0.0060346 0.007129 0.007129 0.007129
133 31 0.035848 0.040036 0.010657 0.95219 0.87909 0.91779 0.68068 0.02775 0.011805 0.0060232 0.00703 0.00703 0.00703
134 32 0.036455 0.03784 0.010277 0.9521 0.89118 0.91821 0.69482 0.02398 0.011316 0.006109 0.006931 0.006931 0.006931
135 33 0.035511 0.040274 0.01034 0.95571 0.89172 0.91894 0.67729 0.025415 0.011374 0.0058415 0.006832 0.006832 0.006832
136 34 0.034818 0.036288 0.0098505 0.96073 0.90876 0.92328 0.73266 0.022878 0.010763 0.0055736 0.006733 0.006733 0.006733
137 35 0.034432 0.035868 0.0099866 0.94404 0.89441 0.90445 0.6819 0.02454 0.010886 0.0055561 0.006634 0.006634 0.006634
138 36 0.034021 0.035818 0.009415 0.95426 0.90633 0.90901 0.68886 0.023201 0.010798 0.0054224 0.006535 0.006535 0.006535
139 37 0.033418 0.03598 0.0094498 0.94152 0.89777 0.89953 0.69698 0.023835 0.010837 0.0053287 0.006436 0.006436 0.006436
140 38 0.033272 0.036502 0.0095944 0.94272 0.90222 0.92314 0.70942 0.023785 0.011131 0.0052042 0.006337 0.006337 0.006337
141 39 0.033521 0.035348 0.0089882 0.94528 0.90316 0.9228 0.7345 0.022518 0.010903 0.0052435 0.006238 0.006238 0.006238
142 40 0.032908 0.035765 0.0095038 0.94537 0.90443 0.92117 0.73349 0.023005 0.010825 0.0052056 0.006139 0.006139 0.006139
143 41 0.031944 0.03523 0.0090051 0.94376 0.89426 0.92003 0.72121 0.021874 0.010633 0.0051577 0.00604 0.00604 0.00604
144 42 0.032284 0.034942 0.0085232 0.94648 0.89959 0.92236 0.72606 0.021799 0.010527 0.0051405 0.005941 0.005941 0.005941
145 43 0.03217 0.035902 0.0087585 0.9491 0.9019 0.92087 0.73303 0.021869 0.01047 0.0051244 0.005842 0.005842 0.005842
146 44 0.031443 0.033035 0.0085704 0.94745 0.90156 0.92477 0.7546 0.020727 0.01034 0.0049707 0.005743 0.005743 0.005743
147 45 0.031114 0.036149 0.0084183 0.94662 0.90115 0.92202 0.72985 0.021511 0.010381 0.0051055 0.005644 0.005644 0.005644
148 46 0.030806 0.033467 0.0085668 0.94773 0.9066 0.91584 0.7378 0.021743 0.010383 0.0049932 0.005545 0.005545 0.005545
149 47 0.03045 0.035342 0.0088827 0.94675 0.89267 0.91217 0.72865 0.021152 0.010576 0.0050758 0.005446 0.005446 0.005446
150 48 0.030546 0.034058 0.0083615 0.9474 0.89399 0.90736 0.72605 0.022204 0.010774 0.0049421 0.005347 0.005347 0.005347
151 49 0.030783 0.035215 0.0082594 0.94012 0.88191 0.91291 0.76995 0.019367 0.010101 0.0049213 0.005248 0.005248 0.005248
152 50 0.030272 0.031735 0.0081784 0.94596 0.89436 0.91247 0.72832 0.022859 0.010223 0.0047657 0.005149 0.005149 0.005149
153 51 0.030048 0.033497 0.0078879 0.9442 0.90128 0.91924 0.77297 0.018489 0.0096083 0.0046946 0.00505 0.00505 0.00505
154 52 0.029478 0.034815 0.0079617 0.94805 0.90482 0.91374 0.73906 0.021018 0.010099 0.0047801 0.004951 0.004951 0.004951
155 53 0.029622 0.033132 0.0073636 0.94613 0.90688 0.90788 0.76831 0.018532 0.0098147 0.0046803 0.004852 0.004852 0.004852
156 54 0.029849 0.034944 0.0074608 0.95061 0.90497 0.90431 0.73547 0.020199 0.010039 0.0046062 0.004753 0.004753 0.004753
157 55 0.028862 0.030966 0.0074558 0.95399 0.90448 0.92083 0.77247 0.017624 0.0094819 0.0045197 0.004654 0.004654 0.004654
158 56 0.028374 0.031805 0.0075731 0.94923 0.9021 0.91864 0.7448 0.021008 0.0099044 0.0046206 0.004555 0.004555 0.004555
159 57 0.028933 0.033019 0.0077383 0.94261 0.90025 0.91444 0.76969 0.018055 0.0092425 0.0044035 0.004456 0.004456 0.004456
160 58 0.02764 0.031741 0.0071853 0.9316 0.90703 0.90207 0.7154 0.019898 0.0099125 0.0043523 0.004357 0.004357 0.004357
161 59 0.027372 0.032124 0.0071181 0.93235 0.90338 0.91095 0.78268 0.017186 0.0096073 0.0043241 0.004258 0.004258 0.004258
162 60 0.027831 0.03038 0.0073338 0.93878 0.90209 0.91096 0.74039 0.02007 0.0099401 0.0042286 0.004159 0.004159 0.004159
163 61 0.028398 0.031714 0.0072932 0.9362 0.90344 0.92164 0.79353 0.017023 0.0093412 0.0041499 0.00406 0.00406 0.00406
164 62 0.02799 0.034212 0.0075848 0.92984 0.90388 0.90626 0.74251 0.01955 0.0094862 0.0041463 0.003961 0.003961 0.003961
165 63 0.026864 0.029966 0.0072165 0.9445 0.90611 0.9103 0.75938 0.018653 0.0093565 0.0041413 0.003862 0.003862 0.003862
166 64 0.026335 0.029561 0.0066826 0.93036 0.90621 0.92241 0.78571 0.017073 0.0095363 0.0040866 0.003763 0.003763 0.003763
167 65 0.026865 0.032126 0.0069433 0.93762 0.8962 0.91818 0.76424 0.019093 0.0098457 0.0040423 0.003664 0.003664 0.003664
168 66 0.025874 0.029991 0.0067089 0.93369 0.90285 0.91089 0.75601 0.01744 0.0095415 0.0040609 0.003565 0.003565 0.003565
169 67 0.025686 0.029558 0.0068254 0.94118 0.89702 0.93001 0.80512 0.016593 0.0091715 0.0040623 0.003466 0.003466 0.003466
170 68 0.026146 0.0306 0.006672 0.9435 0.89793 0.92328 0.78872 0.016968 0.0094819 0.0039386 0.003367 0.003367 0.003367
171 69 0.02575 0.03149 0.0066763 0.95011 0.89324 0.92115 0.77461 0.017017 0.00915 0.0038871 0.003268 0.003268 0.003268
172 70 0.025257 0.029951 0.0062569 0.94152 0.90354 0.93186 0.79316 0.016541 0.0089645 0.0037884 0.003169 0.003169 0.003169
173 71 0.025736 0.031298 0.0063836 0.94456 0.89936 0.98277 0.85375 0.015854 0.0091391 0.0037851 0.00307 0.00307 0.00307
174 72 0.025147 0.029945 0.0065308 0.94031 0.90261 0.98337 0.80994 0.016953 0.0095129 0.0037837 0.002971 0.002971 0.002971
175 73 0.025429 0.029684 0.0067952 0.9484 0.89445 0.97873 0.85639 0.015808 0.0090361 0.0037594 0.002872 0.002872 0.002872
176 74 0.0244 0.02808 0.006261 0.9484 0.89159 0.96903 0.8146 0.01757 0.0091019 0.0037436 0.002773 0.002773 0.002773
177 75 0.024458 0.029165 0.0064301 0.9414 0.90268 0.96465 0.82351 0.016609 0.0093188 0.0037172 0.002674 0.002674 0.002674
178 76 0.024491 0.028867 0.0065167 0.93885 0.90952 0.96421 0.82762 0.015425 0.0091639 0.0037354 0.002575 0.002575 0.002575
179 77 0.024234 0.030084 0.0061172 0.92641 0.92039 0.97935 0.84484 0.015876 0.0093784 0.0036685 0.002476 0.002476 0.002476
180 78 0.023912 0.027955 0.0057952 0.92526 0.92039 0.97048 0.83617 0.016671 0.0094657 0.0036557 0.002377 0.002377 0.002377
181 79 0.023572 0.028896 0.0056073 0.94601 0.89761 0.96783 0.83554 0.015535 0.0090661 0.0036031 0.002278 0.002278 0.002278
182 80 0.023433 0.027542 0.0060494 0.95198 0.89309 0.96861 0.83564 0.01569 0.0090857 0.0035759 0.002179 0.002179 0.002179
183 81 0.023474 0.029614 0.0059355 0.956 0.89106 0.96929 0.83867 0.015587 0.0091519 0.0035584 0.00208 0.00208 0.00208
184 82 0.022964 0.028318 0.0058851 0.95519 0.89445 0.97281 0.84384 0.01506 0.0089855 0.003513 0.001981 0.001981 0.001981
185 83 0.023384 0.028354 0.0057465 0.95494 0.89426 0.98393 0.86829 0.015179 0.0090079 0.0034722 0.001882 0.001882 0.001882
186 84 0.022817 0.027814 0.0059098 0.95244 0.89233 0.97627 0.84317 0.015875 0.0092964 0.0034103 0.001783 0.001783 0.001783
187 85 0.023342 0.027297 0.0060606 0.95319 0.89174 0.97141 0.84056 0.015359 0.0091238 0.0033967 0.001684 0.001684 0.001684
188 86 0.022191 0.027358 0.005542 0.9539 0.88966 0.97417 0.85334 0.015129 0.0091815 0.0034193 0.001585 0.001585 0.001585
189 87 0.022573 0.027708 0.0056441 0.95191 0.89295 0.97479 0.84976 0.015716 0.0091705 0.0033853 0.001486 0.001486 0.001486
190 88 0.022409 0.027443 0.0058222 0.92751 0.9028 0.96398 0.84426 0.015372 0.008944 0.0033563 0.001387 0.001387 0.001387
191 89 0.021741 0.026495 0.0053773 0.94905 0.89763 0.97 0.84371 0.014766 0.0090199 0.0033002 0.001288 0.001288 0.001288
192 90 0.021996 0.028446 0.0058893 0.93917 0.88982 0.96402 0.8402 0.01475 0.0089879 0.0032734 0.001189 0.001189 0.001189
193 91 0.022016 0.027665 0.0055442 0.94716 0.90265 0.97066 0.849 0.01469 0.0089841 0.0033016 0.00109 0.00109 0.00109
194 92 0.021633 0.027603 0.0055049 0.9475 0.90336 0.96807 0.84835 0.014596 0.0091195 0.0032879 0.000991 0.000991 0.000991
195 93 0.021683 0.026557 0.0052333 0.94842 0.90211 0.97055 0.85609 0.014893 0.0090179 0.0032596 0.000892 0.000892 0.000892
196 94 0.021309 0.025954 0.0053442 0.94614 0.90127 0.97183 0.85953 0.014493 0.0088654 0.0032242 0.000793 0.000793 0.000793
197 95 0.021511 0.026043 0.0054107 0.94789 0.90206 0.96895 0.86042 0.014186 0.008769 0.003192 0.000694 0.000694 0.000694
198 96 0.020815 0.024904 0.0052537 0.94738 0.90358 0.9677 0.84651 0.01453 0.0088882 0.0031793 0.000595 0.000595 0.000595

Binary file not shown.

Before

Width:  |  Height:  |  Size: 265 KiB

After

Width:  |  Height:  |  Size: 656 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 259 KiB

After

Width:  |  Height:  |  Size: 658 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 251 KiB

After

Width:  |  Height:  |  Size: 594 KiB

View File

@@ -2,580 +2,467 @@
# -*- coding: utf-8 -*-
"""
YOLO模型封装,提供图像识别功能
优化版 - 增强性能和模型缓存管理
修复版 - 解决模型加载依赖问题
YOLO模型实现,负责目标检测和识别
支持多种YOLO版本YOLOv5、YOLOv8
优化版 - 支持GPU加速和模型优化
"""
import os
import sys # 添加缺失的sys模块导入
import logging
import torch
import yaml
import sys
import time
import logging
import numpy as np
import subprocess
from pathlib import Path
import torch
import cv2
from PIL import Image
import statistics
from collections import deque
from config.settings import MODEL, BASE_DIR
from config.settings import MODEL
logger = logging.getLogger("DNFAutoCloud")
class YOLOModel:
"""YOLO模型封装类 - 优化版"""
"""YOLO模型类 - 优化版"""
def __init__(self):
"""初始化YOLO模型"""
self.device = MODEL.get("device", "cuda" if torch.cuda.is_available() else "cpu")
self.model = None
self.device = MODEL.get("device", "cpu")
self.conf_threshold = MODEL.get("conf_threshold", 0.5)
self.iou_threshold = MODEL.get("iou_threshold", 0.45)
self.img_size = MODEL.get("img_size", 640)
self.half_precision = MODEL.get("half_precision", False)
self.class_names = self._load_class_names()
# 性能监控
# 性能追踪
self.inference_times = deque(maxlen=100)
self.batch_size = MODEL.get("batch_size", 1)
self.use_half_precision = MODEL.get("half_precision", True) and self.device == "cuda"
logger.info(f"正在加载YOLO模型: {MODEL.get('name', 'unknown')}")
logger.info(f"模型权重路径: {MODEL.get('weights', 'unknown')}")
logger.info(f"设备: {self.device}, 半精度: {self.use_half_precision}")
# 检查权重文件是否存在
weights_path = MODEL.get("weights", "")
if not os.path.exists(weights_path):
# 尝试从备选路径加载
alt_paths = [
os.path.join(BASE_DIR, "models", "weights", "best.pt"),
os.path.join(BASE_DIR, "models", "best.pt"),
os.path.join(BASE_DIR, "yolov5", "runs", "train", "exp", "weights", "best.pt")
]
# 初始化模型
self._initialize_model()
def _load_class_names(self):
"""加载类别名称"""
try:
class_names_path = MODEL.get("class_names", "")
if not class_names_path or not os.path.exists(class_names_path):
logger.warning(f"类别名称文件不存在: {class_names_path},使用默认类别")
return [
"monster", "boss", "door", "item", "npc", "player",
"hp_bar", "mp_bar", "skill_ready", "cooldown"
]
for path in alt_paths:
if os.path.exists(path):
weights_path = path
logger.info(f"使用备选模型权重: {weights_path}")
break
# 尝试从YAML加载
try:
import yaml
with open(class_names_path, 'r', encoding='utf-8') as f:
data = yaml.safe_load(f)
if isinstance(data, dict) and "names" in data:
return data["names"]
elif "nc" in data and isinstance(data.get("names", []), list):
return data["names"]
except:
# 如果YAML加载失败尝试直接读取为文本
with open(class_names_path, 'r', encoding='utf-8') as f:
class_names = [line.strip() for line in f.readlines() if line.strip()]
return class_names
except Exception as e:
logger.error(f"加载类别名称时出错: {e}")
return [f"class_{i}" for i in range(10)] # 默认类别名
def _initialize_model(self):
"""初始化YOLO模型"""
try:
# 获取模型路径
weights_path = MODEL.get("weights", "")
if not weights_path:
raise ValueError("未指定模型权重路径")
if not os.path.exists(weights_path):
raise FileNotFoundError(f"找不到模型权重文件: {weights_path}")
# 加载模型
try:
if MODEL.get("engine", "") == "onnx":
self._load_onnx_model()
else:
# 尝试不同的加载方法
try:
self._load_yolov5_custom()
except Exception as e:
logger.warning(f"使用torch.hub加载YOLOv5模型失败: {e}")
logger.info("尝试备选方法加载模型...")
self._load_yolov5_manually()
raise FileNotFoundError(f"模型权重文件不存在: {weights_path}")
logger.info("YOLO模型加载成功")
logger.info(f"正在加载YOLO模型: {weights_path}")
# 确定模型引擎类型
engine = MODEL.get("engine", "pytorch").lower()
if engine == "onnx":
# 使用ONNX模型
if not weights_path.endswith(".onnx"):
weights_path = weights_path + ".onnx"
self._initialize_onnx_model(weights_path)
else:
# 默认使用PyTorch模型
self._initialize_pytorch_model(weights_path)
logger.info(f"YOLO模型加载成功运行于 {self.device} 设备")
except Exception as e:
logger.error(f"加载YOLO模型失败: {e}")
logger.error(f"初始化YOLO模型失败: {e}")
raise
def _load_yolov5_custom(self):
"""使用torch.hub加载YOLOv5模型"""
def _initialize_pytorch_model(self, weights_path):
"""初始化PyTorch YOLO模型"""
try:
# 尝试从本地加载YOLOv5
yolov5_dir = os.path.join(BASE_DIR, "tools", "yolov5")
if os.path.exists(yolov5_dir):
logger.info(f"从本地目录加载YOLOv5: {yolov5_dir}")
sys.path.insert(0, yolov5_dir)
# 检查设备可用性
if self.device.startswith("cuda") and not torch.cuda.is_available():
logger.warning("CUDA不可用切换到CPU模式")
self.device = "cpu"
self.half_precision = False
# 设置设备
device = torch.device(self.device)
# 确定YOLO版本并加载
model_name = MODEL.get("name", "").lower()
if "yolov8" in model_name or weights_path.endswith("v8.pt"):
# YOLOv8
try:
# 尝试直接导入YOLOv5模块
from models.common import DetectMultiBackend
from utils.torch_utils import select_device
from utils.general import check_img_size, non_max_suppression, scale_coords
from ultralytics import YOLO
self.model = YOLO(weights_path)
self.model_type = "yolov8"
logger.info("已加载YOLOv8模型")
except ImportError:
logger.error("无法导入ultralytics请安装: pip install ultralytics")
raise
else:
# YOLOv5默认
try:
sys.path.append(os.path.join(os.path.dirname(__file__), "../tools/yolov5"))
import torch
# 加载模型
self.model = DetectMultiBackend(MODEL.get("weights"), device=self.device)
self.stride = self.model.stride
self.names = self.model.names
self.model = torch.hub.load('ultralytics/yolov5', 'custom',
path=weights_path, device=device)
# 设置参数
self.imgsz = check_img_size((640, 640), s=self.stride)
self.model.conf = self.conf_threshold
self.model.iou = self.iou_threshold
self.model.classes = None # 检测所有类别
self.model.max_det = 100 # 最大检测数量
# 使用半精度
if self.use_half_precision:
# 如果启用半精度且支持
if self.half_precision and self.device != "cpu":
self.model.half()
# 预热模型
self._warmup_model()
self.model_type = "yolov5"
logger.info("已加载YOLOv5模型")
return
except ImportError as e:
logger.warning(f"无法直接导入YOLOv5模块: {e}")
# 尝试从torch.hub加载
logger.info("尝试从torch.hub加载YOLOv5模型")
self.model = torch.hub.load(
'ultralytics/yolov5',
'custom',
path=MODEL.get("weights"),
device=self.device,
force_reload=True
)
# 设置模型参数
self.model.conf = self.conf_threshold
self.model.iou = self.iou_threshold
# 使用半精度
if self.use_half_precision:
self.model.half()
except ImportError:
logger.error("无法导入torch或YOLOv5请确保已正确安装")
raise
except Exception as e:
logger.error(f"加载YOLOv5模型时出错: {e}")
raise
# 预热模型
self._warmup_model()
except Exception as e:
logger.error(f"加载YOLOv5模型失败: {e}")
logger.error(f"初始化PyTorch模型失败: {e}")
raise
def _load_yolov5_manually(self):
"""手动加载YOLOv5模型不依赖torch.hub"""
try:
# 检查YOLOv5目录是否存在不存在则克隆
yolov5_dir = os.path.join(BASE_DIR, "tools", "yolov5")
if not os.path.exists(yolov5_dir):
logger.info("YOLOv5目录不存在正在克隆仓库...")
os.makedirs(os.path.dirname(yolov5_dir), exist_ok=True)
# 克隆YOLOv5仓库
subprocess.run(
["git", "clone", "https://github.com/ultralytics/yolov5.git", yolov5_dir],
check=True
)
# 添加YOLOv5目录到系统路径
sys.path.insert(0, yolov5_dir)
try:
# 尝试导入YOLOv5模块
from models.common import DetectMultiBackend
from utils.torch_utils import select_device
from utils.general import check_img_size, non_max_suppression, scale_coords
# 加载模型
self.model = DetectMultiBackend(MODEL.get("weights"), device=self.device)
self.stride = self.model.stride
self.names = self.model.names
# 设置参数
self.imgsz = check_img_size((640, 640), s=self.stride)
# 使用半精度
if self.use_half_precision:
self.model.half()
# 保存必要的函数
self.non_max_suppression = non_max_suppression
self.scale_coords = scale_coords
# 预热模型
dummy_img = torch.zeros((1, 3, self.imgsz[0], self.imgsz[1]), device=self.device)
if self.use_half_precision:
dummy_img = dummy_img.half()
with torch.no_grad():
for _ in range(2):
self.model(dummy_img)
logger.info("手动加载YOLOv5模型成功")
except ImportError as e:
logger.error(f"导入YOLOv5模块失败: {e}")
# 尝试使用更简单的模型
self._load_fallback_model()
except Exception as e:
logger.error(f"手动加载YOLOv5模型失败: {e}")
# 尝试使用更简单的模型
self._load_fallback_model()
def _load_fallback_model(self):
"""加载备用模型使用PyTorch内置模型"""
logger.info("尝试加载备用模型 (PyTorch YOLO)")
try:
# 使用PyTorch的预训练模型
from torchvision.models.detection import fasterrcnn_resnet50_fpn
self.model = fasterrcnn_resnet50_fpn(pretrained=True)
self.model.to(self.device)
self.model.eval()
# 备用模型的类别
self.names = [
'background', 'monster', 'boss', 'door', 'item', 'npc', 'player',
'hp_bar', 'mp_bar', 'skill_ready', 'cooldown'
]
# 标记使用备用模型
self.using_fallback = True
logger.info("备用模型加载成功")
except Exception as e:
logger.error(f"加载备用模型失败: {e}")
raise
def _load_onnx_model(self):
"""加载ONNX版YOLO模型"""
def _initialize_onnx_model(self, weights_path):
"""初始化ONNX YOLO模型"""
try:
import onnxruntime as ort
# 设置ONNX运行时参数
if self.device == "cuda":
# 设置ONNX运行时选项
sess_options = ort.SessionOptions()
sess_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
# 选择运行设备
if self.device.startswith("cuda") and "CUDAExecutionProvider" in ort.get_available_providers():
providers = ['CUDAExecutionProvider', 'CPUExecutionProvider']
else:
providers = ['CPUExecutionProvider']
# 检查ONNX模型文件
onnx_path = MODEL.get("weights", "")
if not onnx_path.endswith('.onnx'):
onnx_path = onnx_path + '.onnx'
if not os.path.exists(onnx_path):
logger.warning(f"找不到ONNX模型: {onnx_path}")
logger.info("尝试导出PyTorch模型到ONNX格式...")
# 尝试加载PyTorch模型并导出为ONNX
self._load_yolov5_custom()
self._export_to_onnx(onnx_path)
self.device = "cpu"
# 创建ONNX会话
self.onnx_session = ort.InferenceSession(onnx_path, providers=providers)
self.model = ort.InferenceSession(weights_path, sess_options=sess_options,
providers=providers)
# 获取模型输入输出名称
self.input_name = self.onnx_session.get_inputs()[0].name
self.output_names = [output.name for output in self.onnx_session.get_outputs()]
# 获取模型输入输出信息
self.input_names = [input.name for input in self.model.get_inputs()]
self.output_names = [output.name for output in self.model.get_outputs()]
# 加载类别名称
if os.path.exists(MODEL.get("class_names", "")):
with open(MODEL.get("class_names"), "r") as f:
self.class_names = yaml.safe_load(f)
else:
self.class_names = ["object"]
# 标记使用ONNX
self.using_onnx = True
self.model_type = "onnx"
logger.info(f"已加载ONNX模型提供者: {self.model.get_providers()}")
# 预热模型
self._warmup_onnx_model()
dummy_input = np.random.rand(1, 3, self.img_size, self.img_size).astype(np.float32)
input_feed = {self.input_names[0]: dummy_input}
self.model.run(self.output_names, input_feed)
except ImportError:
logger.error("缺少ONNX运行时依赖请安装onnxruntimeonnxruntime-gpu")
logger.info("尝试使用PyTorch模型代替...")
self._load_yolov5_custom()
logger.error("无法导入onnxruntime请安装: pip install onnxruntime-gpu 或 onnxruntime")
raise
except Exception as e:
logger.error(f"加载ONNX模型失败: {e}")
logger.info("尝试使用PyTorch模型代替...")
self._load_yolov5_custom()
def _export_to_onnx(self, onnx_path):
"""将PyTorch模型导出为ONNX格式"""
try:
import torch.onnx
# 准备导出
dummy_input = torch.randn(1, 3, 640, 640, device=self.device)
if self.use_half_precision:
dummy_input = dummy_input.half()
# 导出ONNX
torch.onnx.export(
self.model,
dummy_input,
onnx_path,
verbose=False,
opset_version=12,
input_names=['images'],
output_names=['output']
)
logger.info(f"PyTorch模型成功导出为ONNX格式: {onnx_path}")
except Exception as e:
logger.error(f"导出ONNX模型失败: {e}")
logger.error(f"初始化ONNX模型失败: {e}")
raise
def _warmup_model(self):
"""预热模型(运行空白图像以初始化)"""
dummy_img = torch.zeros((1, 3, 640, 640), device=self.device)
if self.use_half_precision:
dummy_img = dummy_img.half()
# 进行多次推理预热
with torch.no_grad():
for _ in range(2):
self.model(dummy_img)
logger.info("模型预热完成")
def _warmup_onnx_model(self):
"""预热ONNX模型"""
dummy_img = np.zeros((1, 3, 640, 640), dtype=np.float32)
# 进行多次推理预热
for _ in range(2):
self.onnx_session.run(self.output_names, {self.input_name: dummy_img})
logger.info("ONNX模型预热完成")
"""预热模型,避免首次推理延迟"""
try:
logger.info("预热模型...")
if self.model_type == "yolov8":
# YOLOv8预热
dummy_image = np.random.randint(0, 255, (self.img_size, self.img_size, 3), dtype=np.uint8)
self.model(dummy_image, verbose=False)
elif self.model_type == "yolov5":
# YOLOv5预热
dummy_image = torch.zeros((1, 3, self.img_size, self.img_size), device=self.device)
if self.half_precision and self.device != "cpu":
dummy_image = dummy_image.half()
self.model(dummy_image)
logger.info("模型预热完成")
except Exception as e:
logger.warning(f"模型预热出错: {e}")
def detect(self, image):
"""
对图像进行目标检测
行目标检测
参数:
image (PIL.Image): 输入图像
返回:
list: 检测结果,每个结果包含边界框、类别和置信度
list: 检测结果列表
"""
try:
start_time = time.time()
# 根据模型类型选择检测方法
if hasattr(self, 'using_onnx') and self.using_onnx:
# 检查模型是否已初始化
if self.model is None:
logger.error("模型未初始化")
return []
# 选择相应的检测方法
if self.model_type == "yolov8":
detections = self._detect_yolov8(image)
elif self.model_type == "yolov5":
detections = self._detect_yolov5(image)
elif self.model_type == "onnx":
detections = self._detect_onnx(image)
elif hasattr(self, 'using_fallback') and self.using_fallback:
detections = self._detect_fallback(image)
else:
detections = self._detect_pytorch(image)
logger.error(f"不支持的模型类型: {self.model_type}")
return []
# 记录推理时间
inference_time = time.time() - start_time
self.inference_times.append(inference_time)
# 计算平均推理时间
avg_time = sum(self.inference_times) / len(self.inference_times)
if len(self.inference_times) % 10 == 0:
logger.debug(f"平均推理时间: {avg_time:.3f}秒, 当前: {inference_time:.3f}")
return detections
except Exception as e:
logger.error(f"目标检测出错: {e}")
return []
def _detect_pytorch(self, image):
"""使用PyTorch模型进行检测"""
# 在GPU上进行推理
with torch.no_grad():
# 处理不同的模型接口
if hasattr(self, 'stride') and hasattr(self, 'names'):
# 自定义加载的YOLOv5
# 转换图像
img = self._prepare_image_custom(image)
# 推理
output = self.model(img)
# 处理输出
pred = self.non_max_suppression(output[0], self.conf_threshold, self.iou_threshold)
# 解析结果
detections = []
for det in pred[0]:
x1, y1, x2, y2, conf, cls = det
detections.append({
'bbox': [float(x1), float(y1), float(x2), float(y2)],
'confidence': float(conf),
'class_id': int(cls),
'class_name': self.names[int(cls)]
})
def _detect_yolov5(self, image):
"""使用YOLOv5模型进行检测"""
# 转换PIL图像为所需格式
if isinstance(image, Image.Image):
# PIL图像直接传给模型
results = self.model(image, size=self.img_size)
else:
# 转换numpy数组为PIL图像
image = Image.fromarray(np.array(image))
results = self.model(image, size=self.img_size)
# 提取结果
predictions = results.xyxy[0].cpu().numpy() # xyxy格式(n, 6) - x1, y1, x2, y2, conf, cls
# 转换为标准格式
detections = []
for pred in predictions:
x1, y1, x2, y2, conf, cls_id = pred
cls_id = int(cls_id)
# 确保类别ID在范围内
if cls_id < len(self.class_names):
class_name = self.class_names[cls_id]
else:
# 标准torch.hub加载的模型
results = self.model(image)
# 处理结果
detections = []
for pred in results.xyxy[0].cpu().numpy():
x1, y1, x2, y2, conf, cls = pred
detections.append({
'bbox': [float(x1), float(y1), float(x2), float(y2)],
'confidence': float(conf),
'class_id': int(cls),
'class_name': self.model.names[int(cls)]
})
class_name = f"class_{cls_id}"
detections.append({
"bbox": [float(x1), float(y1), float(x2), float(y2)],
"confidence": float(conf),
"class_id": cls_id,
"class_name": class_name
})
return detections
def _detect_fallback(self, image):
"""使用备用模型进行检测"""
# 预处理图像
img = self._prepare_image_pytorch(image)
def _detect_yolov8(self, image):
"""使用YOLOv8模型进行检测"""
# 转换PIL图像为所需格式
if isinstance(image, Image.Image):
# 转换为numpy数组
image_np = np.array(image)
else:
image_np = np.array(image)
# 行推理
with torch.no_grad():
predictions = self.model(img)
# 行推理
results = self.model(
image_np,
conf=self.conf_threshold,
iou=self.iou_threshold,
verbose=False
)
# 处理结果
# 提取结果
detections = []
for i, prediction in enumerate(predictions[0]['boxes']):
score = predictions[0]['scores'][i].item()
if score > self.conf_threshold:
x1, y1, x2, y2 = prediction.tolist()
class_id = predictions[0]['labels'][i].item()
for result in results:
# 获取边界框
boxes = result.boxes
for i in range(len(boxes)):
# 提取坐标conf和类别
box = boxes[i]
x1, y1, x2, y2 = box.xyxy[0].cpu().numpy()
conf = float(box.conf)
cls_id = int(box.cls)
# 映射torchvision的COCO类别到我们的类别
class_name = self.names[min(class_id, len(self.names) - 1)]
# 确保类别ID在范围内
if cls_id < len(self.class_names):
class_name = self.class_names[cls_id]
else:
class_name = f"class_{cls_id}"
detections.append({
'bbox': [float(x1), float(y1), float(x2), float(y2)],
'confidence': float(score),
'class_id': class_id,
'class_name': class_name
"bbox": [float(x1), float(y1), float(x2), float(y2)],
"confidence": conf,
"class_id": cls_id,
"class_name": class_name
})
return detections
def _detect_onnx(self, image):
"""使用ONNX模型进行检测"""
# 预处理图像
input_tensor = self._prepare_image_onnx(image)
# 准备输入图像
if isinstance(image, Image.Image):
# 转换PIL图像为numpy数组
img = np.array(image.resize((self.img_size, self.img_size)))
else:
# 转换任何其他格式为numpy数组
img = cv2.resize(np.array(image), (self.img_size, self.img_size))
# 运行推
outputs = self.onnx_session.run(self.output_names, {self.input_name: input_tensor})
# 图像预处
img = img.transpose(2, 0, 1) # HWC -> CHW
img = np.expand_dims(img, axis=0) # 添加batch维度
img = img / 255.0 # 归一化到[0,1]
img = img.astype(np.float32) # 转换为float32
# 解析输出根据ONNX模型的输出格式可能需要调整
# 假设输出格式为 [batch_id, x1, y1, x2, y2, conf, class_id]
predictions = outputs[0]
# 执行推理
input_feed = {self.input_names[0]: img}
outputs = self.model.run(self.output_names, input_feed)
# 过滤低置信度预测
mask = predictions[:, 5] > self.conf_threshold
filtered_preds = predictions[mask]
# 后处理
# 注意:实际后处理可能因模型而异
# 这里假设输出为[batch, num_detections, 6]格式其中6为[x1, y1, x2, y2, conf, cls]
predictions = outputs[0] # 假设第一个输出是检测结果
# 组织检测结果
# 转换为标准格式
detections = []
for pred in filtered_preds:
x1, y1, x2, y2, conf, cls = pred[1:7]
cls_id = int(cls)
detections.append({
'bbox': [float(x1), float(y1), float(x2), float(y2)],
'confidence': float(conf),
'class_id': cls_id,
'class_name': self.class_names[cls_id] if cls_id < len(self.class_names) else "unknown"
})
# 提取有效检测
valid_preds = predictions[0] # 取第一个batch
for pred in valid_preds:
if len(pred) >= 6: # 检查是否有6个元素
x1, y1, x2, y2, conf, cls_id = pred[:6]
# 过滤低置信度检测
if conf < self.conf_threshold:
continue
cls_id = int(cls_id)
# 确保类别ID在范围内
if cls_id < len(self.class_names):
class_name = self.class_names[cls_id]
else:
class_name = f"class_{cls_id}"
# 将坐标从[0,1]缩放到原始图像尺寸
if max(x1, y1, x2, y2) <= 1.0:
# 坐标是归一化的,需要缩放到原始尺寸
if hasattr(image, 'size'):
orig_w, orig_h = image.size
else:
orig_h, orig_w = image.shape[:2]
x1 *= orig_w
x2 *= orig_w
y1 *= orig_h
y2 *= orig_h
detections.append({
"bbox": [float(x1), float(y1), float(x2), float(y2)],
"confidence": float(conf),
"class_id": cls_id,
"class_name": class_name
})
return detections
def _prepare_image_custom(self, image):
"""为自定义加载的YOLOv5模型准备图像"""
# 转换PIL图像为numpy数组
img = np.array(image)
# 调整大小
img = self._letterbox(img, new_shape=self.imgsz)[0]
# BGR转RGB
img = img[:, :, ::-1].transpose(2, 0, 1)
img = np.ascontiguousarray(img)
# 转换为PyTorch张量
img = torch.from_numpy(img).to(self.device)
img = img.half() if self.use_half_precision else img.float()
img /= 255.0
# 增加批次维度
if img.ndimension() == 3:
img = img.unsqueeze(0)
return img
def _prepare_image_pytorch(self, image):
"""为PyTorch模型准备图像"""
# 转换PIL图像为PyTorch张量
from torchvision import transforms
transform = transforms.Compose([
transforms.ToTensor(),
])
img = transform(image).unsqueeze(0).to(self.device)
return img
def _prepare_image_onnx(self, image):
"""为ONNX模型准备图像"""
# 调整图像大小
img_size = MODEL.get("img_size", 640)
image = image.resize((img_size, img_size), Image.LANCZOS)
# 转换为numpy数组
img = np.array(image).astype(np.float32) / 255.0
# 从HWC转换为CHW格式
img = img.transpose(2, 0, 1)
# 添加批次维度
img = np.expand_dims(img, axis=0)
return img
def _letterbox(self, img, new_shape=(640, 640), color=(114, 114, 114)):
"""调整图像大小并填充YOLOv5风格"""
shape = img.shape[:2] # 当前形状 [高, 宽]
# 缩放比例 (新 / 旧)
r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
# 计算填充
new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1]
# 平均分配填充
dw /= 2
dh /= 2
# 调整大小
if shape[::-1] != new_unpad:
import cv2
img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)
# 添加边框
top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)
return img, r, (dw, dh)
def get_class_names(self):
"""获取类别名称列表"""
if hasattr(self, 'using_onnx') and self.using_onnx:
return self.class_names
elif hasattr(self, 'using_fallback') and self.using_fallback:
return self.names
elif hasattr(self, 'names'):
return self.names
else:
return self.model.names
def get_performance_stats(self):
"""获取性能统计信息"""
if not self.inference_times:
return {"average_time": 0, "min_time": 0, "max_time": 0, "count": 0, "fps": 0}
return {
"average_time": 0,
"min_time": 0,
"max_time": 0,
"fps": 0
}
avg_time = sum(self.inference_times) / len(self.inference_times)
# 修复fps计算逻辑
if avg_time > 0:
fps = 1.0 / avg_time
else:
fps = 0
return {
"average_time": avg_time,
"min_time": min(self.inference_times),
"max_time": max(self.inference_times),
"count": len(self.inference_times),
"fps": fps
}
"median_time": statistics.median(self.inference_times),
"fps": 1.0 / avg_time if avg_time > 0 else 0
}
if __name__ == "__main__":
# 简单测试
logging.basicConfig(level=logging.INFO)
try:
# 初始化模型
model = YOLOModel()
# 加载测试图像
test_image_path = "data/training/images/test/sample_1.jpg"
if os.path.exists(test_image_path):
image = Image.open(test_image_path)
# 检测
detections = model.detect(image)
# 打印结果
print(f"检测到 {len(detections)} 个目标:")
for det in detections:
print(f"类别: {det['class_name']}, 置信度: {det['confidence']:.2f}, 边界框: {det['bbox']}")
# 打印性能
perf = model.get_performance_stats()
print(f"模型性能: {perf['average_time']*1000:.2f}ms, {perf['fps']:.2f} FPS")
else:
print(f"测试图像不存在: {test_image_path}")
except Exception as e:
print(f"测试失败: {e}")