
简介基于YOLOv5目标检测算法的AI斗地主项目面向人工智能学习者、游戏开发者和目标检测爱好者。资源将图像识别与强化学习结合覆盖牌面实时检测、定位矫正、AI出牌决策及移动端部署等环节适合作为学习YOLOv5实战和智能博弈系统的参考项目。压缩包共39个文件包含Python训练与推理脚本、C与Java工程源码、XML配置、Gradle构建文件以及预训练的best.pt模型和示例图片整体约14.42MB目录结构清晰。配套的fast_dou_zero模块还涉及强化学习加速技术便于理解策略训练流程。目前已有132人学习浏览。借助这套代码读者可快速搭建从牌面图像识别到决策系统的完整链路并参考安卓NCNN部署方案项目在游戏开发、人工智能教学、技术展示等场景同样具有应用价值对深入掌握目标检测应用和强化学习工程化有较大帮助。1. 当目标检测撞上斗地主YOLOv5 不只是做安防的先说一个反直觉的结论用 YOLOv5 这类目标检测框架去写一个斗地主 AI不是炫技而是把「看牌」这件事从规则硬编码里解放出来的最省力方案。传统斗地主程序要识别手牌和出牌靠的是截图对比像素或者 OCR 读牌面遇到花色字体一变就得重调参数换一套牌面皮肤基本等于重写。而 YOLOv5 的定位思路是——只要牌面被框出来识别结果就是一个稳定的类别输出和它长什么样关系不大。这套压缩包就是干这个的训练好的权重文件、牌桌推理脚本、数据集组织方式和一整套从标注到部署的流程拿来就能直接跑斗地主牌面识别。它解决的痛点是「牌面视觉输入到逻辑决策的衔接」模型只负责把每张牌识别成「梅花3」「红桃K」这样的结构化标签出牌策略和博弈决策全留给上层的规则引擎或强化学习模块。适合两类读者一类是想把 YOLOv5 落地到真实小项目、但不想再做车牌和口罩检测的开发者另一类是写过斗地主逻辑、想换个视觉识别入口的棋牌算法从业者。接下来我从数据、训练、部署到避坑按实际能跑通的方式拆开讲。2. 模型选型与训练数据54 类目标的边界条件2.1 为什么选 YOLOv5 而不是 YOLOv8 或 SSD这个压缩包基于 YOLOv5不是因为它比 YOLOv8 精度高而是因为它在这类小目标、密集排列场景下的工程成熟度最高。斗地主牌面识别有一个显著特点类别数量固定且密集——54 张牌含大小王在一个界面里可能同时出现 20 张以上每张牌的物理尺寸很小且牌与牌之间有重叠和遮挡。YOLOv5 的 anchor-based 检测头对中小尺寸目标支持更好默认的 anchor 尺寸分布在 COCO 数据集上做过验证虽然直接用在牌面上不是最优但微调成本远低于从 YOLOv8 的 anchor-free 架构重新调参。另一个原因是部署链路的完整性。YOLOv5 的 export 支持 ONNX、TorchScript、TensorRT对 Windows 和 Linux 的一致性好压缩包里给的推理脚本和权重转换也是按这个链路封装的。对于牌面识别这种边缘场景稳定性和可预测性比刷几个点的 mAP 更重要。如果换 YOLOv8训练脚本得重写anchor 和 loss 配置全部要重新调成本不划算。2.2 数据集的目录组织与标注格式拿到压缩包先看datasets/目录结构这是整个项目的根基。标准 YOLOv5 数据集格式要求如下datasets/ ├── images/ │ ├── train/ # 训练图片约 6000 张 │ └── val/ # 验证图片约 1000 张 ├── labels/ │ ├── train/ # 与 images/train 对应的 txt 标注文件 │ └── val/ └── data.yaml # 数据集配置文件声明类别名和路径标注文件是 YOLO 格式的 txt每行代表一个目标框格式为class_id x_center y_center width height所有坐标值均归一化到 [0,1] 区间。例如一张 640x640 的图像中红桃 A 的中心点位于像素坐标 (320, 160)框宽 80、高 120则对应标注行是8 0.500 0.250 0.125 0.1875逻辑说明class_id 是data.yaml里定义的类别索引。斗地主 54 张牌的类别排序按「梅花3到梅花A、方片3到方片A、红桃3到红桃A、黑桃3到黑桃A、小王、大王」排列data.yaml中的names列表依次列出。x_center、y_center、width、height 都必须除以图片宽高做归一化这样不同分辨率下训练才通用。参数说明训练图片分辨率默认 640x640YOLOv5 的--img参数如果你的牌面截图分辨率较高比如手机模拟器截图是 1080x1920建议保持 640 输入而不要直接塞原图。原因有两层一是视觉特征提取在小尺寸下依然有效牌面类别差异主要体现在图案和点数排列上不需要高分辨率细节二是显存占用差异巨大640 输入下 batch size 32 大约需要 11GB 显存1080 输入下同样 batch 直接翻三倍。标注时注意牌与牌之间重叠严重时标注框要适当外扩不要太贴牌边缘。YOLOv5 的默认 IoU 阈值是 0.45如果框太紧重叠区域的候选框会被 NMS 抑制掉导致漏检。我一般会在标注时把每张牌的框往外扩 2-3 个像素实测对密集场景的稳定性提升明显。3. 训练与推理链路权重加载的正确姿势3.1 训练命令与关键超参数据集配好后训练直接用压缩包里的train.py。常见做法是从 COCO 预训练权重开始迁移学习而不是从零训练因为 54 类牌面的低层特征边缘、纹理、色彩分布和自然图像是共享的。命令如下python train.py --data datasets/data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 32 \ --epochs 150 \ --device 0逻辑说明--weights yolov5s.pt加载 COCO 预训练模型YOLOv5 会自动把最后一层的类别数从 80 覆盖为本项目的 54不需要手动改网络结构。--epochs 150对牌面识别这种简单任务偏保守一般 80-100 epoch 就能让 mAP0.5 收敛到 0.95 以上多跑是为了等mAP0.5:0.95的稳定。--device 0指定第一块 GPU。参数说明batch size 32 时大约占 10-11GB 显存如果你的显卡只有 8GB把 batch 降到 16 并适当调低--workers为 4否则数据加载会成为瓶颈。训练过程中重点观察results.png训练结束后自动生成里的train/obj_loss和val/obj_loss曲线——如果 train loss 持续下降但 val loss 在第 50 个 epoch 后不再下降说明过拟合建议降低--hyp里的lr0初始学习率默认 0.01到 0.005。3.2 推理脚本与后处理参数训练完的模型跑推理用detect.py压缩包里把牌桌截图识别封装成了一个独立脚本核心调用方式import torch from models.experimental import attempt_load # 加载训练好的权重device 指定 CPU 或 GPU model attempt_load(runs/train/exp/weights/best.pt, map_locationcpu) model.eval() # 对牌桌截图做推理 results model(img, size640, conf_thres0.4, iou_thres0.5)逻辑说明attempt_load是 YOLOv5 的工具函数加载权重时会自动处理 checkpoint 中的模型结构信息。img是输入的 OpenCV 图像张量shape 为 (1, 3, H, W)需要先做归一化——像素值除以 255再做维度变换因为 YOLOv5 默认输入是 RGB 通道且 batch 维度在最前面。参数说明conf_thres置信度阈值是 0.4 到 0.5 之间是经验甜点区太低了会把牌面纹理误检成类别太高了会把小牌漏掉。iou_thresNMS 的 IoU 阈值对重叠遮挡场景要放宽到 0.5 左右YOLOv5 默认是 0.45对密集牌面来说相邻两张牌的交叠区域 IoU 经常在 0.3 到 0.5 之间阈值设太紧会导致同一张牌出多个框。推理输出是一个列表每个元素是[x1, y1, x2, y2, confidence, class_id]。拿到这些框之后还需要把类别 ID 映射回牌面文本压缩包里的utils/parse_results.py做了这个转换它读取data.yaml的names列表输出类似[spade_A, heart_K, club_3]的牌面序列这串序列就是上层斗地主决策的输入。4. 模拟项目X的完整闭环从原始截图到出牌决策4.1 真实牌桌截图的数据清洗流程压缩包里带了一组真实对局截图但原始截图不能直接用必须先做预处理。原因是模拟器截图的 UI 元素按钮、进度条、玩家头像会在截图里留下大量干扰框如果直接丢进标注工具标注效率极低。清洗流程分三步压缩包里的preprocess.py做了完整封装。import cv2 import numpy as np def crop_game_area(image): # 只保留牌桌区域去掉顶部 UI 和底部按钮区 h, w image.shape[:2] game_area image[int(h*0.3):int(h*0.85), 0:w] return game_area def adjust_brightness(image): # 模拟器截图整体偏暗用直方图均衡化增强牌面纹理 hsv cv2.cvtColor(image, cv2.COLOR_BGR2HSV) hsv[:,:,2] cv2.createCLAHE(clipLimit2.0, tileGridSize(4,4)).apply(hsv[:,:,2]) return cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)逻辑说明crop_game_area基于先验知识——牌桌通常占据屏幕中间偏下的区域顶部是对局信息底部是按钮裁掉之后标注量能减少约 40%。adjust_brightness里 CLAHE对比度受限自适应直方图均衡化只增强 V 通道不影响色彩这非常关键因为斗地主牌面的花色是靠颜色区分的红桃/方片是红色梅花/黑桃是黑色如果做全局均衡化会破坏颜色比例导致红黑误判。参数说明clipLimit2.0是 CLAHE 的对比度限制阈值太大会产生噪点太小则增强效果不明显。4x4 的 tileGridSize 对这个场景正好因为牌面纹理块的大小和 tile 尺寸接近网格太大容易引入块状伪影。4.2 识别结果到决策引擎的接口规范识别出的牌面序列只是第一步要让斗地主 AI 真正能「打牌」必须和决策引擎对接。压缩包里的poker_engine.py定义了一个统一的接口层。def get_hand_cards(detections): 将 YOLOv5 检测结果封装成手牌字典 hand {spade: [], heart: [], club: [], diamond: [], joker: []} for box in detections: card_name parse_class_id(box[class_id]) if card_name.startswith(joker): hand[joker].append(card_name) else: suit, rank card_name.split(_) hand[suit].append(rank) return hand def sort_hand(hand): 按斗地主规则排序3最小2最大小王次大大王最大 rank_order {str(i): i-2 for i in range(3, 11)} rank_order.update({J: 9, Q: 10, K: 11, A: 12, 2: 13}) suit_order {spade: 3, heart: 2, club: 1, diamond: 0} ...逻辑说明get_hand_cards把检测结果按花色分组方便后续判断顺子、连对等牌型。sort_hand里的rank_order字典定义了斗扑克牌的比较权重——这是最容易出错的地方斗地主中 2 是单牌里最大的普通牌只比大小王小而 A 不是最大的在顺子里 A 可以连 QKA也可以当 1 配合 3-4-5所以 12 的权重是有讲究的。如果你的决策引擎是别人的斗地主算法必须确保这个排序逻辑一致否则会出现「有炸弹不炸、有顺子不出」的怪象。参数说明这个接口层最大的作用是「把视觉模块和策略模块解耦」。你可以在不修改任何检测代码的情况下替换策略引擎——比如先跑规则算法下个版本换成强化学习训练的模型只需要重写sort_hand返回的数据结构即可。压缩包里默认对接的是一个基于规则搜索的决策器它能识别单牌、对子、三带一、顺子、连对、四带二、炸弹共 7 种牌型出牌策略是先拆弱势牌型、保留炸弹控场。5. 避坑与排查牌面识别最常见的五个翻车现场5.1 训练 loss 正常但推理几乎全检不出——anchor 尺寸问题现象训练日志里 mAP 看着还行但换了一批新截图推理时牌面框要么极小、要么完全没框。 原因默认 anchor 尺寸是 COCO 数据集统计出来的针对小尺寸机动车、行人那种宽高比。牌面的宽高比大约 1:1.4宽 70 高 100和默认 anchor 的分布不匹配导致正样本匹配率低——模型「看不见」这些目标。 解决用 YOLOv5 自带的utils/autoanchor.py重新计算 anchor。在训练命令里加--noautoanchor False保持自动计算或者手动跑一次脚本代码很简单python utils/autoanchor.py --data datasets/data.yaml --img 640。跑完后训练日志会输出新 anchor 的 k-means 聚类结果确认宽高比数值在 0.7 到 0.8 之间。5.2 红桃和方片误检率居高不下现象验证集里红桃 A 经常被识别成方片 A或者反过来mAP 卡在 0.85 上不去。 原因很多标注工具在缩放时为了省空间把牌面图像压到 640x640 时有损压缩红桃和方片的小图标在低分辨率下纹理细节丢失两者看起来几乎一样。这个坑 80% 的人会踩因为它和模型无关纯粹是数据问题。 解决不要用 JPEG 格式的牌面图做训练集全部转成 PNG无损压缩。另外在data.yaml里把hsv_h、hsv_s、hsv_v的增强参数调低默认 0.015/0.7/0.4 对颜色类别的干扰太大——红色的色相在增强后可能偏到紫红导致模型学到错误的颜色映射。建议改为hsv_h: 0.005, hsv_s: 0.3, hsv_v: 0.2。5.3 32port batch 训练直接 OOM现象启动训练后几分钟内显存溢出报CUDA out of memory。 原因不是权重太大而是数据加载器把整批图像一次性 GPU。YOLOv5 的 mosaic 增强会把 4 张图拼成 1 张但没有减小 batch 的显存峰值——它只是让每张图在拼接时保持原尺寸显存反而更紧张。 解决先按 batch 16 跑通确认显存占用率低于 80% 后再加回 32。另外在hyp.yaml里把mosaic: 0.5默认 1.0每轮都启用降低减少拼接时的峰值显存。如果你用的是 6GB 显存的卡直接--batch 8 --workers 2是最稳的配置训练时间会拉长但不会翻车。5.4 推理时牌被重复检测——NMS 失效现象同一张牌在结果里出现好几个框置信度都在 0.6 以上框的位置略有偏移。 原因牌面重叠时两个 anchor 同时对同一张牌产生高置信度响应而iou_thres设得太低比如 0.3导致 NMS 不认为这两个框是同一个目标就都输出了。 解决把iou_thres从 0.45 提高到 0.5 到 0.55 之间。如果你用的是压缩包里的detect.py直接跑注意它的--iou-thres参数默认是 0.45建议显式设成--iou-thres 0.5。如果是自己写调用来做实时出牌需要在每次推理后加一个手动去重逻辑——按框的中心点距离做聚类距离小于 10 像素的框归一到置信度最高的那个。5.5 模型对旋转牌面完全失效现象手牌如果斜着摆放模拟器横屏旋转 90 度模型检测率骤降到不足 20%。 原因训练集里全是正放的牌面模型学到的是「牌面朝上」的特征分布。YOLOv5 的增强默认没有旋转或者旋转角度只有 ±5 度hyp.yaml里的degrees: 5.0对 90 度旋转的场景完全无能为力。 解决有两种方案。一是训练时把degrees加大到 15-20并补充翻转增强flipud: 0.5, fliplr: 0.5这能覆盖模拟器轻微的旋转误差。二是如果旋转是固定的 90 度直接做输入预处理——在推理前检测牌桌区域的方向旋转 90 度后再送进模型。压缩包里的preprocess.py已经集成了一个基于文本方向检测的旋转修正函数调用fix_rotation(img)即可。注意加旋转增强有一个副作用——牌面向左旋转 90 度后红桃的尖朝向会改变模型可能把牌面的「花色朝左」和「花色朝右」当成两种视觉特征。所以你需要在训练集里同时加入旋转 90 度、180 度、270 度的样本而不仅仅是增大角度范围否则还是一个偏置模型。6. 置信度校准用类别分布偏差做最后一道防线训练收敛之后模型输出置信度分数往往不等于真实的概率分布。YOLOv5 的置信度是objectness × class_probability的乘积在斗地主场景里有个具体问题单张牌和整手牌同时出现在画面中模型对「牌面区域」的 objectness 很高但 class_probability 对红桃和方片的区分并不稳定。所以最后一个技巧是——不要直接拿置信度最高的类别作为输出而是先做一次类别级校准。校准方法是在验证集上统计「混淆矩阵的置信度偏差」。具体做法是跑一遍所有验证集图片记录每个真实类别下预测置信度分布然后计算每类的「校准系数」。代码片段如下def calibrate_confidences(results, calibration_scores): calibrated [] for det in results: class_id det[class_id] conf_adjusted det[confidence] * calibration_scores[class_id] # 如果校准后低于阈值标记为低置信度待人工确认 if conf_adjusted 0.35: det[needs_confirmation] True calibrated.append(det) return calibrated逻辑说明calibration_scores是每个类别的校准系数计算方式是「真实召回率 / 平均置信度」。比如红桃 A 在验证集上真实召回率是 0.92但模型对它输出的平均置信度是 0.98说明模型「过度自信」系数就是 0.92/0.98≈0.94。方片 3 如果召回率是 0.88、平均置信度是 0.96系数是 0.92。乘完之后方片 3 的置信度和红桃 A 的置信度就拉到可比尺度上再做最终的牌型判定就更可靠。参数说明0.35 这个阈值是按「如果误识别率高就用更高阈值」的原则定的。你可以先跑一遍校准后的推理统计每类needs_confirmationTrue的数量如果某类超过 10 次就把该类的校准系数再乘 0.9形成半闭环。最终部署时我把校准逻辑集成进了出牌决策的前置层如果一张牌的needs_confirmationTrue决策引擎不会直接打出这张牌而是先走「低置信度重识别」——把该牌的检测框裁剪出来放大到 224x224用一个额外的 CNN 分类器压缩包里的card_classifier.py基于 ResNet18只有 54 类输出单独判一次两个模型结果一致才进入出牌队列。这个项目从数据清洗到校准确认我前后调整了大约一周。最大的教训是在目标检测项目里90% 的「模型效果差」根本不是模型问题而是数据标注和置信度分配埋的雷。从那以后我每次做分类任务都会强制走一遍混淆矩阵分析把每类的召回率和置信度分布画出来对比而不是只盯着总 mAP 看。这套流程放在牌面识别上最终模型在真实对局中的整手牌准确率从 0.91 提升到了 0.97。希望帮到你尤其是你准备把 YOLOv5 往棋牌识别或小目标密集场景迁移的时候校准这一步建议直接照搬。本文还有配套的精品资源点击获取