news 2026/10/22 16:32:20

YOLOv5斗地主牌面识别:数据增强、训练调优与安卓NCNN部署全攻略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
YOLOv5斗地主牌面识别:数据增强、训练调优与安卓NCNN部署全攻略

简介:面向具备一定深度学习基础、希望快速落地扑克牌识别功能的开发者,这套斗地主牌面识别方案提供了从YOLOv5模型训练到安卓端NCNN部署的完整实现。方案核心是基于YOLOv5训练的高精度检测模型,可自动定位游戏截图中的每张牌面并识别数字和花色;配套推理脚本便于桌面端验证效果,同时通过NCNN框架完成安卓端轻量化转换,替代以往模板匹配,大幅提升识别鲁棒性与泛化能力。压缩包共三十九个文件,涵盖训练及推理代码、安卓工程、构建配置、模型权重、示例图像和文档等,整体仅十四点三八兆字节,结构清晰便于查阅。目前已有五十七人学习下载,适合研究扑克牌识别、棋牌AI或移动端视觉应用的开发者。资源内不仅包含数据处理、模型训练、评估验证的完整代码,还提供可直接构建运行的安卓工程及说明文档,配合示例截图可快速复现整个流程,便于二次开发与功能扩展;预处理工具与通用函数模块能帮助读者适配自定义数据集,降低迁移学习门槛。

1. YOLOv5斗地主牌面识别,难点不在网络而在牌会“发光”

做斗地主牌面识别的第一反应是接一个现成目标检测模型,把 54 张牌当 54 个类别训出来就行。真正动手后你会发现,卡住整个项目的从来不是 YOLOv5 的网络结构,而是牌面数据本身:塑封牌在灯光下会大面积高光反白,手牌区小目标密集排列,同一张牌换个角度颜色和亮度就完全不同。标题里的方案,本质上是围绕“数据要怎么造、模型要怎么收敛、端侧要怎么跑”三件事展开的。这条路适合已经会跑 YOLOv5 基础训练、但对移动端部署链路不熟的开发者,目标是让你拿到一套能从数据集一路走到安卓真机的完整落地路径,而不是只看到一个训练脚本。

2. 牌面数据集怎么做:采集策略、类别设计与扩增流水线

2.1 合成图能让训练先跑起来,但真机上的坑全来自真实光照

网上几乎没有现成的、干净的斗地主牌面检测数据集,能下载到的大多是棋牌游戏截图,带 UI 遮挡、水印和固定渲染风格,直接拿来做检测模型会让模型学到游戏界面的纹理而不是牌本身。常见做法是自己造数据,而且必须是两类混合:一类用牌面素材贴到随机背景上生成合成图,一类用真机俯拍真实牌局。

合成图的优势是能批量覆盖干净曝光、整齐排列、极端色温这些理想情况,一晚上能生成几万张,类别均衡性可以把控到每一张牌出现次数完全一致。真机照片负责覆盖反光、褶皱、亮度不均、手指遮挡、牌与牌边缘挤压这些合成图模拟不自然的场景。两类数据的比例我一般控制在三比七左右,真实拍摄占大头,合成图只做兜底和均衡。纯合成数据上训练的模型,换到真机环境准确率通常会掉十个百分点以上,这是最典型的翻车点。

这里还要说清牌面检测区别于通用物体检测的两个特性。第一,牌面宽高比相对固定,接近三比四,但牌与牌之间的间距极小,手牌区里相邻牌几乎贴在一起,anchor 重叠率天然偏高。第二,牌桌是俯视视角,画面边缘的牌存在明显透视形变,这会让牌面字符产生拉伸。这两点直接影响后面的标注规则、增强参数和 anchor 设置,不是随便拿一套 COCO 训练配置就能直接复用的。

2.2 类别定义不是一个框一张牌那么简单

类别设计有两种路线。方案 A 是直接定义 54 个类别,四种花色各自带上 A、2 到 10、J、Q、K,大小王单独成类。这样训练和推理都最简单,网络输出直接对应最终语义。方案 B 是把点数 13 类和花色 4 类分开预测,端侧再组合成完整牌面。后者听起来更工程化,但实际部署时相当于同时跑两个检测头,后处理要额外做组合匹配,而且花色和点数必须落在同一个框内才能配对,一旦其中一个分类错误,组合结果错得更隐蔽。

务实的选择是方案 A,把易混字形的差异交给数据去解决。标注时还有一个容易忽略的决策:整张牌作为一个框,而不是只框点数区域。只标点数区域会让目标变成小目标里的极小目标,漏检率几何级上升;整框定位最稳定,后续在安卓端要对齐牌面做记牌高亮时也直接有位置可用。大小王要分成黑王和红王两个独立类别,合并成一个“王”会让端侧无法区分两张王牌,这在记牌应用里是致命错误。

类别设计确认后,标注规则要写死三条:第一,只标完整牌面,禁止标局部字符;第二,“10”这张牌必须完整包住两个字符,不能拆开;第三,被遮挡超过四成的牌面直接跳过不标,不要为了凑样本数制造垃圾标签。

2.3 增强参数怎么定:旋转有上限,高光必须治

扩增管线我直接用 Albumentations 实现,配置项比 YOLOv5 内置 hyp 更好控制。核心增强代码如下:

import albumentations as A from albumentations.pytorch import ToTensorV2 # 牌面识别专用增强管线,按顺序执行 transform = A.Compose([ A.RandomBrightnessContrast(brightness_limit=0.2, contrast_limit=0.2, p=0.8), # 模拟不同桌面的明暗差异。注意 brightness_limit 超过 0.3 会把牌面高光推成白板 A.HueSaturationValue(hue_shift_limit=10, sat_shift_limit=20, val_shift_limit=15, p=0.6), # 色温偏移,覆盖暖光桌和冷光桌的差异 A.Rotate(limit=15, border_mode=0, value=0, p=0.5), # 只允许小幅旋转,后面避坑章会细讲为什么不能给大角度 A.RandomScale(scale_limit=(0.1, 0.25), p=0.4, interpolation=1), # 模拟不同拍摄距离下牌在画面里的大小变化 A.CLAHE(clip_limit=2.0, tile_grid_size=(8, 8), p=0.3), # 局部对比度增强,缓解塑料牌面的高光反白 A.Cutout(num_holes=4, max_h_size=20, max_w_size=20, p=0.2), # 模拟筹码、手指遮挡牌面边缘的常见情况 ], bbox_params=A.BboxParams(format='yolo', min_visibility=0.6)) # 使用方式:对每张训练图和gt框同时做变换 augmented = transform(image=img, bboxes=boxes)

这段配置里有两个参数不建议乱动。Rotate 的 limit 严格限制在正负 15 度以内,牌面是有方向语义的,6 倒过来就是 9,2 和 5 也对偶,稍不注意增强环节就把训练标签搞乱了。Cutout 的 num_holes 和尺寸决定了遮挡力度,max_h_size 和 max_w_size 设为 20 像素是为了只遮牌面边缘而不是把整张牌盖死,min_visibility 0.6 保证被遮掉超过四成的样本直接丢弃,不进入训练。

合成数据的生成不复杂,思路是准备一套高清牌面素材,每张牌随机叠加背景图、随机缩放旋转、加光照扰动,再通过上述 Albumentations 管线输出成 YOLO 格式。数据规模上,54 个类别每类建议至少 500 个真实实例,合成数据可以放宽到每类 1000 个以上,总量在三万张左右模型基本能稳定收敛。训练集、验证集、测试集按七比二比一划分,测试集必须只放真机拍摄的照片,合成图一张都不进。

提示:合成数据只用来拉高类别覆盖和补充极端光照样本,不要在纯合成数据上评估模型真实效果,那会严重高估。

3. 用 YOLOv5 把牌面模型训出来:选型、配置与评估误区

3.1 端侧选型:yolov5n 起步,yolov5s 兜底

安卓端部署决定了模型不能只看精度。牌面检测的输入分辨率通常在 640 左右,手牌区在画面中可能只占一小块,模型容量太小会漏小目标,容量太大推理帧率扛不住。主流选择是两个量级:

模型参数量端侧推理耗时适用场景
yolov5n约 190 万低,中端机可跑 20fps+优先选择,帧率敏感场景
yolov5s约 730 万中,约 10-20fps精度优先、帧率可妥协
yolov5m约 2100 万高,不适合移动端实时一般不建议

实际部署中我习惯先训 yolov5n,如果验证集上牌面类别准确率低于 95 再换 yolov5s。原因是斗地主牌面类别虽然多,但每类的视觉结构非常规则,背景也相对单一,容错空间比行人检测这类开放场景大得多,n 模型在多数中端机上已经够用。m 及以上参数量在手机端会遇到内存占用高、发热降频导致的掉帧,不推荐。

3.2 数据配置与训练命令

YOLOv5 训练前的数据配置文件格式固定,只要把路径和类别名写对即可:

# cards.yaml path: ./datasets/cards train: images/train val: images/val names: 0: SA # 黑桃A 1: S2 # 黑桃2 # ... 按四种花色×点数排列 50: CQ # 梅花Q 51: CK # 梅花K 52: BJ # 小王 53: RJ # 大王

类别顺序没有硬性要求,但建议训练集、验证集和后面部署时的类别表完全一致,否则端侧解码会错位。训练命令用标准流程:

python train.py \ --data cards.yaml \ --weights yolov5n.pt \ --img 640 \ --epochs 150 \ --batch-size 16 \ --multi-scale 0.5 1.0

--multi-scale在每轮迭代里随机缩放输入尺寸,区间设 0.5 到 1.0 是为了让网络见过小尺度下的牌面特征,这对牌面这种小目标密集场景很重要,能明显减少漏检。--img 640是基准尺寸,如果训练时发现手牌区域里的牌太小导致 loss 下不去,可以提到 960 训练,推理端仍用 640 输入,让网络在高分辨率下学到的纹理细节在低分辨率推理时也能受益。

anchor 不需要手动配置。YOLOv5 默认开启 autoanchor,训练启动时会根据你的标注框重新聚类 anchor 尺寸,牌面宽高比高度集中在 0.75 附近,自动聚类后的 anchor 分布和 COCO 默认值差异明显。训练日志里如果出现 anchor 重新聚类的提示,说明数据分布已经覆盖默认值,这属于正常现象,不要强行设--noautoanchor用回 COCO 的 anchor。

3.3 训练完成后别只盯着 mAP

模型训练结束,很多人第一件事是看 mAP,然后发现 AP50 已经到了 0.98 就觉得完事了。牌面识别真正要盯的是混淆矩阵:重点看“10”和“8”、“A”和“4”、大小王之间是否存在系统性混淆。这些类别字形接近,混淆矩阵里如果成块出现,说明数据数量或增强强度不够,需要回到上一章补样本调参,而不是继续加 epochs。

还有一个评估习惯:把几张实拍牌桌照片直接丢进 detect.py 看可视化输出,不要只看指标。漏检通常发生在边缘牌和反光牌上,这类问题在 mAP 数值里很小,但真机体验上非常明显。评估指标选择上以 AP50 为准,牌面类别特征清晰,AP50 能反映真实精度;AP50:95 对牌面这种小目标过于严格,数值波动大反而误导调参方向。

训练轮次 150 到 200 轮足够收敛,单张中端显卡跑 yolov5n 大约几小时。如果 loss 在 50 轮后还在明显下降,优先怀疑数据问题而不是网络容量问题,这一点很多新手容易搞反。

4. 牌面识别落地中的五个常见坑与排查顺序

4.1 增强翻转毁掉牌面语义:6 和 9 永远分不清

现象:模型在验证集上“6”和“9”的混淆率异常高,排查聚类发现错误样本几乎全部来自训练增强后的旋转图,还有一部分“2”被识别成“5”。

原因:YOLOv5 默认的 mosaic 增强里包含随机旋转和翻转逻辑,如果同时开了水平翻转和垂直翻转,牌面方向语义会被彻底破坏。6 倒置就是 9,2 倒置就是 5,网络学到的特征在翻转前后自相矛盾,只能靠概率猜。

解决:关闭垂直翻转 flipud,水平翻转同样要关,因为牌面左右翻转后花色和点数方向也不对。Rotate 增强的角度限制在正负 15 度以内,mosaic 增强建议直接关闭或限制其内部旋转角度。训练配置里数据增强部分单独写,不用 YOLOv5 的默认 hyp。

4.2 高光反白把牌拍成“白板”

现象:真机测试时,正对灯光方向的牌面中央出现大面积白斑,模型漏检,偶尔把反光区域误检成“大王”。

原因:塑料牌面表面是镜面反射,灯光直射时反射的是光源而不是牌面图案,训练数据里干净牌面占多数,网络没学过这种极端高光下的特征。

解决:采集真机数据时用散射光源,避免点光源直射;增强管线里加 CLAHE 提升局部对比度;推理端对预览帧做直方图均衡化后再送模型,代价很小但对高光缓解明显。最彻底的做法是买一副亚光牌面的扑克作为测试数据源之一,让过曝和正常场景都进训练集。

4.3 “10”被拆成两个目标

现象:NMS 之后一张牌的框里出现两个小框,一个命中“1”,一个命中“0”,两个候选框置信度都接近 0.9。

原因:标注规则不统一。“10”是双字符牌,部分标注框只框了数字“1”所在的区域,另一部分框了完整“10”,网络同时学到了两种语义,推理时同一位置触发两个响应。

解决:统一标注规则,所有牌面只标整张牌,禁止标注局部字符;NMS 的 IoU 阈值从默认 0.45 调到 0.6,过滤同一牌面上的重复框;单独给“10”补一批样本,让网络明确这个类别是完整双字符结构。

4.4 loss 明明在降,端侧却总是漏小目标

现象:训练曲线的 box_loss 稳定下降,真机实测漏检却集中在画面远处的手牌区,近处的牌都能检测到。

原因:训练样本里牌面在整图中的占比小,经过输入缩放后目标可能只有三十到四十像素,模型对绝对尺度小的目标特征提取能力不足。loss 下降正常,但小目标的梯度贡献被背景淹没。

解决:开启--multi-scale 0.5 1.0,让模型在每个 batch 里接触不同尺度;训练分辨率提到 960,推理端用 640,让网络在大分辨率下学习牌面纹理细节;如果条件允许,对样本中牌面占比过小的图做一次裁切放大再送入训练,相当于给网络增加了小目标的上采样机会。

4.5 换到安卓端精度暴跌

现象:训练时 mAP 到了 0.93,装到手机上识别准确率只有七成,同一个测试集在 PC 上跑结果正常。

原因:端侧链路里有多个变量同时变化,相机预览的分辨率通常不是训练用的 640,自动白平衡让每帧色温不同,NV21 转 RGB 的色彩空间偏移会让颜色整体偏绿偏蓝,再加上 INT8 量化掉点,几层误差叠加后精度断崖式下跌。

解决:端侧测试集必须用真机拍摄的牌局照片,不能用训练验证时的截图;量化校准集要取真实相机预览帧,不能用训练集图片代替;相机预览固定白平衡,或者把图像格式统一转成 RGB 再送推理,保证喂给模型的图像分布和训练增强分布尽量一致。还有一个容易被忽略的点,端侧输入分辨率不是越高越好,预览分辨率太高会让手机发热降频,反而拉低推理速度。

5. 模型转换与安卓端部署:从 ONNX 到 NCNN 的完整链路

5.1 导出 ONNX 并转成 NCNN 格式

训练完成后的 best.pt 不能直接在安卓端用,需要先转成通用中间格式,再转成移动端推理框架的模型格式。常见做法是 ONNX 中转,再转到 NCNN,因为 NCNN 在安卓端的兼容性和优化都成熟,没有额外运行时依赖。

# 第一步:导出 ONNX,simplify 必须开 python export.py --weights runs/train/exp/weights/best.pt \ --include onnx --opset 11 --simplify # 第二步:转 NCNN 格式 onnx2ncnn best.onnx best.param best.bin # 第三步:优化 NCNN 模型,最后一个参数 0 表示 FP32,1 表示 FP16 ncnnoptimize best.param best.bin best_opt.param best_opt.bin 1

导出时的--simplify参数务必保留,ONNX 里会有大量 Shape、Gather 这类只用于图结构计算的节点,不简化会让 NCNN 转换失败或推理性能很差。opset 版本选 11,NCNN 对这个版本的支持最稳,opset 过高会出现不兼容的算子。ncnnoptimize是很多人会跳过的一步,它会把模型里的冗余算子融合掉,同一张图在安卓端能快 10% 到 20%,建议每次转换后都执行。

量化方案的选择直接影响端侧精度和速度,三者的权衡关系如下:

方案模型体积占比精度损失端侧速度推荐度
FP32100%基准慢,中端机吃力仅调试
FP16约 50%几乎无损快,GPU 加速明显优先选择
INT8约 25%需校准集且字形易混最快精度敏感场景慎用

牌面识别的类别差异集中在字形细节上,INT8 量化后“6”和“8”、“A”和“4”的边缘特征容易混在一起。实战里 FP16 已经能覆盖大多数中端机的帧率需求,INT8 只在低端机跑不动时才考虑,而且必须用真机相机帧做校准集,不能偷懒用训练图。

5.2 安卓工程里的 JNI 推理核心

安卓端接入 NCNN 的标准做法是写一层 JNI,Java 负责相机帧获取和 UI,C++ 负责模型加载和推理。核心代码结构如下:

#include "net.h" #include <opencv2/core.hpp> static ncnn::Net net; int load_model(const char* param_path, const char* bin_path) { net.opt.use_vulkan_compute = true; // 手机有 GPU 就开启,速度提升明显 net.opt.num_threads = 2; // 牌面识别单帧任务小,2 线程足够 return net.load_param(param_path) + net.load_model(bin_path); } int detect_card(cv::Mat& rgb, std::vector<Box>& boxes) { // letterbox:等比缩放到 640x640,右侧和下方灰边填充 float scale = std::min(640.0f / rgb.cols, 640.0f / rgb.rows); int new_w = (int)(rgb.cols * scale); int new_h = (int)(rgb.rows * scale); cv::resize(rgb, resized, cv::Size(new_w, new_h)); cv::Mat padded(640, 640, CV_8UC3, cv::Scalar(114, 114, 114)); resized.copyTo(padded(cv::Rect(0, 0, new_w, new_h))); ncnn::Mat in = ncnn::Mat::from_pixels(padded.data, ncnn::Mat::PIXEL_BGR2RGB, 640, 640); in.substract_mean_normalize(mean_vals, norm_vals); // 前向推理 + YOLOv5 后处理解码,代码省略 // 解码出的坐标必须除以 scale 并减去 pad 偏移,才能映射回原图 }

use_vulkan_compute在支持 GPU 的机型上能带来接近一倍的帧率提升,但部分老机型驱动不稳定,真机测试时如果出现闪退,先把这个开关关掉对比。num_threads设 2 而不是喝满 8 个核心,原因是移动端 CPU 发热降频太快,多线程带来的收益会被降频吃掉。substract_mean_normalize的均值和方差必须和训练时保持一致,YOLOv5 默认是 0、255 归一化,不要额外减一个 ImageNet 均值,很多移植翻车都出在这个细节上。

5.3 相机链路与线程安排

相机部分用 CameraX 的 ImageAnalysis 最简单,关键配置是背压策略设为STRATEGY_KEEP_ONLY_LATEST。牌面识别不是高帧率需求,12 到 18fps 足够,不需要每一帧都处理,丢帧比排队处理更有价值。预览分辨率不是越大越好,1080p 的 YUV 转 RGB 会占掉大量 CPU,实测 720p 对牌面识别已经足够。

推理必须放在独立线程,和相机回调、UI 更新全部分离,用一个 HandlerThread 串行消费帧数据。同一个 ncnn::Net 实例不能同时被多个线程调用,否则会出现不明原因的内存错误。每帧处理完要立刻 recycle 掉 Bitmap,安卓的 Binder 内存缓冲区很小,不回收会在连续运行几分钟后直接 OOM。

单帧耗时预算基本是预处理 2 到 5 毫秒,推理 20 到 50 毫秒,后处理 1 到 3 毫秒,整体在 50 毫秒左右就是 20fps。如果实测超过这个量级,先看是不是 YUV 转换耗时过大,再看 GPU 是否真的启用了,最后才考虑换更小的模型。

6. 真机验证三板斧:测试集、帧率与阈值校准

模型部署到安卓端后,验证工作比训练阶段更关键。第一件事是留一套全程不参与训练和验证的真机拍摄测试集:让同事用手机在不同光线、不同桌面上打牌录视频,抽帧一百到两百张,编号存档。这套测试集从第一天就冻结,每次模型迭代、每次量化参数调整,都用它跑一遍,结果可对比才有优化价值。

第二件事是端侧跑分脚本。在手机上写一个批量检测循环,逐帧读取测试集图片,把每个框的类别、置信度、位置输出成 JSON,再比对标准答案。判定正确的标准是:类别完全一致且 IoU 大于 0.5。统计最终准确率,如果和训练时 mAP 差值超过十个百分点,优先检查部署链路的预处理是否和训练一致,而不是怀疑模型。这里的准确率要单类分开统计,“10”和大小王这类难点单独看,整体数值容易掩盖局部崩坏。

第三件事是测耗时时看中位数,不看平均值。手机推理的耗时波动很大,偶发的 GC 停顿和系统调度会让平均值虚高,中位数才能代表真实体验。统计时去掉前二十帧预热数据,待手机稳定后再采样,否则冷启动的加载时间会干扰判断。

如果帧率不达标,优化顺序是固定的:先降输入分辨率,640 降到 416,牌面检测只要牌在画面里占比足够,精度损失通常可控;再对比 GPU 和 CPU 的实际耗量,个别机型 GPU 驱动效率低,关掉 Vulkan 用 CPU 反而更快;最后才考虑 INT8 量化,并且量化后用冻结的真机测试集重新验证一遍,如果准确率跌破阈值就回退到 FP16。

我现在的习惯是每个牌面项目先拿一台最旧的真机跑一遍再谈优化,旧机能稳定运行,新机型基本没风险。这套验证流程虽然朴素,但比看任何训练曲线都可靠。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 18:27:07

Railway上部署小龙虾-OpenClaw:Docker与配置避坑指南

部署开源智能助理这事&#xff0c;我一直觉得最烦人的不是代码&#xff0c;而是环境。好好的项目&#xff0c;本地跑得飞快&#xff0c;一上云端就各种崩&#xff0c;数据库连不上、镜像拉不下来、日志刷屏看半天不知道错在哪。所以当我看到“小龙虾-OpenClaw”这个项目的时候&…

作者头像 李华
网站建设 2026/10/21 4:36:31

3D高斯泼溅与神经渲染:CV技术演进新范式

1. 这不是论文列表&#xff0c;而是一份“CV方向技术演进路线图”如果你最近打开 arXiv 的 cs.CV 分类页面&#xff0c;看到标题里带“2026.09.25”这种未来日期的汇总帖&#xff0c;第一反应可能是&#xff1a;时间错乱&#xff1f;刷屏机器人&#xff1f;还是某位教授在测试版…

作者头像 李华
网站建设 2026/10/23 4:14:24

Unity第三人称角色移动系统详解:从输入到相机跟随的完整实现

1. 项目起底&#xff1a;第三人称角色移动&#xff0c;到底在解决什么问题做Unity开发的人&#xff0c;不管你是做动作游戏、RPG还是开放世界探索&#xff0c;绕不开的第一座山就是“让角色动起来”。输入一个方向、角色响应、镜头跟随&#xff0c;这套链路的体验好坏&#xff…

作者头像 李华
网站建设 2026/10/23 4:07:17

元尚宅乡墅分享农村自建房规划中用地审批与设计建造要点助力农户建安全舒适乡居

农村自建房宅基地使用权申请需满足谁的条件 在农村自建房规划中&#xff0c;宅基地使用权有明确的身份限定。申请人必须是所在村集体经济组织的成员&#xff0c;这是取得用地资格基本前提。已经外迁或者户口落户城市的人&#xff0c;原则上不能再回原宅基地申请建房。符合条件的…

作者头像 李华