简介:基于YOLOV5的FPS类游戏自动瞄准系统,是一套面向目标检测学习者的可运行完整项目。它整合了YOLOv5模型推理、屏幕画面捕获、检测框范围配置及鼠标移动模拟等模块,适合作为毕业设计、课程设计或工程实训的起步参考。资源共110个文件,压缩包约76.98MB,以29个Python脚本、28个YAML参数配置、13个pyc编译文件及3个pt模型权重为主体,另有sh启动脚本、图像样本、文本说明和Dockerfile等辅助内容,目录结构清晰。目前已有883人学习下载,项目内附带训练日志与示例图片,可直观观察YOLOv5训练过程中的标签图、相关图及批次样本,便于理解检测效果。使用前只需在FPSUtils.py中调整分辨率、在FPSdetect.py中指定模型路径,即可快速启动体验。
1. YOLOv5 自动瞄准:一次从检测框到准星的坐标接力
做 FPS 自动瞄准,真正的难点不在模型训练,而在坐标链路:YOLOv5 只给出图像坐标系里的目标框,鼠标需要的是屏幕坐标系的位移。两者之间隔着分辨率换算、检测区域裁剪、推理耗时和鼠标平滑几道关卡,漏掉任何一环,准星都不会听话。
这个项目用 yolov5s 做检测核心,配置、推理、控制拆成三个文件,构成一条完整的视觉到外设闭环,适合做毕设、课程设计,或想看清检测结果如何被消费的 YOLOv5 学习者。项目里自带的 bus.jpg、labels_correlogram.jpg 和 train_batch 系列图片,都是训练和验证阶段留下的产物,可以直接拿来验证链路。
需要划清边界:这套链路只建议在单机、离线或允许 Mod 的场景里验证技术,接入在线对战属于破坏公平性的行为。坐标映射与实时推理的思路本身是中性的,换到巡检、抓取等场景依然成立。
2. YOLOv5s 检测链路与目标坐标映射
2.1 为什么选 yolov5s:精度与帧率的平衡点
YOLOv5 按深度和宽度系数分成 s、m、l、x 四个常用版本。s 是 smallest,权重文件 yolov5s.pt 约 14 MB,在 COCO 上 mAP 大概 37 出头,GTX 1060 级别显卡上单帧推理可以做到 5~10 ms。自动瞄准是强实时任务,从"看到目标"到"准星移动"的整条链路超过 100 ms,操作感就会明显发粘,所以选型的第一优先级是延迟而不是 mAP。
游戏画面里的目标类别固定、背景相对单纯,yolov5s 的精度完全够用。这里有个常见误区:总想换更大的模型提升识别率。实际在自动瞄准场景里,漏检通常不是因为模型不够强,而是训练数据没覆盖目标的外观变化。盲目升级到 yolov5l,帧率几乎掉一半,收益却很小。先保证 30 FPS 以上的稳定推理,再考虑精度,才是正确的调优顺序。项目目录里的 labels_correlogram.jpg 是训练阶段的标注相关性分布图,train_batch1.jpg 和 train_batch2.jpg 是批数据预览,这些图能帮你快速判断数据集本身有没有问题,而不是去怀疑模型。
2.2 检测框坐标到屏幕坐标的换算
2.2.1 坐标系定义与区域偏移
YOLOv5 默认输出 xyxy 格式,即[x1, y1, x2, y2, conf, cls],单位是像素,原点在输入图像左上角。问题在于,喂给模型的帧是从屏幕检测区域裁剪下来的,不是完整屏幕,所以目标在屏幕上的真实位置必须加上检测区域左上角的偏移量。
def box_to_screen(box, region): # box: 模型输出的单个目标框 [x1, y1, x2, y2, conf, cls] # region: FPSUtils.py 里配置的检测区域,含 x/y/w/h cx = (box[0] + box[2]) / 2.0 # 检测框中心 x,区域坐标系 cy = (box[1] + box[3]) / 2.0 # 检测框中心 y,区域坐标系 screen_x = int(region["x"] + cx) screen_y = int(region["y"] + cy) return screen_x, screen_yregion["x"]和region["y"]是检测区域左上角相对屏幕左上角的偏移,cx和cy是目标在裁剪帧里的中心。这里有个精度隐患:如果 region 的宽高和实际截图的尺寸不一致,偏移量会整体错位,表现为目标框在屏幕上"贴"不准目标。这个现象在第 5 章会给出验证方法。
2.2.2 缩放还原:letterbox 之后的坐标补偿
另一个高频坑是图像缩放。推理前通常会把截图 resize 到 640×640 输入给模型,模型输出的是 resize 后的坐标。不做比例还原直接映射屏幕,坐标会整体偏小,准星永远落在目标左上方。
def restore_scale(box, orig_w, orig_h, input_size=640): # 模型在 640x640 下推理,还原到原始裁剪帧尺寸 scale_x = orig_w / input_size scale_y = orig_h / input_size x1, y1 = box[0] * scale_x, box[1] * scale_y x2, y2 = box[2] * scale_x, box[3] * scale_y return [x1, y1, x2, y2]注意这段是简化版,只处理了等比缩放。如果用了 letterbox 补边,还需要把 padding 的像素量先减掉再乘缩放比例,否则目标位置会向下向右偏移一块固定距离。
2.3 推理耗时预算:一条链路能容忍多少延迟
把整条链路拆开看,大致是:屏幕截图 0.5~2 ms,图像预处理 1~3 ms,模型推理 5~10 ms,后处理和坐标换算小于 1 ms,鼠标指令下发 0.1~1 ms。GPU 推理时合计约 10~20 ms,这是"跟手"的前提。一旦 fallback 到 CPU,模型推理单项就要 30~60 ms,整链路逼近 100 ms,准星明显跟不上目标。
| 环节 | GPU 推理耗时 | CPU 推理耗时 | 常见优化手段 |
|---|---|---|---|
| 屏幕截图 | 0.5~2 ms | 0.5~2 ms | 用 mss 替代 PIL.ImageGrab |
| 预处理 | 1~3 ms | 1~3 ms | 直接转 tensor,少做额外复制 |
| 模型推理 | 5~10 ms | 30~60 ms | FP16 半精度、输入尺寸降到 416 |
| 后处理+坐标换算 | <1 ms | <1 ms | 只对通过阈值的目标做换算 |
| 鼠标指令 | 0.1~1 ms | 0.1~1 ms | 走系统硬件层 API,减少调度延迟 |
从表里能看出,CPU 场景下模型推理是绝对瓶颈。常见做法是先把输入尺寸从 640 降到 416,优先保帧率,或者换更轻的检测头。这个项目默认走 GPU 推理,跑之前先确认torch.cuda.is_available()返回 True,否则后面调什么都白搭。
3. 三个核心模块的代码拆解
项目结构很干净,手工维护的只有三个文件:utils/FPSUtils.py 负责配置,FPSdetect.py 负责模型和推理,Main.py 负责主循环。这种拆分方式值得直接抄进自己的项目——配置、推理、控制三层分离,后面调参不需要动逻辑代码。
3.1 FPSUtils.py:分辨率与检测区域的统一出口
打开 utils/FPSUtils.py,需要改的主要是屏幕分辨率、检测区域和截图方式。我的习惯是先写一个get_screen_size()用系统 API 拿真实分辨率,而不是手填 1920×1080,避免显示器切换或窗口缩放后坐标全乱。
# utils/FPSUtils.py import win32api # 屏幕分辨率,也可以运行时自动获取 SCREEN_WIDTH = 1920 SCREEN_HEIGHT = 1080 # 检测区域:只保留屏幕中央,跳过边缘,减少无效推理 DETECT_REGION = { "x": int(SCREEN_WIDTH * 0.25), "y": int(SCREEN_HEIGHT * 0.20), "w": int(SCREEN_WIDTH * 0.50), "h": int(SCREEN_HEIGHT * 0.60), } def get_screen_size(): # 用系统 API 自动获取,避免手填出错 return win32api.GetSystemMetrics(0), win32api.GetSystemMetrics(1)DETECT_REGION 的 x/y 是检测区域左上角,w/h 是宽高。把这四个值集中在一个文件里,是因为后面所有模块都要共用同一组坐标:FPSdetect.py 用它裁剪截图,Main.py 用它做坐标还原。如果直接在各文件里硬编码,改一次分辨率要搜三个文件,很容易漏改一处导致坐标错位。
3.2 FPSdetect.py:模型加载与单帧推理封装
FPSdetect.py 的核心是attempt_load加载权重。项目给出的示例路径是FPSAutomaticAiming\yolov5s.pt,Windows 下反斜杠要用原始字符串或正斜杠。我一般写成环境变量加默认值的组合,换机器不用改代码:
# FPSdetect.py import os import numpy as np import torch from models.experimental import attempt_load from utils.general import non_max_suppression, letterbox from utils.torch_utils import select_device MODEL_PATH = os.environ.get("FPS_MODEL", "yolov5s.pt") def load_model(): device = select_device("0" if torch.cuda.is_available() else "cpu") model = attempt_load(MODEL_PATH, map_location=device) # 加载 FP32 权重 model.eval() return model, deviceattempt_load是 YOLOv5 仓库封装的加载函数,比torch.load多了解析 checkpoint 内 model 字段的逻辑,直接传 .pt 路径即可。map_location=device决定权重落在 GPU 还是 CPU。容易漏的是model.eval(),不调用的话 BN 层和 Dropout 会留在训练模式,同一张图每次推理结果都可能不同,瞄准时准星会像抽风一样乱跳。
推理部分建议封装成独立函数,输入裁剪帧,输出过滤后的目标列表:
def detect(frame, model, device, conf_thres=0.5, iou_thres=0.45): img = letterbox(frame, 640, stride=32)[0] # resize + padding 保持宽高比 img = img.transpose((2, 0, 1))[::-1] # HWC -> CHW, BGR -> RGB img = np.ascontiguousarray(img) img = torch.from_numpy(img).to(device) img = img.float() / 255.0 # 归一化到 0~1 if img.ndimension() == 3: img = img.unsqueeze(0) # 加 batch 维 with torch.no_grad(): pred = model(img)[0] pred = non_max_suppression(pred, conf_thres, iou_thres)[0] return pred.cpu().numpy() # 每行 [x1, y1, x2, y2, conf, cls]这段把 640×640 输入的预处理流程完整走了一遍:letterbox 保持宽高比缩放并补黑边,transpose 做 HWC 到 CHW 转换并反转通道顺序,归一化后进 NMS。新手最容易把通道顺序搞反,导致画面偏红偏蓝,检测结果全乱。如果发现检测框位置和实际目标明显错位,优先查 letterbox 的 padding 量有没有参与坐标还原。
提示:YOLOv5 的 letterbox 会补黑边,严格场景下要把 padding 量记录并减掉再还原坐标,否则目标框整体向右下偏移。
3.3 Main.py:主循环、目标选择与鼠标执行
Main.py 是最后一道拼图,把截图、推理、选目标、移动鼠标串起来。选目标策略很关键:画面里多个目标同时出现时,取置信度最高的框还是取离准星最近的框?后者在实战里更合理,因为自动瞄准的目的是把准星拉到目标上,而不是"盯住某个最强目标"。
# Main.py import time import mss import numpy as np from utils.FPSUtils import DETECT_REGION from FPSdetect import load_model, detect model, device = load_model() def pick_target(dets, screen_center): # 多个目标时,选离屏幕中心最近的有效框 best, best_dist = None, float("inf") for det in dets: cx = (det[0] + det[2]) / 2 + DETECT_REGION["x"] cy = (det[1] + det[3]) / 2 + DETECT_REGION["y"] dist = (cx - screen_center[0]) ** 2 + (cy - screen_center[1]) ** 2 if dist < best_dist: best_dist = dist best = det return best with mss.mss() as sct: region = { "left": DETECT_REGION["x"], "top": DETECT_REGION["y"], "width": DETECT_REGION["w"], "height": DETECT_REGION["h"], } center = (DETECT_REGION["x"] + DETECT_REGION["w"] // 2, DETECT_REGION["y"] + DETECT_REGION["h"] // 2) while True: frame = np.array(sct.grab(region))[:, :, :3] dets = detect(frame, model, device) if dets is not None and len(dets) > 0: target = pick_target(dets, center) tx = (target[0] + target[2]) / 2 + DETECT_REGION["x"] ty = (target[1] + target[3]) / 2 + DETECT_REGION["y"] # move_mouse 按自己的鼠标协议实现,平滑方案见第 4 章 move_mouse(tx, ty) time.sleep(0.005)主循环里我用了 mss 截图,sct.grab只抓指定区域,比 PIL.ImageGrab 快不少。pick_target返回的是原始检测框,但移动鼠标前又做了一次坐标还原,因为detect返回的坐标是 letterbox 还原后的裁剪帧坐标,必须加上 region 偏移才是屏幕坐标,这一点和第 2 章的换算逻辑保持一致。
| 文件 | 职责 | 需要改的参数 | 出错时的典型现象 |
|---|---|---|---|
| utils/FPSUtils.py | 分辨率、检测区域 | SCREEN_WIDTH/HEIGHT、DETECT_REGION | 目标框位置整体偏移 |
| FPSdetect.py | 权重加载、推理封装 | MODEL_PATH、conf_thres / iou_thres | 加载报错 / 漏检误检多 |
| Main.py | 主循环、目标选择、鼠标执行 | move_mouse、pick_target 策略 | 准星乱跳 / 盯错目标 |
4. 参数标定与鼠标平滑:把检测精度落成准星精度
4.1 检测区域的标定方法
DETECT_REGION 设太大会增加无效推理,设太小会漏掉边缘目标。我一般用三分法:横向取屏幕中央 50%,纵向取中央略偏上 60%。原因是 FPS 游戏里目标活动热区集中在中部,上下两端通常是天空和地面,不会出现需要瞄准的目标。具体数值取决于游戏视野,标定方法是在目标出现在屏幕不同位置时,观察检测框是否完整落在区域内。如果目标在区域边缘被截断,模型输出的是一个残破的框,中心点会偏离真实位置,这时候不是调模型,而是把区域向外扩一点。
4.2 置信度阈值与超参数的联动调整
conf_thres 决定"多像才算目标"。0.5 是通用起步值,游戏画面纹理比自然场景简单,可以适当调到 0.6~0.7 减少误检。iou_thres 决定重叠框的合并程度,0.45 是 YOLOv5 默认值。如果发现准星在两个目标间反复横跳,优先检查是不是 iou_thres 过低,导致同一目标被输出成了两个框,而不是急着改平滑参数。
| 参数 | 建议范围 | 调低后果 | 调高后果 |
|---|---|---|---|
| conf_thres | 0.4~0.7 | 误检变多,准星乱动 | 漏检变多,目标丢失 |
| iou_thres | 0.4~0.5 | 同一目标输出多个框 | 相邻目标被合并成一个框 |
| 输入尺寸 | 416~640 | 小目标更难检测 | 推理耗时明显上升 |
| 检测区域宽度 | 屏幕的 40%~60% | 边缘目标不可达 | 无效推理比例变高 |
这组参数要联动着调:输入尺寸降到 416 后,小目标的置信度会整体下降,conf_thres 要跟着放宽一档;反过来,坚持用 640 尺寸就要接受帧率下降,并考虑开启 FP16 半精度推理。YOLOv5 的推理代码里把model.half()调用加上,输入 tensor 也转成 half,在支持 FP16 的显卡上能省掉约 30% 的推理时间。
4.3 鼠标平滑与目标速度前馈
把 move_mouse 直接替换成 tx/ty 后,第一个现象通常是准星剧烈抖动。原因是连续帧里检测框中心在目标身体上来回跳,直接把原始坐标交给鼠标,准星就跟着抖。常见做法是加一个低通滤波,让准星朝目标方向按比例移动,而不是瞬移。
class SmoothAim: def __init__(self, alpha=0.35): self.alpha = alpha # 越大响应越快,越小越稳 self.cur_x = 0.0 self.cur_y = 0.0 def step(self, target_x, target_y): # 每帧只移动剩余距离的一部分,天然抑制抖动 self.cur_x += (target_x - self.cur_x) * self.alpha self.cur_y += (target_y - self.cur_y) * self.alpha return int(self.cur_x), int(self.cur_y)alpha 是平滑系数,0.35 表示每帧吃掉 35% 的剩余距离。目标越远,前半程移动越快,接近目标后自动减速,这正好符合"快速拉近、精细瞄准"的操作习惯。目标高速横移时准星跟不上,把 alpha 提到 0.5;停下时还在小幅摆动,降到 0.2。
不过纯位置反馈有个固有缺陷:目标匀速移动时,准星会有一个固定滞后量,永远追在目标屁股后面。进阶做法是加速度前馈,用最近两帧的目标中心坐标差估算速度,把下一帧的预计位移提前加到目标点上:
def predict_target(prev_center, curr_center, alpha=0.8): # 估算目标速度并外推下一帧位置,alpha 控制速度估计的平滑度 vx = (curr_center[0] - prev_center[0]) * alpha vy = (curr_center[1] - prev_center[1]) * alpha return curr_center[0] + vx, curr_center[1] + vy这一步对移动靶的提升非常明显,代价是会引入一点预测误差,需要根据实际帧率调整外推系数。帧率越稳定,外推越可靠。
提示:调平滑系数时先固定在一个低速移动的目标上观察,不要在混战场景里调,否则你分不清是参数问题还是目标切换问题。
5. 验证链路与三类高频坑
5.1 先用 bus.jpg 打通检测链路
项目目录里的 bus.jpg 是 YOLOv5 官方测试图,也是一张标准的 COCO 验证图。第一次跑通之前不要直接开游戏调试,先用它验证模型和推理代码:把 detect 的输入改成读取 bus.jpg,如果输出里出现置信度较高的 bus 检测框,说明模型加载、预处理、NMS 整条链路是通的。这时候问题只可能出在截图和坐标环节,排查范围立刻缩小一半。
5.2 权重路径与 map_location 报错
attempt_load 最常见的报错是路径分隔符问题。Windows 路径用反斜杠必须写成原始字符串r"FPSAutomaticAiming\yolov5s.pt"或换成正斜杠,否则\y会被当成转义字符。另一个高频报错是 map_location 传了 cuda 但机器上没有可用显卡,torch 直接抛 RuntimeError。对应到 FPSdetect.py 的写法,就是 select_device 返回了 cpu,但 attempt_load 里仍写死 cuda,两处必须保持一致。
5.3 坐标偏移与 DPI 缩放的验证
启动前要改的三处——分辨率、检测区域、鼠标代码——改完后如何确认坐标链路是对的?一个实用的验证方法是把 move_mouse 暂时替换成打印目标坐标,然后在游戏里固定准星,移动角色让目标出现在屏幕不同位置,比对打印坐标和实际目标位置。如果横向对、纵向偏,优先怀疑 Windows DPI 缩放:显示设置里缩放不是 100% 时,系统 API 拿到的分辨率和游戏实际渲染分辨率不一致。解决方式是右键主程序,在兼容性设置里勾选"替代高 DPI 缩放行为"。
验证通过后再把鼠标移动接回主循环。正式运行前务必加一个紧急停止开关:检测到全局热键就跳出 while 循环。这类系统调试时最容易出的问题就是鼠标被永久接管,窗口失焦也停不下来,一个可靠的热键退出比任何精度优化都重要。
本文还有配套的精品资源,点击获取