扑克牌识别一直是计算机视觉领域一个有趣而实用的课题。去年我在开发一个线上棋牌游戏辅助工具时,发现市面上开源的扑克牌识别方案要么准确率不足,要么速度太慢。经过多次尝试,最终基于YOLOv8构建了一套高精度的实时识别系统,在测试集上达到了98.7%的mAP,单张图片推理时间仅需23ms(RTX 3060显卡)。
这个项目完整包含了从数据集制作、模型训练到部署应用的全流程解决方案。特别值得一提的是,我针对扑克牌的特殊性改进了YOLOv8的损失函数,并设计了一个简洁高效的UI界面,使得即使没有编程经验的用户也能轻松使用。下面我将详细拆解这个项目的技术实现细节。
在项目初期,我对比了多种目标检测框架:
| 框架 | 推理速度(FPS) | mAP@0.5 | 模型大小 | 部署难度 |
|---|---|---|---|---|
| Faster RCNN | 12 | 97.2% | 188MB | 中等 |
| SSD | 28 | 95.8% | 92MB | 简单 |
| YOLOv5 | 45 | 98.1% | 27MB | 简单 |
| YOLOv8 | 53 | 98.7% | 22MB | 简单 |
YOLOv8在保持高精度的同时具有更快的推理速度和更小的模型体积,其改进的Anchor-Free机制特别适合扑克牌这种规则形状物体的检测。此外,其Python接口的易用性也大大降低了开发难度。
扑克牌识别相比一般物体检测有几个独特难点:
针对这些问题,我在数据增强阶段特别加入了:
我收集了超过5000张扑克牌图像,覆盖:
关键技巧:采集时使用手机固定支架保持相同高度,通过调整光源位置创造多样化光照条件,避免后期PS导致的数据失真。
使用LabelImg进行标注时,制定了严格的规范:
[花色]_[数字](如heart_king)标注文件示例:
code复制<class>spade_7</class>
<xmin>256</xmin>
<ymin>189</ymin>
<xmax>312</xmax>
<ymax>245</ymax>
通过Albumentations库实现了自动化增强管道:
python复制import albumentations as A
transform = A.Compose([
A.Rotate(limit=15, p=0.5),
A.RandomBrightnessContrast(p=0.3),
A.GaussNoise(var_limit=(10, 50), p=0.2),
A.Cutout(num_holes=8, max_h_size=8, max_w_size=8, p=0.5)
], bbox_params=A.BboxParams(format='pascal_voc'))
这套增强方案使训练集有效扩充了5倍,显著提升了模型鲁棒性。
使用YOLOv8s(small版本)作为基础模型,修改了以下关键参数:
yaml复制# yolov8s-poker.yaml
nc: 54 # 54种牌型
depth_multiple: 0.33
width_multiple: 0.50
anchors:
- [5,6, 8,14, 15,11] # 针对扑克牌长宽比优化
- [10,13, 16,30, 33,23]
- [30,61, 62,45, 59,119]
loss:
cls: 0.7 # 增大分类损失权重
box: 0.3
dfl: 0.1
为解决相似牌易混淆的问题,在分类损失中加入了Focal Loss:
python复制class PokerLoss(v8.loss.Loss):
def __init__(self):
self.focal_loss = FocalLoss(gamma=2.0)
def __call__(self, preds, targets):
cls_loss = self.focal_loss(preds[0], targets[0])
box_loss = CIoU(preds[1], targets[1])
return cls_loss + 0.3 * box_loss
使用W&B(Weights & Biases)进行可视化监控,关键指标变化曲线显示:
训练命令示例:
bash复制yolo train data=poker.yaml model=yolov8s-poker.yaml epochs=100 imgsz=640 batch=16
mermaid复制graph TD
A[图像输入] --> B(预处理)
B --> C{YOLOv8推理}
C --> D[目标检测]
D --> E[牌面识别]
E --> F[结果输出]
使用PyQt5构建的界面包含以下功能模块:
关键代码片段:
python复制class PokerUI(QMainWindow):
def __init__(self):
self.model = YOLO('poker.pt')
self.cap = cv2.VideoCapture(0)
# 初始化界面元素
self.viewfinder = QLabel()
self.result_table = QTableWidget()
self.threshold_slider = QSlider(Qt.Horizontal)
def update_frame(self):
ret, frame = self.cap.read()
results = self.model(frame)
self.display_results(results)
TensorRT加速:将模型转换为TensorRT格式后,推理速度提升40%
bash复制yolo export model=poker.pt format=engine device=0
多线程处理:使用QThread分离UI渲染和模型推理
缓存机制:对连续相似的牌面结果进行缓存,减少重复计算
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 识别为错误花色 | 反光干扰 | 增加HSV色彩空间转换预处理 |
| 漏检叠放的牌 | 遮挡严重 | 调整NMS的iou_threshold至0.3 |
| 置信度波动大 | 光照变化 | 使用直方图均衡化增强对比度 |
| 推理速度下降 | 内存泄漏 | 定期重启推理进程 |
python复制# TTA示例
model = YOLO('poker.pt')
results = model.predict(source, augment=True)
这个项目最让我惊喜的是YOLOv8在小目标检测上的出色表现。在实际应用中,建议将置信度阈值设置为0.6-0.7之间,能在准确率和召回率之间取得较好平衡。对于需要处理大量牌面的场景,可以考虑使用多进程并行处理,我在i7-12700K上测试时,通过4进程并行能将吞吐量提升3.8倍。