基于YOLO目标检测及OpenCV实现的游戏代玩人工智能体(Auto Gaming Agent) [3] (更新)

本文介绍了使用最新YOLOv5模型进行目标检测的更新,包括模型选择、训练过程及Pytorch推理预测。作者分享了训练数据准备、模型训练的步骤,并展示了实时识别的实现,最终通过多线程实现实时游戏窗口的目标检测,提升了准确率和稳定性。
摘要由CSDN通过智能技术生成

自从上次断更以后已经过去3年,是之前几篇不断收到的点赞、评论、私信支持着博主(既然明日之后这个游戏现在还活着)索性就继续更新下去吧 [doge][doge][doge]


一、目标检测算法更新

人工智能领域的算法更新迭代速度非常之快,上一篇中使用的目标检测模型yolov3已然过时,目前yolo算法已经更新至第5代,在准确度、速度、稳定性、易用性上已经远远超越之前。本篇就与时俱进,使用最新的yolov5s6模型来重构我们的目标检测任务。虽然说是重构,但实际代码量和操作步骤减少了非常多。

yolov5继承了各代yolo的传统,出厂即提供多种不同大小的模型,这次v5版本更是直接一口气发布了4个模型(s, m, l, x),而截至目前已经更新到10个模型之多(n, s, m, l, x, n6, s6, m6, l6, x6),简直选择困难症犯了。这里不需要纠结,越大的模型肯定越准确同时帧率相对越低,所以直接选一个最低帧率需求下自己硬件能满足的模型就好,训练和部署的步骤都是一样的跟模型选择无关,对博主来说yolov5s6就是最适合的模型。

Modelsize
(pixels)
mAPval
0.5:0.95
mAPval
0.5
Speed
CPU b1
(ms)
Speed
V100 b1
(ms)
Speed
V100 b32
(ms)
params
(M)
FLOPs
@640 (B)
YOLOv5n64028.045.7456.30.61.94.5
YOLOv5s64037.456.8986.40.97.216.5
YOLOv5m64045.464.12248.21.721.249.0
YOLOv5l64049.067.343010.12.746.5109.1
YOLOv5x64050.768.976612.14.886.7205.7
YOLOv5n6128036.054.41538.12.13.24.6
YOLOv5s6128044.863.73858.23.612.616.8
YOLOv5m6128051.369.388711.16.835.750.0
YOLOv5l6128053.771.3178415.810.576.8111.4
YOLOv5x6128055.072.7313626.219.4140.7209.8

二、YOLOv5模型训练

1. 数据准备

训练数据主要指图像和标记,YOLO各个版本的算法都采用统一的标记格式,即标准化过后的中心点标记,这里yolov5跟v3需要的训练数据是完全一样的。如果已经有了之前训练v3的数据即可零转换马上训练v5的模型,如果还没有数据那么数据标注的步骤也是和原来一样的,这里不再赘述(详见上一篇中的“创建自定义数据集”章节)。

2. 训练模型

首先下载yolov5的源码然后安装必要的运行库,官方是使用了近几年大火的pytorch取代了原本的darknet,如果有英伟达显卡就可以安装pytorch的cuda版本,没有就安装cpu版本。博主用的30系显卡必须安装cuda11.0以上的pytorch版本才能正常使用gpu加速。

git clone https://github.com/ultralytics/yolov5
cd yolov5
pip install -r requirements.txt

安装好以后先准备一下 .yaml 格式的训练配置文件,主要设置数据集的路径 path、训练数据路径 train、验证数据路径 val、和可选的测试数据路径 test,以及我们目标总共分类的数目 nc 还有类名 names,其中 train, val, test 都是相对 path 的路径。我们把这个配置文件命名为 bot.yaml 然后放到主目录下的 data/ 文件夹,配置文件内容如下

# Train/val/test sets as 1) dir: path/to/imgs, 2) file: path/to/imgs.txt, or 3) list: [path/to/imgs1, path/to/imgs2, ..]
path: datasets/qrsl  # dataset root dir
train: images  # train images (relative to 'path')
val: images  # val images (relative to 'path')
test:  # test images (optional)

# Classes
nc: 11  # number of classes
names: ['tree', 'stone', 'herb', 'wolf', 'zombie', 'dear', 'bear', 'helicopter', 'berry', 'mushroom', 'cole']  # class names

接下来我们把标记好的数据集放到上面配置文件指定的位置 datasets/qrsl,图片位于 datasets/qrsl/images,标记位于 datasets/qrsl/labels,最终目录结构如下
数据集文件结构
一切准备就绪后在命令行中输入一行代码即可开始训练

python train.py --img 1280 --batch 1 --epochs 100 --data bot.yaml --weights yolov5s6.pt 

使用 --img 来设定模型输入的图像尺寸,--batch 设定训练的批量大小,--epochs 设定最大的训练次数,--data 设定训练配置文件,--weights 设定预训练模型。

这里输入尺寸可以根据实际应用场景的目标大小来设置,一般越大的输入尺寸能检测到的小目标就越精确但是耗时会成指数增加。越大的图像尺寸训练时消耗的cpu和gpu内存也会更多,如果训练集很大的话非常容易导致内存不足而无法运行,这时候就需要调节批量大小来使数据顺利的载入训练。博主在训练的时候就遇到了cpu内存不足的情况,所以设置 --batch 1 每次只输入一张图片才可以开始训练。当然越大的批数量可以使训练收敛的更快而且模型的健壮性会更好,所以还是根据自己硬件来选择一个最大的批量大小。

显示如下的进度条就说明训练已经开始了,每个epoch分别进行一次训练和一次验证,系统会自动保存一个最近的和一个最佳的checkpoint到 runs/train/exp*/weights 路径,中途停止以后下次训练参数设置比如 --weight runs/trian/exp*/weights/last.pt 就会从上次的checkpoint继续,完成到最后一个epoch以后就自动生成优化过的模型,模型参数会变少(主要裁掉了batchnorm等推理时无用的层)但也无法继续训练。
在这里插入图片描述
可以从打印的信息看到比较丰富的训练指标数据,一些比较重要的指标:

  • box – 标记框的loss
  • obj – 单目标的loss
  • cls – 分类的loss
  • P – 精确率
  • R – 召回率
  • mAP@.5 – 置信度50%以上的平均精度
  • mAP@.5:.95 – 置信度50%~95%的平均精度

精确率反应的是识别出的框的正确率,召回率反应的是有多少正确的框被识别出来,像我们这个任务可以允许一些目标没被识别出来,但是被识别出来的目标一定要准确,所以也就是精确率对我们来说更重要。mAP反应了所有类别的综合精度,要是不想看其他的数据光看这个准没错。

训练指标达到如下的程度基本就是一个比较合格的模型了
在这里插入图片描述
这里因为我的数据量偏少只有700多张图,就没有划分训练和验证集,验证集也是训练过的,所以看起来的数据都很虚高,这存在过拟合的风险,不过对我们的任务影响不大,毕竟训练截取的场景就是我们实际应用的场景。一般比较健壮的模型最好需要每个类1000张,每个类10000个标记,然后划分10%左右的数据进行验证,训练数据包含5%左右的无标记图像用来减少False Positive,但是对于我们这个任务不太有必要。

所有训练都完成后在对应的runs/train/exp*/weights 路径找到 best.pt 文件作为我们最终拿去预测的最佳模型。

三、Pytorch推理预测YOLOv5

1. 引入库

直接用pytorch的hub包进行预测是非常简单的,只需要opencv,pytorch和numpy三个常见的库

import cv2
import torch
import numpy as np

2. 加载模型

直接使用 torch.hub 包来加载yolov5源码和我们自己训练的模型,device='0'用来指定运行在gpu上,如果没有gpu就不用这个参数。

yolov5 = torch.hub.load('ultralytics/yolov5', 'custom', path='best.pt', device='0')

2. 读图预测

用opencv来读取一张没有训练过的图片,然后输入到我们的模型中进行推理,最后输出结果转成numpy格式的标记框数组,代码非常少。

img = cv2.imread('scrsht/1652240462.png')
img = img.copy()[:,:,::-1] # BGR to RGB
results = yolov5(img,size=1280) 
bboxes = np.array(results.xyxy[0].cpu())

3. 可视化

使用opencv画出所有识别出的框

def drawBBox(image,bboxes):
    for bbox in bboxes:
        x0,y0,x1,y1 = int(bbox[0]),int(bbox[1]),int(bbox[2]),int(bbox[3])
        cv2.rectangle(image, (x0, y0), (x1, y1), (255,0,0), 2)
    return image

img = drawBBox(img.copy(),bboxes)
cv2.imshow("", img)
cv2.waitKey()

在这里插入图片描述

四、实时识别

以上只是测试单张图片,在上面代码的基础上结合高速截屏库 mss 和多线程库 threading 可以实现实时识别游戏窗口。使用 win32gui 库来定位窗口在桌面的具体位置坐标。

import mss
import cv2
import os
import threading
import time
import torch
import numpy as np
from win32gui import FindWindow, GetWindowRect

yolov5 = torch.hub.load('ultralytics/yolov5', 'custom', path='best.pt', device='0')
yolov5.conf = 0.6
yolov5.iou = 0.4

COLORS = [
    (0, 0, 255), (255, 0, 0), (0, 255, 0), (255, 255, 0), (0, 255, 255),
    (255, 0, 255), (192, 192, 192), (128, 128, 128), (128, 0, 0),
    (128, 128, 0), (0, 128, 0), (128, 0, 128), (0, 128, 128), (0, 0, 128)]

LABELS = ['tree','stone','herb','wolf','zombie','dear','bear','helicopter','berry','mushroom','cole']

img_src = np.zeros((1280,720,3),np.uint8)

def getScreenshot():
    id = FindWindow(None, "明日之后 - MuMu模拟器")
    x0,y0,x1,y1 = GetWindowRect(id)
    mtop,mbot = 30,50
    monitor = {"left": x0, "top": y0, "width": x1-x0, "height": y1-y0}
    img_src = np.array(mss.mss().grab(monitor))
    img_src = img_src[:,:,:3]
    img_src = img_src[mtop:-mbot]
    return img_src, [x0,y0,x1,y1,mtop,mbot]


def getMonitor():
    global img_src
    while True:
        # last_time = time.time()
        img_src, _ = getScreenshot()
        #print("fps: {}".format(1 / (time.time() - last_time+0.000000001)))


def yolov5Detect():
    cv2.namedWindow("",cv2.WINDOW_NORMAL)
    cv2.resizeWindow("",960,540)
    cv2.moveWindow("",1560,0)
    global img_src
    while True:
        img = img_src.copy()
        bboxes = getDetection(img)
        img = drawBBox(img,bboxes)
        cv2.imshow("", img)
        if cv2.waitKey(1) & 0xFF == ord("q"):
            cv2.destroyAllWindows()
            break

def getLargestBox(bboxes,type):
    largest = -1
    bbox_largest = np.array([])
    for bbox in bboxes:
        if LABELS[int(bbox[5])] in type:
            x0,y0,x1,y1 = int(bbox[0]),int(bbox[1]),int(bbox[2]),int(bbox[3])
            area = (x1-x0)*(y1-y0)
            if area > largest:
                largest = area
                bbox_largest = bbox
    return bbox_largest

def drawBBox(image, bboxes):
    for bbox in bboxes:
        conf = bbox[4]
        classID = int(bbox[5])
        if conf > yolov5.conf and classID==0:
            x0,y0,x1,y1 = int(bbox[0]),int(bbox[1]),int(bbox[2]),int(bbox[3])
            color = [int(c) for c in COLORS[classID]]
            cv2.rectangle(image, (x0, y0), (x1, y1), color, 3)
            text = "{}: {:.2f}".format(LABELS[classID], conf)
            cv2.putText(image, text, (max(0,x0), max(0,y0-5)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
    return image

def getDetection(img):
    bboxes = np.array(yolov5(img[:,:,::-1],size=1280).xyxy[0].cpu())
    return bboxes

if __name__ == '__main__':
    t1 = threading.Thread(target=getMonitor,args=())
    t1.start()
    t2 = threading.Thread(target=yolov5Detect,args=())
    t2.start()
    

最终的运行效果在这个视频展示
https://www.bilibili.com/video/BV15B4y197Vo/

因为相比三年前的渣配置,这次鸟枪换大炮,在1280x720的原生分辨率下也能达到40~50fps了,整体准确率和稳定性上升了一个档次。

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值