尧图精选

YOLO+Python视觉识别实战:从屏幕捕获到键鼠自动化的完整链路

🕒 发布时间:2026/10/2 18:59:11 📁 来源:尧图网络
简介这是一份基于YOLO目标检测的DNF手游自动化脚本源码面向有一定Python基础、希望实现游戏图像识别与自动操作的玩家或开发者。压缩包共18个文件以5个Python脚本为核心配套11张PNG标注/识别结果示例图、1个MP4演示视频以及1个README说明文档整体大小约79.85MB。脚本采用YOLOv5训练模型并导出NCNN权重根目录放入.bin与.param文件即可直接运行同时支持Label Studio标注流程内置Gate、Hero、Item、Mark、Monster、Monster_Fake六个目标类别。已实现的功能包括人物、怪物、材料、门等物体的实时识别以及自动寻路、过图、固定人物攻击、依据怪物数量调整攻击策略、识别狮子头房间、开局释放Buff技能、拾取掉落物含粉装识别、自动再次挑战等。目前已有853人学习下载适合希望研究游戏自动化脚本编写思路、YOLO工程落地方案或快速搭建图像识别自动化流程的Python学习者参考。1. 基于 YOLO 的 Python 源码搞 DNF这个视觉脚本到底怎么落地很多人想写 DNF 自动化脚本第一反应是把固定坐标录成键盘鼠标宏。你在游戏里只要动一下人物、切一次图这些坐标全部作废。真正能落地的是视觉识别这套基于 YOLO 的 Python 源码用屏幕捕获拿到画面交给预训练模型推理得到怪物和掉落物的位置再把坐标映射回屏幕去驱动键鼠。这套源码解决的问题很具体把目标检测的完整部署链路从模型加载、推理过滤到坐标换算、键鼠控制压缩成开箱即用的脚本。适合正在学 YOLO 部署的开发者也适合想在个人测试环境里研究游戏视觉自动化的玩家。拿到手不需要重训模型但要先把 Python 环境、依赖和预训练权重准备好这就是第 2 章要讲的内容。先给个边界这个项目的合理用途是学习视觉定位闭环和离线回放验证不要在真实在线游戏环境里干扰生态平衡这一点后面还会再提。2. 环境搭建与预训练权重下载Python 版本、依赖和模型一次配齐2.1 Python 安装与依赖清单版本卡在哪几个关键点上先说 Python 安装。这套代码基于 ultralytics 生态按我拆项目时的排错经验Python 3.10 是最稳的版本。3.12 以上常常遇到 torch 没有对应 wheel装不上 CUDA 版本3.9 以下又会撞上新版 numpy 的兼容问题。我一般会直接装 3.10.x 的 64 位安装包安装时勾选 Add to PATH后面就不用手动去配环境变量。依赖文件的常见做法是放一个 requirements.txt把版本锁死内容大概是这样ultralytics8.2.100 torch2.3.1 torchvision0.18.1 mss10.0.0 pywin32306 pydirectinput1.0.4 opencv-python4.10.0.84 numpy1.26,2.0 PyYAML6.0.1安装命令建议分两步走先建虚拟环境再装依赖python -m venv .venv .venv\Scripts\activate pip install -r requirements.txt如果你本机有 NVIDIA 显卡先把 torch 换成 CUDA 版本再装其他依赖pip install torch2.3.1 torchvision0.18.1 --index-url https://download.pytorch.org/whl/cu121逻辑说明先装 torch 再装 ultralytics是因为 ultralytics 会自动往上拉 torch 的最新版而最新版不一定有匹配你显卡驱动版本的 CUDA wheel。cu121 对应 CUDA 12.1本地驱动最好不低于 525 系列。装完之后用python -c import torch;print(torch.cuda.is_available())验证返回 True 再继续别急着跑模型。这里有两个容易忽略的点。numpy 必须压在 2.0 以下opencv-python 4.10 对 numpy 2.x 的 dtype 行为不兼容会出现类似module numpy has no attribute float的报错。pydirectinput 是后面控制鼠标要用的pyautogui 在游戏场景里不太可靠原因会在第 4 章展开。mss 比 PIL.ImageGrab 的抓屏速度快一倍以上对 DNF 这种需要连续截图的场景它是默认选择。2.2 预训练权重怎么选nano 起步small 只在验证精度时换开箱即用的源码一般会带一个模型配置文件里面写weights: ./weights/yolov8n.pt。这是 YOLOv8 的 nano 版本模型文件大约 6MB。首次 import ultralytics 并调用 YOLO 类时它会尝试从官方 assets 拉取权重如果网络不通常见做法是手动下载 yolov8n.pt 放进项目 weights 目录。这个下载地址在国内访问不太稳定所以我习惯在项目里保留一个本地权重文件夹源码优先读本地。选择逻辑并不复杂。DNF 的画面是横版格斗风格目标普遍很小但类别差异明显nano 的精度足够日常调试推理速度快很多。CPU 上做实时检测时nano 大概是 small 的两倍帧率。我用 GTX 1060 6G 跑 640 imgsz 的 nano能到 25 帧上下换成 yolov8s 直接掉到 15 帧以下。如果你后面要自己标注训练也从 nano 继续训练而不是换大模型训练时间差 3 倍以上。有个细节值得记下来这套源码的类别映射不是 MS-COCO 的 80 类而是作者自定义的0: mob、1: pick也就是怪物和掉落物两类。你拿到代码后先看model.names输出的类别名确认编号对上了再跑主脚本否则坐标定位和筛选逻辑会全部错位。2.3 配置文件把屏幕区域、置信度阈值和动作延迟集中管理不要在每个 python 文件里硬编码参数。这份源码的开箱即用靠的是一个 YAML 配置我看过的大致结构是这样weights: ./weights/yolov8n.pt imgsz: 640 conf_thres: 0.30 iou_thres: 0.45 device: cpu # 检测区域0 表示整屏 region: x: 0 y: 0 w: 0 h: 0 # 键鼠控制参数 move_delay: 0.10 click_delay: 0.05 pick_radius: 120每个参数的用途和推荐值如下。参数作用建议值imgsz喂给模型的输入分辨率640目标小可改 800 或 1280conf_thres检测置信度阈值0.25~0.30漏检时降到 0.15iou_thres非极大值抑制的 IoU 阈值0.45devicecpu 或 0GPU 序号有显卡写 0CPU 写 cpuregion只检测屏幕的一部分游戏窗口区域减少误检move_delay鼠标移动到目标位置的时间0.1太快会被输入层忽略click_delay点击后的停顿0.05pick_radius捡物时允许偏移的像素半径120注意region是给游戏窗口用的。DNF 窗口化模式下游戏区域比屏幕小如果直接全屏检测会把桌面图标、任务栏也带进模型误检率立刻上升。我一般是在第 3 章讲的窗口句柄获取到 rect 之后把 rect 填回配置里的 region让模型只看到游戏画面。3. 屏幕捕获与目标识别把 YOLO 的推理结果换算成屏幕坐标3.1 获取 DNF 窗口句柄用窗口 rect 而不是全屏截图拿到游戏窗口的位置是第一步。用 win32gui 的 FindWindow 最直接窗口标题常见的是“地下城与勇士”import win32gui def get_dnf_window(): # 标题可能带版本号用前缀匹配更稳 hwnd win32gui.FindWindow(None, 地下城与勇士) if hwnd 0: return None, None, None left, top, right, bottom win32gui.GetWindowRect(hwnd) return hwnd, (left, top, right, bottom)如果窗口标题匹配不到常见做法是改用进程名枚举import win32gui, win32process, psutil def find_hwnd_by_exe(exe_name): result [] def callback(hwnd, _): try: _, pid win32process.GetWindowThreadProcessId(hwnd) proc psutil.Process(pid) if proc.name().lower() exe_name.lower(): result.append(hwnd) except Exception: pass win32gui.EnumWindows(callback, None) return result[0] if result else 0这个按进程名枚举的方式比按标题找更稳。DNF 的窗口标题偶尔会带区服后缀但游戏进程名不会变。用窗口 rect 的意义有两个一是 mss 抓屏可以直接按 rect 的坐标抓省掉全屏扫描的无效区域二是后面做屏幕坐标映射时只需要计算全局屏幕坐标的偏移。mss 抓屏代码如下import mss import numpy as np def grab_screen(region): monitor { left: region[0], top: region[1], width: region[2] - region[0], height: region[3] - region[1], } with mss.mss() as sct: raw sct.grab(monitor) return np.array(raw)[:, :, :3].astype(np.uint8)这里返回的是 HWC 格式的 RGB 数组。mss 默认返回 BGRA 四通道我用[:, :, :3]去掉 alpha 再转成 RGBultralytics 官方模型训练时用的是 RGB 图像。如果你发现 YOLO 检测结果颜色不对先检查这里是不是忘了处理通道顺序。这个函数的耗时直接决定了整个循环的帧率mss 抓 1920×1080 区域大约 10ms 左右如果你用 PIL.ImageGrab同样的区域可能要 30ms 以上。3.2 推理主流程模型只加载一次循环里只做 predict模型加载是这里最常见的玄学翻车点。YOLO()的初始化要读权重、建网络、检查环境花一两秒。如果你把它写进循环里每次迭代都重新初始化帧率会骤降到 0。正确的做法是启动时加载一次循环里只用predictimport yaml from ultralytics import YOLO import time cfg yaml.safe_load(open(config.yaml, encodingutf-8)) model YOLO(cfg[weights]) def detect_objects(frame): results model.predict( sourceframe, imgszcfg[imgsz], confcfg[conf_thres], ioucfg[iou_thres], devicecfg[device], verboseFalse, ) boxes results[0].boxes dets [] for box in boxes: x1, y1, x2, y2 [float(v) for v in box.xyxy[0]] cls int(box.cls[0]) conf float(box.conf[0]) dets.append({ cls: cls, conf: conf, x1: x1, y1: y1, x2: x2, y2: y2, cx: (x1 x2) / 2, cy: (y1 y2) / 2, }) return dets参数说明imgsz640表示输入图像会被等比缩放并 padding 到 640×640 做推理。模型输出的 xyxy 坐标已经过 letterbox 逆变换映射回原始 frame 的分辨率所以 dets 里的坐标是相对 frame 左上角的像素值。verboseFalse是关掉每帧打印否则控制台会被刷爆也影响帧率。提示results[0].boxes.xyxy的坐标轴对应模型输入帧不是全局屏幕。坐标换算放到 3.3 节做。我习惯在解析时就把框的中心点 cx、cy 算好后续做键鼠控制直接用不用每次遍历再算一遍。类别编号 cls 要对照上一章讲的类别映射表mob 是 0、pick 是 1。3.3 坐标回映射屏幕偏移、区域缩放和 DPI 三件事一起处理YOLO 出来的坐标是基于你传入 frame 的坐标系但鼠标操作需要的是全局屏幕坐标。如果抓屏时用的就是 region rect而且 frame 尺寸和 region 尺寸完全一致那么从 frame 坐标到屏幕坐标只需要平移region [cfg[region][x], cfg[region][y], cfg[region][w], cfg[region][h]] def frame_to_screen(px, py): # 当 region 的 w/h 和 frame 的 w/h 完全一致时直接平移 sx region[0] px sy region[1] py return sx, sy但如果你抓屏后又做了裁剪、缩放或者 region 里包含了窗口边框frame 宽高和 region 宽高对不上就要按比例换算scale_x cfg[region][w] / frame.shape[1] scale_y cfg[region][h] / frame.shape[0] sx cfg[region][x] px * scale_x sy cfg[region][y] py * scale_y这里 scale_x 和 scale_y 的计算要放在 grab_screen 之后因为 frame.shape 是实际抓到的分辨率。如果两者不一致还直接用平移点击位置会随目标在画面中的位置偏移越靠右下角偏得越多。还有一种情况是 Windows 的 DPI 缩放。系统显示缩放 125% 时mss 抓到的物理像素坐标和屏幕逻辑坐标不一致如果不处理鼠标会点偏。进程启动时先执行import ctypes ctypes.windll.shcore.SetProcessDpiAwareness(2)注意这段代码要在创建窗口、抓屏之前调用。设成 2 表示 per-monitor DPI aware之后 GetWindowRect 返回的就是物理像素mss 的坐标也是物理像素鼠标控制层也用物理像素就统一了。如果你不想改进程 DPI 模式就要拿缩放系数去乘os_scale ctypes.windll.shcore.GetScaleFactorForDevice(0) / 100每次把逻辑坐标乘以这个系数。两套方案只能选一套混用就会出现偏移这个坑第 5 章还会再展开。4. 识别结果驱动键鼠从检测框到自动操作的闭环与适用边界4.1 行为优先级先处理怪物还是先捡掉落物检测结果是一堆框但自动化逻辑需要一个决策顺序。刷图时通常要优先打怪、清完怪再捡物但脚本没法保证每次都能把怪打掉所以源码里跑的逻辑一般是优先级队列def decide_action(dets): mobs [d for d in dets if d[cls] 0] picks [d for d in dets if d[cls] 1] if mobs: nearest max(mobs, keylambda d: d[conf]) return attack, nearest if picks: target max(picks, keylambda d: d[conf]) return pick, target return idle, None这里取置信度最高的目标作为首选而不是距人物最近的。原因是 DNF 画面里目标小模型输出的中心点本身有误差置信度高的框通常定位更稳。如果你想按距离排需要把人物坐标也检测出来在源码里通常是增加一个类别2: player然后对 mobs 按距离求最小。没有 player 类别时不要尝试从固定位置猜人物中心横版游戏的人物站位会变猜错了会让决策逻辑来回抖动。主循环while is_running: frame grab_screen(game_rect) dets detect_objects(frame) action, target decide_action(dets) if action attack: click_at(target[cx], target[cy], buttonleft) elif action pick: click_at(target[cx], target[cy], buttonleft) else: time.sleep(0.1) time.sleep(0.05)这套循环的问题在于如果模型偶尔漏检一帧人物会立刻停在原地发呆。我在调试时会给它加防抖连续 3 帧都没有目标才进入 idle。方法就是维护一个计数器不足三帧无目标时不动作避免因为单帧误检导致行为频繁切换。4.2 键鼠控制为什么优先用 pydirectinput 而不是 pyautoguipyautogui 是很多人熟悉的鼠标控制库但在 DNF 这种对输入时机敏感的场景里它有两个问题点击动作基于 Windows SendInput 消息部分游戏的输入层会直接忽略而且 pyautogui 自带 0.1 秒的默认间隔连续点击会明显拖慢节奏。源码里用的是 pydirectinputimport pydirectinput import time def click_at(sx, sy, buttonleft): pydirectinput.moveTo(sx, sy, durationcfg[move_delay]) time.sleep(cfg[move_delay]) pydirectinput.click(buttonbutton) time.sleep(cfg[click_delay])参数说明moveTo 的 duration 控制在 0.05~0.15 之间。太快比如设成 0 瞬移过去在某些输入环境下会被判定为不合理的鼠标动作太慢又会让脚本整体响应变差。我一般取 0.1实测在测试窗口里比较稳定。pydirectinput 仍然基于 SendInput只是参数封装更接近 DirectInput 的习惯它不是万能的。如果游戏完全不吃这个点击常见做法是再退一层到驱动级模拟这属于输入层的深度定制我建议你先用录屏回放验证检测是否正常不要一上来就折腾驱动否则你根本分不清是检测问题还是输入问题。提示主循环里的 sleep 不要小于 0.02 秒。除了 CPU 占用极高过于密集的鼠标事件流也会让系统输入处理排队反而丢失点击。4.3 最小闭环验证离线跑通再连键鼠在把鼠标控制真正接进游戏之前必须做一个离线闭环录一段游戏视频用视频帧作为输入跑检测和 decide_action把结果打印出来不实际点击。这样可以看两个问题检测出来的框是不是稳定决策逻辑有没有反复切换目标。这个离线验证我习惯写在单独的脚本里和主程序分开。用 cv2.VideoCapture 读视频帧调用同一套 detect_objects 和 decide_action然后把框画在画面里输出成图片。第 6 章会给出完整代码。先离线再在线能省掉至少一半的调参时间。4.4 适用边界这套玩法只建议在个人测试环境验证这里要说清楚边界。用 YOLO 识别游戏画面是一个典型的视觉自动化学习项目把 Python 目标检测模型跑通让模型和鼠标形成闭环这个技术能力本身有普适价值。但如果你把它接到有真实玩家在线、有官方反作弊机制的 DNF 环境里会因为干扰游戏平衡触犯游戏规则也可能带来账号风险。我的建议是脚本只用于录屏回放、本地测试窗口或者你自己搭的离线实验环境做技术验证。技术本身是中性的用来学习 YOLO 部署完全没问题别拿去搬砖或打金。后面的进阶调优也是围绕离线验证展开的不影响你学会完整的检测部署流程。5. 避坑指南识别、帧率、点击偏移三大类常见问题的排障记录5.1 现象模型在 DNF 画面里基本框不出东西现象跑起来后控制台没有任何输出或者只有血条、图标被框出来怪物和掉落物一个都不识别。原因预训练 COCO 权重不认识 DNF 里的目标它能识别的是行人、车、猫狗这类自然物体另外 conf 默认 0.3 对卡通风格的小目标本来就不友好。解决第一步把 config.yaml 里 conf_thres 降到 0.15imgsz 从 640 提到 800先用这两项看看能否救回来。第二步还不行就要走自训练截取 20 张游戏画面标注怪物和掉落物用 yolov8n 预训练权重继续训练 20 个 epoch替换 weights 目录下的 best.pt。自训练细节在第 6 章。5.2 现象CPU 推理帧率低到没法用现象循环跑起来每帧推理要 0.5 秒以上自动操作像幻灯片。原因设备没有 NVIDIA 显卡时device 却写了 0模型加载失败后可能退回 CPU 但配置没改回来或者 imgsz 开到了 1280CPU 上这种成本完全不划算。解决确认 device 写成 cpuimgsz 固定 640模型换成 yolov8n。如果还是慢把模型导出成 ONNX用 onnxruntime 跑 CPU 推理帧率能再提升 20%~50%。再进一步把推理放到独立线程避免阻塞主循环。我个人在 CPU 上只追求验证链路通不追求实时性实时性依赖第 2 章说的 CUDA 环境。5.3 现象鼠标点击位置偏了几十像素现象模型框的中心点在怪物身上鼠标点下去却偏移到右下角偏移量和系统显示缩放比例几乎一致。原因Windows 显示缩放是 125% 或 150%mss 抓的是物理像素pyautogui 或 pydirectinput 内部用的是逻辑坐标两者没有换算。解决进程启动时调用 SetProcessDpiAwareness(2)让整个程序在物理像素坐标系里工作。如果不想改全局 DPI就在每次屏幕坐标映射时乘以 os_scale 系数。两套方案选一个不要混用。验证方式点击前打印坐标和屏幕分辨率移动鼠标后看位置是否和打印值一致。5.4 现象模型把血条、装饰边框当成怪物现象脚本把血条、小地图图标、怪物名字标签也框成目标导致键鼠乱点。原因自训练或预训练的类间区分不够游戏 UI 元素和目标的边缘特征在某些分辨率下很接近模型被误导。解决加一个尺寸和宽高比过滤。DNF 的怪物框宽高一般在 40~300 像素长宽比在 0.3~3.0 之间UI 元素通常是细长条或极小的区域def valid_box(d, frame_shape): w d[x2] - d[x1] h d[y2] - d[y1] if w 40 or h 40: return False if w frame_shape[1] // 2 or h frame_shape[0] // 2: return False if h 0: return False ratio w / h return 0.3 ratio 3.0这是调试中很常见的过滤手段。不要为了“模型准”去无限标数据先约束目标框的物理范围能解决八成误点问题。尺寸过滤放在 decide_action 之前执行只让通过的框参与决策。5.5 现象首次启动卡在网络请求上无法继续现象第一次运行脚本控制台卡在类似 Downloading 的步骤几分钟不结束后面直接报超时。原因ultralytics 首次调用不存在的权重或字体文件时会尝试从官方 CDN 下载国内直连有时不稳定。解决提前把 yolov8n.pt 下载到本地 weights 目录并确认 config.yaml 里 weights 写的是本地相对路径。另外设置环境变量YOLO_CONFIG_DIR指向缓存目录提前把权重和字体文件放进去再禁止自动下载。这样做之后脚本启动就是纯本地运行不再依赖网络。6. 进阶调优小目标检测与离线验证的实战技巧6.1 小目标检测的两个关键调整imgsz 和数据质量DNF 里的掉落物可能是 20×20 像素的小框在 640 imgsz 下只占特征图几个格点容易被忽略。把 imgsz 提到 1280 对小目标有效但推理时间几乎翻倍。我的折中方案是只针对“捡物”类别做二次放大先用 640 检测到大致区域把区域裁剪出来放大到 320 再喂一次模型精细定位中心点。代价是每帧多一次推理但只发生在有疑似目标时整体影响不大。6.2 离线批量验证录屏回放的第一步换任何权重、改任何阈值我都会强制先离线跑一遍录屏回放再判断好不好用import cv2 def run_replay(video_path, model, out_dir): cap cv2.VideoCapture(video_path) frame_id 0 while True: ok, frame cap.read() if not ok: break dets detect_objects(frame, model) for d in dets: cv2.rectangle(frame, (int(d[x1]), int(d[y1])), (int(d[x2]), int(d[y2])), (0, 255, 0), 2) cv2.imwrite(f{out_dir}/frame_{frame_id:06d}.jpg, frame) frame_id 1 cap.release()这段回放把检测结果画回每一帧生成连续图片你用播放器翻一遍就能看出哪些帧漏检、哪些框抖动剧烈、哪些 UI 元素被误框。比盯实时窗口更稳因为回放可以反复暂停比对。参数上我一般把 conf 在目标值上下各测一档比如 0.15、0.30、0.45看漏检和误检的平衡点。真要提升 DNF 场景的检测质量数据量不需要很大。常见做法是截取 50~100 张图用 labelImg 标注 mob 和 pick 两类训练时用预训练权重继续训练 30 epochbatch_size 4imgsz 640yolo train model./weights/yolov8n.pt datadnf_dataset.yaml epochs30 imgsz640 batch4训练完后把 runs/detect/train/weights/best.pt 替换回 config.yaml 的 weights 路径。只要几十张标注图模型对 DNF 风格的适应性就会明显好于原始 COCO 权重因为游戏画面和自然图像的纹理差异很大单靠调阈值补不回来。源码包拉到本地后建议先做两件事一是按第 2 章把环境装好二是录一段 30 秒的游戏画面跑这段回放脚本确认检测输出正常再谈接键鼠。离线验证这个习惯成了我后来每次调模型的例行收尾先回放、看画框再统计漏检率最后才决定要不要把新权重应用进去。如果我跳过回放直接开主循环十次有八次会因为某个误检目标把节奏打乱。从那以后我每次替换权重、调阈值都强制走一遍这段录屏回放路径把帧编号、目标数和平均置信度存到日志里对比确认无异常才放行。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →