ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于YOLOv8与Streamlit的足球球员和球检测跟踪方案

基于YOLOv8与Streamlit的足球球员和球检测跟踪方案 简介一份基于YOLOv8与Streamlit的足球检测与跟踪实战资源面向Python计算机视觉学习者与体育分析爱好者。资源以Streamlit搭建可视化主界面内含“如何使用、团队颜色、模型超参数和检测”三个Tab页可实现足球运动员、裁判、足球的检测与跟踪并支持球员球队预测及战术地图上球员/球位置估计。压缩包共67个文件涵盖29张jpg图片、15张png图片、5个mp4演示视频、4个yaml配置文件、4个pt权重模型、2个py源码及csv/txt/json/ipynb等辅助文件整体大小约380.97MB。目前已有422人学习下载配套内容完整读者可直接运行Streamlit应用并结合YOLOv8模型权重与视频样例快速复现检测流程适合作为计算机视觉课程项目或足球运动分析入门实践。1. 为什么说这套组合是足球视频分析里性价比最高的方案如果你在电脑前剪过一场90分钟的比赛录像想统计球员跑动、识别越位或者数清双方触球次数你会发现纯手工处理不仅慢而且主观。基于YOLOv8Streamlit实现球员和足球检测与跟踪正是把目标检测、多目标跟踪和Web可视化一次性串起来的完整方案YOLOv8负责逐帧框出人和球跟踪算法负责给每个目标一个稳定IDStreamlit负责把整个过程变成浏览器里能点的界面。这套组合解决的问题很具体不依赖昂贵的商业视频分析软件用一台普通GPU机器就能处理比赛视频并产出检测框、跟踪轨迹和结构化统计数据。这篇文章面向的读者是做体育数据分析、AI课程设计或者自建比赛分析工具的人。你会看到我从环境搭建、推理脚本、训练自有数据到Streamlit界面改写的完整路径也会看到我把大家最容易翻车的几个环节单独列了一章。内容不绕弯子按可直接复现的顺序写。2. 检测加跟踪的完整链路先搞清楚每个环节在解决什么问题2.1 为什么检测选YOLOv8而不是更早的版本任务边界与部署成本的平衡球员和足球检测属于典型的小目标检测场景。足球在1080p画面里往往只有二三十个像素球员全身框相对好做但球在快速运动时会模糊、畸变、被身体遮挡。YOLOv8相比前代把C2f模块替换了C3模块特征融合更细对这类小目标的召回率有明显提升同时它原生支持端到端的Anchor-Free检测头省掉了锚框聚类和后处理里大量逻辑分支。在实际部署时我更看重YOLOv8的另一个特点导出和推理接口统一。无论是PyTorch权重直接推理还是导出成ONNX在CPU上跑它的API都收敛到同一套predict调用这对后面要接Streamlit的服务化场景非常友好。如果你用的是YOLOv5或者更早的版本接口虽然也能用但往往要自己写NMS处理、坐标缩放和数据协议开发量会多出不少。2.2 跟踪为什么不能只靠检测从单帧框到持续ID的必经之路纯检测只能回答“这一帧哪里有球员和球”但无法回答“5号球员在10秒前的位置在哪”。要做到跟踪我们需要给每个检测框分配一个稳定ID并预测它在下一帧的位置。常见做法是ByteTrack或者DeepSORT。ByteTrack的优势在于它把低分框也纳入匹配过程而球员互相遮挡、球被腿挡住时检测器给框的置信度会骤降如果直接把低分框丢掉ID就会频繁跳变球还会在遮挡后彻底丢失。DeepSORT则额外引入外观特征适合跨摄像头场景。但足球比赛通常只有一个固定机位球员运动相对平滑ID跳变的主要原因不是外观变化而是遮挡和检测抖动所以ByteTrack在这类场景下是性价比最高的选择。换个说法跟踪模块的价值不是让检测更准而是把检测结果的时间连续性补起来让Streamlit界面上能看到一条条移动轨迹而不是一堆散落的框。2.3 Streamlit在这个项目里的定位把模型输出变成可交互工具Streamlit承担的是轻量级前端加服务层。它和Gradio相比布局自由度稍高支持侧边栏参数控制、视频逐帧回放和数据表格展示更适合做“工具型”应用而不是“演示型”应用。实际体验中用户要的不是一个输入图片返回结果的一次性页面而是能调置信度阈值、选跟踪算法、看统计曲线的工作台Streamlit的会话缓存机制可以让这些状态在交互间保持。值得一提的是Streamlit的图片和视频组件对OpenCV的BGR格式适配并不直接需要转成RGB再喂给st.image。这个转换看似不起眼却是我见过最多人在界面里看到颜色诡异的第一反应。后面第4章的避坑清单里会专门讲。3. 把项目跑起来从环境配置到完整推理的最小步骤3.1 环境搭好一份不会在依赖上反复折腾的安装清单无论你用的是Ubuntu 20.04还是Windows 10核心依赖就三件PyTorch、Ultralytics和Streamlit。如果你只有CPU机器不要直接pip install torch那样会拉取约200MB的CPU版本后续换CUDA版本要重装。正确顺序是先到PyTorch官网按GPU型号选好安装命令再装其余依赖。# 建议在虚拟环境中进行Python版本锁定在3.9-3.11 python -m venv venv source venv/bin/activate # Windows下使用 venv\Scripts\activate # 先装PyTorch以CUDA 11.8为例 pip install torch2.0.1 torchvision0.15.2 --index-url https://download.pytorch.org/whl/cu118 # 再装检测框架和界面库 pip install ultralytics streamlit opencv-python numpy pandas这段命令的安装逻辑是先确认深度学习张量库再装Ultralytics作为检测和跟踪的统一入口最后装Streamlit和视频处理库。有一个细节值得说明Ultralytics会连带安装OpenCV但如果你之前机器上有旧版建议显式指定opencv-python版本避免视频解码格式不兼容导致读取MP4失败。版本号不需要追新能稳定跑通即可。3.2 最小推理脚本先让检测在单张图片上给出正确结果在接Streamlit之前先把YOLOv8的检测能力跑通。下面这段脚本锚定的是“读视频、逐帧检测、把结果写字幕文件”的标准流程。第一次运行时Ultralytics会自动下载yolov8n或yolov8m的预训练权重只有80类COCO类别其中person类对球员有效sports ball类对足球有效。from ultralytics import YOLO import cv2 # 模型权重n是最小模型m是速度与精度折中按机器性能选 model YOLO(yolov8m.pt) # 用视频文件作为输入源也可以换成网络摄像头地址 cap cv2.VideoCapture(match.mp4) fps cap.get(cv2.CAP_PROP_FPS) width int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) writer cv2.VideoWriter(output.mp4, cv2.VideoWriter_fourcc(*mp4v), fps, (width, height)) while cap.isOpened(): ret, frame cap.read() if not ret: break # conf控制置信度阈值imgsz控制输入分辨率越大对小目标越友好但越慢 results model(frame, conf0.25, imgsz1280, verboseFalse) annotated results[0].plot() writer.write(annotated) cap.release() writer.release() print(检测完成output.mp4 已生成)这里的核心参数有三个conf决定多少分以下的检测框会被丢弃0.25是较宽松的默认值处理足球比赛时建议调到0.15到0.2因为球在远处时置信度低但却是关键目标imgsz决定输入到网络的缩放尺寸1280在GPU上能较好兼顾速度和足球这个极小目标verboseFalse关闭每个文件的日志输出避免跑长视频时屏幕被刷屏。3.3 加入跟踪逻辑用相同接口拿到逐帧稳定IDUltralytics从8.0版本开始内置了ByteTrack的调用入口不需要单独安装tracker库。下面的代码在检测结果上叠加了跟踪并以CSV形式输出每个目标的位置序列这正是后续分析球员跑动距离的数据基础。from ultralytics import YOLO import csv model YOLO(yolov8m.pt) cap cv2.VideoCapture(match.mp4) # 注意tracker_config参数指定了ByteTrack的配置文件路径 results_gen model.track( sourcematch.mp4, conf0.2, iou0.5, imgsz1280, trackerbytetrack.yaml, persistTrue, saveTrue, nametracked_output ) # track方法也可以直接返回结果生成器这里再手动处理一次以便导出CSV csv_file open(tracking_data.csv, w, newline) writer csv.writer(csv_file) writer.writerow([frame, id, class, x_center, y_center, width, height, conf]) for frame_idx, result in enumerate(results_gen): if result.boxes is None or result.boxes.id is None: continue ids result.boxes.id.int().cpu().tolist() cls result.boxes.cls.int().cpu().tolist() confs result.boxes.conf.cpu().tolist() xywh result.boxes.xywh.cpu().tolist() for i, obj_id in enumerate(ids): x, y, w, h xywh[i] writer.writerow([frame_idx, obj_id, cls[i], round(x, 2), round(y, 2), round(w, 2), round(h, 2), round(confs[i], 3)]) csv_file.close()persistTrue表示跟踪ID会跨帧保持这是最关键的参数漏掉它ID会每帧重置跟踪等于没做。配置文件bytetrack.yaml在Ultralytics的config文件夹里里面有几个阈值与遮挡漂移直接相关这部分放到第4章展开。把跟踪结果导出CSV是很多人忽略但极有价值的一步因为后续画轨迹图、算热力图、按ID裁剪球员画面都依赖这张表。3.4 Streamlit界面接线让参数调节变成点选而不是改代码检测和跟踪脚本跑通后把它裹进Streamlit就比较机械了。核心思路是把conf阈值、imgsz、模型类型等参数放进侧边栏把处理后的视频帧渲染在主区域。Streamlit的st.file_uploader支持直接上传视频到内存配合tempfile写到磁盘供OpenCV读取。import streamlit as st import tempfile import cv2 from ultralytics import YOLO from pathlib import Path st.set_page_config(page_title球员与足球检测跟踪, layoutwide) st.title(基于YOLOv8的足球比赛分析工具) # 侧边栏集中管理所有参数 with st.sidebar: conf st.slider(置信度阈值, 0.05, 0.90, 0.20, 0.05) imgsz st.selectbox(推理分辨率, [640, 1280, 1600]) model_choice st.selectbox(模型规格, [yolov8n.pt, yolov8m.pt]) uploaded st.file_uploader(上传比赛视频, type[mp4, avi, mov]) if uploaded is not None: # Streamlit的UploadedFile不是常规文件路径必须落盘 with tempfile.NamedTemporaryFile(deleteFalse, suffix.mp4) as tmp: tmp.write(uploaded.read()) video_path tmp.name col1, col2 st.columns([3, 2]) with col1: run_btn st.button(开始检测与跟踪) with col2: show_traj st.checkbox(绘制轨迹线, valueTrue) if run_btn: model YOLO(model_choice) cap cv2.VideoCapture(video_path) st_frame st.empty() # 只处理前300帧作为预览调试时避免长时间等待 frame_count 0 while cap.isOpened(): ret, frame cap.read() if not ret or frame_count 300: break results model.track(frame, confconf, imgszimgsz, trackerbytetrack.yaml, persistTrue) annotated results[0].plot() # OpenCV是BGR顺序Streamlit期望RGB顺序不转颜色就是反的 annotated_rgb cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB) st_frame.image(annotated_rgb, channelsRGB) frame_count 1 cap.release()这段代码把前面两个脚本合并成了一个可交互应用。三个值得注意的接缝临时文件必须落盘因为Ultralytics接收的是文件路径而不支持文件对象每帧调用st_frame.image会持续刷新占位组件达到视频播放效果颜色空间转换放在最后一个环节避免多个组件各转一次浪费算力。4. 把模型训练到自己的比赛数据上数据集、参数和常见误区4.1 标注自己的数据集Labelme标注格式如何转换到YOLO格式预训练模型对职业比赛画面效果尚可但对低角度直播、室内五人制或草皮颜色接近球衣的场景仍然需要微调。第一步是标注。常见做法是用Labelme画多边形或矩形框但YOLOv8训练要的是txt格式的归一化坐标。这里有一个转换脚本把Labelme的json批量转成YOLO训练格式。import json import os from glob import glob def labelme_to_yolo(json_path, output_dir, class_names): with open(json_path, r, encodingutf-8) as f: data json.load(f) img_w data[imageWidth] img_h data[imageHeight] txt_name os.path.splitext(os.path.basename(json_path))[0] .txt with open(os.path.join(output_dir, txt_name), w) as out: for shape in data[shapes]: label shape[label] if label not in class_names: continue cls_id class_names.index(label) # Labelme的矩形是两点YOLO需要中心点加宽高这是转换中最大的坑 points shape[points] xs [p[0] for p in points] ys [p[1] for p in points] x_min, x_max min(xs), max(xs) y_min, y_max min(ys), max(ys) x_center (x_min x_max) / 2 / img_w y_center (y_min y_max) / 2 / img_h width (x_max - x_min) / img_w height (y_max - y_min) / img_h out.write(f{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\n) # 类别顺序固定训练时数据集的classes.txt要与此保持一致 class_names [player, ball] os.makedirs(yolo_labels, exist_okTrue) for json_file in glob(labelme_json/*.json): labelme_to_yolo(json_file, yolo_labels, class_names)标注时最容易犯的错是把球门、裁判也框进去。建议只标player和ball两类其他对象一律忽略这样训练时背景类不会因为标注不一致而引入噪声。另外足球视频分辨率普遍在1080p以上建议把原图切成若干小块再标注或者使用Slicing Aided Hyper Inference这类切图推理方案否则球在416像素的缩略图里只有几个像素标注和训练效果都会大打折扣。4.2 训练参数怎么调一句命令行跑通并理解每个数字含义YOLOv8的训练入口是命令行但参数含义比命令本身更重要。以下是针对足球场景的推荐配置。yolo detect train \ datadataset.yaml \ modelyolov8m.pt \ epochs100 \ imgsz1280 \ batch8 \ lr00.01 \ lrf0.01 \ mosaic1.0 \ mixup0.2 \ close_mosaic10 \ patience20 \ device0dataset.yaml需要手动指定训练和验证图片路径及类别数。epochs设为100够用配合patience20让它在验证集不再提升时提前停止。imgsz1280是足球场景的关键球太小用640训练会让球的位置信息严重丢失。mosaic和mixup是数据增强mosaic1.0混合四张图对多人运动场景有效close_mosaic10表示最后10轮关闭马赛克增强让模型在接近真实分布的数据上收敛这个细节能显著减少验证集上的震荡。lr0和lrf是初始和最终学习率微调场景建议lr0从0.005开始。训练完成后用下面的命令验证模型在验证集上的mAP50-95重点关注ball类别的AP因为球是最难提升的那一个。yolo val modelruns/detect/train/weights/best.pt \ datadataset.yaml \ imgsz1280 \ conf0.001把置信度阈值在验证时降到0.001是为了完整评估模型的召回能力如果用默认的0.25很多低置信度但位置正确的小球框会被过滤AP会被低估。4.3 数据集划分的边界坑同场比赛不能同时出现在训练集和验证集足球视频的帧高度相关相邻两帧几乎相同。如果随机划分数据训练集和验证集会包含同一场比赛的相邻帧验证指标会虚高得离谱。正确的划分方式是按视频片段切分把每场比赛切成若干段整个片段分到训练或验证而不是按帧随机分。import random from glob import glob # 按视频名分组同一场视频的所有帧进同一个集合 video_groups {} for img_path in glob(frames/*.jpg): # 假设命名格式是 match1_00123.jpg video_name img_path.split(/)[-1].split(_)[0] video_groups.setdefault(video_name, []).append(img_path) random.seed(42) all_videos list(video_groups.keys()) random.shuffle(all_videos) val_count max(1, int(len(all_videos) * 0.2)) val_videos all_videos[:val_count] train_lines, val_lines [], [] for video, paths in video_groups.items(): target val_lines if video in val_videos else train_lines for p in paths: target.append(p) with open(train.txt, w) as f: f.write(\n.join(train_lines)) with open(val.txt, w) as f: f.write(\n.join(val_lines))这个分组逻辑能防止数据泄漏。还有一个容易被忽视的点如果你从网上爬取的素材包含不同比赛的镜头切换建议每个镜头单独取名否则两场比赛的画面会被当成同一组同样会造成验证失真。5. 避坑清单足球检测跟踪场景里最常见的五个问题5.1 球在远处或快速运动时检测框反复闪烁现象球在画面远处时只有数像素大小检测框一会有一会无跟踪ID根本无法稳定建立。原因输入分辨率不足球被缩到极低分辨率后特征消失加上运动模糊让球的纹理进一步退化。解决把推理imgsz提升到1600甚至1920只在GPU显存充足时使用。如果仍闪烁考虑对检测结果做时间滤波连续三帧中至少两帧检测到球才确认球存在。具体做法是在Streamlit处理循环里维护一个长度为3的球检测历史队列球的最终状态取多数表决。5.2 球被遮挡后ID变化或直接丢失现象球员带球跑过另一名球员身后球消失两到三帧重新出现后被分配了一个新ID。原因ByteTrack的匹配机制依赖检测框的IoU球被完整遮挡时没有检测框可供匹配tracker会把球标记为lost重新出现时按新目标处理。解决最有效的办法是给球单独启用轨迹预测。在Streamlit侧加一个状态机当球丢失时记录丢失前最近的五帧位置用线性外插预测后续位置当球再出现在预测位置附近时把旧ID重新绑定。预测窗口不超过15帧超过就放弃。5.3 球员互相遮挡导致ID互换现象两名球员交错跑位后ID发生交换原本是5号的框变成了7号跑动距离统计会直接出错。原因ByteTrack只做运动匹配没用到外观特征。球员穿同色球衣且运动方向交叉时运动模型无法区分。解决升级到DeepSORT或BoT-SORT。Ultralytics的tracker参数可以直接切换在配置文件的embedding相关部分设置外观特征提取维度。代价是推理速度下降10%到20%。如果坚持用ByteTrack又想让ID稳定可以在训练时把球员按球衣号码作为独立类别标注但这会让标注成本大幅上升一般只有职业级分析才这么做。5.4 Streamlit播放视频颜色偏蓝绿色现象视频画面整体色调异常绿色草皮变成蓝色。原因OpenCV的imread返回的是BGR三通道顺序Streamlit的st.image默认按RGB解析两通道互换导致色偏。解决在调用st.image之前用cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)转换并用参数channelsRGB显式声明。还有一个类似的问题如果你把处理后的视频写入文件再让Streamlit播放写入时用VideoWriter的BGR格式是正常的因为编码器预期BGR输入只有直接渲染到浏览器时才需要转换。5.5 视频处理速度远慢于预期现象GPU显卡型号不旧但视频推理时每秒只能处理3到4帧。原因逐帧调用model.track时Python和C层之间的数据拷贝消耗大量时间另外imgsz设置为1600后计算量成倍增长。解决用Ultralytics的source参数直接传入视频路径让推理在内部批处理和流式读取而不是在循环里手动read。这样能让处理速度提升两到三倍。如果机器显存不足减少batch或者把模型从m降到n。要注意的是在Streamlit场景下为了逐帧刷新界面无法直接走全自动流式这时做一个缓冲区每读取十帧做一次批量推理比逐帧调用快得多。6. 从跑通到可用跟踪ID稳定性验证与画面叠加技巧当检测和跟踪流程稳定后最容易出成绩的方向是把跟踪数据转化成可视化证据。我在做完基础版本后第一件事不是调精度而是画轨迹图和热力图。为什么顺序这么排因为跟踪结果本身的质量决定了统计数据的可信度而可视化只是把这个可信度呈现出来。画轨迹时最实用的技巧是按ID分组取中心点序列再在原始视频帧上逐段连线。注意两次连续的点之间要过滤超过阈值的大位移否则跟踪ID一旦跳变你会画出一条跨越整个球场的斜线。过滤参数一般取画面宽度的15%。更细一层的验证是做ID切换率统计处理一段两分钟的视频输出每个ID的首次出现和消失帧如果3号ID消失了又出现且位置离消失点很远说明跟踪产生了断裂。用这个指标能快速判断是不是需要把ByteTrack换成BoT-SORT也能验证你写的时间滤波有没有真正生效。我自己习惯在最终交付前对整个视频重新跑一遍并在Streamlit里增加一个“轨迹回放”按钮选择任意一个ID单独渲染它的轨迹和检测帧序列这一步能发现很多肉眼不容易注意到的跟踪断裂。等全部验证完成后再把stats.csv导出按帧和时间段聚合生成触球次数、跑动距离这类高维度指标。值得提醒的是你的方案越完善别人问的问题就越具体。准备一份简短的参数说明表把conf、imgsz、tracker类型和对应效果记录在案能帮你省掉很多后顾之忧。这套方案我从最初的单帧检测改到带ByteTrack跟踪再到Streamlit上做交互界面踩过的最深的一个坑就是过早追求模型精度而忽视跟踪链路的稳定性。现在我把每个环节的参数都做了收敛换数据集时改动的只有标注内容和路径配置整体流程不再需要从头摸索。希望这份笔记能帮你在球员与足球检测跟踪这条路上少走几步弯路。本文还有配套的精品资源点击获取
返回列表