基于YOLOv5与强化学习的AI斗地主:从视觉识别到决策实战
2026/9/3 7:25:33 网站建设 项目流程

简介:本资源是一个基于YOLOv5实现的AI斗地主识别与辅助决策系统,面向人工智能、自动化、电子信息等专业的在校学生、教师及初级开发者,解决扑克牌图像实时检测、牌面识别与游戏逻辑联动等典型CV+规则引擎融合问题。压缩包共40个文件,含10个核心Python脚本(如infer.py、my_datasets.py)、4个XML标注配置、4个C++/H文件(支撑移动端部署适配)、2个PT模型权重(含best.pt)、以及Gradle构建脚本、README文档和演示截图等,整体14.42MB,结构清晰,模块划分明确,便于理解目标检测落地全流程。已有89人学习下载,资源源自高分课程设计项目,答辩获95分并经导师认可,附带完整技术文档与可运行代码,涵盖数据预处理、YOLOv5训练微调、牌面识别后处理及简易斗地主规则判定逻辑,适合毕设、课设或CV工程实践进阶参考。

1. 项目概述:当YOLOv5“看”懂了你的牌

最近在整理硬盘里的老项目,翻到了一个挺有意思的玩意儿——一个用YOLOv5实现的AI斗地主。这可不是简单的规则引擎,而是一个能“看见”屏幕、自动识别手牌和公共牌,然后做出出牌决策的“外挂级”项目。当初做它纯粹是出于技术好奇:计算机视觉能不能在非标准化的游戏UI上稳定工作?强化学习在斗地主这种不完全信息博弈里能玩到什么水平?今天就把这个项目的里里外外拆解一遍,从环境搭建、模型训练到策略设计,把踩过的坑和最终跑通的方案都摊开来聊聊。无论你是想学习YOLOv5的实际应用,还是对游戏AI感兴趣,这个项目都能提供一个从理论到落地的完整视角。

这个项目本质上是一个自动化游戏代理。它的工作流程可以概括为:通过屏幕截图获取游戏画面,利用训练好的YOLOv5模型检测并识别出所有的扑克牌(包括自己的手牌、地主牌、已出的牌),将这些视觉信息转化为结构化的牌型数据,最后基于这些数据运行决策算法,模拟鼠标点击完成出牌。整个过程完全离线,不依赖游戏官方的任何接口,因此其核心挑战就在于视觉识别的鲁棒性和决策算法的有效性。下面,我们就从最关键的视觉检测部分开始。

2. 核心思路与方案选型:为什么是YOLOv5?

面对“从游戏画面中识别扑克牌”这个问题,有好几条技术路径可选。最简单粗暴的是模板匹配,但斗地主游戏UI风格多样,牌的位置、大小、背景都可能变化,模板匹配的泛化能力太差。另一种思路是使用OCR识别牌面上的数字和字母,但J、Q、K、A、2这些字符在艺术字体下识别率堪忧,且无法区分花色。经过权衡,我选择了基于深度学习的目标检测方案,它能同时定位牌的位置并识别其类别(如“红桃A”、“黑桃5”)。

在目标检测模型选型上,YOLOv5在当时(现在依然)是一个平衡了速度、精度和易用性的绝佳选择。相较于更早的YOLOv3/v4,v5的PyTorch实现更加友好,训练脚本封装完善,对于快速原型开发非常合适。它的轻量化版本(如YOLOv5s)完全能满足实时性要求(每秒数十帧),在消费级GPU甚至CPU上都能流畅运行。此外,其活跃的社区和丰富的预训练模型也为迁移学习提供了便利。

注意:项目选型时需考虑可持续性。YOLOv5虽然经典,但后续有v6、v7、v8等版本。本项目选用v5主要是因其在2021-2022年时生态最成熟,资料最多。若今天启动类似项目,可以评估更新的版本,但v5的学习路径和原理依然具有很高的参考价值。

整个系统的架构可以划分为三个核心模块:

  1. 视觉感知模块:基于YOLOv5的扑克牌检测与识别。
  2. 信息处理模块:将检测到的牌面框坐标和类别,转换为游戏逻辑所需的牌型数据结构(如手牌列表、上家出牌牌型)。
  3. 决策执行模块:基于当前牌局状态,调用决策算法(规则引擎或强化学习模型)计算出最优出牌,并控制鼠标执行操作。

这个架构清晰地将视觉、逻辑、控制解耦,方便每个模块独立调试和优化。

3. 数据集制作与模型训练:教AI认识每一张牌

任何监督学习模型的上限都取决于数据集的质量。对于扑克牌检测,我们需要收集大量包含扑克牌的游戏截图,并精确标注每一张牌的位置和类别。

3.1 数据收集与标注

我采用了“游戏模拟器+自动截图”的方式批量获取数据。在Windows上运行主流的斗地主游戏客户端(如腾讯欢乐斗地主),使用Python的pyautogui库控制游戏并定时截图。为了增加数据的多样性,需要覆盖不同场景:

  • 手牌区:牌密集排列,可能有部分重叠或遮挡。
  • 出牌区:牌分散在桌面上,角度、大小可能不一。
  • 地主牌区:三张牌扣放或明牌。
  • 不同游戏主题和牌面样式:这是泛化性的关键。

标注工具选用LabelImg,它支持YOLO格式(中心点x, y,宽度w,高度h,均归一化)。类别标签就是54张扑克牌(52张常规牌+大小王)。这里有个细节:是否区分花色?对于斗地主规则,花色只在顺子中可能起作用(且通常不严格区分),但为了信息的完备性,我选择了区分,类别名如heart_ace,spade_5,black_joker等。

实操心得:标注时,框(Bounding Box)要紧贴牌面边缘,但不必过于精确。对于重叠的牌,标注可见部分即可。数据集至少需要3000张以上有效标注图片,才能保证模型在复杂背景下的识别稳定性。我最终整理了约5000张图片,按8:1:1划分训练集、验证集和测试集。

3.2 YOLOv5模型训练与调优

训练环境基于PyTorch 1.7+和CUDA 11.0。直接克隆YOLOv5官方仓库,其清晰的目录结构让上手非常容易。

git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt
  1. 数据配置:在data/目录下创建poker.yaml文件,定义数据集路径和类别。

    # poker.yaml path: ../datasets/poker train: images/train val: images/val nc: 54 # 类别数 names: ['heart_ace', 'heart_2', ..., 'black_joker', 'red_joker']
  2. 模型选择:从轻量化的yolov5s.pt预训练模型开始迁移学习。预训练模型在COCO等大型数据集上学到的通用特征(边缘、纹理)能加速收敛。

    python train.py --img 640 --batch 16 --epochs 100 --data data/poker.yaml --cfg models/yolov5s.yaml --weights yolov5s.pt
    • --img 640: 输入图像尺寸。更大的尺寸可能提升小目标检测精度,但会增加计算量。
    • --batch 16: 批大小,根据GPU显存调整。
    • --epochs 100: 训练轮数,可通过早停(Early Stopping)策略优化。
  3. 关键调参

    • 学习率(lr):使用--lr0--lrf参数。一般从0.01开始,配合余弦退火调度器。
    • 数据增强:YOLOv5默认开启了Mosaic、MixUp等强增强。对于扑克牌这种形状规则、可能旋转的目标,可以适当调整--degrees(旋转角度)和--shear(剪切变换)的强度。
    • 锚框(Anchor):YOLOv5会针对你的数据集自动计算最佳锚框尺寸,这是一个非常实用的特性。

训练过程中,使用tensorboard --logdir runs监控损失曲线和评估指标(如mAP@0.5)。当验证集上的mAP趋于稳定且过拟合迹象不明显时,即可停止训练。

3.3 模型导出与性能测试

训练完成后,得到的最佳模型保存在runs/train/exp/weights/best.pt。为了部署,通常将其转换为ONNX或TorchScript格式以提升推理速度或兼容不同环境。

python export.py --weights runs/train/exp/weights/best.pt --include onnx

在测试集上评估模型性能,重点关注:

  • 整体mAP:应达到95%以上,才能保证后续流程的可靠性。
  • 特定类别召回率:检查“大小王”、“2”、“A”等关键牌是否容易被漏检。
  • 推理速度:在目标部署设备(如你的开发机)上,用detect.py脚本测试实时帧率(FPS)。YOLOv5s在GTX 1660上处理640x640图像可达100+ FPS,完全满足实时要求。

4. 游戏状态解析与牌型判断

模型输出了扑克牌的类别和位置,但这只是原始数据。我们需要将其转化为游戏能理解的逻辑状态。这部分代码的健壮性直接决定了AI的“视力”是否可靠。

4.1 从像素坐标到逻辑位置

游戏画面是固定的,因此我们可以通过硬编码的屏幕区域坐标来区分不同区域的牌:

  • 手牌区域:通常位于屏幕底部。
  • 出牌区域:位于屏幕中央。
  • 地主牌区域:位于屏幕顶部。

在代码中,我们根据检测框的中心点坐标(x_center, y_center)来判断一张牌属于哪个区域。例如:

def classify_card_region(box, img_height): x_center, y_center = box[0], box[1] if y_center > img_height * 0.7: return 'hand' elif img_height * 0.3 < y_center < img_height * 0.7: return 'played' else: return 'unknown'

4.2 牌面信息聚合与数据结构化

对于同一区域的牌,需要进一步处理。例如,手牌区的多张牌是水平排列的,我们需要根据它们的水平坐标进行排序,得到一个有序的手牌列表。

# 假设hand_boxes是手牌区域的所有检测框 hand_boxes_sorted = sorted(hand_boxes, key=lambda b: b[0]) # 按x中心坐标排序 hand_cards = [box[5] for box in hand_boxes_sorted] # 获取类别索引

接下来,将类别索引(如heart_ace)转换为内部表示。我定义了一个Card类,包含点数(rank)和花色(suit)属性,并方便地比较大小。

class Card: suits = {'heart': 'H', 'diamond': 'D', 'club': 'C', 'spade': 'S'} ranks = {'3': 3, '4': 4, ..., 'king': 13, 'ace': 14, '2': 15, 'black_joker': 16, 'red_joker': 17} def __init__(self, yolo_class_name): # 解析yolo_class_name,如‘heart_ace’ parts = yolo_class_name.split('_') if len(parts) == 2: self.suit = self.suits.get(parts[0], '') self.rank = self.ranks.get(parts[1], 0) # ... 处理大小王

这样,我们就将视觉信息转化为了一个List[Card]对象,代表了当前玩家的手牌。

4.3 牌型判断引擎

这是斗地主AI的逻辑核心之一。给定一组牌,需要判断它是否符合游戏规则,以及是什么牌型(单张、对子、顺子、炸弹等)。

def check_card_type(cards): cards = sorted(cards, key=lambda c: c.rank) count = len(cards) if count == 1: return 'single', cards[0].rank elif count == 2: if cards[0].rank == cards[1].rank: return 'pair', cards[0].rank elif {cards[0].rank, cards[1].rank} == {16, 17}: return 'rocket', 999 # 王炸最大 elif count == 3: # 检查三张相同 ... elif count == 4: # 可能是炸弹(四张相同)或三带一 ... # ... 更复杂的牌型:顺子、连对、飞机等 return None, 0 # 无效牌型

编写一个健壮的牌型判断函数需要仔细处理各种边界情况,例如A-2-3-4-5算不算顺子(在斗地主中通常不算),以及连对中是否允许2点出现等。

5. AI出牌决策策略:从规则到强化学习

有了精准的游戏状态感知,接下来就是大脑——决策系统。我尝试了两种策略,由简入繁。

5.1 基于规则的策略(初级版)

这是最直观的方法,模拟人类玩家的基本思路。可以设计一系列优先级规则:

  1. 跟牌规则:如果上家出牌,在自己的手牌中寻找相同牌型且点数更大的牌组合。如果没有,则不出(Pass)。
  2. 出牌规则:当自己获得出牌权时(新回合或上家Pass后),按照一定策略选择牌型打出。例如:
    • 优先出较长的顺子或连对,以快速减少手牌数量。
    • 保留大牌(2、王、A)和炸弹作为控制权。
    • 手牌很少时,优先出能一次走完的牌型。
  3. 炸弹使用策略:炸弹是稀缺资源。规则可以设定:仅在以下情况使用炸弹:(a) 能直接获胜;(b) 阻止对手可能的一次出完牌;(c) 自己手牌很差,需要抢夺出牌权。

用代码实现就是一个巨大的if-elif-else状态机。虽然逻辑直白,但要想玩得好,规则会变得极其复杂和臃肿,难以处理所有局面,尤其是需要长远规划(记牌、算牌)时。

5.2 基于强化学习的策略(进阶版)

为了让AI学会更优的、甚至超越人类经验的策略,我引入了强化学习(RL)。我们将斗地主一局游戏建模为一个部分可观测马尔可夫决策过程

  • 状态(State):当前玩家的手牌、已出的所有牌、剩余牌堆(推断)、当前回合信息等。由于是部分可观测,我们只能推断其他玩家的手牌分布。
  • 动作(Action):选择一组牌打出,或者不出(Pass)。
  • 奖励(Reward):最终赢得比赛获得+1奖励,输掉获得-1奖励。也可以设计中间奖励,如每打出一手牌给予微小负奖励(鼓励尽快出完),或成功压制对手给予正奖励。

我采用了深度Q网络(DQN)作为算法框架。状态需要被编码成固定长度的向量(例如,用一个54维的向量表示每种牌在自己手中的数量,再用其他向量表示公共信息)。动作空间是离散的,但非常大(所有合法的出牌组合+Pass)。直接处理如此大的动作空间不现实,因此需要设计一个动作生成器:先根据当前手牌和上家牌型,生成所有合法的跟牌或出牌动作候选集,然后由DQN网络评估每个候选动作的Q值,选择最高的执行。

网络结构相对简单,输入层接收状态向量,经过几个全连接层,输出层对应每个候选动作的Q值。

import torch.nn as nn class DQN(nn.Module): def __init__(self, state_dim, action_dim): super(DQN, self).__init__() self.fc1 = nn.Linear(state_dim, 128) self.fc2 = nn.Linear(128, 128) self.fc3 = nn.Linear(128, action_dim) def forward(self, x): x = torch.relu(self.fc1(x)) x = torch.relu(self.fc2(x)) return self.fc3(x)

训练过程需要大量的自我对弈(Self-play)。让三个AI玩家(可以共享同一个网络,也可以使用不同策略的网络)不断进行游戏,收集状态、动作、奖励、下一状态的序列(s, a, r, s'),存储到经验回放缓冲区,然后随机采样进行网络更新。

踩坑实录:RL训练非常耗时且不稳定。初期最大的问题是奖励稀疏(只有终局才有),导致学习缓慢。解决方案是加入人工设计的中间奖励,例如:成功出完一手牌奖励+0.01,被对手压制奖励-0.005。同时,使用目标网络(Target Network)双DQN(Double DQN)技巧来稳定训练。即使这样,训练出一个勉强能玩的AI也需要数十万局游戏,对计算资源要求很高。

6. 系统集成与自动化控制

将视觉模块、状态解析模块和决策模块串联起来,就构成了完整的自动化流程。同时,我们需要一个控制模块来执行AI的决策。

6.1 主循环流程

系统的主循环通常以固定的频率(如每秒10次)运行,流程如下:

  1. 截图:使用pyautogui.screenshot()或更快的mss库捕获游戏窗口区域。
  2. 推理:将截图送入YOLOv5模型,得到检测结果。
  3. 状态更新:解析检测结果,更新当前玩家的手牌、桌面出牌等信息。
  4. 决策触发:判断是否轮到本方操作。可以通过检测游戏UI上的特定标志(如“出牌”按钮高亮)或根据回合逻辑推断。
  5. 决策与执行:如果轮到本方,决策模块根据当前状态计算出牌动作。然后,控制模块将牌面组合映射到屏幕坐标,模拟鼠标点击出牌。

6.2 模拟鼠标操作

pyautogui库可以方便地控制鼠标移动和点击。关键是将“出哪几张牌”这个逻辑动作,映射到屏幕上具体的坐标。

  • 手牌点击:我们已经知道每张手牌在屏幕上的位置(来自YOLO检测框)。对于要出的多张牌,需要按游戏规则顺序点击(例如,斗地主中需要先选中牌,再点击“出牌”按钮)。这里需要模拟点击和短暂的延迟。
    import pyautogui import time def click_cards(card_boxes): for box in card_boxes: x, y = box_center_to_screen(box) # 将归一化坐标转换为屏幕坐标 pyautogui.moveTo(x, y, duration=0.1) # 移动鼠标 pyautogui.click() time.sleep(0.05) # 短暂延迟,防止操作过快被游戏忽略 # 点击“出牌”按钮 pyautogui.moveTo(play_button_x, play_button_y, duration=0.1) pyautogui.click()
  • 稳定性优化:直接模拟鼠标操作可能因为游戏响应延迟或动画效果而失败。需要加入重试机制和状态确认。例如,点击“出牌”按钮后,等待一段时间并再次截图,确认牌是否真的被打出。

6.3 工程化与调试

一个完整的项目还需要考虑很多工程细节:

  • 配置文件:将游戏窗口位置、按钮坐标、模型路径、决策参数等写入配置文件(如config.yaml),便于适配不同分辨率和游戏版本。
  • 日志系统:详细的日志对于调试至关重要。记录每一帧的检测结果、决策依据、执行的操作,当AI行为异常时,可以回溯查找问题。
  • 优雅退出:设置全局热键(如F12)来暂停或终止AI程序,防止失控。
  • 性能监控:实时显示FPS、决策耗时等信息,帮助优化瓶颈。

7. 常见问题、优化与扩展方向

在实际开发和运行中,会遇到各种各样的问题。这里记录一些典型问题及其解决方案。

7.1 视觉检测常见问题

问题现象可能原因解决方案
漏检某些牌,尤其是边缘牌1. 训练数据中边缘样本不足。
2. 数据增强过度导致模型对位置敏感度下降。
1. 补充边缘位置的截图进行标注和训练。
2. 调整数据增强参数,减少随机裁剪和旋转的幅度。
将游戏UI其他元素误检为牌背景干扰,训练数据未覆盖足够多的UI样式。1. 在数据集中加入更多包含复杂UI的负样本(不包含牌但包含其他UI的图片),并在标注时明确标记为背景。
2. 增加模型输入尺寸,让模型能看到更多上下文信息以区分。
检测速度慢,无法实时运行1. 模型过大(如使用了YOLOv5x)。
2. 在CPU上运行。
1. 换用更小的模型(YOLOv5n或YOLOv5s)。
2. 确保使用GPU进行推理。使用torch.cuda.is_available()检查。
3. 将模型转换为TensorRT或OpenVINO等优化格式。
同一张牌被重复检测多次NMS(非极大值抑制)阈值设置不当。调整推理时的--iou-thres(交并比阈值)和--conf-thres(置信度阈值)参数。对于扑克牌这种目标,可以适当提高iou-thres(如0.6)来合并重叠框。

7.2 决策与执行问题

  • AI总是Pass或乱出牌(规则策略):检查牌型判断函数是否正确,特别是复杂牌型(飞机带翅膀、四带二)的判断逻辑。确保跟牌规则中“牌型相同且点数更大”的逻辑覆盖所有情况。
  • AI决策超时:决策算法,特别是RL策略中的动作生成,可能组合爆炸。需要优化动作生成器,提前剪枝明显不合理的出牌选择(例如,手牌很少时还考虑出长顺子)。
  • 鼠标点击无效或点错位置:游戏可能有动画延迟,或窗口位置发生了变化。增加操作后的等待时间,并实现一个“状态校验”循环:执行操作后,等待几帧,再次检测画面,确认操作是否生效,如未生效则重试或报警。
  • 无法适应不同游戏客户端:这是此类项目最大的挑战。解决方案是抽象一层“游戏适配器”。为每个需要支持的客户端编写一个适配器模块,负责处理该客户端特有的UI布局、颜色、按钮位置等。主程序通过配置选择加载哪个适配器。

7.3 项目优化与扩展

  1. 模型轻量化与加速:尝试使用YOLOv5的量化(Quantization)和剪枝(Pruning)技术,进一步减小模型体积、提升推理速度,使其能在树莓派等嵌入式设备上运行。
  2. 引入记忆与推理:当前的AI只关注当前时刻的牌面。高级玩家会“记牌”和“算牌”。可以在状态表示中融入历史出牌信息,并尝试用神经网络或概率模型来推断剩余牌的分布,让AI具备初步的推理能力。
  3. 多智能体协作与对抗:在三人斗地主中,当地主和当农民的策略截然不同。可以训练两个不同的策略网络,或者设计一个网络能根据自身角色(地主/农民)和盟友信息调整策略。更进一步,可以研究农民之间的协作信号,虽然游戏内无法直接通信,但可以通过出牌传递隐含信息。
  4. 从监督学习到自监督学习:收集大量人类高手对局数据,用行为克隆(Behavior Cloning)或逆强化学习(Inverse RL)来初始化AI策略,可能比纯RL从头训练更快、更稳定。
  5. 图形化界面与交互:为项目开发一个简单的控制面板,可以实时显示AI“看到”的牌、它的决策置信度、当前状态估计等,方便调试和演示。

这个项目从技术验证的角度是成功的,它验证了YOLOv5在复杂UI游戏环境中进行特定目标检测的可行性,并串联起了计算机视觉、游戏逻辑和自动控制。虽然距离战胜人类顶尖高手还有很长的路,但作为一个学习和探索的载体,它充满了挑战和乐趣。所有源码和详细的文档注释都已整理在项目仓库中,希望能给有兴趣的朋友提供一个扎实的起点。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询