简介:Yolo麻将计算器应用程序是一款结合YOLO实时目标检测技术的麻将计分工具,面向麻将爱好者、比赛裁判及教学人员,针对不同地区计分规则复杂、手工算分易错等痛点,提供拍照识别牌面并自动计算分数的解决方案。压缩包共收录133个文件,大小约318KB,以png图片资源、dart与swift源码、h/cpp/cc等C/C++实现、plist/xcconfig工程配置、gradle/cmake构建脚本为主,兼顾iOS、Windows及移动端工程结构,便于理解图像识别与客户端交互的结合方式。内容预览中包含各平台插件注册、窗口管理等工程骨架文件,可协助开发者梳理跨平台应用的整体组织结构。通过解压可获得完整工程目录,直接运行调试,并对比学习图像捕获、模型调用与牌型映射等模块的实现细节。目前已有97人学习下载,适合具备一定移动开发基础、希望借助YOLO实现麻将视觉计分的读者研究参考,也可作为后续二次开发和规则定制的基础框架。
1. Yolo麻将计算器应用:扫码台面自动算分,这套资源比手写记分牌靠谱在哪
四个人围着牌桌,胡牌后对着手机拍一张,台面上所有牌被识别出来,番型、得分、谁点炮一屏算完——这是 Yolo麻将计算器应用(mahjong calc app with Yolo.zip)解决的核心问题。它不是传统意义上按图搜索的 OCR 识别,而是用 YOLO 目标检测模型在摄像头画面里先定位每一张麻将牌,再交给计算模块做牌型匹配和计分。对正在做图像识别落地项目、或者想把 YOLO 从“识别猫狗”迁移到真实棋牌场景的开发者来说,这套资源的价值在于它把数据集、训练脚本、推理代码和计分逻辑串成了完整闭环。适合两类人:一类是拿它当 YOLO 实战课,看数据标注和模型调参怎么配合;另一类是麻将类小工具开发,直接参考它的识别流程做二次开发。
2. 为什么麻将识别要选 YOLO 而不是 OCR:先看清牌面问题的本质
2.1 麻将识别的难点拆解:密集、倾斜、小目标
麻将牌识别和普通文档 OCR 最大的区别在于目标形态不同。OCR 面对的是规整的文字行,而摄像头下的麻将牌是密集排布的方形物体,牌与牌之间缝隙小,部分牌还会互相叠压。更麻烦的是角度:手机俯拍时,靠近镜头的牌大、远离镜头的牌小,同一张牌在不同位置会呈现不同倾斜角度。这种情况用目标检测比 OCR 更合适,因为 YOLO 这类检测模型输出的是“目标框 + 类别”,天然能处理位置和尺度的变化。
我拆这套资源时第一件事是确认它的检测类别设计。麻将牌按花色分组,万、筒、条各 9 种数字牌,加上东南西北中发白,常见分类是 27 或 34 类。如果附带花牌或春夏秋冬,类别数还会增加。类别数量直接决定了模型输出层的设计,也影响训练数据量需求——每类牌至少要有一定数量的样本,否则模型容易把样本少的类别忽略掉。
2.2 YOLO 系列选型:nano 还是 small,取决于推理设备
资源里给的推理代码默认加载的是 YOLO 轻量级权重。常见做法是优先用 YOLOv8n 或 YOLOv8s,因为麻将识别是实时应用,摄像头画面要连续处理,模型体积和推理速度比极端精度更重要。我在类似项目里测过,YOLOv8n 的权重约 6MB 左右,在普通 CPU 上一帧 640x640 图大约 30-50ms,算上视频流解码可以跑到 20fps 以上,够用;而 YOLOv8s 精度更高,但推理时间和内存占用都会明显增加。如果识别场景是固定摄像头俯拍,补光均匀、牌面平铺,nano 就已经足够;如果是手机手持拍摄、光线多变,建议用 small 或加多角度数据增强。
2.3 资源包内容清单:先搞清楚拿到的每一块能干什么
解压后整个包的结构大致如下,我把关键部分的用途列出:
| 文件/目录 | 作用 | 说明 |
|---|---|---|
dataset/ | 标注后的麻将牌图像数据集 | YOLO 格式标注,train/val 已划分 |
weights/best.pt | 训练好的 YOLO 检测权重 | 加载后可直接推理识别牌面 |
train.py | 训练入口脚本 | 可改数据集路径和超参数重训练 |
detect.py | 推理脚本 | 支持图片、视频和摄像头实时识别 |
mahjong_calc/ | 牌型统计与计分模块 | 输入检测结果,输出番型和分数 |
config.yaml | 数据集配置与类别映射 | 决定训练时识别多少种牌 |
requirements.txt | Python 依赖清单 | 安装后即可跑通环境 |
拿到资源不建议直接跑detect.py,先把config.yaml里的类别名和你的牌种对齐,再加载weights/best.pt对一张测试图推理,确认识别效果正常,最后才进入训练或二次开发。这套顺序能帮你判断是模型问题还是代码环境问题。
3. 数据集标注与训练参数配置:照抄能跑,但要懂每个参数改的是什么
3.1 YOLO 格式标注长什么样:不是画框就完事
YOLO 格式的标注文件是 txt,每行对应一张图中的一张牌,格式为class_id x_center y_center width height,四个坐标值都是归一化到 0-1 的浮点数。比如某张牌在 640x640 的图中中心点位于 (320, 480),宽 60 高 80,则标注为6 0.5 0.75 0.09375 0.125,分母分别是图的宽和高。
# 检查标注是否合法:越界框会导致训练时报错或精度下降 def validate_label(label_file, img_w, img_h): with open(label_file, 'r') as f: for line in f.readlines(): cls, x, y, w, h = map(float, line.strip().split()) # 归一化坐标必须落在 [0,1] 区间 if not (0 <= x <= 1 and 0 <= y <= 1): print(f"中心点越界: {label_file}") return False # 框宽高不应超过原图尺寸,也不应为 0 if w <= 0 or h <= 0 or w > 1 or h > 1: print(f"宽高异常: {label_file}") return False return True这段是训练前最值得加的一道校验。我遇到过数据集里混入了几张用标注工具导出时坐标变成绝对像素值的图,训练 Loss 曲线一路不收敛,检查半天才发现是标注归一化不一致。跑训练前把整个数据集的标注文件都过一遍校验,能省下大量调试时间。
3.2 训练脚本关键参数:imgsz、batch、epochs 的取舍
资源里的train.py是基于 YOLO 官方训练接口封装的,核心参数改动集中在模型尺寸、批大小和训练轮数。下面是一份可运行的训练命令示例:
python train.py \ --data config.yaml \ --weights yolov8n.pt \ --imgsz 640 \ --batch 16 \ --epochs 100 \ --device 0 \ --patience 20参数说明:
--imgsz 640:输入图像尺寸。麻将牌是小目标,低于 416 时后排小牌很容易被忽略。如果显存不够,优先降到 512 而不是直接改模型结构。--batch 16:批大小。显卡 8GB 以上可以开到 32,批大小过小会导致 BatchNorm 统计不稳定,收敛变慢。--epochs 100:训练轮数。用预训练权重迁移时 100 轮基本足够,mAP 长时间不涨就触发早停。--patience 20:连续 20 轮验证集指标无提升就自动停止,防止过拟合浪费算力。
我在模拟牌桌数据集 D 上复现时,把 epochs 从 100 改到 150,mAP50 大约涨了 2 个点,但时间成本增加了近一半。对于麻将牌这种纹理差异明显的目标,100 轮已经能收敛到可用水平,加轮数收益有限。
3.3 训练完看什么指标:mAP 和混淆矩阵的读法
YOLO 训练结束后 runs 目录里会生成 confusion_matrix.png、PR_curve.png、results.png 等文件。对麻将识别这个场景,我用两个指标判断模型是否可用:
| 指标 | 参考阈值 | 说明 |
|---|---|---|
| mAP50 | > 0.95 | 检测框与真实框 IoU 阈值为 0.5 时的平均精度,麻将牌类别差异大,低于 0.95 说明还有系统误检 |
| mAP50-95 | > 0.80 | 更严格的标准,对重叠牌、小目标更敏感,低于 0.80 时建议检查标注边缘是否贴合牌面 |
混淆矩阵重点看两类错误:一是某两类牌互相误检,常见于“五筒”和“六筒”这种点数接近的牌,解决办法是补充这两类的边界样本;二是背景被误检成牌,通常因为反光或桌布纹理干扰。如果出现整行整列的错误,先去查标注文件里对应类别的标签是否错位。
4. 从检测框到台面统计:后处理逻辑才是计算器能算对的钥匙
4.1 检测结果的去重与置信度过滤
摄像头对着牌桌连续抽帧时,同一张牌在相邻帧里会被检测多次。如果直接把每帧的检测框喂给计分模块,会出现同一张牌被重复计数。常见做法是维护一个“帧间投票”队列:连续 N 帧检测到相同位置相同类别的牌,才确认它存在于牌面。
# 帧间去重:同一位置的牌连续多帧确认后才计入最终牌组 def confirm_tiles(detections, history, conf_thresh=0.6, stable_frames=3): confirmed = [] for det in detections: cls = det['class'] box = det['box'] # [x1, y1, x2, y2] center = ((box[0]+box[2])/2, (box[1]+box[3])/2) key = (cls, round(center[0], 1), round(center[1], 1)) history[key] = history.get(key, 0) + 1 if history[key] >= stable_frames: confirmed.append(det) return confirmed这段代码的关键是把“单帧看到”和“稳定存在”区分开。conf_thresh控制单次检测的置信度下限,低于 0.6 的检测框基本是噪声;stable_frames控制确认所需的连续帧数,设 3 比较合适,既能过滤手指遮挡造成的闪烁,又不会让计算延迟太长。注意这里的 key 用了坐标取整,两张不同牌被分到同一 key 的概率极低,因为牌与牌的中心点间距远大于 0.1 像素精度。
4.2 牌型判定:如何处理顺子、刻子和雀头
检测模型只负责输出“这里有一张七万”,至于七万能和哪些牌组成顺子、刻子,需要单独的牌型逻辑处理。麻将计算器最常见的是标准立直规则:一手牌有四个面子(顺子或刻子)和一个雀头即可胡牌,特殊牌型如七对、十三幺需要单独判断。
# 简化的牌型分组:将 14 张手牌分成面子+雀头组合 def group_tiles(hand): # hand: list of (suit, rank),如 ('wan', 7) 表示七万 tiles = {} for suit, rank in hand: tiles.setdefault(suit, []).append(rank) for suit, ranks in tiles.items(): ranks.sort() # 贪心匹配刻子,再匹顺序子,剩余两张若相同则作为雀头 melds = [] for suit, ranks in tiles.items(): i = 0 while i < len(ranks) - 2: if ranks[i] == ranks[i+1] == ranks[i+2]: melds.append((suit, ranks[i], 'triplet')) del ranks[i:i+3] else: i += 1 return melds这段是简化版,实际要处理的情况更多:同一花色里顺子和刻子优先级需要动态规划判断,而不是简单贪心;“边张”“嵌张”的听牌判断也要在分组逻辑里体现。我在实际项目中是把分组问题拆成递归搜索:枚举所有可能的雀头候选,剩余 12 张牌能否组成四个面子,能则记录一个胡牌组合。资源包里的mahjong_calc/模块实现了完整判断,建议先读它的数据结构和递归函数再改自己的需求。
4.3 计分规则配置文件化:番型表与分数计算
麻将计分规则各地差异很大,资源默认用的是常见国标规则,番型从平和、断幺到清一色、杠上开花。计分部分的关键设计是不要把规则硬编码在判定函数里,而是抽成配置表:
# score_rules.json 片段 { "men_zen": {"fan": 1, "desc": "门前清自摸"}, "tanyao": {"fan": 1, "desc": "断幺九"}, "pinghu": {"fan": 1, "desc": "平和"}, "hun_yao": {"fan": 2, "desc": "混幺九"}, "qing_yi_se": {"fan": 6, "desc": "清一色"}, "da_si_xi": {"fan": 13, "desc": "大四喜"} }我一般会把番型判定写成独立函数,每类番型只检查自己需要的条件,最后汇总;而不是写一个巨大的 if-else 链。比如清一色只判断所有牌是否同一花色,平和只判断是否有面子全顺子且雀头非字牌。配置化后的好处是换规则时不动判定逻辑,只改 JSON 里的番数和条件开关。新手容易踩的坑是:把番型条件写进计分函数里,结果换规则版本时整个模块要重写。
5. 实战避坑:五个最容易翻车的识别细节,从现象到解决一次说清
5.1 牌面反光导致同一张牌一会儿识别成“发”一会儿识别成“白”
现象:摄像头下白色麻将牌的镜面反光区域随角度变化,模型在某个角度把“白板”识别成“發”,下一帧又恢复正常。
原因:麻将牌表面材质反光导致局部高光,高光区域覆盖了牌面雕刻纹理的关键特征,模型无法稳定提取判别信息。本质是训练数据里缺少高反光样本。
解决:在数据集里加入多角度拍摄的高光图,或者推理前对图像做高光抑制。常见做法是把 BGR 图像转 HSV,对 V 通道过高的像素做局部压暗:
def suppress_highlight(img, threshold=200, reduce_ratio=0.6): hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) v = hsv[:, :, 2] mask = v > threshold hsv[:, :, 2][mask] = (v[mask] * reduce_ratio).astype('uint8') return cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)这段代码把超过灰度 200 的高光区域压到原来的 60%,能让反光从“白色亮斑”变成“偏灰纹理”,保留住牌面线条。注意 threshold 要和你的摄像头曝光匹配,暗光环境下盲目压暗反而让小牌更看不清。
5.2 重叠牌漏检:两张牌叠在一起只出来一个框
现象:某家手牌摆放较密,前后牌叠压了约 1/4,检测结果只识别到前面一张,后排牌的类别在结果里消失。
原因:YOLO 对严重重叠目标的处理依赖 NMS(非极大值抑制),两个框 IoU 超过阈值时会被当成同一目标合并,后排被抑制掉。
解决:修改 NMS 阈值,把默认的 0.45 降到 0.3;同时把检测置信度阈值从 0.25 提到 0.4,宁可漏掉不确定目标也不要把两张牌合并成一个错误框。如果重叠超过半个牌身,这类漏检不可避免,需要在拍摄端拉大俯拍角度,让牌与牌之间的投影重叠减少。
5.3 点炮方判定错误:台面静态识别做不了时序判断
现象:A 玩家打出七万,B 胡牌,计算器识别出台面有七万却把分数算成自摸。
原因:计分逻辑只看最终台面的牌,不看打牌顺序。谁点炮取决于这张牌是从哪家打出的,需要跟踪每一轮的舍牌动作。
解决:给计算器加一个“摸打跟踪”状态机:检测到新牌从手牌区域移动到台面中央时,记录当前出牌玩家;胡牌发生时优先匹配最近一次出牌记录。我在自己做的棋牌工具里就是加了一个last_discard变量,每次检测结果里台面牌数量增加时才更新它。
5.4 暗杠和补花识别不出:摄像头看不到牌背
现象:某家暗杠了四张牌,手牌数量稳定在 13 张,计算器按常规牌型判断提示“非正常手牌数”。
原因:暗杠的牌不展示牌面,摄像头永远看不到这四张牌,检测结果里它们只表现为牌背。计算器如果只按亮牌统计,手牌数永远是 13+4=17 或 17,不符合常规牌型。
解决:这类情况不能靠视觉解决,需要在 UI 上加入手动确认按钮。资源里的计算器模块预留了add_hidden_tiles(player, tiles)接口,检测到手牌数异常时就弹窗让玩家手动确认暗杠内容,确认后再进入牌型计算。这不是检测模型的缺陷,而是信息源边界问题——摄像头看不到的信息,就不该硬猜。
5.5 训练时 Loss 收敛但 mAP 很低:先查类别数配置
现象:训练过程 Loss 曲线正常下降,验证集 mAP50 却卡在 0.3 左右,检测结果全是“筒子”类。
原因:config.yaml中的类别顺序和标注工具的类别 ID 不一致。比如标注时“一筒”是class 0,但配置文件里class 0对应的是“一萬”,模型学到的标签语义整体错位。
解决:训练前打印一遍config.yaml的类别列表,选一张标注好的图,用脚本读它的 txt 标注和原始图像对比,确认 class_id 对应的名称确实正确。这是我复现任何 YOLO 数据集踩过的最大坑,没有之一。那次我排查到凌晨才发现数据集是用旧版本标注工具导出的,类别顺序和新的配置文件对不上。
6. 再进一步:边端部署与连续帧刷新策略,让计算器像 App 一样跟手
资源包里的推理脚本默认跑在 PC 上,加载 PyTorch 权重直接推理。真想做成手机应用,还有两道坎:模型压缩和触发策略。
模型压缩方面,常规做法是先把best.pt导成 ONNX,再转成 TFLite 并做 int8 量化。量化后模型体积从几十 MB 压到几 MB,手机 CPU 也能跑。需要注意两点:一是量化需要校准数据集,随便拿几张测试图校准会掉点严重,我一般会从训练集里均匀采样 200 张图参与校准;二是 int8 量化对反光、暗光这类边缘样本更敏感,量化后在真机测试集的 mAP 最好再验证一遍。
触发策略方面,直接对每一帧都做完整牌型计算会让手机发热且界面乱跳。我习惯做一级“稳定帧缓存”:只有连续 5 帧的检测结果完全一致时,才让 UI 刷新一次计分结果。这样既避免手指入画导致的界面闪变,又减少计算频率,实测续航能提升约 30%。
至于从这套资源复现到自定义牌种,比如加入花牌或特殊百搭牌,路径很明确:收集新牌图像,用标注工具画框,加入dataset/并更新config.yaml的类别列表,然后用第 3 章的命令重新训练。迁移学习下,新类别只需几十张图就能达到可用精度,前提是原模型能稳定识别旧类别,否则会互相干扰。
我做棋牌识别工具的习惯是:每次拿到别人的 YOLO 项目,先不改一行代码,直接从数据集和配置入手,把“标注-配置-验证”这条链走通,再动训练和推理——因为大多数翻车都发生在数据准备这个环节,而不是模型结构。这套麻将计算器资源把这条链完整串好了,按顺序复现一轮,你应该能感受到从检测框到计分结果的整个闭环是怎么咬合的。希望帮到你。
本文还有配套的精品资源,点击获取