ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于YOLOv5的AI斗地主:计算机视觉与游戏决策的工程实践

基于YOLOv5的AI斗地主:计算机视觉与游戏决策的工程实践 简介本资源是一个基于YOLOv5实现的AI斗地主识别与辅助决策系统面向人工智能、自动化、电子信息等专业的在校学生、教师及初级开发者解决扑克牌图像识别、牌面定位与游戏逻辑联动等典型CVAI落地问题。压缩包共40个文件含10个核心Python脚本如infer.py、my_datasets.py、模型权重best.pt、C/Java跨平台部署相关文件cpp/h/java、Gradle构建配置及Markdown文档涵盖数据预处理、模型训练、推理部署与安卓端集成全流程整体大小为14.42MB。已有89人学习下载项目源自高分课程设计答辩评分95分经导师指导并实测运行稳定。用户可直接用于毕业设计、课程实践或竞赛原型开发配套详细文档清晰说明环境配置、训练流程、推理调用及扩展修改方法特别适合从目标检测入门到多模态游戏AI进阶的学习者。1. 项目概述当YOLOv5遇见斗地主最近在整理过往项目时翻出了一个挺有意思的“存货”——一个基于YOLOv5实现的AI斗地主项目。这可不是一个简单的游戏外挂或者脚本而是一个从零开始用计算机视觉技术去“看懂”斗地主游戏界面并模拟人类决策进行出牌的完整系统。简单来说就是让AI学会“看”屏幕打牌。这个项目的核心思路是把一个看似属于强化学习或游戏AI领域的问题转化成了一个经典的计算机视觉目标检测问题。我们不需要去破解游戏协议、注入内存也不需要游戏提供任何API接口。AI只需要像人一样通过“眼睛”即屏幕截图来获取信息识别出当前桌面上有哪些牌、谁出了什么牌、自己的手牌是什么、当前的出牌轮次和规则状态。然后基于这些视觉信息结合一套定制的出牌逻辑做出出牌决策最后通过模拟鼠标点击来完成操作。这听起来有点“笨”但实际效果却出奇地好尤其是在规则固定、界面UI相对稳定的游戏环境中。它不依赖任何游戏内部数据通用性更强也避开了很多法律和合规上的灰色地带。整个项目用Python实现核心是YOLOv5这个强大的目标检测框架辅以PyAutoGUI等自动化工具。无论是对于想学习YOLOv5实际应用的同学还是对游戏AI感兴趣的朋友这个项目都是一个非常不错的练手素材。它涵盖了从数据采集标注、模型训练、到逻辑集成、自动化测试的完整AI项目流程。2. 项目核心思路与技术选型解析2.1 为什么选择“视觉感知”而非“内存读取”在实现游戏AI时通常有两条技术路径一是直接读取游戏进程的内存数据获取最精确的游戏状态二是通过计算机视觉分析游戏画面。我们选择了后者主要基于以下几点考量通用性与安全性内存读取高度依赖于特定的游戏客户端版本、内存地址偏移游戏一个更新就可能导致整个系统失效。更重要的是这种方式通常违反游戏用户协议存在封号风险。而视觉方案完全在用户层面操作不侵入游戏进程行为模式更接近真实玩家合规风险低得多。技术栈的聚焦与复用视觉方案的核心是目标检测这是一个非常成熟且活跃的AI领域。使用YOLOv5这类框架我们可以将主要精力集中在“教AI认牌”这个核心问题上而不是去逆向复杂的游戏内存结构。训练好的模型也具备一定的泛化能力对于UI类似的同类型游戏或不同版本可能只需微调即可适用。学习价值对于一个演示或学习型项目视觉方案能完整串联起数据标注、模型训练、应用部署的全链路技术展示面更广学习价值更高。2.2 YOLOv5为何是它而不是别的在目标检测领域选择众多从经典的Faster R-CNN到YOLO系列自身也有多个版本。最终锁定YOLOv5是基于它在易用性、速度和精度之间取得的绝佳平衡极致的开发效率YOLOv5的PyTorch实现非常清晰其提供的命令行工具和预配置的模型文件yolov5s.pt,yolov5m.pt等让从零开始训练一个自定义数据集变得异常简单。几行命令就能完成训练、验证和导出大大降低了入门门槛。出色的性能YOLOv5在COCO等标准数据集上表现优异其网络结构和训练技巧如自适应锚框计算、Mosaic数据增强等保证了在轻量化模型如YOLOv5s上也能获得不错的精度这对于需要实时响应的游戏AI场景至关重要。活跃的社区与生态YOLOv5拥有庞大的用户社区遇到任何问题几乎都能找到解决方案或讨论。其代码库持续更新对ONNX、TensorRT等推理引擎的导出支持也非常友好便于后续部署到不同平台。注意虽然YOLOv5官方仓库已不再活跃转向了Ultralytics的YOLOv8等后续版本但YOLOv5的代码成熟、资料丰富对于这样一个特定场景的固定项目而言稳定性和可复现性比追求最新版本更重要。2.3 整体系统架构设计整个AI斗地主系统可以划分为四个核心模块它们以流水线的方式协同工作屏幕捕获模块负责以固定的频率如每秒1-5帧截取游戏窗口的画面。这里使用PIL.ImageGrab或mss库后者速度更快。关键在于精准定位游戏窗口并只截取包含牌桌信息的区域减少无关像素的干扰提升后续处理速度。视觉感知模块YOLOv5核心这是系统的大脑。接收截图后调用我们训练好的YOLOv5模型进行推理。模型需要能识别出多种目标类别例如“我的手牌_黑桃A”、“我的手牌_红桃2”、“桌面牌_梅花K”、“对手一出牌_方块10”、“出牌按钮”、“不出按钮”等。模型输出所有检测到的卡牌及其位置、类别信息。游戏状态解析与决策模块这是系统的心脏。它将视觉感知模块输出的“一堆检测框”转化为结构化的游戏状态信息。例如根据“我的手牌_”类别的检测结果整理出当前AI玩家手中持有的牌列表根据“桌面牌_”和“对手一出牌_*”等理解当前的牌局进展。然后基于斗地主的规则单张、对子、顺子、炸弹等结合当前状态运行决策算法可以是基于规则的专家系统也可以接入一个简单的策略网络来选择最优或较优的出牌组合。自动化执行模块这是系统的手。一旦决策模块决定了要出哪几张牌该模块需要计算出这些牌在屏幕上的具体坐标可以从YOLOv5的检测框信息中获取然后通过pyautogui库控制鼠标依次点击这些牌最后点击“出牌”按钮。对于“不出”的情况则直接点击“不出”按钮。3. 从零到一构建斗地主卡牌检测模型3.1 数据采集与标注一切的基础模型的性能上限很大程度上由数据集决定。对于斗地主卡牌检测我们需要一个覆盖各种场景的高质量数据集。数据采集 我们通过运行斗地主游戏可以是单机版或特定平台的客户端手动或通过脚本进行大量对局并在游戏过程中定时截屏。截屏需要覆盖所有关键场景游戏开始前的手牌分发。自己出牌、对手出牌、盟友出牌的不同回合。牌桌上牌数多少不一的情况空桌、少量牌、满桌牌。不同游戏皮肤、分辨率下的界面。卡牌重叠较少的情况便于标注。数据标注 使用标注工具如LabelImg或CVAT对每张截图进行标注。标注的类别需要精心设计这直接影响后续状态解析的复杂度。这里有两种策略策略A精细分类为每一张独立的牌建立一个类别如“spade_A”黑桃A、“heart_K”红桃K等共计54个类别。这样模型直接输出牌面信息状态解析最简单但模型需要区分的类别多训练难度稍大。策略B粗分类数字识别先检测“卡牌”这个通用类别然后利用卡牌图像的区域再通过一个额外的数字/花色识别模型或OCR来识别具体牌面。或者可以折中分为“手牌”、“桌面牌”、“对手牌”等大类再结合位置信息判断归属。我们项目采用了策略A因为YOLOv5有能力处理几十个类别的分类问题且实现更端到端。实操心得标注时框Bounding Box应紧密贴合卡牌的边缘但不必过于精确。确保同一张截图里所有可见的牌都被标注即使它们部分重叠。建议收集至少2000-3000张标注图像并按8:1:1的比例划分训练集、验证集和测试集。3.2 YOLOv5模型训练与调优环境准备# 克隆YOLOv5官方仓库使用特定版本以保证复现性 git clone -b v6.0 https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txt # 安装依赖数据准备 将标注好的数据集图片和对应的.txt标注文件按照YOLOv5要求的目录结构放置datasets/ └── poker/ ├── images/ │ ├── train/ # 训练图片 │ └── val/ # 验证图片 └── labels/ ├── train/ # 训练标签 └── val/ # 验证标签每个.txt标签文件内容格式为class_id x_center y_center width height坐标是归一化后的值。配置文件修改创建数据配置文件poker.yamlpath: ../datasets/poker # 数据集根目录 train: images/train # 训练集路径相对于path val: images/val # 验证集路径 nc: 54 # 类别数这里是54张不同的牌 names: [spade_A, spade_2, ..., heart_K, heart_A, joker_black, joker_red] # 类别名称列表选择模型配置文件从models/目录下选择一个如yolov5s.yaml将其中的nc参数修改为54。开始训练python train.py --img 640 --batch 16 --epochs 100 --data poker.yaml --cfg models/yolov5s.yaml --weights yolov5s.pt --name poker_exp--img 640: 输入图像尺寸。斗地主卡牌较小640足够增大可能提升精度但会降低速度。--batch 16: 批大小根据GPU内存调整。--epochs 100: 训练轮数可通过观察验证集损失曲线决定是否早停。--weights yolov5s.pt: 加载预训练权重这是加速收敛和提升性能的关键。--name poker_exp: 本次实验的名称所有日志、模型都会保存在runs/train/poker_exp下。调优技巧数据增强YOLOv5默认开启了Mosaic、MixUp等增强对于卡牌检测非常有效。可以适当调整hyp.scratch.yaml中的增强参数但初学者建议先用默认值。锚框Anchor聚类YOLOv5会针对你的数据集自动计算锚框尺寸通常不需要手动修改。学习率与优化器使用默认的SGD优化器即可。如果训练损失震荡或下降缓慢可以尝试减小学习率--lr0。评估指标重点关注mAP0.5平均精度均值。训练完成后在测试集上运行python val.py ...查看各类别的精确率Precision、召回率Recall和mAP。对于“不出按钮”这类关键类别召回率尤为重要漏检会导致AI无法跳过回合。3.3 模型导出与集成训练得到的最佳模型通常是runs/train/poker_exp/weights/best.pt需要集成到我们的AI系统中。import torch from PIL import Image import numpy as np # 加载自定义训练好的模型 model torch.hub.load(ultralytics/yolov5, custom, path./runs/train/poker_exp/weights/best.pt, force_reloadTrue) model.conf 0.6 # 置信度阈值高于此值才认为是检测到 model.iou 0.45 # NMS的IoU阈值 # 准备图像 img Image.open(current_screen.jpg) # 推理 results model(img, size640) # 保持与训练时相同的尺寸 # 解析结果 detections results.pandas().xyxy[0] # 转换为Pandas DataFrame包含xmin, ymin, xmax, ymax, confidence, class, namedetections这个DataFrame就包含了当前画面中所有检测到的卡牌信息是我们后续逻辑判断的输入。4. 游戏逻辑与决策引擎的实现4.1 从视觉检测到结构化游戏状态YOLOv5模型输出的是原始的检测框列表我们需要将其转化为程序可理解的游戏状态对象。这个过程主要包括目标过滤与分类根据detections中的name字段将检测框分为“我的手牌”、“桌面牌堆”、“上家出的牌”、“出牌按钮”、“不出按钮”等逻辑组。这里依赖我们标注时设定的类别命名规则。手牌识别所有“我的手牌_*”类别的检测框提取其name中的牌面信息如“heart_A”构成一个当前手牌的列表my_hand。牌桌状态识别当前出牌轮次通过检测“桌面牌堆”区域是否有牌以及“上家出的牌”区域是否有牌来判断。如果“上家出的牌”为空则表示是新一轮出牌的开始。上家牌型分析“上家出的牌”区域的所有检测框提取牌面信息并判断其牌型单张、对子、顺子、炸弹等和牌型大小。这需要实现一个牌型分析函数。地主牌在游戏开始时需要识别出三张地主牌。坐标处理技巧由于屏幕截图和游戏窗口可能存在缩放或者游戏内UI元素位置相对固定我们可以定义一些屏幕区域的相对坐标或模板来辅助判断某些元素如按钮是否存在或者对检测框进行二次校验。4.2 基于规则的出牌决策引擎对于斗地主AI决策引擎的复杂度可以天差地别。我们从最简单的基于规则的引擎开始它已经能表现出不错的水平。核心决策流程如下def make_decision(my_hand, last_cards, my_role, remaining_cards_count): my_hand: 当前手牌列表如 [heart_A, spade_K, ...] last_cards: 上家出的牌列表如果是新一轮则为空列表 [] my_role: 身份landlord地主 或 peasant农民 remaining_cards_count: 对手手牌数量估算 # 1. 判断当前回合状态 if not last_cards: # 我是当前轮第一个出牌的 return decide_first_play(my_hand, my_role) else: # 我需要接上家的牌 # 2. 分析上家牌型 last_type, last_key analyze_cards_type(last_cards) # 3. 在我手牌中寻找所有能压过对方的合法牌型组合 possible_plays find_all_valid_plays(my_hand, last_type, last_key) if not possible_plays: # 没有能压的牌 return PASS else: # 4. 从所有可能出牌中选择一个策略如出最小的能压的牌、出能改变牌型的牌等 return select_best_play(possible_plays, my_hand, my_role, remaining_cards_count)关键函数实现analyze_cards_type(cards): 分析一组牌是什么牌型及其“关键值”。例如[‘heart_5‘ ‘spade_5‘]是对子5。[‘heart_3‘ ‘diamond_4‘ ‘spade_5‘]是顺子关键值为5。需要处理所有斗地主规则单张、对子、三带一、三带二、顺子、连对、飞机、炸弹、王炸。find_all_valid_plays(my_hand, last_type, last_key): 这是一个搜索问题。需要遍历手牌中所有可能的子集组合判断其牌型并与上家牌型比较大小。优化技巧包括按牌值排序后优先搜索包含大牌的组合对于顺子、连对等可以基于牌值范围快速生成候选。select_best_play(): 这是策略的核心。简单策略可以是“出牌后手牌数最少”或“出牌后剩余牌的平均点数最小”。更复杂的策略可以引入“记牌”估算剩余炸弹概率、“控场”作为农民时尽量让队友上手等逻辑。注意事项规则引擎的边界情况极多必须进行充分的单元测试。例如炸弹可以压任何其他非炸弹牌型但火箭王炸最大。四带二只能作为炸弹使用不能当作普通牌型接牌。这些规则的编码要格外小心。4.3 与自动化模块的衔接决策引擎输出一个出牌指令可能是一个牌面列表如[‘spade_A‘ ‘heart_A‘]也可能是“PASS”。自动化模块需要执行它。import pyautogui import time def execute_play(play_command, card_detections): if play_command PASS: # 定位并点击“不出”按钮 pass_button_pos find_button_position(‘pass_button‘) # 可通过图像匹配或固定坐标 pyautogui.click(pass_button_pos) print(AI选择不出牌。) else: # play_command 是一个牌面列表 for card_name in play_command: # 在本次检测结果card_detections中找到对应牌面且属于“我的手牌”的检测框 target_card find_my_card_detection(card_detections, card_name) if target_card: # 计算检测框的中心坐标 center_x (target_card[‘xmin‘] target_card[‘xmax‘]) / 2 center_y (target_card[‘ymin‘] target_card[‘ymax‘]) / 2 pyautogui.click(center_x, center_y) time.sleep(0.1) # 点击间隔避免操作过快被游戏视为异常 else: print(f警告未在屏幕上找到牌 {card_name}可能检测失败。) # 所有牌点击完毕后点击“出牌”按钮 play_button_pos find_button_position(‘play_button‘) pyautogui.click(play_button_pos) print(fAI出牌: {play_command})find_button_position函数可以通过模板匹配pyautogui.locateOnScreen实现但更可靠的方法是结合游戏窗口固定布局使用相对坐标计算。5. 系统集成、测试与性能优化5.1 主循环与状态机将上述所有模块整合到一个稳定的主循环中并设计一个清晰的状态机来控制AI的行为流。import cv2 from PIL import ImageGrab import win32gui # 用于窗口定位Windows class AIPokerPlayer: def __init__(self, model_path, window_title斗地主): self.model load_model(model_path) self.window_title window_title self.game_state IDLE # IDLE, WAITING_MY_TURN, DECIDING, EXECUTING, GAME_OVER self.hwnd None def find_game_window(self): # 使用win32gui找到游戏窗口句柄和位置 self.hwnd win32gui.FindWindow(None, self.window_title) if self.hwnd: rect win32gui.GetWindowRect(self.hwnd) self.window_rect (rect[0], rect[1], rect[2], rect[3]) # left, top, right, bottom return True return False def capture_screen(self): # 截取游戏窗口区域 img ImageGrab.grab(bboxself.window_rect) return cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR) def run(self): if not self.find_game_window(): print(未找到游戏窗口) return print(AI斗地主启动窗口已定位。) while True: if self.game_state IDLE: # 检测游戏是否开始例如识别“开始游戏”按钮 if self.detect_game_start(): self.game_state WAITING_MY_TURN elif self.game_state WAITING_MY_TURN: # 持续截图判断是否轮到我出牌 screenshot self.capture_screen() if self.is_my_turn(screenshot): # 通过检测“出牌”按钮高亮或倒计时判断 self.game_state DECIDING elif self.game_state DECIDING: screenshot self.capture_screen() # 1. 视觉感知 detections self.model.predict(screenshot) # 2. 状态解析 game_state_info self.parse_detections(detections) # 3. 决策 play_action self.decision_engine.make_decision(game_state_info) self.current_action play_action self.game_state EXECUTING elif self.game_state EXECUTING: # 执行出牌或不出 self.execute_action(self.current_action, self.last_detections) time.sleep(1) # 等待出牌动画和回合切换 self.game_state WAITING_MY_TURN # ... 其他状态处理 time.sleep(0.2) # 主循环延迟降低CPU占用5.2 性能优化与稳定性提升一个可用的AI必须稳定且高效。推理速度优化模型轻量化如果使用yolov5s.pt仍感觉延迟可以尝试更小的模型如yolov5n.pt或在训练时使用剪枝、量化等技术。推理引擎将PyTorch模型导出为ONNX格式并使用ONNX Runtime进行推理通常能获得更快的速度。对于极致性能可以尝试TensorRT。截屏优化使用mss库代替PIL.ImageGrab速度提升显著。只截取游戏窗口的必要区域牌桌区域而不是整个窗口。检测稳定性提升多帧融合对于关键状态如“是否轮到我”可以连续分析几帧的检测结果采用投票机制来决定避免单帧检测抖动造成的误判。状态缓存游戏状态如我的手牌不会每帧都剧烈变化。可以缓存上一次的结果只有当检测到“牌被出掉”或“新牌发出”等事件时才更新缓存。这能减少对模型的频繁调用并提高逻辑稳定性。鲁棒性处理异常检测与恢复在execute_play函数中如果某张牌点击失败应有重试机制或 fallback 策略如尝试出另一组牌。心跳检测定期检查游戏窗口是否依然处于前台、是否卡死、是否弹出意外对话框如“网络断开”并设计相应的恢复逻辑。5.3 测试策略与效果评估如何知道你的AI打得好不好单元测试对analyze_cards_typefind_all_valid_plays等核心函数编写大量测试用例覆盖所有牌型和边界情况。模拟对局测试开发一个简单的命令行模拟器让AI与一组固定的手牌进行对战观察其出牌逻辑是否符合预期。实战测试回放录制一些人类对局的视频或截图序列让AI离线“观看”并做出决策将它的决策与人类实际出牌进行对比分析。天梯测试在游戏平台上运行AI进行大量对局统计胜率。这是最真实的测试但需要确保AI行为符合平台规则避免因操作过快被判定为脚本。评估指标胜率、叫地主频率、作为农民的配合度、炸弹使用时机合理性等。踩坑实录初期测试时AI经常在应该“不出”的时候乱出小牌原因是检测模块将一些UI装饰误识别为手牌。解决方法是在标注数据时增加一些包含UI背景、动画的负样本不包含任何卡牌目标或者在训练时提高置信度阈值model.conf。另一个常见问题是顺子识别错误因为训练数据中长顺子样本较少。需要通过数据增强专门生成更多长顺子的合成图像加入训练集。6. 项目扩展与进阶思考完成基础版本后这个项目还有巨大的扩展和优化空间。6.1 从规则引擎到强化学习基于规则的引擎上限明显且规则维护复杂。一个自然的演进方向是引入强化学习RL。思路将斗地主游戏建模为一个马尔可夫决策过程MDP。状态State就是视觉感知模块解析出的结构化信息手牌、桌面牌、玩家身份、历史出牌等。动作Action就是所有合法的出牌组合或“不出”。奖励Reward可以简单定义为最终赢牌得1输牌得-1也可以设计中间奖励如成功压牌、打出炸弹等。挑战状态空间和动作空间巨大。直接使用深度Q网络DQN可能难以收敛。可以采用Actor-Critic框架或者结合蒙特卡洛树搜索MCTS与神经网络即AlphaGo式的方法。实践可以先在一个简化的斗地主模拟环境中训练RL智能体再将其决策逻辑替换掉项目中的规则引擎。这本身就是一个庞大的子项目。6.2 引入记忆与对手建模目前的AI只关注当前瞬间的状态是“无记忆”的。一个更强的AI应该能“记牌”和“读人”。记牌通过视觉信息记录已经打出的牌从而精确计算出剩余牌池中可能存在的牌特别是大小王和2、A等关键牌的概率。这能极大地优化炸弹威慑和残局策略。对手建模通过分析对手的出牌习惯例如是否喜欢留炸弹、出牌是否激进为不同的对手建立简单模型并据此调整自己的策略。例如面对保守的对手可以更积极地抢夺出牌权。6.3 工程化与部署让项目从一个实验脚本变成一个健壮的工具。配置化将所有硬编码的参数如游戏窗口标题、按钮坐标、模型路径、置信度阈值等抽离到配置文件中。用户界面使用PyQt或Tkinter开发一个简单的控制面板可以方便地启动/停止AI调整速度查看当前识别状态和决策日志。日志与复盘详细记录每一局的出牌序列、AI的决策依据和视觉检测结果。这不仅是调试的利器也是生成高质量训练数据用于优化RL或检测模型的来源。跨平台适配目前代码可能依赖Windows的win32gui。可以考虑使用pygetwindow等跨平台库来定位窗口。对于Mac或Linux下的游戏需要调整截图和自动化方法。这个基于YOLOv5的AI斗地主项目就像搭积木从最基础的视觉识别开始一步步加入游戏逻辑、自动化操作最终形成一个能独立运行的智能体。整个过程充满了挑战也涵盖了AI项目落地的多个核心环节。无论你是想深入理解目标检测的应用还是对游戏AI充满好奇亲手实现一遍这个项目收获绝对远超你的想象。代码和详细文档就在那里下一步就是打开编辑器开始你的“造物”之旅了。本文还有配套的精品资源点击获取
返回列表