ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

用YOLO打造麻将计算器:从模型训练到App交付的完整实践

用YOLO打造麻将计算器:从模型训练到App交付的完整实践 简介Yolo麻将计算器应用程序是一套基于YOLO实时目标检测的麻将计分工具源码面向希望借助图像识别自动核算牌面分数的业余玩家、比赛组织者与麻将规则学习者。项目涵盖移动端与桌面端的完整工程结构展示从获取牌面、YOLO模型识别牌张再到按不同地区规则汇总分数的处理链路可有效降低人工计分出错率并提升对局效率。资源压缩包共133个文件大小318KB以源码和工程配置为主png为界面与牌面素材dart、swift、kt、cpp、h等对应Flutter多端与原生模块xcconfig、plist、gradle等承担各平台构建配置同时附带md说明与txt参考文本便于按需查阅。已有91人学习对于正在研究YOLO落地或开发棋牌类辅助应用的技术人员这份工程提供了可直接研读的识别框架、计分逻辑及多端打包思路能够节省从零搭建基础架构的时间。 打麻将的人应该都有过这种经历牌桌上几位牌友算番算得热火朝天有人一口咬定是满贯有人坚持只有跳满翻来覆去谁也说不清。这时候如果有个工具能看一眼牌面就把番数算明白矛盾当场就能化解。我去年闲着没事用YOLO目标检测做了个麻将计算器App把牌面识别和番数计算串成了一条流水线。这篇就聊聊从模型选型、数据处理到最终打包交付的完整过程重点讲那些文档里不会写的坑。1. 麻将牌识别的难点拆解为什么这个场景天然适合YOLO先把这个项目的任务拆清楚。所谓麻将计算器核心是两个环节一是识别桌面上的麻将牌二是根据牌型计算番数。第二个环节是纯逻辑问题写规则判断就行难点全在第一个环节——让程序能从摄像头画面里准确找出每一张牌并且认出它是什么。麻将识别的难点和通用物体检测不太一样。常见的目标检测任务比如行人检测、车辆检测目标大、特征明显模型很容易抓住关键信息。但麻将牌有几个先天的麻烦牌面小。一局牌铺开来单张牌在画面里可能只有几十个像素宽属于典型的小目标检测场景。牌面图案高度相似。万子、条子、筒子的区别就是几个符号同一张牌换个光照角度视觉差异可能比不同牌之间的差异还大。密集排列。手牌通常紧挨着摆放检测框很容易重叠一个框把两张牌框进去的情况非常常见。数量波动。不同牌局牌数不一样而且出牌区的牌和手牌区的牌大小、角度都不同。这些特点叠加在一起传统图像处理方案基本撑不住。你可以用模板匹配做但角度一变、光照一变就废了也可以用OCR识别牌面文字但筒子和条子的图案不是标准字符OCR模型很难泛化。对比下来YOLO这种基于边界框回归的目标检测方案是最稳妥的选择。它直接输出什么东西在什么位置天然契合找出每一张牌并分类的需求。而且YOLO系列经过这么多年的迭代对小目标的支持已经相当成熟推理速度快部署到手机端也没压力。2. 数据集制作识别精度的上限在数据不在模型很多新手做这类项目上来就急着调模型结果训出来效果稀烂然后四处找网络结构的问题。以我实际体验来说麻将识别这类垂直场景精度上限几乎完全由数据决定。模型只是一个拟合工具喂进去的数据长什么样它就学成什么样。2.1 数据采集的几个要点我前前后后采集了大概两万多张麻将牌照片来源包括自己拿手机在不同光线、不同桌面对着牌墙拍摄这是最可控的数据来源。从网上找了一些麻将素材图通过程序合成的方式扩充样本。用视频抽帧的方式把打牌过程录下来每隔几帧取一张图。采集阶段最容易犯的错是数据太干净。很多人拍训练数据的时候会把牌摆得整整齐齐、光线均匀结果模型在真实场景下直接崩掉。我后来特意加入了大量脏数据手指遮挡的牌、牌与牌叠压的瞬间、逆光时的牌面、运动模糊的画面。这些数据虽然看起来不美观但能大幅提升模型的鲁棒性。2.2 标注策略标注工具我用的LabelImg界面老套但胜在稳定。这里有一个关键决策标注框到底要不要紧贴牌面边缘我建议框稍微比牌面大一点点把牌的白色边框也算进去。原因是模型在推理时如果牌面反光导致局部区域过曝框内信息依然能提供足够的上下文分类器不容易被带偏。另外所有标注框的方向要保持一致不要一会儿标成水平的一会儿顺着牌的角度标斜的。YOLO默认的检测框是轴对齐的矩形如果训练数据里出现太多角度不一致的框模型会学得很混乱。2.3 数据增强是保命手段麻将牌的类别很多常规的万条筒加字牌加花牌合计有三十多种。想让每个类别都收集到足够多的样本工作量非常大。我靠数据增强把样本量翻了三四倍尤其是这两个操作随机旋转范围控制在正负15度以内。旋转太大牌面字符会失真反而干扰学习。HSV颜色抖动模拟不同灯光下的色温变化这对浅色牌面尤其重要。还有一个很多教程不会提的点负样本。也就是不包含任何麻将牌的画面。我在采集数据时专门拍了大量的桌面空镜、手部特写、牌盒照片标注的时候全部标记为背景。这样做的目的是让模型学会什么都没有的置信度分布避免在推理时对桌面的纹理产生误检。2.4 验证集要模拟真实牌局训练集可以靠合成和增强把数据量做大但验证集一定要贴近真实场景。我单独录了十几局真实打牌的视频从中抽取了上千帧作为验证集。这样做的好处是你能清楚地看到模型在真实环境下的表现而不是被训练集的漂亮指标骗过去。3. 模型选型与训练调优小目标检测的关键设置数据准备好了接下来就是选模型和调参。我当时对比了YOLOv5、YOLOv8和YOLOX最终选了YOLOv8s。理由有几点一是v8的C2f结构在特征提取上比v5的C3更高效同精度下模型体积更小二是v8的Anchor-Free设计省去了锚框调参的麻烦对不同尺寸的牌面适应更好三是Ultralytics的生态完善导出ONNX、TensorRT都很方便。3.1 输入尺寸的选择输入分辨率是第一个要仔细想的参数。YOLOv8默认是640x640但麻将牌太小直接按640训练小牌的特征信息很容易在浅层特征图里丢掉。我把输入尺寸调到了960测试下来小牌识别率有明显提升。代价是训练和推理速度都慢了不少但对于一个辅助性质的App来说精度优先是合理的取舍。如果对性能有更高要求可以在推理时用TTATest Time Augmentation或者切图推理。切图推理就是把大图切成几块分别检测再合并结果效果不错但实现复杂度会上去后期维护成本不低。3.2 训练参数的经验值一些关键训练参数我直接给出来供参考batch size16显存不够就8别硬撑。epochs300起步。这个数据量下前150轮loss下降挺快后面的轮次主要是在稳定小目标的分类边界。optimizerSGD配合cosine学习率调度比AdamW更适合目标检测这种密集回归任务。mosaic增强默认开启但我在最后30个epoch关掉了让模型在接近真实分布的数据上做微调。3.3 重叠框问题的处理热搜词里有yolo模型重叠框这个词说明这是很多人踩过的坑。麻将牌密集排列时两个检测框高度重叠是常态。YOLO的NMS非极大值抑制在后处理时会留下置信度最高的框但如果两个框的IoU过高NMS会直接干掉其中一个导致漏检。我调的参数是NMS的IoU阈值从默认的0.45调到了0.3。这意味着两个框只要有30%的重合就会被抑制。听起来很激进但实测对密集排列的麻将牌效果很好因为同一张牌不会有两个高置信度的框同时存在而相邻牌之间的框虽然空间上挨得近但置信度差距往往明显NMS能够正确保留。还有一个辅助手段是开启YOLOv8的agnostic NMS。默认的NMS是按类别分别做的如果两张不同花色的牌靠在一起可能会输出两个重叠的框。打开了class-agnostic模式后所有类别放在一起做抑制重叠框问题缓解了不少。3.4 模型蒸馏的锦上添花如果追求极致精度可以试一下用YOLOv8x当teacher模型把知识蒸馏到YOLOv8s上。具体做法是先训一个大的模型然后在训练小模型时在损失函数里加一项teacher模型输出的软标签作为监督。我实测下来小模型的mAP能提升两到三个点推理速度几乎不受影响。实现起来要多写不少代码但对精度有执念的话值得一试。4. 计算器逻辑从检测结果到番数汇总的工程实现识别模型只是前端真正让应用产生价值的是背后的番数计算引擎。这部分的复杂度取决于你对麻将规则的覆盖程度。我一开始只做了简单的断幺九平和役牌等基础役种后来才陆续加上了立直一发宝牌这种需要记录场况的规则。4.1 手牌结构化模型输出的是检测框坐标和类别得先把它转成标准的34种牌计数数组。我的做法是按检测框的中心点Y坐标排序分出上半区的牌通常是手牌和下半区的牌通常是牌河里的舍牌。再按X坐标排序把手牌从左到右排列成序列。这个环节有个很实际的问题如果某张牌的检测框没有正确识别出来后续的牌型判断就会连锁出错。我的应对方案是引入一个未知牌的中间状态不确定的牌不参与计算只给出提示让用户确认。这一步虽然看起来简单但避免了大量因误检导致的错误计算体验提升很明显。4.2 牌型拆分算法日麻的番数计算需要先判断和牌牌型再找到所有可能的拆分方式。判断能不能和算法上就是一个标准的搜索问题先排除特殊牌型七对子、国士无双然后普通牌型递归地分离刻子和顺子。递归拆分是我写得最痛苦的部分。麻将牌A在多个牌型里可能重复使用需要遍历所有拆分路径才能找出最合理的组合。如果只取第一个可行解很可能会漏掉高番数的拆分方式。我后来改成枚举所有可能的拆分组合计算每种组合的番数取最大值输出。4.3 规则引擎的扩展性设计麻将规则在不同地区差异很大。虽然我主要做的是日麻规则但一开始就考虑到未来可能扩展国标麻将所以规则引擎没有写死在判断逻辑里而是拆成了条件函数和计分函数两张表。新增规则时只需要在表里加一行不需要动核心逻辑。这个设计在后期帮了大忙后续加宝牌里宝牌的时候基本没动原来的牌型判断代码只增加了条件判断函数和目标番数的计算权重。5. 交付与部署ZIP打包和实机运行的那些坑项目做完要交付Windows端打包用PyInstallerAndroid端用ONNX Runtime直接跑。打包和部署过程中踩了不少坑正好也对应到zip相关的那些搜索词这里集中说一下。5.1 PyInstaller打包的隐藏依赖模型文件、配置文件、规则表这些资源文件PyInstaller默认不会跟脚本一起打包必须在spec文件里显式添加。我在这上面栽过一次跟头本机运行一切正常打包出来换台电脑跑启动就报找不到模型文件。排查到最后才发现资源文件压根没进包。资源文件处理方案是加一个相对路径解析函数先判断是打包环境还是开发环境再决定从sys._MEIPASS还是从当前目录读取文件。这个函数的逻辑很简单但不处理的话打包后的应用几乎必炸。5.2 移动端推理的量化问题Android端我用的是YOLOv8导出的ONNX模型再转成NCNN格式跑推理。这里有一个精度和速度的平衡问题FP32模型在手机上跑推理一张图大约需要200毫秒勉强能用但不流畅转成INT8量化后速度提升到80毫秒左右但小牌面的精度掉了一些。我最终的方案是混合精度推理即用INT8模型做初步检测如果某张牌的置信度低于0.5再裁出那个区域送进FP32模型做二次识别。这样既保证了速度又保住了边缘case的准确率。5.3 压缩包交付注意别把环境目录打进去说到zip这个词很多人图省事把整个虚拟环境或者node_modules目录直接压缩交付。这种做法害人害己压缩包体积巨大且容易缺文件。我见过有同行收到交付包解压后跑不起来折腾半天发现是venv里的符号链接在压缩过程中丢了。正确的做法是只打包源码、依赖清单和资源文件让接收方自己重建环境。依赖用requirements.txt锁版本模型文件单独放一个目录并附上说明文档。这样压缩包体积小可复现性强。如果真的需要离线环境用Docker镜像也比硬塞一个残缺的目录靠谱得多。还有一个细节是压缩格式。遇到过invalid zip archive: could not find eocd这种报错的朋友大概率是压缩包传输过程中被截断或者下载不完整。交付时建议在压缩包里加一个MD5校验文件接收方解压前先校验哈希能避免一堆莫名其妙的问题。5.4 启动检查清单实机部署后我把启动流程做成了一次自检避免用户碰到错误一头雾水检查模型文件是否存在不存在则报出明确的路径错误提示。检查相机权限是否已授权Android端要动态申请权限。检测手机CPU是否支持所需的指令集NCNN某些优化算子需要ARMv8.2以上的架构。这套自检逻辑花了半天时间写但大大降低了用户反馈问题的频率算是一笔很划算的投资。6. 实测结果与调优记录最终模型在验证集上的指标是mAP50达到0.97mAP50-95约0.84。听起来还不错但实际使用中最头疼的其实是识别对了但置信度不高的情况。我把置信度阈值从默认的0.25调到了0.45误检明显减少但偶尔会漏掉一些暗光环境下的牌。后来加了一个联动逻辑检测框面积小于某个阈值时置信度阈值自动降低0.1因为小目标本来就难识别稍微放宽一点阈值换取更少的漏检整体体验是提升的。另外模型在比赛实际使用的牌和训练数据里的牌存在色差时表现会下降。不同品牌的麻将牌颜色、字体、图案细节都有差异。如果你的应用要面向大众最好在数据集里混入多种样式的麻将牌图片哪怕每个风格的样本量不大也能显著增强模型的泛化能力。7. 经验总结与后续扩展思路整个项目从零到一跑通最深的体会是目标检测模型只是整个应用的一小部分数据工程和逻辑工程才是真正花时间的地方。数据决定了模型的天花板逻辑决定了应用能不能被真正用起来。后续我打算做的几个优化方向引入牌桌状态识别自动判断当前是自摸还是荣和省去手动选择的步骤。加入流局判定和听牌提示进一步扩展辅助功能。把模型换成更新版本的YOLO架构利用更强的特征提取能力压榨小目标检测的精度上限。最后再分享一个小技巧如果你打算长期维护这样一个项目记得为数据采集、标注和版本管理建立一套流程。我在项目中期就经历过一次训练数据版本混乱导致精度莫名其妙回退的事故。后来把所有数据文件统一命名加上版本号训练记录和数据集版本一一对应再也没有出现过类似问题。工程化的习惯往往比调参技巧更能决定一个项目的上限。本文还有配套的精品资源点击获取
返回列表