基于YOLO的台球识别实战:从数据集构建到部署的完整指南

发布时间:2026/8/27 5:49:47
基于YOLO的台球识别实战:从数据集构建到部署的完整指南 简介目标检测是计算机视觉领域的核心技术其核心任务是定位并分类图像中的多个目标。YOLO作为主流的目标检测框架凭借端到端的单阶段结构和出色的推理速度成为许多实时识别应用的基石。在实际工程中针对特定场景的定制化训练往往决定最终效果尤其当目标具有尺寸小、颜色相似、光照变化剧烈等特征时常规的预训练模型难以直接胜任。本文以台球识别为切入点深入解析如何利用YOLO构建一个鲁棒的小目标检测系统。从数据集的采集与标注策略、数据增强技巧到模型选型、训练参数调优、混淆矩阵分析再到ONNX导出与摄像头实时部署完整覆盖了项目落地的关键环节。通过合理运用预训练权重与针对性增强策略模型在真实球厅环境下取得了稳定的识别精度为体育场景中的智能分析提供了可行的技术方案。 做台球识别这个项目最开始的动机其实很朴素周末和朋友打球总有人问这球能不能打走位走哪边问多了我就想干脆做个实时识别工具把球的位置、类别、可能的击球线路直接框出来。但真上手之后才发现台球识别看起来只是目标检测的一个应用实际上坑比想象中多得多——球的色泽受光照影响极大、球与球之间的遮挡频繁、黑八和棕色球的颜色在特定灯光下几乎分不清更别说台呢的绿色纹理对浅色球的干扰。这篇文章就围绕这个基于YOLO的台球识别项目把从数据集构建、模型选型、训练调参到部署实测的完整链路拆开讲清楚。适合的目标读者是已经跑通过YOLO基础流程、想把它落地到具体项目的新手或者正在做体育类小目标检测、需要对YOLO做针对性调优的开发者。我会把训练过程中的参数配置、踩过的坑、以及实测中遇到的检测失败案例都写出来尽量做到可以直接照着复现。1. 台球识别为什么不能随便跑起来难点拆解目标检测的项目做多了会发现一个规律真正卡住你的往往不是模型结构而是场景里那些看起来不重要的细节。台球识别就是典型。1.1 台球视觉场景的核心难点我一开始天真地以为台球有鲜明的颜色、规整的圆形应该比行人检测简单得多。跑完第一个版本才知道这个场景有三个绕不开的坎第一个坎是光照的剧烈变化。台球厅的灯光通常是多盏顶灯组合照在球面上会形成高光斑黑色八号球几乎就是一块黑一个亮斑如果模型没有见过足够多的高光样本很容易把黑色八号球漏检成桌面背景。更麻烦的是不同球厅的灯光色温差别很大暖黄色灯光下黄色球和白色球的边界会变得非常模糊。第二个坎是球的相似性。中式八球一共16颗球其中1到7号是纯色球9到15号是花色球颜色分布高度重叠——黄色球有1号和9号蓝色球有2号和10号红色球有3号和11号。如果只靠颜色分类模型必然会在同色系的实球和花球之间反复横跳。第三个坎是目标小且密集。一个标准中式八球球桌尺寸大约是2.54米乘1.27米在常见的俯视摄像头画面里一颗球的直径在图像上也就占二三十个像素勉强算小目标。而16颗球挤在球桌下半区域时目标之间的遮挡、粘连、阴影交错都非常严重。注意做台球识别本质上做的不是我认得这是红色的球这件事而是在复杂光照和局部遮挡下这个目标是什么类别、精确位置在哪这件事。想清楚这一点后面所有的数据集设计和模型调优才有方向。1.2 方案选型为什么是YOLO而不是传统视觉或分类网络传统视觉方案不是不能做用霍夫圆检测 颜色阈值分割也能在固定角度、固定光照的测试视频里跑出不错的效果。但问题在于极度脆弱换个球厅、调个灯光、或者摄像头角度偏移十度阈值就得重新调。我在这个项目早期也试过HSV颜色空间的分割方案实测在单一球厅的准确率能达到85%左右但换到另一个灯光更暗的球厅直接掉到60%以下。YOLO的价值在于把颜色特征 纹理特征 上下文特征糅合在一起学习对光照、角度的变化鲁棒性远超手工特征方案。而且从工程落地角度YOLO系列一直在做推理速度和部署友好性的平衡导出ONNX后用OpenCV DNN或者ONNX Runtime就能跑不需要依赖复杂的训练框架这对后续要集成到摄像头实时预览工具里非常关键。方案选型的结论是主干用YOLO做端到端的目标检测类别设计成11类——白球单独一类1到7号独立一类8号黑球独立一类9到15号花色球各自独立分类。有人会问9到15号明明长得一样都是白底加彩色条纹为什么不合并成一类花色球这个后面在数据集和模型评估部分细说这里先给结论合并会导致击球决策时无法区分具体球号实战价值大打折扣。1.3 项目整体结构与工作流整个项目的流程可以拆成四个阶段数据准备采集不同球厅、不同角度、不同光照下的球桌画面标注16颗球的位置和类别制作YOLO格式数据集。模型训练基于YOLOv8n或YOLOv8s预训练权重做微调反复调整输入分辨率、增强策略、训练轮数。推理验证在验证集上分析PR曲线和混淆矩阵找出最容易混的类别对针对性补充数据。部署应用把最好的一版模型导出成ONNX接入摄像头实时推流输出每颗球的坐标和类别再叠加击球辅助线。这四步看着简单但每一步展开都有大量细节。下面按实际执行顺序细讲重点放在数据集和训练调优——这两个环节决定了最终效果的上限。2. 数据集构建最枯燥但决定上限的一步很多YOLO新手在跑通官方COCO预训练模型之后觉得识别也不是很难嘛然后自己做数据集时随便拍个几十张图就开训最后mAP只有零点几一脸懵。台球识别这个项目里数据集的质量直接决定模型能不能在真实球厅存活。2.1 数据采集方案与要点数据采集阶段要解决两个问题拍什么和怎么拍。拍什么指的是覆盖足够多的场景变化。我最终的数据集来自三个不同球厅加上自己搭的简易支架在不同时间段拍摄一共采集了约1200张原始图像。具体做了这几个变化机位角度大部分是垂直于球桌上方的俯视视角同时混入约30度到45度的侧视角。因为实际使用中手机支架或吊顶摄像头的安装角度不可能是完美的垂直俯视模型必须对透视形变有一定容忍度。光照条件分别在白天自然光、球厅顶灯全开、顶灯关闭只留周边环境光三种条件下采集。特别注意收集球面上有明显高光斑的样本这些是模型最容易漏检的难点样本。球局状态包括开球后球散开的局面、连续击打后的乱局、球贴库贴近桌边、球与球几乎相切的情况。怎么拍我建议用支持手动调节曝光和快门速度的摄像头快门速度至少1/500秒以上否则击球瞬间球在运动画面会出现运动模糊标注出来的框位置不准训练时等于在教模型学错误答案。2.2 标注格式、工具与类别体系标注工具我用的是LabelImg虽然界面老旧但胜在稳定对YOLO格式支持良好。每张图生成一个同名txt文件每行内容为class_id x_center y_center width height其中x_center、y_center、width、height都是相对于图像宽高的归一化值。比如一颗球的标注是 7 0.6532 0.4128 0.0312 0.0312代表类别id为7中心点位于图像横向65.32%、纵向41.28%的位置宽高各占图像尺寸的3.12%。类别id的设计我踩过一个坑。第一版我把类别顺序定义为白球0、1号1、2号2……8号8、9号9……15号15这样虽然直观但训练时发现模型在13号花色黑条纹和8号纯黑之间频繁混淆——因为它们颜色特征实在太接近了。后来我把类别顺序调整成按色系分组并且把8号黑球单独放在最后一位让模型在分类头里对黑色系的球有更强的区分信号混淆情况才有所缓解。完整的类别体系如下类别ID标签名对应球颜色特征0cue_ball白球纯白高光明显1yellow_solid1号黄色实心2blue_solid2号蓝色实心3red_solid3号红色实心4purple_solid4号紫色实心5orange_solid5号橙色实心6green_solid6号绿色实心7maroon_solid7号深红/棕色实心8black_88号黑色高光9yellow_stripe9号白底黄色条纹10blue_stripe10号白底蓝色条纹11red_stripe11号白底红色条纹12purple_stripe12号白底紫色条纹13orange_stripe13号白底橙色条纹14green_stripe14号白底绿色条纹15maroon_stripe15号白底深红条纹注意标注时花色球的框必须严格贴合球体边缘不要把白底的部分漏掉也不要框进周围台呢的颜色。这个看起来吹毛求疵的要求其实直接影响模型对白底这个关键特征的感知。我重新标注了大约300张图以后模型对花球的召回率明显提升。2.3 数据增强策略YOLOv8训练时自带一部分增强策略比如马赛克增强、随机翻转、HSV色域扰动。但对台球数据我额外补充了两个自定义增强随机亮度和对比度扰动。这个很好理解球厅灯光的差异本质就是亮度和对比度的差异让模型多看一些不同曝光的图像能显著提升跨球厅的鲁棒性。我通常在albumentations里加RandomBrightnessContrast亮度限制设置±0.15。随机高光斑点模拟。模拟球面上的高光区域——在一部分训练图像上随机叠加白色半透明圆形斑点。这个增强听起来有点野路子但实测对黑色八号球的召回率提升非常明显从0.91提高到0.96。原因是高光在这些球面目标上不是噪声而是真实场景中的稳定特征提前让模型学会有高光的黑色圆也是黑八比让它从零去硬记要有用得多。2.4 数据集规模与训练集验证集划分1200张原始图像经过离线增强后实际参与训练的约4000张。按照9:1划分训练集和验证集同时保证同一个球厅的画面不会同时出现在两个集合里——这一步很多人会忽略如果同一球厅的画面一部分进训练、一部分进验证验证集的分数会虚高换到新球厅直接现原形。关于类别数量这个数据集规模属于偏小的但台球场景的特殊性在于球的数量有限且外观变化主要来自光照而不是类内差异。所以4000张左右训练图足够让模型学到关键特征但前提是前面说的场景覆盖度要够。如果只在一个固定球厅采集哪怕采集一万张换个灯光照样掉点。3. 模型选型与训练参数调优3.1 YOLOv8还是YOLOv11从项目角度谈选型网上关于YOLOv8和YOLOv11的对比讨论很多聚焦到台球识别这个具体项目我的结论是首选YOLOv8n或YOLOv8s暂时没太大必要上YOLOv11。原因有三点。第一台球目标尺寸小、数量稳定16颗不是大规模开放场景模型容量的需求不高YOLOv8s的参数量已经足够第二YOLOv8的生态最成熟从训练到导出的坑基本都被前人踩过遇到问题搜解决方案十分方便第三YOLOv11虽然引入了更高效的C3k2模块和注意力机制推理速度和精度都有提升但对这个项目来说提升幅度不显著而部署时对框架版本的要求更严格。我实际上用YOLOv11s也做过对比实验在相同数据集上mAP50从0.972升到0.978提升有限但导出ONNX后在某些环境下的算子兼容性问题反而多了一个排查负担。这类够用就好的选型判断在工业项目里非常重要不是所有项目都要追最新版本。3.2 训练参数配置与调试过程训练脚本我基于ultralytics库的YOLOv8接口核心训练命令长这样from ultralytics import YOLO model YOLO(yolov8s.pt) results model.train( databilliard.yaml, epochs200, imgsz640, batch16, lr00.01, lrf0.01, momentum0.937, weight_decay0.0005, warmup_epochs3, patience20, augmentTrue, seed42, device0, )逐个说下关键参数imgsz640是默认值但对台球识别来说640分辨率下远处球可能只有十几个像素属于小目标范畴。我试过把输入提到960mAP确实涨了0.4个百分点左右但推理耗时从8毫秒涨到16毫秒对于实时预览工具来说这个时间翻倍不可接受。最终选择640为主训练时额外加一点随机缩放scale0.5来模拟球在不同距离下的大小变化。batch16要看显存决定。我用的是一张12GB显存的卡YOLOv8s在640分辨率下batch16勉强放得下。显存不够的话可以降到8或者切到YOLOv8n。lr00.01是官方推荐初始学习率。如果发现训练初期loss下降太慢可以尝试0.02但要注意观察是否出现震荡。patience20是早停参数连续20个epoch验证集mAP没有提升就停止训练。台球数据集小通常60到80个epoch就能收敛200轮上限只是为了让早停机制有足够空间判断。3.3 预训练权重的作用用COCO预训练权重做初始化对这个项目帮助很大。虽然COCO数据集里没有台球类别但模型在COCO上已经学到了通用的纹理、边缘、颜色分布特征这些底层特征对台球检测同样有效。我对比过从零训练和COCO预训练微调两个版本从零训练需要约120轮才能达到的mAP预训练版本40轮左右就到了训练时间节省近一半。一个容易犯的错误是预训练权重里最后分类头的类别数是80如果数据集类别不是80直接加载会报错。ultralytics库内部会自动处理分类头的替换但如果自己写训练脚本需要记得把最后一层改成num_classes16台球场景的类别数。4. 训练过程、指标解读与踩坑排查4.1 训练损失曲线的正常形态台球识别这个项目训练过程的损失曲线大概是这样的前5个epochbox_loss从约1.5快速下降到0.8左右cls_loss从约2.0下降到1.2之后进入缓慢下降区间到第40到60轮时box_loss稳定在0.35到0.45之间cls_loss稳定在0.3左右验证集的mAP50会冲到0.95以上。如果到的损失曲线完全不符合这个形态说明训练配置或数据集出了问题。常见的有两种情况一是loss下降得异常快前3个epoch就直接收敛到很低。这通常不是好事说明数据集太简单或模型容量过大验证集上表现很可能不如预期。遇到这种情况可以检查训练集和验证集是否存在重叠比如同一段视频抽帧的图被同时放进了两边。二是loss怎么都不降卡在一个平台期。这多半是学习率问题试试把lr0调低到0.005也可能是数据增强过强把球的纹理特征全破坏了导致模型学不到有效信息。4.2 训练指标为0的排查链路训练指标全是0这个坑我在多个YOLO项目里都遇到过台球识别项目也不例外。如果你跑训练发现mAP一直显示0不要急着去调模型结构按照下面这条链路排查第一步检查标签文件是否为空。YOLO格式的txt文件如果内容是空的训练时这个目标就没有被计算损失自然不会产生正样本。用脚本统计一下所有标注文件的行数和类别分布import os from collections import Counter label_dir datasets/billiard/labels/train counter Counter() empty_files 0 for fname in os.listdir(label_dir): path os.path.join(label_dir, fname) with open(path, r) as f: lines [line.strip() for line in f.readlines() if line.strip()] if not lines: empty_files 1 for line in lines: counter[int(line.split()[0])] 1 print(fempty files: {empty_files}) print(counter)第二步检查类别ID是否越界。YOLO的类别ID从0开始如果数据集的类别数是16那么标注文件里出现17、18这种数字训练时就会报错或者跳过这些样本。尤其要检查那些用脚本批量生成的标注文件很容易出现id从1开始而不是从0开始的低级错误。第三步检查归一化坐标是否越界。x_center、y_center必须在0到1之间width和height也必须在0到1之间。如果标注工具导出时没有归一化或者用了像素坐标直接填入训练时目标框会跑到图像外loss计算异常验证时自然没有正确检测结果。第四步检查数据集配置文件里的路径是否正确。billiard.yaml文件里train和val路径指向的是存放图片的文件夹ultralytics会自动去同级的labels文件夹找标注文件。如果图片和标签的目录层级不匹配训练时加载不到标注也不会报错只会默默地训练成啥也检测不到。我排查完这套流程之后发现之前训练指标全是0的问题大多数是标签文件里类别ID整体偏移了一位——比如把白球标成了1而不是0导致模型学到的类别语义和验证时用的完全不搭。4.3 验证集上的混淆矩阵分析训练完以后用model.val()可以生成混淆矩阵。对台球识别来说重点关注这几个容易混淆的类别对8号黑球和15号花色球。15号是白底黑条纹8号是全黑带高光在低分辨率下两者非常容易混。这个很难靠训练彻底解决我采取的策略是在数据集里增加了200张特写镜头下的8号球和15号球对比图让模型更多看到两者的细节差异。黄色系和橙色系。1号黄和5号橙在暖色灯光下边界模糊。这个问题的核心其实是光照色温单靠模型训练解决不了我在数据增强里进一步加大了HSV色相扰动的范围把hue从默认的0.015提高到0.03让模型学会区分黄色和橙色的语义而不是死记某个RGB值。通过对混淆矩阵的分析我发现了一个有意思的结论模型对某颗球在哪的判断很多时候依赖的是球与球之间的相对位置和球桌边界的上下文。比如在三角形摆球区域模型倾向于认为摆在一起的球是那些常见的开球组合。这个发现提示我后续可以考虑在模型中引入球桌全局特征比如把输入分辨率拉大让模型看到更完整的场景信息确实带来了一定的精度提升。5. 部署落地从检测框到台球实战辅助训练出一版mAP50到0.97的模型只是第一步。真正让这个项目能用还得解决部署和交互问题。5.1 导出ONNX与推理速度台球识别场景中推理速度直接影响用户体验因为击球决策需要在击球前给出最好是实时画面上的逐帧叠加。PyTorch模型的推理在GPU上虽然快但换到只有CPU的笔记本或者嵌入式设备就有点吃力。我推荐用ultralytics自带接口导出ONNXmodel.export(formatonnx, opset12, simplifyTrue)导出后用ONNX Runtime做推理在CPU上处理一帧640x640的图像大约需要25到40毫秒取决于具体硬件对于台球这类相对静态的场景完全够用。如果希望进一步提高速度可以先缩小输入分辨率到480再推理但要注意小球目标在这个分辨率下漏检率会上升。5.2 摄像头实时识别的完整流程实时识别流程分四步读取摄像头画面把画面缩放到640x640YOLO输入尺寸。运行模型推理得到所有检测框的坐标、类别和置信度。按置信度阈值我取0.35过滤低质量检测并对重叠框做NMS。把检测框坐标映射回原始画面尺寸叠加绘制同时显示类别和置信度。代码骨架大致长这样import cv2 import numpy as np import onnxruntime as ort session ort.InferenceSession(billiard.onnx, providers[CPUExecutionProvider]) cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: break img cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) img_resized cv2.resize(img, (640, 640)) img_input img_resized.astype(np.float32) / 255.0 img_input np.transpose(img_input, (2, 0, 1))[None, ...] outputs session.run(None, {session.get_inputs()[0].name: img_input}) # outputs 解析每个检测为 [x_center, y_center, w, h, obj_conf, class_confs...] # 反归一化到原图坐标画框标注这一版的识别框标出来后我遇到的第一个实际问题就是稳定性和置信度抖动。台球静止时模型对同一颗球的置信度会在0.55到0.75之间跳如果直接把置信度低于0.6的框过滤掉画面里就会有一闪一闪的现象。解决方法是做时间维度的平滑保存最近5帧的检测结果只有当一颗球在连续3帧中被稳定检测到才显示如果中间断了一帧则用上一帧的位置做插值。这个逻辑有效减少了闪烁。5.3 从像素坐标到球桌坐标的转换检测框给出的只是图像上的像素坐标而台球实战更关心的是球在球桌上的实际相对位置。我做了个简单的透视变换在球桌四个角放置标记点用cv2.getPerspectiveTransform计算变换矩阵把图像坐标投影到一张2D球桌图上。实现起来不复杂src_points np.array([[x1, y1], [x2, y2], [x3, y3], [x4, y4]], dtypenp.float32) dst_points np.array([[0, 0], [table_w, 0], [table_w, table_h], [0, table_h]], dtypenp.float32) M cv2.getPerspectiveTransform(src_points, dst_points) # 对每颗球的中心点做变换 ball_pos cv2.perspectiveTransform(ball_center.reshape(1, 1, 2), M).reshape(2)有了球在球桌上的归一化坐标就可以进一步做击球路线模拟、目标球进洞可行性分析这类进阶功能。不过要注意透视变换依赖四个角点被稳定检测到如果摄像头角度太偏变换误差会急剧放大所以实际使用中建议摄像头尽量保持接近俯视。5.4 误检漏检的兜底策略再好的模型在真实环境里也会犯错。部署时我做了两层兜底球桌区域限制检测框的中心点如果落在球桌区域之外直接丢弃。这个逻辑很简单但能过滤掉大量误检比如球杆、杯子的边缘。数量合理性判断台球数量是固定的如果某一帧检测到的球数超过18或者少于8说明这一帧的检测大概率有问题这时候输出上一帧的平均位置作为兜底同时提示检测异常。这两层兜底让实际使用体验提升很多。识别工具真正的价值不在完美而在于大部分时间稳定可用、偶尔出错时不会给用户带来灾难性的误导。6. 进一步优化方向与踩坑心得6.1 小目标专项优化输入分辨率与SPD-Conv如果想把远处的小球也识别得更准最直接的办法是提高输入分辨率。我在640的基础上试过960mAP50提升约0.4个百分点但推理耗时翻倍。另一个更高效的方向是引入SPD-ConvSpace-to-Depth Convolution它专门针对小目标检测设计能在不显著增加计算量的前提下保留更多细节信息。ultralytics没有直接内置SPD-Conv需要改模型结构工作量不大但对小目标提升显著如果你的项目对远台球识别要求高可以往这个方向试试。6.2 光照自适应的预处理前面提到球厅灯光变化是影响识别稳定性的最大因素。部署时我在预处理阶段加了一个自适应灰度均衡img cv2.cvtColor(frame, cv2.COLOR_BGR2LAB) l, a, b cv2.split(img) clahe cv2.createCLAHE(clipLimit3.0, tileGridSize(8, 8)) l clahe.apply(l) img cv2.merge((l, a, b)) img cv2.cvtColor(img, cv2.COLOR_LAB2BGR)这个处理让画面在偏暗球厅里依然能保留球面颜色特征模型在暗光场景下的漏检率下降明显。代价是每帧多出约3毫秒的预处理时间对实时性影响可以忽略。6.3 多球厅泛化测试的结论模型最终在训练时未见过的一个新球厅做了测试。这个球厅的光线偏冷白、台呢颜色稍微偏蓝模型的表现是纯色球的识别准确率保持较高花色球的分类准确率有所下降尤其是11号红条纹和14号绿条纹偶尔会被误判成对方。这说明模型对光照环境的变化有一定鲁棒性但颜色纹理相近的花色球仍是最容易翻车的部分。如果要做进一步的泛化路径是明确的不是简单增加训练图片数量而是要继续增加球厅多样性——不同色温的灯光、不同颜色的台呢、不同材质的球有些球厅用更光泽的球反光特性完全不同。6.4 个人踩坑记录与建议最后记录几个最容易让人白费时间的坑希望能帮后来者省点力气高清大图里的球反而难识别。因为YOLO会把输入图缩放到640原图里球占50像素缩放后可能只有20像素小目标信息丢失。如果原图分辨率很高比如4K可以先在球桌区域做裁剪再缩放而不是直接全图缩放。标注时不要过度追求精确到亚像素。台球运动的模糊、阴影、高光导致真实标注本身就有几个像素的误差花大量时间精修边缘对最终模型精度提升其实很有限。我建议标注时保证框的中心位置准确即可边界差两三个像素影响不大。训练时不要盲目加大数据增强。台球颜色特征精细过强的马赛克增强会把球的颜色纹理打乱反而导致模型学不到关键区分特征。我的经验是对台球这类颜色语义强的场景马赛克增强开启但概率降到0.5左右HSV扰动适当加强其他增强保持默认即可。验证集一定要跨场景划分。如果你的数据集来自多个球厅务必按球厅划分训练集和验证集而不是随机划分。按球厅划分后验证集的mAP可能会比随机划分低2到3个百分点但这个数字才是真实部署时能预期的水平。千万不要被随机划分的虚高分骗了。做这个项目最深的感触是YOLO本身只是一个工具箱真正决定项目成败的是你对场景的理解深度。台球识别看似是标准的目标检测但光照、小目标、颜色相似性这些细节才是真正的隐藏难点数据集和调优策略全都围绕这些难点展开模型结构反而是最不需要操心的地方。目前这版模型已经能在我的日常使用中稳定识别16颗球并给出到位的定位帮助等后续积累更多球厅的测试数据我会继续迭代花色球分类这一块争取把最后的这一小段短板也补齐。本文还有配套的精品资源点击获取