简介:基于 YOLO 的 DNF 自动化脚本,面向游戏脚本开发者和 Python/YOLO 入门者,开箱即用。资源共包含 18 个文件,其中 5 个 Python 脚本分别承担交互控制、图像识别、游戏动作等模块,11 张图片用于目标分类展示,mp4 演示视频直观呈现运行效果,md 说明文档补充配置与使用细节,整个 zip 压缩包约 79.85MB,已有 853 人学习下载。项目以 YOLOv5 为检测核心,需将导出并转换后的 ncnn 权重 .bin 与 .param 放入根目录即可运行,分类覆盖 Gate(门)、Hero(角色)、Item(物品)、Mark(标记)、Monster(怪物)等对象,标注阶段可参考 Label Studio 工作流。源码实现了人物/怪物/材料/门识别、自动寻路过图、固定角色攻击、按怪物数量动态攻击、识别狮子头房间、开局使用 buff、拾取掉落物(含粉装识别)以及自动再次挑战等功能,并附带 best.pt 转 onnx 再转 ncnn 的完整转化步骤,适合作为游戏视觉自动化项目的落地参考。
1. 这个标题要解决什么问题:把 YOLO 和 Python 变成能“看见”屏幕的脚本
第一次冲着“基于 YOLO 的 Python 脚本搞 DNF”这个标题来的,多半不是想要一个通用目标检测 Demo,而是想让脚本自己看见屏幕里的东西:在一张横版游戏画面里识别怪物、拾取物和 Boss 动作,再决定下一步操作。过去这类脚本靠图像颜色匹配,换个背景就翻车;YOLO 的做法是把画面交给预训练模型,直接拿回目标的类别、坐标和置信度,代码量反而更少。需要先说清楚边界:我给的是开箱即用的屏幕目标检测工程模板,检测结果可以接日志、截图存档、数据统计或自动化测试,不建议把它接到在线游戏的自动操作链路上,封号与合规风险都得自己掂量。适合已经有 Python 基础、想用 YOLO 做窗口画面识别和自动化采集的从业者,这篇不教内存修改,也不涉及脱机外挂。
2. 搭建能跑 YOLO 的 Python 环境,选对第一个预训练模型
2.1 用 Python 3.10 虚拟环境装依赖,先别污染系统解释器
YOLO 的 Python 环境坑,十有八九出在依赖装脏了。常见做法是先建一个虚拟环境,把 ultralytics 和 torch 都装进去。我一般用 3.10 或 3.11,这两个版本对 torch 的 Windows 预编译包支持最稳,3.12 偶尔会遇到某些扩展包还没跟上编译版本的问题。
python -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate python -m pip install --upgrade pip pip install ultralytics先激活虚拟环境再装包,否则 pip 可能装到全局 site-packages 里,后面换项目就乱了。升级 pip 不是走过场,老版本 pip 解析 torch 的依赖索引偶尔会失败,报一堆No matching distribution。国内网络慢的话,给最后一行加上镜像源参数:pip install ultralytics -i https://pypi.tuna.tsinghua.edu.cn/simple。装完可以用pip list | findstr ultralytics(Windows)或pip list | grep ultralytics确认版本。
2.2 预训练模型下载与选型:别一上来就上大模型
ultralytics 包装好后,第一次跑推理会自动下载对应权重。很多做 yolo 搭建的人在这里被卡住:看着终端停在下载进度条不动。原因是权重托管在境外对象存储上,网络不稳定时容易中断。解决方式很简单:手动把yolov8n.pt下载下来放进项目根目录,ultralytics 检测到当前目录已有同名权重就不会再走自动下载。
选型上,我强烈建议从yolov8n开始。n 是 nano,体积最小、CPU 也能跑;s、m、l 依次变大,精度更高但推理耗时翻倍。做屏幕画面识别这种场景,目标本身清晰、类别少,n 和 s 的差距肉眼几乎看不出来。
| 模型 | 体积 | 适合场景 | 一句话选型建议 |
|---|---|---|---|
| yolov8n.pt | 约 6MB | CPU 实时、轻量嵌入式 | 默认首选 |
| yolov8s.pt | 约 21MB | 有 GPU、想稍微提精度 | 显存 4G 以上再考虑 |
| yolov8m.pt | 约 49MB | 离线分析、不在乎速度 | 不建议接实时屏幕流 |
| yolov8l.pt | 约 83MB | 高精度离线任务 | 屏幕识别没必要 |
模型文件的后缀是.pt,这是 PyTorch 权重格式,做 Python 脚本调用最方便。后面如果追求部署速度,可以再导出成 ONNX 用 onnxruntime 跑,但那是后话,先把.pt链路跑通。
2.3 用一张图和一行命令验证环境
环境装没装对,别急着写屏幕捕获,先用单张图片跑一遍推理,这一步能过滤掉大部分环境问题。
yolo predict model=yolov8n.pt source=bus.jpg imgsz=640bus.jpg是官方示例图,第一次运行会自动下载。如果你在的项目目录里没有这张图,随便放一张自己的图片,把 source 换成路径即可。这行命令走的是 CLI 入口,约等于帮你验证了模型下载、CUDA/CPU 设备检测、推理和画框全链路。
更贴近脚本写法的,是直接在 Python 里调用:
from ultralytics import YOLO model = YOLO("yolov8n.pt") res = model.predict("bus.jpg", conf=0.25, verbose=False)[0] print(res.names) print(res.boxes)res.names是 80 个 COCO 类别的 id 到名称映射,res.boxes里装着所有检测框,包括坐标、置信度、类别 id。能打印出这两样东西,说明 torch、ultralytics、权重文件都正常,可以进入下一步做屏幕捕获了。
3. 开箱即用的屏幕检测脚本:用 mss 抓帧、用 YOLO 推理、用回调接管结果
3.1 一个能直接跑的工程结构长什么样
做这种脚本我不喜欢堆单文件,拆成三个文件最舒服:detector.py放检测类,main.py放业务逻辑,config.yaml放参数。以后换权重、改置信度、调 ROI,都不需要动代码。
screen_vision/ ├── config.yaml ├── detector.py ├── main.py └── venv/config.yaml是我每次都会建的文件,参数集中管理比在代码里翻常量强太多:
weights: yolov8n.pt conf: 0.3 iou: 0.5 imgsz: 640 device: cpu # 有 GPU 改成 0 roi: [0, 0, 1920, 1080]roi是检测区域,后面 4.2 会专门讲。先把这四个键读懂:conf 是置信度门槛,iou 是 NMS 去重阈值,imgsz 是送入模型的缩放尺寸,device 决定跑 CPU 还是 GPU。这套结构的好处是不管你怎么改参数,代码文件不需要动,重启脚本就生效。
3.2 屏幕捕获用 mss,一秒抓 30 帧不掉队
屏幕捕获常见的有 OpenCV 的cv2.VideoCapture、PIL 的ImageGrab、以及 mss 库。我一般直接用 mss,理由就一个字:快。ImageGrab在 Windows 上也不算慢,但 mss 是 C 实现,多开几个抓取区域也不容易掉帧。抓出来的帧是 BGRA 格式,需要转成 BGR 再喂给 YOLO。
import mss import numpy as np import cv2 with mss.mss() as sct: monitor = sct.monitors[1] # 1 代表主显示器 frame = sct.grab(monitor) img = np.array(frame) bgr = cv2.cvtColor(img, cv2.COLOR_BGRA2BGR)sct.monitors[1]是整个主屏,monitors[0]是所有显示器的合并区域,多显示器场景才需要碰它。grab()的入参也可以传一个 dict,只抓屏幕的一部分,比如{"left": 0, "top": 0, "width": 800, "height": 600}。抓到的 frame 对象底层是 bytes,转 numpy 数组后一定要做颜色空间转换,不转的话后面 YOLO 推理出的结果会偏色、误检率明显升高。
3.3 把 YOLO 推理包成一个 detector 类,结果通过回调抛出去
这里给出这个工程最核心的代码,也就是中枢检测类。设计思路是:屏幕捕获、推理、坐标解析全部封装在类内部,对外只暴露一个on_results回调。这样业务方在main.py里只需要关心“检测到目标之后干什么”,不用每天和 numpy 数组、YOLO 的 Results 对象搏斗。
import cv2 import mss import numpy as np from ultralytics import YOLO class ScreenDetector: def __init__(self, weights="yolov8n.pt", conf=0.3, iou=0.5, imgsz=640, device="cpu", roi=None): self.model = YOLO(weights) self.conf = conf self.iou = iou self.imgsz = imgsz self.device = device self.roi = roi # (x, y, w, h),相对屏幕左上角 self.last_frame = None def on_results(self, dets): # 子类重写这个回调 raise NotImplementedError def run(self, monitor_index=1, interval=0.05): with mss.mss() as sct: monitor = sct.monitors[monitor_index] while True: frame = sct.grab(monitor) img = cv2.cvtColor(np.array(frame), cv2.COLOR_BGRA2BGR) self.last_frame = img.copy() if self.roi is not None: x, y, w, h = self.roi img = img[y:y + h, x:x + w] res = self.model.predict( img, conf=self.conf, iou=self.iou, imgsz=self.imgsz, device=self.device, verbose=False )[0] dets = [] for box in res.boxes: dets.append({ "name": res.names[int(box.cls[0])], "conf": float(box.conf[0]), "xyxy": [int(v) for v in box.xyxy[0].tolist()], }) self.on_results(dets) cv2.waitKey(int(interval * 1000))关键点在predict的参数:verbose=False一定要开,否则终端每一帧都会刷一行 log,肉眼没法看,也在拖慢循环。res.boxes里每个 box 的xyxy是相对当前输入图片的,如果你前面做了 ROI 裁剪,这里的坐标是相对 ROI 的,后面用的时候要自己加回偏移。回调on_results是同步执行的,如果回调里做文件写入或网络请求,主循环会被阻塞,这种情况建议把回调里的耗时操作丢到线程池。
3.4 把结果画出来确认效果,但别一直开着
调试阶段需要可视化,直接把检测框画在帧上再看一眼,这是最直观的验证方法。
from detector import ScreenDetector import cv2 class Viewer(ScreenDetector): def on_results(self, dets): img = self.last_frame.copy() for d in dets: x1, y1, x2, y2 = d["xyxy"] cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, f"{d['name']} {d['conf']:.2f}", (x1, y1 - 6), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imshow("detect", img) cv2.waitKey(1) Viewer(roi=(300, 200, 900, 600)).run()调试时开着预览没问题,但正式跑的时候强烈建议把cv2.imshow和cv2.waitKey都去掉。这里有句血泪经验:imshow在部分机器上会强制垂直同步,把抓帧速率直接拖到 10 帧以下,线上跑监控时一定要关掉预览,否则你会以为 YOLO 很慢,其实慢在显示。
4. 把帧率从 5 提到 20 的三处调参:置信度、ROI 与推理设备
4.1 置信度 conf 与 IoU 先调哪个
跑通之后第一件事是调参。很多新手纠结 conf 和 iou 到底哪个先动,我的经验是:先调 conf,再调 iou。conf 是门槛,低于这个置信度的框直接扔掉;iou 是 NMS 去重阈值,用来合并重叠框。画面里频繁出现 0.1 置信度的鬼影框,那是 conf 太低;同一个目标周围拖出一串框,那是 iou 太低。
| 场景 | 建议 conf | 建议 iou | 说明 |
|---|---|---|---|
| 屏幕静态图标识别 | 0.5 | 0.4 | 图标本身就稳定,调高 conf 压误检 |
| 游戏/视频动态画面 | 0.3 | 0.5 | 目标会动,框抖动时适当降 conf |
| 连续帧跟踪 | 0.25 | 0.6 | 宁可多框不可漏,靠后面逻辑过滤 |
改动直接在config.yaml里改,不需要动代码。有个翻车案例:有人把 conf 拉到 0.8 之后发现目标全没了,最后发现画面里的目标本来就只有 0.6 的置信度,阈值设太高等于全部丢弃。调参前先打印一帧所有框的 conf 分布,这个习惯能省很多无用功。
4.2 用 ROI 把检测范围从全屏缩小到一块
全屏 1920x1080 送入 YOLO,模型会把整张图缩到 640x640 再推理。如果目标永远只出现在屏幕中间一块,比如横版动作游戏的战斗区域,就没必要让模型看整个屏幕。ROI 裁剪有两个好处:一是减少背景干扰,误检率肉眼可见下降;二是让模型把分辨率的“预算”花在真正的目标上。
# config.yaml 里这样设 roi: [400, 300, 800, 450]代码里对应这一段:
x, y, w, h = self.roi img = img[y:y + h, x:x + w]注意一个小坑:裁剪后检测框的坐标是相对 ROI 的,要还原成屏幕坐标必须加回偏移。也就是x_screen = x_roi + roi_x,y_screen = y_roi + roi_y。我见过有人忘了这一步,画框怎么都对不上,还以为是模型漂移。ROI 对帧率的实际提升没有想象中大,因为推理耗时主要由 imgsz 决定,但它对误检的改善非常明显,这点在 5.5 里还会提到。
4.3 设备、imgsz 和 half:把帧率拉起来的三板斧
CPU 上跑实时屏幕检测,帧率往往只有个位数。提帧率最有效的三件事,按收益排序:降 imgsz > 换 GPU > 开 half。
| 配置组合 | imgsz | 设备 | half | 典型效果 |
|---|---|---|---|---|
| CPU 基础 | 640 | cpu | 关 | 能跑,帧率低 |
| CPU 优先 | 320 | cpu | 关 | 延迟明显下降,精度略降 |
| GPU 基础 | 640 | 0 | 关 | 实时无缝 |
| GPU 优化 | 480 | 0 | 开 | 帧率高且精度损失小 |
res = self.model.predict( img, conf=self.conf, iou=self.iou, imgsz=320, device="0", half=True, verbose=False )half=True是半精度推理,只对 GPU 有效,CPU 上开反而可能更慢,因为 CPU 对 FP16 的加速支持不稳定。imgsz不需要和训练尺寸严格一致,YOLO 会自动缩放输入,所以从 640 降到 320 是合法的提速手段。如果你在device="0"上报错,先跑一句python -c "import torch; print(torch.cuda.is_available())",输出 False 就说明装的是 CPU 版 torch,需要重装对应 CUDA 版本。
5. 五个必踩的坑:从环境报错到误检与合规边界
这一章的每一条都来自真实折腾经历。除了代码层面的坑,还要提醒一件更重要的事:这套屏幕检测工程本身是中性的,可以做界面自动化测试、数据采集、辅助标注,但如果你打算把它接到在线游戏的自动操作链路上,封号是最轻的后果,违规风险完全需要自己承担。后面 5.5 会再展开说。
5.1 报错No module named 'torch',但明明装过 ultralytics
现象:pip install ultralytics成功,跑 Python 脚本却报ModuleNotFoundError: No module named 'torch'。
原因:ultralytics 的依赖列表并强制捆绑安装最新版 torch,某些镜像源或网络环境下 pip 会跳过 torch 的安装;另一个常见情况是系统里有多个 Python,终端激活的是 A 环境的 venv,pip 却指向 B 环境。
解决:先where python(Windows)或which python(Linux/macOS)确认当前解释器路径,再手动补装pip install torch torchvision。装完用python -c "import torch; print(torch.__version__)"验证。如果发现 pip 装到了别的环境,直接删掉 venv 重建,比 debug 解释器路径快得多。
5.2yolov8n.pt自动下载卡住,进度条纹丝不动
现象:第一次运行yolo predict,终端停在Downloading https://... yolov8n.pt好久,最后超时。
原因:权重文件从境外对象存储拉取,内网或网络不稳定时连接会被掐断。这个和项目代码没关系,是网络链路问题。
解决:手动把yolov8n.pt下载好,放到当前工作目录,再运行脚本。ultralytics 加载模型时会优先在当前目录找同名文件,找到就不再走下载逻辑。还有个习惯:把权重文件和工程放在一起,不要放在临时目录,否则换个终端路径又触发一遍下载。
5.3 mss 抓屏黑屏,或者抓出来是全黑画面
现象:用 mss 抓游戏窗口,得到的 numpy 数组全黑,或者每隔几帧黑一次。普通桌面正常,一进游戏就黑。
原因:全屏独占模式下,显卡把画面直接输出到硬件覆盖层,普通的 GDI/Desktop Duplication 抓不到表面数据。常见于全屏 DX 游戏和某些视频播放器。
解决:把目标程序从全屏独占改成无边框窗口化,mss 就能抓到。如果必须抓全屏,需要换成支持 DXGI 的采集方案,比如d3dshot或直接用 Windows 图形捕获 API。屏幕识别类项目我建议统一窗口化运行,省掉这一整类问题。
5.4 检测框总是往左上偏一截,坐标对不上
现象:画框检测结果正常,但矩形框和屏幕上真实物体位置整体偏移,偏移量固定。
原因:Windows 的 DPI 缩放没有感知。屏幕分辨率 1920x1080、系统缩放 125% 时,mss拿到的坐标是物理像素,而窗口/画面坐标是逻辑像素,两者之间差了一个缩放系数。
解决:在脚本入口加一段进程级 DPI 感知设置:
import ctypes try: ctypes.windll.shcore.SetProcessDpiAwareness(1) except Exception: ctypes.windll.user32.SetProcessDpiAwarenessCompat()SetProcessDpiAwareness(1)让进程认为自己是 DPI aware,系统不再对坐标做缩放映射。注意这段代码只在 Windows 上有意义,Linux/macOS 需要包一层异常保护。设置完重启脚本,偏移问题立刻消失。
5.5 COCO 预训练模型误检严重,该换权重就换权重
现象:模型把画面里的石头、水面反光识别成 person,或者把目标圆领识别成领带,检测结果一团乱。
原因:COCO 预训练模型只见过 80 类日常生活物体,游戏画面里的角色、物品、特效它一个都没见过。它输出“person”只是因为形状有点像,不是真的认出来了。看评估指标的时候也会遇到混淆矩阵总合不唯一的问题,不用慌,YOLO 的混淆矩阵是按预测框统计的,一个框可以对应一个 GT 也可以被忽略,行列合计不等于总样本数很正常。
解决:如果目标确实属于 COCO 80 类(人、车、狗这类),先调高 conf 到 0.5 试试。如果目标属于自定义类别,只有一个根治办法:收集几百张包含目标的截图,用 LabelImg 或 X-AnyLabeling 标注,微调一个自己的权重。预训练模型不是万能的,它不是黑匣子,只是没见过你的场景。
6. 从“能检测”到“会干活”:命中截图存档与自定义模型训练
检测到了目标,接下来才是脚本真正产生价值的地方。最稳妥的落地方式是:命中目标时把当前帧存成图片、把检测记录追加进 CSV。这套逻辑既可用于 UI 自动化测试的异常捕获,也能做视频抽帧统计,完全合规。
import csv import time import cv2 from detector import ScreenDetector class Archiver(ScreenDetector): def on_results(self, dets): if not dets: return ts = time.strftime("%Y%m%d_%H%M%S") cv2.imwrite(f"captures/{ts}.jpg", self.last_frame) with open("results.csv", "a", newline="") as f: writer = csv.writer(f) for d in dets: writer.writerow([ts, d["name"], round(d["conf"], 3), *d["xyxy"]])回调里先判断有没有目标,再落盘,避免每帧都写文件把磁盘写满。captures目录要提前建好,脚本里没有自动建目录的逻辑,这是有意为之——显式报错比默默跳过更容易排查。CSV 里的坐标是 ROI 相对坐标,如果你在 config 里设了 roi,入库前记得加回偏移。
做了存档之后,你会攒下一批“错得很有价值”的样本。这些就是微调的自留地:用标注工具框出你要的目标,写一个dataset.yaml,指定训练和验证路径,然后跑:
yolo detect train data=dataset.yaml model=yolov8n.pt epochs=50 imgsz=640训练时盯两个曲线:box_loss和cls_loss。损失值降不下去,九成不是模型问题,而是标注框画歪了或类别标反了。50 轮对几百张数据的小样本足够,再多轮容易过拟合。训练完用yolo predict model=runs/detect/train/weights/best.pt回到原来的屏幕检测循环,权重文件路径替换掉即可。
最后留一个验证习惯:每次改完参数学会先跑一段“实录回放”,把之前录制的屏幕视频或截图序列喂给检测脚本,而不是直接对着实时画面调。同样的输入才能对比出前后差别,否则你根本分不清是模型变好了还是画面刚好变了。这个习惯救过我太多次。希望帮到你。
本文还有配套的精品资源,点击获取