ARTICLE DETAIL

资讯详情

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

YOLOv8结合Streamlit构建足球战术地图

YOLOv8结合Streamlit构建足球战术地图 简介面向具备Python基础、希望快速上手YOLO目标检测与Streamlit部署的计算机视觉学习者这份资源以软件/插件形式提供一套完整的足球赛事分析一体化项目。项目基于YOLOv8完成球员、裁判、足球的检测与跟踪支持球队归属预测并可在战术地图上实时估计球员和球的坐标位置主界面通过Streamlit搭建三个Tab页覆盖帮助说明、团队颜色设置、模型超参数调节与检测结果展示。压缩包共67个文件类型涵盖pt模型权重、yaml配置文件、py源码、mp4测试视频以及jpg/png示例图片等模型权重可离线直接推理视频用于验证跟踪效果源码和配置便于二次开发与调参。整体约380.97MB已有421人学习下载。读者可获得从数据配置、模型训练到Web界面部署的完整项目实践适合用作业余项目或课程设计参考。1. 球场视野下的 YOLOv8Streamlit从检测器到战术地图的策略拿到一段比赛录像大多数人只能给出一列带框的检测结果而拿到这套Football-Analytics-with-Deep-Learning-and-Computer-Vision源码的人看到的是球员、裁判、足球的实时位置被投影到一张战术地图上同时主客队被自动聚类成两种颜色。这套东西本质上是两件独立的事一个负责感知YOLOv8 检测球员/裁判/球 球场关键点一个负责呈现Streamlit 三个 Tab 把模型参数、检测结果和战术俯视图组织成可交互界面。对做体育分析、边缘视频理解甚至只是想跑通 YOLOv8 全链路的人来说它最大的价值不是模型多新而是把“检测”和“地图位置估计”这两类任务拆成了两个独立模型再用一条清晰的数据流串起来。本文按“模型选型 → 环境与 Streamlit 骨架 → 坐标投影与聚类 → 验证与踩坑”的顺序把整个 pipeline 的原理和可复现步骤展开。2. 双模型架构球员/足球检测与球场关键点回归的选型与数据组织2.1 为什么拆成两个独立模型而不是一个多任务模型常见做法是把“球员检测”和“球场关键点检测”塞进同一个模型里做多任务学习但这套项目没有这么干。reason 很直接两个任务的输出语义完全不同。球员检测是密集小目标检测需要对人的边缘、姿态造成的形变有强响应而球场关键点检测本质是回归任务预测的是中心圆、禁区角、中线两端这些固定拓扑点在图像上的坐标对纹理细节不敏感。混在一起训练时反向传播的梯度会在检测头和关键点头之间互相干扰结果往往是球员边界框抖动、关键点漂移。项目里用models/Yolo8L Players和models/Yolo8M Field Keypoints两个权重来承担这两个任务对应两个 dataset yamlplayers dataset.yaml和pitch dataset.yaml。数据组织上players 数据集负责player、referee、football三类pitch 数据集负责球场关键点。调用时两个模型互不共享参数先跑球员检测拿到边界框再跑关键点模型拿到透视变换所需的源点这种解耦让每一步的错误都局部可定位——检测漏了人是检测的事投影歪了是透视变换的事不会互相甩锅。2.2 数据集标注与 YAML 组织方式两个数据集各自独立成 yaml结构上遵循 YOLOv8 的标准格式。players 数据集的标注内容如下path: datasets/players train: images/train val: images/val names: 0: player 1: referee 2: footballpitch 数据集的 yaml 稍有不同它标注的是关键点坐标而不是边界框。常见做法是标注 16 个球场拓扑关键点覆盖中线两端、中圈、上下禁区角和禁区线中点。yaml 里只维护路径和名称关键点坐标本身存放在 label 文件里每行格式为class_id x1 y1 x2 y2但这里 x、y 都落在图上而不是边界框中心。训练时 YOLOv8 会自动根据kpt_shape解析关键点分支。数据集模型输入类别/目标输出含义playersYOLOv8Lplayer、referee、football边界框 类别 置信度pitchYOLOv8M16 个球场关键点每个关键点的 (x, y) 坐标两张表的输出在后端正好接上球员检测给的是“谁在哪”关键点检测给的是“场地坐标系在哪”。如果你要训练自己的数据集注意类别名顺序必须和模型训练时一致YOLOv8 是按 names 的 index 顺序输出 class id 的顺序一旦改掉加载旧权重后推理结果会整体错位这是新手最容易栽的地方。2.3 YOLOv8L 与 YOLOv8M 的选型依据和 C2F 结构YOLOv8 的主干网络用 C2F 模块替代了 YOLOv5 的 C3核心变化是输入先经过 split 分成两支一支直接 concat另一支经过 Bottleneck 后在每层都做 concat。梯度回传时这种密集的跳连让浅层特征能更直接地收到深层损失信号对球员这种姿态多变、尺度不一的目标更友好。选 L 而不是 S/M 做球员检测不是炫算力而是球员在 1920×1080 的比赛画面里通常只占 40×80 像素左右属于典型小目标小模型在这种尺度下召回率掉得很快。关键点模型用 M是因为关键点检测对语义抽象要求低但对空间分辨率有一定要求M 的宽度足以拟合 16 个点之间的空间关系没必要上 L 增加延迟和显存。实际跑推理时两个模型各跑一次conf0.25时球员模型在一帧里能稳定召回大部分在画面内的队员裁判因为衣服颜色和球员接近偶尔漏检这个现象和模型容量无关更多是训练样本里裁判占比不足后续可以针对该类做样本加权。3. 从环境到推理依赖管线与 Streamlit 三个 Tab 的落地3.1 environment.yml 与依赖版本组织源码根目录的environment.yml一次性解决 conda 环境问题。我一般会先看这个文件再动手配环境而不是直接 pip install避免 CUDA 版本和 PyTorch 对不上导致 import torch 直接崩。name: football-analytics channels: - pytorch - conda-forge - defaults dependencies: - python3.10 - pytorch2.1.0 - torchvision0.16.0 - cuda-toolkit11.8 - pip - pip: - ultralytics8.0.136 - streamlit1.26 - opencv-python4.8.1.78 - scikit-learn1.3.0 - pandas - numpy创建环境conda env create -f environment.yml然后conda activate football-analytics。这套依赖里最需要关注的是ultralytics和opencv-python的版本ultralytics8.0.136 的YOLO类接口稳定换了太新的版本有时会对predict返回的Boxes对象结构造成调整。如果你不是用 CUDA 11.8而是用 12.x 的卡建议直接把cuda-toolkit换成 12.1 并同步更新 PyTorch。CPU 也能跑但 1080p 视频下 L 模型单帧推理在 2 秒以上Streamlit 里基本没法做实时预览只能逐帧处理视频后回放结果。3.2 main.py 的三 Tab 结构与 Streamlit 交互Streamlit 的入口是main.py界面用st.tabs切成三个面板如何使用、团队颜色、模型超参数和检测。这个设计比一个大页面堆参数更清晰模型参数调整和检测结果展示隔离不会在调试时互相遮挡。import streamlit as st from detection import run_detection st.set_page_config(page_titleFootball Analytics, layoutwide) tab_usage, tab_team, tab_detect st.tabs( [如何使用, 团队颜色, 模型超参数和检测] ) with tab_usage: st.markdown( 上传比赛视频系统使用 YOLOv8 检测球员、裁判和足球 并输出带战术地图的渲染视频。可用的示例视频为 demo_vid_1.mp4 和 demo_vid_2.mp4点击侧边栏即可加载。 ) with tab_team: team_color_mode st.selectbox(球队识别模式, [自动聚类, 手动指定]) st.info(自动聚类使用 KMeans 在 HSV 空间对球员主色分组) with tab_detect: conf st.slider(置信度阈值, 0.1, 0.9, 0.25, 0.05) iou st.slider(IoU 阈值, 0.1, 0.9, 0.45, 0.05) imgsz st.selectbox(推理分辨率, [640, 960, 1280], index1) if st.button(开始检测, typeprimary): run_detection(confconf, iouiou, imgszimgsz)st.slider返回的浮点值直接透传给run_detection这套代码把界面和推理完全解耦起推理在detection.py里Streamlit 只负责参数传递和结果展示。imgsz设 1280 对小目标召回更友好但帧率会明显下降项目默认给的比例是对画质和速度的折中。3.3 detection.py 的模型加载、推理参数与前向输出detection.py里加载两个模型的套路值得直接复用。它没有在每次推理时重新实例化模型而是在模块加载时缓存在内存中避免 Streamlit 点击按钮时重复 load 权重吃掉几秒钟。from ultralytics import YOLO players_model YOLO(models/Yolo8L Players.pt) pitch_model YOLO(models/Yolo8M Field Keypoints.pt) def run_detection(conf0.25, iou0.45, imgsz1280, sourcedemo_vid_1.mp4): results_players players_model.predict( sourcesource, confconf, iouiou, imgszimgsz, classes[0, 1, 2], devicecuda:0 ) results_pitch pitch_model.predict( sourcesource, conf0.15, imgszimgsz, devicecuda:0 ) return results_players, results_pitch注意第二次调用pitch_model.predict时的conf我设成了 0.15 而不是默认的 0.25。关键点模型的输出是坐标和置信度没有类别竞争的问题阈值太高会把低置信度的关键点直接过滤掉导致透视变换缺角。核心参数几个维度conf控制误检和漏检的平衡越低召回越高但会引入噪声框iou作用于 NMS密集场景下调高到 0.5 以上能减少相邻球员框的互相抑制imgsz影响小目标召回率显存只有 6G 时用 1280 可能爆显存可以降到 960。设备选择上devicecuda:0会隐式要求显存充足若机器没有 N 卡就把该项改为cpu。classes[0, 1, 2]在这里其实是显式地限定只从预训练类别中取这三类防止权重里混入其他类别的输出。如果你的模型是从零训的这段可以去掉否则会对类别索引造成约束。4. 坐标到战术地图透视变换、球队配色聚类与完整检测链路4.1 从视频画面到战术俯视图的透视变换拿到球员在画面上的像素坐标还不够要把它们投影到tactical map.jpg这种规整的俯视图上关键一步是求单应性矩阵。项目用cv2.getPerspectiveTransform配合球场关键点输出计算变换矩阵核心思路是原画面中的场地边界在中线和禁区线的交点上取 4 个“源点”在战术地图上取对应的 4 个“目标点”得到一个 3×3 的透视变换矩阵之后所有检测到的球员坐标都用这个矩阵投射过去融合成身位俯视图。这一步的实现可以直接嵌入detection.py的坐标后处理段。import cv2 import numpy as np def project_to_tactical_map(src_pts_img, map_size(1920, 1080)): # 源点取自 pitch 模型输出的关键点例如中圈左右端点、上下禁区角 src_pts np.array( [ src_pts_img[mid_circle_left], src_pts_img[mid_circle_right], src_pts_img[box_top_left], src_pts_img[box_bottom_right], ], dtypenp.float32, ) dst_pts np.array( [ [map_size[0] * 0.35, map_size[1] * 0.5], [map_size[0] * 0.65, map_size[1] * 0.5], [map_size[0] * 0.15, map_size[1] * 0.2], [map_size[0] * 0.85, map_size[1] * 0.8], ], dtypenp.float32, ) matrix cv2.getPerspectiveTransform(src_pts, dst_pts) return matrixcv2.getPerspectiveTransform要求输入恰好 4 对点少于 4 对会直接报错所以关键点模型必须先输出完整场地拓扑。dst 点我按战术地图的比例放位置保证变换后的画面和地图的长宽结构一致。这段代码最容易被忽略的是src_pts必须真的落在同一个平面上取点时如果选到了画面里因为镜头畸变而弯曲的边线位置透视变换的误差会从中心向边缘扩散表现为球员在地图上的位置和实际站位偏差越来越大。4.2 球队颜色聚类HSV 空间下的 KMeans项目里“团队颜色”Tab 实现的是主客队自动分离。很直观的想法是按 RGB 空间聚类但 RGB 三个通道对光照变化非常敏感同一支球队在不同光照下的色差可能比两支球队的色差还大。常见做法是先把检测到的球员区域从 BGR 转到 HSV然后对色相 H 通道做聚类同时用饱和度和明度排除阴影和过曝区域。from sklearn.cluster import KMeans def predict_team(player_crops_bgr, n_clusters2): hsv_list [] for crop in player_crops_bgr: hsv cv2.cvtColor(crop, cv2.COLOR_BGR2HSV) h, s, v cv2.split(hsv) # 过滤掉低饱和度的背景和阴影像素 mask (s 30) (v 40) if mask.sum() 0: continue hsv_list.append(h[mask].reshape(-1, 1)) if len(hsv_list) n_clusters: return None features np.vstack(hsv_list).astype(np.float32) km KMeans(n_clustersn_clusters, random_state0).fit(features) return km.cluster_centers_.flatten()聚类的输入不是整张图而是每个球员边界框内的像素。n_clusters 固定为 2 隐含了一个假设画面里只有主客两队。如果视频里出现第三支队伍或者裁判服大面积入镜聚类结果就会把裁判也归到某一队。所以主界面上专门有一个“手动指定”选项自动聚类效果不理想时可以用 st.color_picker 手动给主客队上色这一步是纯 UI 上的兜底方案。4.3 完整检测与跟踪链路在 demo_vid 上的复现detection.py把检测、聚类、透视变换串起来后输出结果统一落在outputs/目录。这里用一个简化版的链路伪代码展示数据流向。def run_pipeline(video_pathdemo_vid_1.mp4): cap cv2.VideoCapture(video_path) matrix None while cap.isOpened(): ret, frame cap.read() if not ret: break dets players_model.predict(frame, conf0.25, imgsz1280)[0] kps pitch_model.predict(frame, conf0.15, imgsz1280)[0] if matrix is None: matrix project_to_tactical_map(kps) team predict_team(crop_player_boxes(frame, dets.boxes.xyxy)) render_frame draw_detections_and_map(frame, dets, team, matrix) writer.write(render_frame) cap.release() writer.release()matrix is None这个判断是个小技巧单应性矩阵只依赖场地关键点视频播放期间场地不动矩阵算一次缓存即可不需要逐帧重算。predict_team建议每隔 30 帧做一次而不是逐帧聚类因为球员在高速跑动时衣服形变和人眼遮挡会让聚类结果在不同帧之间跳变。如果你要加入真正的跟踪而不是只做逐帧检测可以在dets之后挂一个ByteTrack或botsort用边界框中心点的距离和 IoU 做跨帧关联这时矩阵计算和团队聚类逻辑不变只是输出里多一个 track_id 字段。4.4 常见误用与边界条件最容易踩的坑有三个。第一拿cv2.getPerspectiveTransform直接处理包含大面积非线性畸变的广角镜头画面这类画面透视模型不成立投影结果会整体漂移建议先用cv2.undistort做畸变校正再加单应性。第二对每个球员 crop 区域单独做阈值分割再聚类如果球员在画面里只占十几个像素分割出来的是噪声而不是球衣颜色聚类结果基本随机。第三只按比赛视频的某一帧做颜色聚类然后整个视频都用这套颜色遇到两队球衣颜色接近的中后段比赛分类准确率会断崖式下降更稳的做法是每隔一段视频都重新聚类一次。哪一步有问题从输出的渲染视频里能直接看出来框和名字对不上是聚类问题框在地图上的点不在边线范围内是透视变换问题根本没有框是检测阈值问题。5. 验证与踩坑环境检查、FP16/批次推理与输出自检先验证环境是否真的可用。加载两个权重时不应只看模型文件是否存在而是要验证类别名是否正确输出尤其是 players 模型的 names 顺序。命令如下python -c from ultralytics import YOLO m YOLO(models/Yolo8L Players.pt) print(m.names) 打印结果应是{0: player, 1: referee, 2: football}。如果 key 混乱或names缺 key说明权重和 yaml 不匹配重装ultralytics到8.0.136或重新下载权重。pitch 模型同理应打印 16 个关键点名称。这一步能过滤掉绝大多数“模型加载失败”和“类别错位”问题。推理性能方面用 FP16 半精度推理是性价比最高的加速方式。显存只有 6G 的卡比如 GTX 1660 Ti用 FP32 跑 L 模型 1280 分辨率单帧显存约 3.2G此时把predict参数加上halfTrue显存占用能压到 2G 左右推理时间从 180ms 降到 140ms 上下。但注意关键点模型不建议轻易开 FP16球场关键点的定位对数值精度更敏感半精度在边缘场景下会引入 0.51 个像素的抖动对后续透视变换反而是副作用。批次推理也要克制。视频处理时可以攒 batch 提升 GPU 利用率常规做法是batch4在输入端把连续 4 帧一起送进模型。但 Streamlit 交互页面上单帧预览时开 batch 反而拖慢首帧延迟。之前的验证数据是batch1 时 1080p 视频实测约 6.5 FPSbatch4 提升到 9 FPS再往上加 batch 显存会先撑不住。输出自检时优先看outputs/下有没有生成渲染视频和坐标导出文件。取一帧把球员的检测框中心点和投影到战术地图上的点同时画出来对比demo_vid_1.mp4原始画面和地图位置是否在同一水平线附近。球员在地图上的投影点若整体偏移超过 10 像素回去检查src_pts的选点是否贴合中线与禁区线的实际位置。多个目标在地图上挤成一团时先确认检测框本身没重叠再检查是不是聚类把两队分成了同一颜色。逐帧过一遍输出视频按“检测框 → 聚类颜色 → 地图坐标”三个维度依次排除基本一轮就能定位到具体问题模块。若outputs/为空先检查工作目录下 demo 视频路径是否存在再确认predict的source参数指向的是本地路径而不是未下载的 URL。对比一下demo_vid_1.mp4与outputs/下的渲染视频若投影点的漂移超过 10 像素优先检查透视变换的 src 选点是否贴合中线与禁区线。本文还有配套的精品资源点击获取
返回列表