掘金 人工智能 18小时前
AI玩游戏的一点尝试(1)—— 架构设计与初步状态识别
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文分享了使用AI技术开发《闪耀优俊少女》游戏脚本的经验。作者首先设计了基于AI的架构,通过图像输入、状态识别、信息处理、决策和点击结果来实现游戏自动化。文章详细介绍了数据采集、标注、模型训练和验证的过程,并展示了如何构建一个能够准确识别游戏状态的卷积神经网络模型。最后,作者提到了未来将探索无监督学习,以进一步细化状态分类,实现更全面的游戏自动化。

🖼️ **架构设计:** 作者构建了一个由AI驱动的游戏脚本架构,包括图像输入、状态识别、信息处理、决策和点击结果等环节,旨在实现游戏的自动化操作,摆脱传统脚本的局限。

📊 **数据采集与标注:** 为了训练模型,作者编写脚本每秒截图一次并保存画面,收集了大量游戏截图数据。随后,对数据进行标注,区分出训练界面、准备比赛界面和其他界面,为后续模型训练做好准备。

🧠 **模型训练与验证:** 作者使用卷积神经网络(CNN)模型进行状态识别,模型经过训练后在验证集上获得了99%的准确度。作者还优化了数据采集代码,在截图后通过模型判断并自动分类,实现了游戏状态的精准识别。

🚀 **下一步计划:** 作者计划探索无监督学习,以实现更细粒度的状态分类,从而减少手动标注的工作量,并提升AI脚本的自动化程度,以适应更复杂的游戏操作。

前言

闪耀优俊少女突然堂堂复活,也可以把搁置的AI养马项目重新捡起来了。之前虽然做出了手动养马的脚本,但是接入AI的效果并不理想。通过OCR识别的准确度始终有问题,通过手动编码来处理和识别各种状态也让逻辑变的十分臃肿,不利于AI接入。

这次希望利用之前的经验,从头开始设计一个大部分由AI驱动的养马脚本。

架构设计

图像输入(第三方) -> 状态识别(模型) -> 信息处理(模型) -> 决策(模型) -> 点击结果(第三方)

之前是脚本是通过特定区域的图像查找来判断当前处于哪个界面的,这就需要游戏跑到对应的界面再停下来截图去处理,非常麻烦。这次希望可以通过后台截图无感知玩游戏的同时,通过大量的数据进行状态的归类和判断。

图像输入

github.com/LmeSzinc/Az…

截图部分参考了LmeSzinc大佬制作的碧蓝航线脚本实现,使用了DroidCast插件。

数据采集

想要训练模型,第一步肯定是收集数据。先写了一个脚本每秒截图一次并保存画面。

import loggingfrom base import adb, screen, utilfrom datetime import datetimefrom pathlib import Pathimport cv2import timelog = logging.getLogger('monitor')def screenshot():    image = screen.screenshot()    image_id = datetime.now().strftime("%Y%m%d_%H%M%S")    image_path = Path("data/ocr/images") / f"{image_id}.png"    cv2.imwrite(str(image_path), util.to_bgr(image))    log.info(f"截图保存到 {image_path}")    return imageadb.init("127.0.0.1:16416")screen.width = 720screen.height = 1280screen.init()while True:    screenshot()    time.sleep(1)

几局游戏下来,就有了几千张图片数据,于是迫不及待准备试试状态识别。

数据标注

刚开始准备老老实实选用监督模型进行训练,自然少不了标注数据。好在数据量不算大,花了点时间把训练界面、准备比赛界面和其他界面区分开了,就先写一个判断是否是训练界面的模型试试吧。

数据预处理

对于游戏来说,几乎整个屏幕区域都有可能显示有用的信息,具体的信息位置是根据界面决定的,因此在状态识别模型中我很难想到能对界面进行什么处理操作,只是简单的压缩了下分辨率:

    transform = transforms.Compose([        transforms.Resize((320, 180)),  # 将1280x720缩放到320x180        transforms.ToTensor(),    ])

训练模型

根据AI的建议创建了一个卷积神经网络(CNN)模型:

class StateClassifier(nn.Module):    def __init__(self):        super().__init__()        self.conv1 = nn.Conv2d(3, 32, 3, padding=1)        self.pool = nn.MaxPool2d(2, 2)        self.conv2 = nn.Conv2d(32, 64, 3, padding=1)        self.conv3 = nn.Conv2d(64, 128, 3, padding=1)        self.fc1 = nn.Linear(128 * 40 * 22, 256)        self.fc2 = nn.Linear(256, 3)        self.relu = nn.ReLU()        self.dropout = nn.Dropout(0.5)            def forward(self, x):        x = self.pool(self.relu(self.conv1(x)))        x = self.pool(self.relu(self.conv2(x)))        x = self.pool(self.relu(self.conv3(x)))        x = x.view(-1, 128 * 40 * 22)        x = self.dropout(self.relu(self.fc1(x)))        x = self.fc2(x)        return x

验证模型

没想到的是只用1epoch就已经有了99%的准确度,于是优化了下数据采集的代码,在截图后通过模型判断并自动分类:

import loggingimport torchfrom torchvision import transformsfrom PIL import Imageimport timefrom datetime import datetimefrom pathlib import Pathimport cv2from base import adb, screen, utilfrom train_state_classifier import StateClassifierfrom config import NORMALIZE_MEAN, NORMALIZE_STDlogger = logging.getLogger('predict_state')logger.setLevel(logging.INFO)device = torch.device('cuda')model = StateClassifier().to(device)model.load_state_dict(torch.load('data/state_classifier.pth', map_location=device))model.eval()transform = transforms.Compose([    transforms.Resize((320, 180)),    transforms.ToTensor(),    transforms.Normalize(mean=NORMALIZE_MEAN, std=NORMALIZE_STD)])def predict_image(image):    image = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))    image = transform(image).unsqueeze(0).to(device)        with torch.no_grad():        outputs = model(image)        probabilities = torch.softmax(outputs, dim=1)[0]        state = torch.argmax(outputs, dim=1).item()            return state, probabilities[state].item()adb.init("127.0.0.1:16416")screen.width = 720screen.height = 1280screen.init()logger.info("开始预测游戏状态")while True:    try:        image = util.to_bgr(screen.screenshot())        state, confidence = predict_image(image)        logger.info(f"预测状态: {state}, 置信度: {confidence:.2%}")                image_id = datetime.now().strftime("%Y%m%d_%H%M%S")        image_dir = Path("data/predict") / f"{state}"        image_dir.mkdir(parents=True, exist_ok=True)                if confidence < 0.9:            track_dir = image_dir / "track"            track_dir.mkdir(parents=True, exist_ok=True)            image_path = track_dir / f"{image_id}_{state}_{confidence:.2f}.png"        else:            image_path = image_dir / f"{image_id}_{state}_{confidence:.2f}.png"                    cv2.imwrite(str(image_path), image)                time.sleep(1)    except KeyboardInterrupt:        logger.info("停止预测")        break    except Exception as e:        logger.error(f"预测出错: {str(e)}")        time.sleep(1) 

又玩了几局游戏,分类基本很准确,只有准备比赛界面因为数据量太少容易判断失误,准备了更多数据后重新训练就彻底没问题了。

下一步

如果要让AI接管大部分游戏画面的操作,那么状态必须划分的很细(每个界面都是一个单独的状态去执行不同的逻辑)。这样所需的数据量如果要我一个个去分类有点太痛苦了,于是准备试试无监督学习能否帮我自动进行分类。

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

AI 游戏脚本 状态识别 深度学习 自动化
相关文章