基于YOLOv8的端侧麻将识别与部署实践
目录
摘要:本文记录了一个从零开始的端侧麻将牌识别项目全流程。基于YOLOv8n训练了支持42类牌面识别的轻量化模型,通过TensorFlow Lite部署至Android端,在普通手机上实现40ms级实时推理。文章详细阐述了数据清洗、模型训练、端侧优化及关键代码实现,并分享了实际开发中的踩坑经验。项目源码及模型已开源,供技术交流。
一、数据处理
数据来源为多个公开麻将数据集(含标注图片)。原始数据存在格式不统一、标注噪声等问题,主要处理工作如下:
-
格式统一:将COCO、VOC等不同格式统一转为YOLO格式(
class_id x_center y_center width height归一化坐标)。 -
清洗去重:通过脚本剔除边界框偏移超过20%、类别名称不一致(如“五万”与“5w”)的样本,共过滤约300张低质量图片。
-
合并增强:最终得到1.8万张标注图片,覆盖白天/夜晚/暖光、俯拍/斜拍、牌面不同程度磨损等场景,按8:1:1划分训练/验证/测试集。
-

-

清洗示例:原始数据中存在同一张图片在不同数据集中重复的情况,基于MD5哈希去重后保
证了训练集与测试集无重叠。
二、模型训练
采用YOLOv8n作为基模型(兼顾精度与速度),训练配置如下:
| 参数 | 取值 |
|---|---|
| 输入分辨率 | 640×640 |
| 优化器 | AdamW |
| 初始学习率 | 0.001 |
| 学习率调度 | Cosine annealing |
| 数据增强 | Mosaic, MixUp, HSV调整 |
| 训练轮数 | 100 |
| 批量大小 | 64 |
最终在验证集上的性能:
-
mAP50 = 94.6%
-
mAP50-95 = 0.745
-

模型支持标准麻将牌面全类别识别(万、筒、条、字牌共42类)。
轻量化处理:训练后将模型导出为TensorFlow Lite格式,并应用动态范围量化,模型体积从6.2MB压缩至2.1MB,推理速度提升约30%,同时精度下降小于0.5%。
三、安卓部署
3.1 模型集成
在Android Studio中引入TFLite依赖(build.gradle):
gradle
dependencies {
implementation 'org.tensorflow:tensorflow-lite:2.14.0'
implementation 'org.tensorflow:tensorflow-lite-gpu:2.14.0'
}
模型加载与推理核心代码(Kotlin):
kotlin
class TFLiteHelper(context: Context) {
private var interpreter: Interpreter
init {
val model = loadModelFile(context)
val options = Interpreter.Options().apply {
setNumThreads(4) // CPU多线程
addDelegate(CompatibilityList().delegates.firstOrNull()) // GPU委托自动启用
}
interpreter = Interpreter(model, options)
}
fun predict(bitmap: Bitmap): List<Detection> {
val input = preprocess(bitmap) // 转换为ByteBuffer,归一化
val output = Array(1) { Array(25200) { FloatArray(42 + 5) } }
interpreter.run(input, output)
return postprocess(output, bitmap.width, bitmap.height)
}
}
3.2 后处理(NMS实现)
YOLOv8导出TFLite后不包含NMS,需在App端实现:
kotlin
fun postprocess(output: Array<Array<FloatArray>>, imgW: Int, imgH: Int): List<Detection> {
val detections = mutableListOf<Detection>()
for (i in 0 until 25200) {
val conf = output[0][i][4]
if (conf > 0.25) {
val classScores = output[0][i].sliceArray(5 until output[0][i].size)
val (cls, score) = classScores.withIndex().maxByOrNull { it.value }!!
if (score > 0.5) {
val x = output[0][i][0] * imgW
val y = output[0][i][1] * imgH
val w = output[0][i][2] * imgW
val h = output[0][i][3] * imgH
detections.add(Detection(cls, score, RectF(x - w/2, y - h/2, x + w/2, y + h/2)))
}
}
}
return nonMaxSuppression(detections, 0.45) // 自定义NMS
}
3.3 实时预览与优化
-
使用CameraX的
ImageAnalysis分析流,设置setBackpressureStrategy(STRATEGY_KEEP_ONLY_LATEST)避免帧积压。 -
推理耗时:CPU约300ms(测试机型:红米k70pro)。
-
帧率控制:每帧推理完成后立即绘制,维持实时性。
四、效果展示
| 场景 | 光线条件 | 识别准确率(mAP50) | 平均推理时间(cup) |
|---|---|---|---|
| 俯拍 | 正常日光 | 95.2% | 200ms |
| 斜拍 | 暖色灯光 | 93.1% | 200ms |
| 俯拍 | 弱光 | 88.5% | 200ms |
图中展示了摄像头实时识别牌面、识别框跟随、手牌类别统计等功能。
五、实现的功能
-
摄像头实时识别牌面,支持全类别识别
-
手牌状态的本地识别与统计
-
作为端侧AI图像识别的功能演示,验证YOLOv8在移动端的部署流程
-

当前限制:牌河区域自动识别尚未集成(后续可扩展),其他基础功能已跑通。
六、踩坑与心得
-
TFLite与YOLOv8的后处理
导出时若直接使用model.export(format="tflite"),得到的模型不包含NMS。需要在移动端重新实现,且注意输出张量的维度顺序([1, 25200, 47]),否则解析会出错。 -
坐标映射偏差
CameraX返回的ImageProxy与预览视图尺寸不一致,需通过Matrix变换将检测框正确映射到屏幕坐标。最终采用imageProxy.image?.let { image -> }获取原始宽高,结合预览View的缩放比例进行转换。 -
GPU委托兼容性
部分老机型(Android 8以下)GPU委托失败,需增加回退机制:try { interpreter.useGpuDelegate() } catch(e: Exception) { interpreter.useCpu() }。 -
数据增强对泛化性的影响
加入Mosaic和MixUp后,弱光下识别率明显提升,但模型对极端磨损牌面仍有混淆,后续计划针对这类样本做离线增强。
七、项目结构与环境
运行步骤
-
用Android Studio打开项目,同步Gradle。
-
将
model.tflite放入app/src/main/assets/目录。 -
连接Android设备(需支持Camera2 API),点击Run即可。
-

开发环境
-
训练:Python 3.10, PyTorch 2.0, Ultralytics YOLOv8.0
-
部署:Android Studio Hedgehog, minSdk 24, targetSdk 34, TensorFlow Lite 2.14
开源资源
-
完整Android Studio源码(模型加载、相机预览、识别绘制、逻辑处理)
-
转换好的TFLite模型(可替换)
八、声明
本项目仅用于计算机视觉与端侧模型部署的技术学习,所有数据均为公开数据集,不涉及任何实际场景应用。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)