
简介目标检测是计算机视觉中最基础也最具应用价值的技术方向YOLO作为单阶段检测器的代表以实时性和精度平衡著称被广泛应用于工业质检、安防监控和体育分析等场景。在台球这类小球类运动中目标物体仅占画面20-30像素属于典型的小目标检测任务同时面临高光反射、颜色相似、相互遮挡等复杂挑战。本文从需求拆解出发系统梳理了基于YOLO的台球识别完整链路包括视频抽帧与标注策略、按颜色分类的类别设计、数据集划分的防泄漏技巧以及训练参数调优与指标解读。进一步文章深入探讨了推理阶段的坐标后处理、透视变换到台面坐标、多帧平滑等工程要点并给出小目标漏检、误检过滤、部署提速等常见问题的排查速查表。无论是入门目标检测项目还是准备落地体育智能分析系统这套从数据到部署的实践经验都具有直接参考价值。 台球自动识别这件事我最早是在朋友的中式台球俱乐部桌上看到高清顶摄像头的当时就想如果能把画面里每颗球的实时位置自动标出来训练复盘、进球统计甚至裁判辅助就都能做了。真正动手后才发现项目名义上是“基于YOLO的台球识别”但难点并不只是调用一个模型而是从数据采集到训练再到推理细节的一整条链路。这篇文章把我完整跑通的方案、参数、踩坑点全部分享出来。如果你想做一个YOLO落地的小项目或者正打算拿台球场景练手这里面的很多问题都是共通的。1. 项目整体设计台球识别到底难在哪1.1 场景需求拆解做一个项目前我习惯先把需求拆细再决定技术路线。台球识别看起来简单好像只要把桌面球找到就行但实际需求列出来有不小压力一颗台球在1080p画面里通常只占20-30像素属于不折不扣的小目标检测场景桌面上最多同时出现16颗球球号、颜色都有细微差别目标之间还会互相遮挡台球桌面高光反射很重白球在不同角度下的亮度差异极大红球在阴影里几乎变成深褐色最终我想做的是让系统“看懂”场上局面而不只是输出一堆框所以位置坐标的稳定性比单帧检测的准确率更关键。顺着这些约束我一开始就排除了传统OpenCV颜色分割方案。台球颜色虽然容易做HSV阈值但亮度一变化同样的阈值就不成立想稳定识别十六类球几乎不可能。深度学习目标检测反而天然适应这类视觉变化尤其是YOLO这类单阶段检测器速度和精度都能兼顾。1.2 为什么选YOLO版本怎么定YOLO这个系列已经谈不上新了但拿来落地依然是最合适的一档。它的优势有几个单阶段结构一次前向直接输出类别和坐标实时性有保证Ultralytics把数据格式、训练封装、预训练权重、导出工具全都整合好了花最少的时间跑通全流程社区案例极多不管遇到什么问题基本都能找到答案。版本选择上我最初用YOLOv8n跑通后来换成YOLO11s精度提升明显。v11和v8最直观的区别是网络结构在Head部分做了优化同样输入分辨率下小目标的召回率更好。台球识别恰好是小目标密集场景v11的收益是能实际感受到的。当然版本不是越大越好。如果最终要部署到嵌入式设备或工业工控机上我建议从n或s起步先保证实时性再根据精度缺口决定要不要上m或l。我自己做桌面端原型s就够了。迁移到低算力平台时再考虑蒸馏或者量化那是另一套优化路径。2. 数据集准备比训练难十倍的事2.1 视频采集与抽帧台球识别没有现成好用的公开数据集。网上能搜到“冒险岛yolo标记数据集”这类游戏资源但跟真实台球场景差距太大只能说练手可以做项目还是得自己采。我的采集方案是固定机位正对整张台面覆盖全部六个袋口。摄像头用普通USB超广角1080p帧率调30fps就够了太高反而让抽帧时相邻帧重复度过大。录制时特意覆盖了不同场景白天自然光、晚上顶灯、开球后球散开的瞬间、中袋附近聚集球的情况还有球杆偶尔入画的画面。录完原始视频后按每5帧抽1帧大约从几小时素材里抽出了5000多张图。抽帧后我先把明显模糊、动态模糊严重、视角完全被遮挡的图筛掉留下大概4300张可用图片。这个过程很枯燥但直接决定模型上限纯靠网络下载类似图片拼出来的数据集泛化能力会差很多。2.2 标注与类别设计取舍标注工具我用的LabelImg输出YOLO的txt格式。每颗球一个框框要紧紧贴住球体边缘。这里有个容易忽略的细节球是圆的而球的成像在透视下接近椭圆标注框如果太松会把大量桌面背景包进正样本里模型学到的特征就不干净了。更大的取舍在类别设计。最直觉的思路是16颗球分16类但实操之后我发现这个方案非常别扭球号在低分辨率下本来就难辨认标注员很难保证每颗球的编号标记一致16个类别的样本在不同场景下数量不均衡模型容易在某些类上学不好训练收敛慢mAP很难上到理想水平。后来我改成按颜色分8类把相近颜色合并成一组白球一组、黄球一组、蓝球一组、红球一组、紫球一组、棕球一组、绿球一组、黑球单独一组。这样做标注速度快了很多类别分布更均匀模型也更容易学。对大部分台球辅助分析功能按颜色其实已经够用。如果你确实需要识别具体球号我建议采用“两阶段”思路先用按颜色分类的检测模型把球框出来再对每个球框区域跑一个轻量分类器识别球号。这个方案比端到端16类检测稳得多也容易维护。2.3 语义划分与增强策略数据集划分有个大坑必须提醒同一段视频连续帧之间相似度极高如果随机划分训练集和验证集验证集里会混进大量“训练帧的近亲”mAP会虚高一上真实场景就露馅。正确做法是按视频片段划分。我按录制时间段把素材分成8个片段取前6个片段做训练第7个片段做验证第8个片段做最终测试。这样验证结果基本能反映真实场景的泛化能力。增强方面我开启了Ultralytics默认的HSV色彩增强同时加强了hsv_h、hsv_s、hsv_v的幅度。台球桌对光照变化极敏感这种色彩类增强对鲁棒性帮助很大。水平翻转和垂直翻转也打开了台球桌面是对称场景翻转不会违背逻辑反而能平衡球的位置分布。3. 环境配置与模型训练3.1 环境怎么搭训练环境是Python 3.10 PyTorch 2.1 Ultralytics单卡RTX 3060 12G训练640分辨率足够。装Ultralytics很简单pip install ultralytics但GPU环境有个常见坑装好PyTorch后CUDA版本和驱动不匹配训练时报错或者特别慢。我的建议是先查驱动支持的CUDA版本再装对应版本的PyTorch。比如驱动支持CUDA 12.1就装cu121版本的PyTorch不要盲目装最新版。如果只有CPU流程也能跑但建议把imgsz降到320epochs减少到几十轮先验证整个流程。3.2 数据集YAML配置Ultralytics训练前要准备一个YAML文件描述数据路径和类别。我的是这样的path: /data/billiard train: images/train val: images/val nc: 8 names: 0: white 1: yellow 2: blue 3: red 4: purple 5: brown 6: green 7: black这个文件里最容易犯的错就是类别编号。标注工具生成的class id是从0开始的如果YAML里names从1开始写或者漏掉某个索引训练出来的模型类别会错位。我建议训练前写个脚本检查所有标注txt里的第一列数字确保范围在[0, nc-1]内。3.3 训练参数设置与选取思路训练命令我习惯写成这样yolo detect train datadataset.yaml modelyolo11s.pt epochs150 imgsz640 batch16 patience20 optimizerAdamW lr00.001几个关键参数聊聊我为什么这么设置imgsz640台球直径在画面里很小分辨率低于512会明显漏检。12G显存跑640没问题如果显存紧张可以降到544但不能低于512。batch1612G显存跑yolo11s可以如果batch大小导致OOM优先减到8而不是减小分辨率。optimizerAdamW小数据集上AdamW收敛比SGD稳定曲线也更平滑。这算是我个人偏好的经验值。patience20早停轮数避免过拟合。我这次训练在第90轮附近触发早停保存了最优权重。modelsyolo11s.pt使用COCO预训练权重做迁移学习收敛速度和最终精度都比从零训高很多。哪怕类别不同底层特征也通用。3.4 训练过程与指标解读训练过程中主要盯验证集上的metrics/mAP50(B)和metrics/mAP50-95(B)以及box_loss、cls_loss是否同步下降。我这次的结果mAP50大约0.86mAP50-95大约0.63。球类目标外观高度相似这个分数配合自定义过滤规则已经足够实用了。如果你训练出来的mAP50持续很低甚至为0大概率不是模型问题而是数据或标注格式问题。最典型的三个原因标注txt里的class id越界标注坐标没有归一化到0-1labels目录和images目录的文件名不是一一对应的。每次切换数据集后我都强制自己先跑一段自动化检查再开始训练能省下大量排查时间。4. 推理与坐标后处理4.1 基础推理脚本训练完成后用Ultralytics的API推理非常简单from ultralytics import YOLO model YOLO(best.pt) results model.predict(match.mp4, conf0.35, iou0.5, saveTrue)但实际项目中不能只依赖saveTrue你需要自己从Results对象里解析框坐标、类别和置信度用来做后续逻辑。我习惯写一段类似这样的处理代码import cv2 from ultralytics import YOLO model YOLO(best.pt) cap cv2.VideoCapture(input.mp4) while cap.isOpened(): ret, frame cap.read() if not ret: break results model(frame, conf0.35, verboseFalse)[0] for box in results.boxes: x1, y1, x2, y2 box.xyxy[0].tolist() conf float(box.conf[0]) cls int(box.cls[0]) label f{model.names[cls]} {conf:.2f} cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2) cv2.putText(frame, label, (int(x1), int(y1)-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) cv2.imshow(result, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这里的conf0.35是我实测下来的值。默认0.25会让一些桌面高光、袋口圆形金属环被误检成球调到0.35正好保留真实检测结果又能过滤掉低置信度误检。另一个细节是把verboseFalse关掉否则每一帧都会刷一串日志控制台完全没法看。4.2 从像素坐标到台球桌坐标如果只想知道“画面里有没有球、球在哪”上面的代码就够了。但我想做的是把像素坐标转换成台球桌平面坐标这样才能判断球是否靠近袋口、有没有入袋、两球之间距离多大。这里需要做透视变换不能简单等比缩放。因为摄像头相对台面有俯仰角画面边缘畸变会让简单比例关系不成立。我的做法是用OpenCV的findHomography在台球桌四个角放四个标定点记录它们的物理坐标从画面中分别取这四个点的像素坐标计算单应性矩阵把所有检测球中心点都乘上这个矩阵得到对应的桌面上坐标。实际应用效果很直观。袋口在物理坐标系里有固定位置当某个球的变换后坐标进入袋口区域就触发“进球候选”事件。有了这个层面系统才真正有业务说服力。4.3 多帧平滑与轨迹稳定单帧检测结果在静止球上也会有±2像素的小抖动。如果直接拿这些坐标去画轨迹折线会毛糙得没法看。最省事的办法是加一个指数移动平均smoothed 0.7 * current 0.3 * previous如果球被短暂遮挡后重新出现EMA也能让位置过渡得自然一些。对于更复杂的遮挡问题可以上卡尔曼滤波会给每个球做运动预测在丢帧时补出合理位置。不过卡尔曼滤波的调参成本不低容易出现震荡对大多数项目来说EMA已经足够。我自己交付的录像回放模块用的就是EMA效果已经很干净。5. 踩坑实录与排查速查表5.1 小目标漏检这是台球识别里最典型的痛点。球在1080p画面里只占20-30像素多个球挤在一起或者贴边时YOLO经常漏检。我排查的思路是先分清楚是数据问题还是模型问题在验证集上逐张看推理叠加图如果漏检集中在球密集或球靠近袋口边缘的图那基本是训练数据缺少这种多样性。对应的解决办法有几种训练前先对原图做ROI裁切把球台区域从整幅画面中裁出来让球在输入图中的尺寸占比变大提升imgsz到736甚至768我有一次仅仅从640提到736mAP50涨了2个点标注时确保框紧贴球体边缘不能为了省事把框拉大一圈。5.2 相近颜色球容易认错绿球和棕球在黄色台呢上偶尔会互相认错红球在阴影里被标成棕球的情况也有发生。这背后是类别间视觉特征相似加上标注边界框包含背景导致的。我的处理手段有两层。第一层是数据侧调整标注框边缘避免把台面背景包进正样本增加不同亮度下的样本尤其是低光场景。第二层是推理侧加规则修正比如同一颗球在连续帧里的类别不应该突然跳变用上一帧的类别和位置做约束如果当前帧检测结果跟历史轨迹冲突太大就优先信任历史。这种“模型预测业务规则”的组合在实际系统中极其重要YOLO本身不可能在所有时刻都完美但加上简单的时空约束后整体可靠性会高很多。5.3 训练指标一直为0遇到训练几轮loss不降、mAP一直是0的情况先不要怀疑模型和数据本身按下面顺序排查数据集YAML里的path是否指向正确目录训练日志里有没有显示图片和标签加载数量labels里的class id是否从0开始nc是否和类别数一致标注文件的文件名和图片文件名是否一一对应标注坐标是否归一化到0-1之间。我深刻的一次教训是换数据集时忘了同步labels目录训练正常启动但每张图都没有对应的标签结果训练了几个小时mAP纹丝不动。后来我给自己定了条规矩每次训练前先跑下面这段检查import os from glob import glob img_files glob(images/train/*.jpg) label_files glob(labels/train/*.txt) print(f图片数: {len(img_files)}, 标签数: {len(label_files)})如果图片和标签数量差很多一定有问题先定位数据再谈训练。5.4 环境兼容与推理延迟训练机GPU没问题但部署到别的机器上时经常卡在CUDA或OpenCV版本不匹配。如果项目只做实时回放CPU推理其实也可以接受1080p下大约每帧150-200ms能凑合看。要更实时的话导出成ONNX模型是常用做法yolo export modelbest.pt formatonnx然后通过ONNX Runtime加载就不需要整个PyTorch环境了部署体积小很多实测在PC CPU上每帧能到50-80ms基本接近实时。再往下压帧率就是TensorRT或者OpenVINO的路线大家可以根据部署硬件决定。5.5 台球桌高光和袋口圆形物的误检桌面边缘高光呈长条形却被误检成球形目标的情况也遇到过袋口金属环是圆形的更容易被当成球。这种问题光调conf阈值解决不干净。我加了一个简单的几何过滤规则球在正常机位下宽高比应该接近1如果检测框的宽高比明显偏离0.7到1.3的范围就视为可疑目标过滤掉。这个规则简单但不依赖训练能在后处理阶段消灭掉一大类误检属于收益极高的一个小细节。5.6 几张快查表现象常见原因快速排查训练mAP一直为0标签文件缺失或class id越界检查labels目录与图片数量是否一致验证集mAP虚高训练/验证划分混入同一视频片段按视频片段划分不随机切小目标漏检输入分辨率过低或球体占比太小提升imgsz或先做ROI裁切相近颜色误检标注框包含太多背景收紧标注框增加光照多样性推理帧率过低部署机没有GPU且未做模型压缩导出ONNX并开启OpenMP优化低置信度误检多conf阈值过低调高到0.35并搭配宽高比过滤6. 项目扩展的方向台球识别只是前端感知真正有增值空间的是把检测结果变成业务能力。比如自动计分连续多帧检测到某颗球从运动变成静止状态并且球心进入袋口区域就判定进球比如击球顺序判断追踪白球和其他球的运动状态可以自动判断开球方和犯规情况。这些功能本质上是把检测结果喂给一个状态机流程清晰但前提是检测结果必须稳定坐标系变换也必须准确。另一个思路是改用YOLO的实例分割模型而不是检测模型。检测模型给的是框分割模型能给出球的轮廓这对计算球与球之间的碰撞角度和连续撞击关系帮助很大。代价是标注方式要从矩形框变成多边形工作量明显上升但下游分析能力的上限也会提高。如果你还想进一步把“识别”升级成“理解”可以考虑多任务学习。比如在YOLO基础上扩展球杆检测和击球动作关键点检测几个任务共享特征提取主干。我在后续版本里就加了一个击球动作模块先用检测模型定位球杆区域再用一个轻量分类器判断击球动作是否规范。这种“检测分类”组合起来实际体验比单纯一个大模型要灵活得多。项目做完回头看最值的投入其实不在模型调参而在数据质量和工程细节。YOLO本身足够成熟真正拉开差距的是你愿不愿意把标注反复审一遍、把后处理规则一遍遍打磨。这套“先按场景拆需求再让模型适配约束最后用规则补齐短板”的思路放到任何目标检测项目里都适用。本文还有配套的精品资源点击获取