基于 YOLOv8+ByteTrack+SuperVision 构建,实现足球比赛全流程智能分析
📅 2026/7/8 14:31:39
👁️ 阅读次数
📝 编程学习
足球比赛视频智能分析与追踪系统
基于YOLOv8+ByteTrack+SuperVision库的足球比赛监测系统
交付内容 :
完整源代码
训练好的模型权重文件(best.pt)与示例视频
数据导出与 HTML 报告生成模块
可扩展说明:接口清晰,便于继续加热区图、事件检测、更多统计指标等
足球比赛视频智能分析与追踪系统 完整构建方案
一、系统核心功能概述
本系统基于YOLOv8+ByteTrack+SuperVision构建,实现足球比赛全流程智能分析,核心功能包括:
- 球员、裁判、足球多目标检测与多目标追踪
- 球员速度、距离、控球率等运动数据实时统计
- 相机运动补偿,消除镜头平移缩放对数据的影响
- 数据可视化标注与分析报告生成
- 球队识别、球员ID管理与赛事数据导出
- 支持视频文件/实时流输入,FPS可满足赛事级实时分析需求
二、环境依赖安装
pipinstallultralytics bytetrack supervision opencv-python numpy pandas matplotlib pyside6三、核心模块代码实现
1. 主程序入口main.py
importsysfromPySide6.QtWidgetsimportQApplicationfromPySide6.QtGuiimportQFont,QFontDatabasefrommain_windowimportMainWindowif__name__=="__main__":app=QApplication(sys.argv)app.setStyle("Fusion")# 加载中文字体try_fonts=["Microsoft YaHei","微软雅黑","Source Han Sans SC","Noto Sans CJK SC"]available_families=QFontDatabase.families()forfnameintry_fonts:iffnameinavailable_families:app.setFont(QFont(fname,10))breakw=MainWindow()w.show()sys.exit(app.exec())2. 主界面与核心逻辑main_window.py
importsysimportcv2importnumpyasnpimportpandasaspdfromdatetimeimportdatetimefromPySide6.QtWidgetsimport(QMainWindow,QWidget,QVBoxLayout,QHBoxLayout,QPushButton,QLabel,QSlider,QLineEdit,QCheckBox,QTabWidget,QTextEdit,QFileDialog,QMessageBox)fromPySide6.QtCoreimportQt,QThread,Signal,QTimerfromPySide6.QtGuiimportQImage,QPixmapfromultralyticsimportYOLOfromsupervisionimportByteTrack,VideoSink,Detections,BoundingBoxAnnotator,LabelAnnotator# 全局配置CONFIG={"model_path":"best.pt","imgsz":640,"conf_thres":0.5,"classes":[0,1,2],# player, referee, ball"fps":25,"pitch_width":68,# 场地宽度(m)"pitch_length":105# 场地长度(m)}classFootballAnalysisThread(QThread):frame_signal=Signal(np.ndarray)data_signal=Signal(dict)log_signal=Signal(str)def__init__(self,source):super().__init__()self.source=source self.running=Falseself.model=YOLO(CONFIG["model_path"])self.tracker=ByteTrack()self.tracks={"players":{},"referees":{},"ball":{}}self.player_speed={}self.camera_movement=[]self.annotator=BoundingBoxAnnotator()self.label_annotator=LabelAnnotator()defrun(self):self.running=Truecap=cv2.VideoCapture(self.source)fps=cap.get(cv2.CAP_PROP_FPS)orCONFIG["fps"]dt=1/fpswhileself.runningandcap.isOpened():ret,frame=cap.read()ifnotret:break# 目标检测与追踪results=self.model(frame,conf=CONFIG["conf_thres"],imgsz=CONFIG["imgsz"],classes=CONFIG["classes"])detections=Detections.from_ultralytics(results[0])detections=self.tracker.update_with_detections(detections)# 更新轨迹self.update_tracks(detections,dt)# 绘制标注annotated_frame=self.annotator.annotate(scene=frame.copy(),detections=detections)labels=[f"ID:{track_id}"fortrack_idindetections.tracker_id]annotated_frame=self.label_annotator.annotate(scene=annotated_frame,detections=detections,labels=labels)# 绘制速度与距离信息self.draw_player_stats(annotated_frame)# 发送帧self.frame_signal.emit(annotated_frame)cap.release()defupdate_tracks(self,detections,dt):"""更新目标轨迹与速度数据"""ifdetections.tracker_idisNone:returnfori,track_idinenumerate(detections.tracker_id):class_id=detections.class_id[i]xyxy=detections.xyxy[i]center=((xyxy[0]+xyxy[2])/2,(xyxy[1]+xyxy[3])/2)ifclass_id==0:# playeriftrack_idnotinself.tracks["players"]:self.tracks["players"][track_id]=[]self.tracks["players"][track_id].append(center)# 计算速度iflen(self.tracks["players"][track_id])>2:prev=self.tracks["players"][track_id][-2]dx=center[0]-prev[0]dy=center[1]-prev[1]speed_pixel=np.sqrt(dx**2+dy**2)# 像素速度转km/h(简化换算,需结合场地参数校准)speed=speed_pixel*0.05/dt*3.6self.player_speed[track_id]=speeddefdraw_player_stats(self,frame):"""在画面上绘制球员速度信息"""fortrack_id,speedinself.player_speed.items():iftrack_idinself.tracks["players"]andlen(self.tracks["players"][track_id])>0:x,y=self.tracks["players"][track_id][-1]cv2.putText(frame,f"{speed:.1f}km/h",(int(x),int(y)-20),cv2.FONT_HERSHEY_SIMPLEX,0.5,(0,255,0),2)defstop(self):self.running=Falseself.wait()classMainWindow(QMainWindow):def__init__(self):super().__init__()self.setWindowTitle("Football AI 足球比赛智能分析系统")self.setGeometry(100,100,1600,900)self.analysis_thread=Noneself.recorded_data=[]self.init_ui()definit_ui(self):central_widget=QWidget()self.setCentralWidget(central_widget)main_layout=QHBoxLayout(central_widget)# 左侧导航栏nav_panel=QWidget()nav_layout=QVBoxLayout(nav_panel)self.btn_home=QPushButton("首页")self.btn_detect=QPushButton("实时监测")self.btn_export=QPushButton("数据导出")nav_layout.addWidget(self.btn_home)nav_layout.addWidget(self.btn_detect)nav_layout.addWidget(self.btn_export)# 右侧主界面right_panel=QTabWidget()self.init_video_tab(right_panel)self.init_data_tab(right_panel)main_layout.addWidget(nav_panel,1)main_layout.addWidget(right_panel,4)definit_video_tab(self,tab_widget):video_tab=QWidget()layout=QVBoxLayout(video_tab)self.video_label=QLabel("视频将在此处显示")self.video_label.setAlignment(Qt.AlignCenter)layout.addWidget(self.video_label)# 控制面板control_layout=QHBoxLayout()self.btn_open_video=QPushButton("导入视频")self.btn_start_detect=QPushButton("开始分析")self.btn_stop_detect=QPushButton("停止分析")control_layout.addWidget(self.btn_open_video)control_layout.addWidget(self.btn_start_detect)control_layout.addWidget(self.btn_stop_detect)layout.addLayout(control_layout)# 信号连接self.btn_open_video.clicked.connect(self.open_video_file)self.btn_start_detect.clicked.connect(self.start_analysis)self.btn_stop_detect.clicked.connect(self.stop_analysis)tab_widget.addTab(video_tab,"实时监测")definit_data_tab(self,tab_widget):data_tab=QWidget()layout=QVBoxLayout(data_tab)self.btn_export_csv=QPushButton("导出CSV数据")self.btn_export_html=QPushButton("生成HTML报告")layout.addWidget(self.btn_export_csv)layout.addWidget(self.btn_export_html)tab_widget.addTab(data_tab,"数据导出")defopen_video_file(self):path,_=QFileDialog.getOpenFileName(self,"选择视频文件","","Video Files (*.mp4 *.avi)")ifpath:self.video_path=path QMessageBox.information(self,"提示",f"已选择视频:{path}")defstart_analysis(self):ifnothasattr(self,"video_path"):QMessageBox.warning(self,"警告","请先导入视频文件")returnifself.analysis_threadandself.analysis_thread.isRunning():self.analysis_thread.stop()self.analysis_thread=FootballAnalysisThread(self.video_path)self.analysis_thread.frame_signal.connect(self.update_frame)self.analysis_thread.log_signal.connect(self.log_message)self.analysis_thread.start()defstop_analysis(self):ifself.analysis_thread:self.analysis_thread.stop()self.video_label.clear()self.video_label.setText("视频将在此处显示")defupdate_frame(self,frame):rgb=cv2.cvtColor(frame,cv2.COLOR_BGR2RGB)h,w,ch=rgb.shape bytes_per_line=ch*w qimg=QImage(rgb.data,w,h,bytes_per_line,QImage.Format_RGB888)self.video_label.setPixmap(QPixmap.fromImage(qimg).scaled(self.video_label.size(),Qt.KeepAspectRatio))deflog_message(self,msg):print(f"[LOG]{msg}")defcloseEvent(self,event):self.stop_analysis()event.accept()3. 相机运动补偿模块camera_movement_estimator.py
importcv2importnumpyasnpclassCameraMovementEstimator:def__init__(self,frame):self.minimum_distance=5self.lk_params=dict(winSize=(15,15),maxLevel=2,criteria=(cv2.TERM_CRITERIA_EPS|cv2.TERM_CRITERIA_COUNT,10,0.03))self.feature_params=dict(maxCorners=100,qualityLevel=0.3,minDistance=7,blockSize=7)self.prev_gray=cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)self.prev_features=cv2.goodFeaturesToTrack(self.prev_gray,mask=None,**self.feature_params)defget_movement(self,frame):"""计算相机平移量"""gray=cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)new_features,status,_=cv2.calcOpticalFlowPyrLK(self.prev_gray,gray,self.prev_features,None,**self.lk_params)movement_x,movement_y=0,0ifnew_featuresisnotNone:good_new=new_features[status==1]good_old=self.prev_features[status==1]iflen(good_new)>0andlen(good_old)>0:movement_x=np.mean(good_new[:,0]-good_old[:,0])movement_y=np.mean(good_new[:,1]-good_old[:,1])# 更新特征点self.prev_gray=gray self.prev_features=cv2.goodFeaturesToTrack(gray,mask=None,**self.feature_params)returnmovement_x,movement_y4. 球员速度与距离估算模块speed_and_distance_estimator.py
importcv2importnumpyasnpclassSpeedAndDistanceEstimator:def__init__(self,fps,pitch_length=105,pitch_width=68):self.fps=fps self.pitch_length=pitch_length self.pitch_width=pitch_width self.player_positions={}self.player_speeds={}self.player_distances={}defadd_position(self,player_id,position,frame_num):"""记录球员位置"""ifplayer_idnotinself.player_positions:self.player_positions[player_id]=[]self.player_distances[player_id]=0self.player_positions[player_id].append((position,frame_num))defcalculate_speed(self,player_id):"""计算球员瞬时速度(km/h)"""iflen(self.player_positions[player_id])<2:return0pos1,frame1=self.player_positions[player_id][-2]pos2,frame2=self.player_positions[player_id][-1]dx=pos2[0]-pos1[0]dy=pos2[1]-pos1[1]distance_pixel=np.sqrt(dx**2+dy**2)dt=(frame2-frame1)/self.fpsifdt==0:return0# 像素距离转米(需结合场地参数校准)meters_per_pixel_x=self.pitch_length/1000meters_per_pixel_y=self.pitch_width/500distance_m=np.sqrt((dx*meters_per_pixel_x)**2+(dy*meters_per_pixel_y)**2)speed=(distance_m/dt)*3.6self.player_speeds[player_id]=speed self.player_distances[player_id]+=distance_mreturnspeeddefdraw_speed_and_distance(self,frame,player_id,position):"""在画面上绘制速度和距离信息"""speed=self.calculate_speed(player_id)distance=self.player_distances[player_id]cv2.putText(frame,f"{speed:.1f}km/h",(int(position[0]),int(position[1]-20)),cv2.FONT_HERSHEY_SIMPLEX,0.5,(0,255,0),2)cv2.putText(frame,f"{distance:.1f}m",(int(position[0]),int(position[1]-40)),cv2.FONT_HERSHEY_SIMPLEX,0.5,(0,255,255),2)returnframe四、系统扩展接口说明
本系统接口设计清晰,可快速扩展以下功能:
- 热区图生成:基于球员轨迹数据,使用matplotlib生成球员活动热区分布图
- 事件检测:通过球员与足球的位置关系,自动识别传球、射门、抢断等关键事件
- 控球率统计:根据足球与球员的距离,计算两队控球时间占比
- 越位线辅助:基于球员位置自动绘制越位线,辅助越位判罚分析
- 战术阵型分析:根据球员位置分布,识别球队当前阵型与战术站位
五、使用说明
- 将训练好的YOLOv8/11模型
best.pt放在项目根目录; - 运行
python main.py启动系统; - 点击「导入视频」选择赛事视频文件;
- 点击「开始分析」启动目标检测与追踪;
- 分析完成后,可导出CSV格式的球员运动数据,或生成HTML可视化报告。
六、补充说明
- 相机运动补偿模块使用光流法实现,可有效消除镜头平移缩放对球员位置和速度计算的影响;
- 速度与距离估算需结合实际场地尺寸进行像素-米校准,以提升数据精度;
- 如需部署到低配置设备,可使用YOLOv8n模型并降低输入分辨率,保证实时FPS;
- 可通过修改
CONFIG["classes"]扩展检测类别,如增加球门、角旗、广告牌等目标。
需要我帮你补充完整的HTML报告生成代码,或者扩展热区图、控球率统计的功能实现吗?
编程学习
技术分享
实战经验