基于YOLO算法实现网球运动实时分析(附源码)

e94c72d411790c3e7b9f80c07cfd0bad.gif

大家好,我是小F~

今天给大家介绍一个计算机视觉实战的项目。

该项目使用YOLO算法检测球员和网球,并利用cnn提取球场关键点。

进而分析视频中的网球运动员,测量他们的速度、击球速度和击球次数。

ac215435d724f6066b7df6db9a514c1f.gif

使用win10电脑,Python 3.9.7,相关依赖版本如下。

numpy==1.22.4
opencv_python==4.8.0.74
pandas==2.2.2
torch==2.0.1
torchvision==0.15.2
ultralytics==8.0.178

可以使用conda创建Python环境,然后执行主程序。

电脑无需GPU,普通CPU电脑即可~

# 创建Python环境
conda create --name tennis_analysis  python=3.9.7
# 激活环境
conda activate tennis_analysis
# 安装依赖
pip install -r requirements.txt -i https://mirror.baidu.com/pypi/simple# 执行程序
python main.py

主程序代码如下。

from utils import (read_video,save_video,measure_distance,draw_player_stats,convert_pixel_distance_to_meters)
import constants
from trackers import PlayerTracker, BallTracker
from court_line_detector import CourtLineDetector
from mini_court import MiniCourt
import cv2
import pandas as pd
from copy import deepcopydef main():# Read Videoinput_video_path = "input_videos/input_video.mp4"video_frames = read_video(input_video_path)# Detect Players and Ballplayer_tracker = PlayerTracker(model_path='yolov8x')ball_tracker = BallTracker(model_path='models/yolo5_last.pt')player_detections = player_tracker.detect_frames(video_frames,read_from_stub=True,stub_path="tracker_stubs/player_detections.pkl")ball_detections = ball_tracker.detect_frames(video_frames,read_from_stub=True,stub_path="tracker_stubs/ball_detections.pkl")ball_detections = ball_tracker.interpolate_ball_positions(ball_detections)# Court Line Detector modelcourt_model_path = "models/keypoints_model.pth"court_line_detector = CourtLineDetector(court_model_path)court_keypoints = court_line_detector.predict(video_frames[0])# choose playersplayer_detections = player_tracker.choose_and_filter_players(court_keypoints, player_detections)# MiniCourtmini_court = MiniCourt(video_frames[0])# Detect ball shotsball_shot_frames = ball_tracker.get_ball_shot_frames(ball_detections)# Convert positions to mini court positionsplayer_mini_court_detections, ball_mini_court_detections = mini_court.convert_bounding_boxes_to_mini_court_coordinates(player_detections,ball_detections,court_keypoints)player_stats_data = [{'frame_num': 0,'player_1_number_of_shots': 0,'player_1_total_shot_speed': 0,'player_1_last_shot_speed': 0,'player_1_total_player_speed': 0,'player_1_last_player_speed': 0,'player_2_number_of_shots': 0,'player_2_total_shot_speed': 0,'player_2_last_shot_speed': 0,'player_2_total_player_speed': 0,'player_2_last_player_speed': 0,}]for ball_shot_ind in range(len(ball_shot_frames) - 1):start_frame = ball_shot_frames[ball_shot_ind]end_frame = ball_shot_frames[ball_shot_ind + 1]ball_shot_time_in_seconds = (end_frame - start_frame) / 24  # 24fps# Get distance covered by the balldistance_covered_by_ball_pixels = measure_distance(ball_mini_court_detections[start_frame][1],ball_mini_court_detections[end_frame][1])distance_covered_by_ball_meters = convert_pixel_distance_to_meters(distance_covered_by_ball_pixels,constants.DOUBLE_LINE_WIDTH,mini_court.get_width_of_mini_court())# Speed of the ball shot in km/hspeed_of_ball_shot = distance_covered_by_ball_meters / ball_shot_time_in_seconds * 3.6# player who the ballplayer_positions = player_mini_court_detections[start_frame]player_shot_ball = min(player_positions.keys(),key=lambda player_id: measure_distance(player_positions[player_id],ball_mini_court_detections[start_frame][1]))# opponent player speedopponent_player_id = 1 if player_shot_ball == 2 else 2distance_covered_by_opponent_pixels = measure_distance(player_mini_court_detections[start_frame][opponent_player_id],player_mini_court_detections[end_frame][opponent_player_id])distance_covered_by_opponent_meters = convert_pixel_distance_to_meters(distance_covered_by_opponent_pixels,constants.DOUBLE_LINE_WIDTH,mini_court.get_width_of_mini_court())speed_of_opponent = distance_covered_by_opponent_meters / ball_shot_time_in_seconds * 3.6current_player_stats = deepcopy(player_stats_data[-1])current_player_stats['frame_num'] = start_framecurrent_player_stats[f'player_{player_shot_ball}_number_of_shots'] += 1current_player_stats[f'player_{player_shot_ball}_total_shot_speed'] += speed_of_ball_shotcurrent_player_stats[f'player_{player_shot_ball}_last_shot_speed'] = speed_of_ball_shotcurrent_player_stats[f'player_{opponent_player_id}_total_player_speed'] += speed_of_opponentcurrent_player_stats[f'player_{opponent_player_id}_last_player_speed'] = speed_of_opponentplayer_stats_data.append(current_player_stats)player_stats_data_df = pd.DataFrame(player_stats_data)frames_df = pd.DataFrame({'frame_num': list(range(len(video_frames)))})player_stats_data_df = pd.merge(frames_df, player_stats_data_df, on='frame_num', how='left')player_stats_data_df = player_stats_data_df.ffill()player_stats_data_df['player_1_average_shot_speed'] = player_stats_data_df['player_1_total_shot_speed'] / \player_stats_data_df['player_1_number_of_shots']player_stats_data_df['player_2_average_shot_speed'] = player_stats_data_df['player_2_total_shot_speed'] / \player_stats_data_df['player_2_number_of_shots']player_stats_data_df['player_1_average_player_speed'] = player_stats_data_df['player_1_total_player_speed'] / \player_stats_data_df['player_2_number_of_shots']player_stats_data_df['player_2_average_player_speed'] = player_stats_data_df['player_2_total_player_speed'] / \player_stats_data_df['player_1_number_of_shots']# Draw output## Draw Player Bounding Boxesoutput_video_frames = player_tracker.draw_bboxes(video_frames, player_detections)output_video_frames = ball_tracker.draw_bboxes(output_video_frames, ball_detections)## Draw court Keypointsoutput_video_frames = court_line_detector.draw_keypoints_on_video(output_video_frames, court_keypoints)# Draw Mini Courtoutput_video_frames = mini_court.draw_mini_court(output_video_frames)output_video_frames = mini_court.draw_points_on_mini_court(output_video_frames, player_mini_court_detections)output_video_frames = mini_court.draw_points_on_mini_court(output_video_frames, ball_mini_court_detections,color=(0, 255, 255))# Draw Player Statsoutput_video_frames = draw_player_stats(output_video_frames, player_stats_data_df)## Draw frame number on top left cornerfor i, frame in enumerate(output_video_frames):cv2.putText(frame, f"Frame: {i}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)save_video(output_video_frames, "output_videos/output_video.avi")if __name__ == "__main__":main()

最终可以在output_videos文件夹下看到结果。

9e796e09a6fde183cca899fd82e1ff54.png

结果如下所示。

145bb8c4bda7bcf194eda68a0b796ff1.png

这里还提供了网球模型训练的代码,大家可以使用Colab或Kaggle的免费GPU进行训练。

感兴趣的小伙伴,可以自行去学习下~

02b7ec9a500caba8bc58819b6c392885.png

项目源码,公众号后台回复:「网球分析」,即可获得。

万水千山总是情,点个 👍 行不行

推荐阅读

320554683c0cfed55272b40b6c2a5496.jpeg

151a0abceb004e46d346cde0fff18ad1.jpeg

2901b93a9c4c05c93aadd47dba41aeb3.jpeg

···  END  ···

7fdc980d1452027cbbf5e40cf3578f53.jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/16523.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【源码】java + uniapp交易所源代码/带搭建教程java交易所/完整源代码

java uniapp交易所源代码/带搭建教程java交易所/完整源代码 带简洁教程,未测 java uniapp交易所源代码/带搭建教程java交易所/完整源代码 - 吾爱资源网

【古董技术】ms-dos应用程序的结构

序 制定一个MS-DOS应用程序计划需要认真分析程序的大小。这种分析可以帮助程序员确定MS-DOS支持的两种程序风格中哪一种最适合该应用程序。.EXE程序结构为大型程序提供了好处,因为所有.EXE文件之前都有额外的512字节(或更多)的文件头。另一方…

C++第十七弹---string使用(下)

✨个人主页: 熬夜学编程的小林 💗系列专栏: 【C语言详解】 【数据结构详解】【C详解】 目录 1、标准库中的string类 1.1、string类的常用接口说明 1.1.1、string类对象的修改操作 1.1.2、string类对象非成员函数重载 总结 1、标准库中的…

牛客热题:有效括号

📟作者主页:慢热的陕西人 🌴专栏链接:力扣刷题日记 📣欢迎各位大佬👍点赞🔥关注🚓收藏,🍉留言 文章目录 牛客热题:有效括号题目链接方法一&#x…

MySQL视图教程(01):创建视图

MySQL 创建视图 在 MySQL 中, CREATE VIEW 语句用于创建一个数据库视图(View)。 MySQL 是一种常用的关系型数据库管理系统,提供了 CREATE VIEW 语法,用于创建视图(View)。视图是一种虚拟的表&…

Mycat+Mysql搭建数据集群实现数据分片存储

前言 MyCAT介绍 * 一个彻底开源的,面向企业应用开发的“大数据库集群”; * 支持事务、ACID、可以替代MySQL的加强版数据库; * 一个可以视为“MySQL”集群的企业级数据库,用来替代昂贵的Oracle集群; * 一个融合内存缓存技术、Nosql技术、HDFS大数据的新型SQL; * 一个新颖…

QCC---DFU升级变更设备名和地址

QCC---DFU升级变更设备名和地址 这个很多人碰到这个疑问,升级了改不了设备名和地址 /******************************************************************************* Copyright (c) 2018 Qualcomm Technologies International, Ltd. FILE NAME sink_dfu_ps.c DESCRIPT…

2024.5.1学习记录

1、代码随想录:贪心刷题 2、react 高级使用( hoc render、props、函数组件、serState 传送门等) 3、游山玩水

《拯救大学生课设不挂科第四期之蓝桥杯是什么?我是否要参加蓝桥杯?选择何种语言?如何科学备赛?方法思维教程》【官方笔记】

背景: 有些同学在大一或者大二可能会被老师建议参加蓝桥杯,本视频和文章主要是以一个过来人的身份来给与大家一些思路。 比如蓝桥杯是什么?我是否要参加蓝桥杯?参加蓝桥杯该选择何种语言?如何科学备赛?等…

2023年信息素养大赛小学组C++智能算法复赛试题解析

2023年信息素养大赛小学组C++智能算法复赛真题 智能算法挑战复赛小学组(总共4道题)T1. 判断数字出现了几次 【题目描述】 给定一个正整数 n,判断从 1 到这个数本身的所有数中,一共出现了多少次数字k。 【输入格式】 输入共1行,包括一个正整数n和一个正整数k。(0<n<…

JavaEE之线程(7)_单例模式(设计模式概念、单例模式优点、懒汉、饿汉模式)

一、什么是设计模式&#xff1f; 单例模式是设计模式中较为常见的一种。那么&#xff0c;什么是单例模式&#xff1f; 设计模式&#xff08;Design Pattern&#xff09;都是一些相对优秀的解决方案&#xff0c;很多问题都是典型的、有代表性的问题&#xff0c;学习设计模式&am…

C#面:如果出现ASP.NET中的事件不能触发可能由于什么原因造成

当 ASP.NET 中的事件不能触发时&#xff0c;可能由以下几个原因造成&#xff1a; 事件绑定错误&#xff1a;请确保事件正确地绑定到相应的控件上。在 ASP.NET 中&#xff0c;可以通过在前端代码或者后端代码中使用事件处理程序来绑定事件。如果事件没有正确地绑定到控件上&…

为什么本科毕业后我坚定地选择了就业而不是考研?

大家好&#xff0c;我是小布丁。今天来聊聊我为什么本科毕业后选择了就业而不是考研。 在整个大学期间&#xff0c;我被亲戚拷问最多的问题就是&#xff1a;准备考研吗&#xff1f;相信很多大学生都遇到过这种情况吧。 如果你说准备还好&#xff0c;亲戚大概率就不会问下去&a…

js计算字符串大小存储所占字节数

在JavaScript中&#xff0c;计算字符串所占的大小&#xff08;占用的字节数&#xff09;并不直接&#xff0c;但可以通过一些方法间接得到。 我们需要知道一个前提&#xff0c;英文字母 lenght 和字节数是一样的&#xff1a;都是1&#xff0c;而中文 lenght1&#xff0c;字节数…

golang sqlite主从数据同步插件开发

### golang sqlite主从数据同步插件开发思路 参考Mysql的主从同步机制&#xff0c;Mysql是产生binlog&#xff0c;然后把binlog日志同步到从服务上。 同理&#xff0c;我们按sql执行顺序记录所有的增删改查的sql语句&#xff0c;然后调用接口把sql语句传到从服务上执行。 数…

关于软件设计模式的理解

系列文章 关于时间复杂度o(1), o(n), o(logn), o(nlogn)的理解 关于HashMap的哈希碰撞、拉链法和key的哈希函数设计 关于JVM内存模型和堆内存模型的理解 关于代理模式的理解 关于Mysql基本概念的理解 关于软件设计模式的理解 文章目录 前言一、软件设计模式遵循的六大原则…

前端面试题日常练-day35 【面试题】

题目 希望这些选择题能够帮助您进行前端面试的准备&#xff0c;答案在文末。 1. 以下哪个是使用jQuery选择所有具有CSS类名"myClass"的元素的正确语法&#xff1f; a) $(".myClass") b) $("myClass") c) $("#myClass") d) $("…

FURNet问题

1. 为什么选择使用弱监督学习&#xff1f; 弱监督学习减少了对精确标注数据的依赖&#xff0c;这在医学图像处理中尤为重要&#xff0c;因为高质量标注数据通常需要大量专业知识和时间。弱监督学习通过利用少量标注数据或粗略标注数据来训练模型&#xff0c;降低了数据准备的成…

元组推导式

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 使用元组推导式可以快速生成一个元组&#xff0c;它的表现形式和列表推导式类似&#xff0c;只是将列表推导式中的“[]”修改为“()”。例如&#xf…

python深入解析字符串操作的八大神技

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、字符串的长度与切片 示例代码 二、去除多余的空格 示例代码 三、字符串的开头与包含…