简介本资源是一套基于YOLOv5目标检测算法实现的《地下城与勇士》DNF游戏自动化辅助脚本系统面向计算机视觉初学者、游戏AI实践者及自动化工具开发爱好者解决游戏中高频重复操作如技能释放、方向移动、怪物识别的效率瓶颈问题。压缩包共95个文件含32个Python源码涵盖图像捕获、键鼠控制、YOLOv5推理、小目标识别等核心模块、34个编译后pyc文件、8个模型配置yaml、2个预训练pt权重文件、以及测试截图、README说明和Docker部署支持文件整体27.27MB结构完整兼顾可读性与可运行性。已有404人学习下载读者可直接复现DNF场景下的实时目标检测自动交互闭环获得从数据采集、模型微调、屏幕帧处理到底层输入模拟的全链路工程实践参考尤其适合理解游戏自动化中CV与系统交互的协同设计逻辑。1. 为什么用 YOLOv5 做 DNF 自动脚本不是玄学而是工程刚需你见过凌晨三点还在手动刷深渊的玩家吗不是肝帝是被 UI 延迟、技能 CD 判定抖动、小怪位置漂移逼到写脚本的人。DNF地下城与勇士这类横版格斗游戏UI 元素密集、角色动作快、特效遮挡强传统图像匹配OpenCV 模板匹配在“深渊派对入口按钮闪一下就消失”“怪物血条被技能光效盖住一半”时直接失效——这不是识别不准是识别逻辑崩了。而 YOLOv5 的价值恰恰在于它把“找图”升级成“理解画面语义”它不靠像素比对而是用卷积网络学出“这是哥布林头上的红血条”“这是左下角闪烁的‘进入’按钮”“这是技能栏第3格冷却中的冰霜之环图标”。标题里那个.zip文件本质是一套闭环落地方案从采集 DNF 游戏窗口截图 → 标注 UI 元素和怪物框 → 训练轻量 YOLOv5s 模型 → 部署到本地 Python 进程 → 实时推理坐标 → 转成 Windows API 级鼠标/键盘指令。它不碰游戏协议层不改内存不调用任何外挂 DLL纯视觉驱动所以能绕过绝大多数客户端级反作弊如腾讯 TGP 的基础行为检测但代价是——你得亲手调参、压帧率、扛误触发。适合两类人一是想用真实项目练透 YOLOv5 全流程的 CV 工程师二是厌倦了 RPA 脚本在 DNF 里频繁失焦、点错技能栏的硬核玩家。别信“一键全自动”这玩意儿上线前至少要调 3 轮标注、2 轮超参、1 轮帧率压测。2. 从游戏窗口截屏到 YOLOv5 训练数据集四步闭环构建DNF 自动脚本的数据根基不是网上随便扒的“DNF 数据集”而是你本地实机录的、带真实延迟和分辨率的截图。YOLOv5 对数据分布极其敏感——训练图全是 1920×1080 无缩放截图推理时遇到 2560×1440 或开启游戏内 UI 缩放mAP 直接掉 30%。下面是我压测验证过的最小可行路径全程用 Python OpenCV labelImg不依赖任何云标注平台。2.1 实时截取 DNF 游戏窗口非全屏保 UI 原始比例关键不是“能截图”而是“截得准”。DNF 在窗口化模式下常有边框、任务栏遮挡直接pyautogui.screenshot()会包含无关区域。必须用 Windows API 获取精确句柄import win32gui import win32con import numpy as np from PIL import Image def capture_dnf_window(): # 精确匹配 DNF 窗口标题注意国服是地下城与勇士台服是Dungeon Fighter hwnd win32gui.FindWindow(None, 地下城与勇士) if not hwnd: raise RuntimeError(DNF 窗口未找到请确认游戏已启动且标题未被修改) # 获取窗口客户区尺寸排除标题栏/边框 left, top, right, bottom win32gui.GetClientRect(hwnd) # 转换为客户区左上角屏幕坐标 client_pos win32gui.ClientToScreen(hwnd, (0, 0)) left client_pos[0] top client_pos[1] # 截图使用 win32gui 保证无缩放失真 w, h right - left, bottom - top hwndDC win32gui.GetWindowDC(hwnd) mfcDC win32gui.CreateCompatibleDC(hwndDC) saveBitMap win32gui.CreateCompatibleBitmap(hwndDC, w, h) win32gui.SelectObject(mfcDC, saveBitMap) win32gui.BitBlt(mfcDC, 0, 0, w, h, hwndDC, 0, 0, win32con.SRCCOPY) bmpinfo win32gui.GetBitmapInfo(saveBitMap) bmpstr win32gui.GetBitmapBits(saveBitMap, True) img np.frombuffer(bmpstr, dtypeuint8) img.shape (h, w, 4) # BGRA img cv2.cvtColor(img, cv2.COLOR_BGRA2RGB) # 转 RGB 供 YOLOv5 使用 win32gui.DeleteObject(saveBitMap) win32gui.DeleteDC(mfcDC) win32gui.ReleaseDC(hwnd, hwndDC) return img # 每秒截 5 帧存为 JPEG压缩率 95平衡体积与画质 import time for i in range(200): frame capture_dnf_window() cv2.imwrite(fdataset/raw/frame_{i:04d}.jpg, frame, [cv2.IMWRITE_JPEG_QUALITY, 95]) time.sleep(0.2) # 控制采集节奏避免 GPU 占用过高提示win32gui.GetClientRect()是关键它返回的是客户区内尺寸比GetWindowRect()更可靠。如果游戏用了 DirectX 全屏独占模式此法失效——此时必须切到“无边框窗口”模式这是 DNF 官方支持的兼容模式。2.2 标注核心目标只标 4 类拒绝过度泛化YOLOv5 不是万能的标注越精简模型越鲁棒。我反复验证后锁定以下 4 类目标对应 DNF 自动刷图最刚需场景类别 ID类别名标注理由示例截图特征0enter_btn深渊/团队副本入口按钮位置固定但常闪烁白色文字“进入”蓝色底纹宽高比约 3:11monster_head怪物头顶血条用于判断是否存活及距离红色长条黄色边框顶部居中高度30px2skill_cd技能栏冷却图标CD 圆圈覆盖层半透明黑色圆盘白色数字直径约 40px3boss_hpBOSS 血条唯一需要高精度定位的元素红色长条金色边框位于屏幕正上方中央为什么只标这 4 类标player_char角色本体会导致模型学习“角色在画面中心”但自动脚本实际需要的是“怪物在哪”“按钮在哪”角色位置是干扰项不标item_drop掉落物因 DNF 掉落特效多、颜色杂、尺寸小YOLOv5s 在 640×640 输入下召回率不足 60%所有标注用labelImgv2.0.0导出为 YOLO 格式.txt必须关闭“Verify Image”选项——DNF 截图常有轻微压缩噪点验证会误判为损坏图而跳过。2.3 构建 YOLOv5 训练目录结构与配置文件YOLOv5 官方要求严格目录结构且data.yaml中的nc类别数和names必须与标注完全一致否则训练会静默失败dataset/ ├── images/ │ ├── train/ # 160 张截图含不同副本、不同角色、不同缩放 │ ├── val/ # 40 张截图独立于训练集含高难度场景如“雾都赫伊斯” │ └── test/ # 20 张截图留作上线前最终验证 ├── labels/ │ ├── train/ # 对应 images/train/ 的 .txt 标注文件 │ ├── val/ │ └── test/ └── data.yaml # 关键配置文件data.yaml内容务必手敲勿复制网络模板train: ../images/train val: ../images/val test: ../images/test nc: 4 names: [enter_btn, monster_head, skill_cd, boss_hp]参数说明nc: 4是硬性要求若写成nc: 1模型会把所有目标当同一类names顺序必须与 labelImg 中类别 ID 严格对应ID 0→enter_btnID 1→monster_head……错一位推理时类别全乱。3. 训练轻量 YOLOv5s 模型超参选择与收敛监控YOLOv5s 是 DNF 自动脚本的黄金选择——它在 GTX 10606GB上能跑 42 FPS模型大小仅 14MB且对小目标如skill_cd图标的召回率比 YOLOv5m 高 7%。但默认超参在 DNF 数据上会过拟合必须调整。3.1 修改models/yolov5s.yaml中的 anchor 适配小目标DNF UI 元素普遍小skill_cd直径约 40px在 640×640 输入中仅占 6% 宽度原版 anchor基于 COCO 数据集对小目标不友好。需重聚类# 1. 先用原始 anchor 训练 10 轮生成预测框统计 python train.py --data dataset/data.yaml --cfg models/yolov5s.yaml --weights --epochs 10 --batch-size 16 --name yolov5s_init # 2. 提取训练集所有标注框运行 k-means 聚类YOLOv5 自带工具 python utils/autoanchor.py --input dataset/labels/train/ --n 9 --img-size 640输出结果类似# 新 anchor按宽高比排序替换 models/yolov5s.yaml 中的 anchors anchors: - [10,13, 16,30, 33,23] # 小目标层P3 - [30,61, 62,45, 59,119] # 中目标层P4 - [116,90, 156,198, 373,326] # 大目标层P5血泪经验autoanchor.py输出的 anchor 必须手动填入yolov5s.yaml不能直接覆盖。原 anchor 在 P3 层是[10,13, 16,30, 33,23]新 anchor 若第一组数值小于 10YOLOv5 会报anchor must be 0错误——此时需将最小值设为 8下限再微调。3.2 关键超参设置解决 DNF 特有抖动问题DNF 游戏画面存在两种抖动1技能释放时 UI 元素位移如血条短暂上移2网络延迟导致帧间物体跳变。这会让模型学习到错误的“运动轨迹”必须用超参抑制python train.py \ --data dataset/data.yaml \ --cfg models/yolov5s.yaml \ --weights \ --epochs 150 \ --batch-size 16 \ --img 640 \ --rect \ # 启用矩形训练减少 padding 导致的 UI 变形 --single-cls \ # 所有类别共用一套 anchor提升小目标一致性 --cos-lr \ # 余弦退火学习率避免后期震荡 --lr0 0.01 \ # 初始学习率设为 0.01默认 0.01但 DNF 数据需保持 --lrf 0.1 \ # 最终学习率 lr0 * lrf 0.001防止过拟合 --iou-thres 0.45 \ # NMS IOU 阈值调低避免同类目标如多个 skill_cd被合并 --name yolov5s_dnf为什么--single-cls必开DNF 的enter_btn和boss_hp形状差异极大前者扁长后者细长分开学习 anchor 会让模型在enter_btn上过拟合而在boss_hp上漏检。共用 anchor 强迫网络学习更泛化的特征表达。3.3 监控收敛只看val/box_loss和metrics/mAP_0.5YOLOv5 的 TensorBoard 日志里有十几项指标但对 DNF 脚本只需盯死两项val/box_loss必须稳定在 0.05 以下低于 0.03 为优高于 0.08 说明定位不准脚本会点偏metrics/mAP_0.5必须 ≥ 0.820.5 是 IoU 阈值低于 0.75 时monster_head检出率不足脚本无法持续攻击。避坑train/obj_loss下降快但val/box_loss不降这是典型过拟合——立即停止训练用--evolve参数进化超参而非增加 epoch。4. 部署推理引擎实时检测 坐标映射 Windows API 指令生成训练完的yolov5s_dnf.pt模型不能直接扔进脚本。DNF 自动脚本的瓶颈不在识别而在“识别结果→鼠标点击”的毫秒级链路。YOLOv5 默认推理耗时 28msGTX 1060但加上坐标转换、防抖滤波、API 调用整链路必须压到 ≤ 45ms否则跟不上游戏 60FPS 节奏。4.1 构建最小推理 pipeline去掉一切冗余import torch import cv2 import numpy as np from models.experimental import attempt_load from utils.general import non_max_suppression, scale_coords from utils.plots import plot_one_box class DNFDetector: def __init__(self, weightsyolov5s_dnf.pt, devicecuda:0): self.model attempt_load(weights, map_locationdevice) self.device device self.names self.model.module.names if hasattr(self.model, module) else self.model.names self.stride int(self.model.stride.max()) # 32 # 预热 GPU首次推理慢必须预热 img torch.zeros((1, 3, 640, 640), devicedevice) _ self.model(img) def detect(self, img_rgb): # 1. 图像预处理YOLOv5 标准流程 img cv2.resize(img_rgb, (640, 640)) img img[:, :, ::-1].transpose(2, 0, 1) # BGR to RGB, to 3x640x640 img np.ascontiguousarray(img) img torch.from_numpy(img).to(self.device).float() / 255.0 if img.ndimension() 3: img img.unsqueeze(0) # 2. 推理禁用 augmentDNF 截图无翻转需求 pred self.model(img, augmentFalse)[0] # 3. NMSIoU0.45与训练一致 pred non_max_suppression(pred, conf_thres0.5, iou_thres0.45)[0] # 4. 坐标还原到原始截图尺寸关键 # 注意capture_dnf_window() 返回的是原始分辨率如 1920x1080 # 而模型输入是 640x640需按比例缩放 orig_h, orig_w img_rgb.shape[:2] pred[:, :4] scale_coords((640, 640), pred[:, :4], (orig_h, orig_w)).round() return pred.cpu().numpy() # 使用示例 detector DNFDetector() while True: frame capture_dnf_window() # 2.1 节的函数 results detector.detect(frame) # 耗时 ≈ 28ms # 解析结果只取置信度 0.6 的框防误触 for *xyxy, conf, cls in results: if conf 0.6: continue x1, y1, x2, y2 map(int, xyxy) cls_name detector.names[int(cls)] print(f[{cls_name}] ({x1},{y1}) - ({x2},{y2}), conf{conf:.2f})逻辑说明scale_coords()是核心它把模型输出的 640×640 坐标精准映射回原始截图尺寸如 1920×1080。若跳过此步直接用(x1x2)//2, (y1y2)//2算中心点点击位置会偏移 15% 以上。4.2 坐标映射到 Windows 屏幕解决 DPI 缩放陷阱Windows 10/11 默认开启 DPI 缩放如 125%pyautogui.click(x,y)会把(100,100)当作物理像素点但 DNF 窗口坐标是逻辑像素。必须获取当前 DPI 缩放比import ctypes def get_dpi_scale(): try: # 获取系统 DPI 缩放比例 ctypes.windll.shcore.SetProcessDpiAwareness(1) scale ctypes.windll.shcore.GetScaleFactorForDevice(0) / 100 return scale except: return 1.0 # 默认 100% dpi_scale get_dpi_scale() print(f当前 DPI 缩放: {dpi_scale}x) # 将检测坐标转为屏幕绝对坐标 hwnd win32gui.FindWindow(None, 地下城与勇士) left, top, right, bottom win32gui.GetWindowRect(hwnd) client_left, client_top, _, _ win32gui.GetClientRect(hwnd) # 计算窗口客户区左上角相对于屏幕的偏移 offset_x left (right - left - client_left) // 2 offset_y top (bottom - top - client_top) // 2 # 最终点击坐标 窗口偏移 检测框中心 DPI 补偿 click_x offset_x (x1 x2) // 2 click_y offset_y (y1 y2) // 2 # DPI 补偿系统缩放后物理像素 逻辑像素 × dpi_scale click_x int(click_x * dpi_scale) click_y int(click_y * dpi_scale)参数说明GetWindowRect()返回窗口外框含标题栏GetClientRect()返回客户区内框二者差值就是标题栏/边框宽度。offset_x/y是客户区左上角的屏幕坐标所有检测坐标必须叠加此偏移。4.3 防抖与指令队列让脚本不“抽风”YOLOv5 每帧都可能检测到enter_btn但按钮只在特定状态如副本加载完成才可点击。直接每帧点击会触发游戏保护机制。必须加状态机class DNFAutoScript: def __init__(self): self.last_click_time 0 self.click_cooldown 1.5 # 秒级冷却防连点 self.state idle # idle, waiting_enter, attacking, boss_fight def handle_detection(self, results): now time.time() for *xyxy, conf, cls in results: if conf 0.65: # 提高阈值只响应高置信度 continue cls_name detector.names[int(cls)] if cls_name enter_btn and self.state idle: if now - self.last_click_time self.click_cooldown: self.click_center(xyxy) self.last_click_time now self.state waiting_enter elif cls_name monster_head and self.state in [waiting_enter, attacking]: # 只要看到怪物头就持续攻击模拟长按技能 self.press_skill_key(q) # 假设 Q 是主力技能 elif cls_name boss_hp and self.state ! boss_fight: self.state boss_fight self.activate_boss_mode() def click_center(self, xyxy): x1, y1, x2, y2 map(int, xyxy) center_x (x1 x2) // 2 center_y (y1 y2) // 2 # 调用 Windows API 点击比 pyautogui 更稳定 ctypes.windll.user32.SetCursorPos(center_x, center_y) ctypes.windll.user32.mouse_event(2, 0, 0, 0, 0) # MOUSEEVENTF_LEFTDOWN ctypes.windll.user32.mouse_event(4, 0, 0, 0, 0) # MOUSEEVENTF_LEFTUP为什么用ctypes.windll.user32pyautogui在 DNF 窗口焦点丢失时会失效而 Windows API 级调用始终有效且延迟更低≈ 8ms vs 15ms。5. 避坑指南DNF 自动脚本的 5 个血泪现场这些坑我都在凌晨 2 点的测试服里踩过每个都导致脚本上线即翻车。不写原理只给现象、原因、解法。5.1 现象模型在训练集上 mAP 0.92但实机推理时enter_btn检出率 30%原因训练时用了--rect矩形训练但实机截图是标准 16:9而--rect会 pad 成 640×640 的正方形导致按钮被拉伸变形。YOLOv5 学到的是“拉伸后的按钮”而非“原始按钮”。解决训练时禁用--rect改用--img 640 --pad 0强制所有图 resize 到 640×640不 pad并确保采集截图时 DNF 分辨率固定为 1920×1080或你的目标分辨率。5.2 现象脚本点击位置总是偏右 20 像素且随游戏窗口大小变化原因GetWindowRect()返回的坐标是窗口外框但 DNF 窗口在最大化时有 1 像素阴影边框GetClientRect()未扣除此边框导致偏移计算误差。解决不用GetClientRect()改用win32gui.DwmGetWindowAttribute()获取真实客户区内边距DWMWA_EXTENDED_FRAME_BOUNDS 9 rect ctypes.wintypes.RECT() ctypes.windll.dwmapi.DwmGetWindowAttribute( hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, ctypes.byref(rect), ctypes.sizeof(rect) ) client_left rect.left client_top rect.top5.3 现象skill_cd图标在技能刚释放时总被漏检但冷却中又能检出原因技能释放瞬间CD 图标是半透明渐入动画YOLOv5 的 confidence threshold0.5过滤掉了低 opacity 区域。解决对skill_cd类单独降低置信度阈值在 NMS 前做类别级阈值# 在 detect() 函数中NMS 前 for i, (*xyxy, conf, cls) in enumerate(pred): if int(cls) 2: # skill_cd 类别 ID conf max(conf, 0.3) # 强制不低于 0.3 pred[i, 4] conf5.4 现象脚本运行 10 分钟后 CPU 占用飙升至 95%帧率暴跌原因win32gui.GetWindowDC()未释放 DC 句柄每帧创建新 DC 导致 GDI 句柄泄漏Windows 限制 10000 个。解决必须在capture_dnf_window()结尾添加win32gui.DeleteDC(mfcDC)和win32gui.ReleaseDC(hwnd, hwndDC)且顺序不能颠倒先删 mfcDC再释放 hwndDC。5.5 现象切换到其他窗口再切回 DNF脚本突然停止响应原因win32gui.FindWindow()在窗口失去焦点时返回0后续GetWindowDC()报错但异常被静默吞掉。解决在capture_dnf_window()开头加健壮性检查if not hwnd or not win32gui.IsWindowVisible(hwnd): # 尝试激活窗口 win32gui.SetForegroundWindow(hwnd) time.sleep(0.1) if not win32gui.IsWindowVisible(hwnd): raise RuntimeError(DNF 窗口不可见请检查游戏是否最小化或崩溃)6. 进阶技巧用帧间差分 Kalman 滤波稳住 BOSS 血条跟踪DNF BOSS 战时boss_hp血条会随伤害跳动、被技能特效遮挡YOLOv5 单帧检测易抖动。单纯提高置信度阈值会漏检而降低又增误检。我的解法是不依赖单帧检测而用多帧轨迹预测。6.1 构建血条运动模型为什么 Kalman 比滑动平均更准滑动平均如取最近 5 帧中心点均值在 BOSS 瞬间位移如“巴卡尔·瞬移”时会拖出残影Kalman 滤波则建模“血条位置 上一帧位置 速度 × Δt”当 BOSS 瞬移时速度突变会被滤波器识别为异常自动降权该帧观测值。实测 Kalman 将boss_hp跟踪抖动降低 62%。import numpy as np from filterpy.kalman import KalmanFilter from filterpy.common import Q_discrete_white_noise class BossHPSmoother: def __init__(self): # 状态向量 [x, y, vx, vy]观测向量 [x, y] self.kf KalmanFilter(dim_x4, dim_z2) self.kf.x np.array([0, 0, 0, 0]) # 初始状态位置0, 速度0 self.kf.F np.array([[1, 0, 1, 0], # 状态转移矩阵 [0, 1, 0, 1], [0, 0, 1, 0], [0, 0, 0, 1]]) self.kf.H np.array([[1, 0, 0, 0], # 观测矩阵 [0, 1, 0, 0]]) self.kf.P * 1000. # 初始协方差大适应未知起始位置 self.kf.R np.array([[5, 0], # 观测噪声YOLO 检测误差约 ±5px [0, 5]]) self.kf.Q Q_discrete_white_noise(dim2, dt1/60, var0.1) # 过程噪声适配 60FPS def update(self, x, y): # x, y 是 YOLO 检测到的血条中心坐标 z np.array([x, y]) self.kf.predict() self.kf.update(z) # 返回滤波后的位置x, y return self.kf.x[0], self.kf.x[1] # 在主循环中使用 smoother BossHPSmoother() while True: frame capture_dnf_window() results detector.detect(frame) # 只提取 boss_hp 检测 boss_boxes [r for r in results if int(r[5]) 3 and r[4] 0.7] if boss_boxes: x1, y1, x2, y2 map(int, boss_boxes[0][:4]) smooth_x, smooth_y smoother.update((x1x2)//2, (y1y2)//2) # 用 smooth_x, smooth_y 做后续逻辑而非原始坐标6.2 帧间差分辅助在 YOLO 失效时兜底当 BOSS 释放全屏技能如“绝望之塔·黑洞”血条被完全遮盖YOLOv5 检出率为 0。此时启用帧间差分Frame Difference作为备用通道class FrameDiffFallback: def __init__(self): self.prev_gray None def detect_boss_hp_diff(self, frame): gray cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY) if self.prev_gray is None: self.prev_gray gray return None # 计算帧差 diff cv2.absdiff(gray, self.prev_gray) _, thresh cv2.threshold(diff, 30, 255, cv2.THRESH_BINARY) # 形态学去噪 kernel np.ones((3,3), np.uint8) thresh cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel) # 找最大连通域假设血条是最大静态区域 contours, _ cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if contours: largest max(contours, keycv2.contourArea) x, y, w, h cv2.boundingRect(largest) # 验证是否符合血条宽高比3:1 到 5:1 if 3 w/h 5 and w 100: self.prev_gray gray return (x, y, xw, yh) self.prev_gray gray return None fallback FrameDiffFallback() # 在主循环中若 YOLO 未检出 boss_hp则调用 fallback.detect_boss_hp_diff(frame)参数说明cv2.threshold的阈值 30 是经验值——太低20会把粒子特效当血条太高50会漏掉弱遮挡。w 100过滤掉小噪点3 w/h 5确保是血条而非 BOSS 头像。我坚持用这套组合YOLOv5 做主检测Kalman 做轨迹平滑帧差做失效兜底。上线前在“雾都赫伊斯”副本压测 8 小时boss_hp跟踪连续性达 99.2%比纯 YOLO 方案高 17 个百分点。这背后没有黑匣子只有三样东西一次标错的boss_hp边框导致模型学歪、两次忘记ReleaseDC导致句柄泄漏、三次 Kalman Q 矩阵调错导致预测发散。希望帮到你。本文还有配套的精品资源点击获取