defget_choose_player_ids(self, court_keypoints, player_dict_from_first_frame): print("get choose player ids...") distances = [] for track_id, bbox in player_dict_from_first_frame.items(): player_center = get_center_of_bbox(bbox)
min_distance = float('inf') court_keypoints_len = len(court_keypoints) for i in range(0, court_keypoints_len, 2): court_keypoint = (court_keypoints[i], court_keypoints[i + 1]) distance = measure_distance(player_center, court_keypoint) if distance < min_distance: min_distance = distance distances.append((track_id, min_distance)) # sort the distances in ascending order distances.sort(key=lambda x: x[1]) return [i[0] for i in distances[:2]]
2.网球检测与标定
ball detect model 基于 yolov5l6u pretrain model 进行 fine tune,数据集使用 tennis-ball-detection,包含576张1280x720的网球比赛图片,跑100个epoch,结果如下:
definterpolate_ball_positions(self, ball_positions): """ :param ball_positions: [{}, {1: [895.4949340820312, 613.216064453125, 909.893798828125, 637.4019165039062]}, {}...] :return: """ ball_positions = [position.get(1, []) for position in ball_positions] # convert the list into pandas dataframe df_ball_positions = pd.DataFrame(ball_positions, columns=['x1', 'y1', 'x2', 'y2']) # interpolate the missing values df_ball_positions = df_ball_positions.interpolate() df_ball_positions = df_ball_positions.bfill() # fill the first missing value ball_positions = [{1: result} for result in df_ball_positions.to_numpy().tolist()]
return ball_positions
插值后的结果:
4.keypoints的检测
keypoints detect model 基于 Resnet50,使用pytorch搭建,数据集使用TennisCourtDetector里提供的链接,包含8841张各式网球场的图片
# min court keypoint has the same order as the court keypoint closest_mini_court_keypoint = get_key_point_by_index(closest_court_key_point_index, self.drawing_key_points)
ball_positions = [position.get(1, []) for position in ball_positions] # convert the list into pandas dataframe df_ball_positions = pd.DataFrame(ball_positions, columns=['x1', 'y1', 'x2', 'y2']) # interpolate the missing values df_ball_positions = df_ball_positions.interpolate() df_ball_positions = df_ball_positions.bfill() # fill the first missing value
minimum_change_frames_for_hit = 25 for i in range(1, len(df_ball_positions) - int(minimum_change_frames_for_hit * 1.2)): negative_position_change = df_ball_positions['delta_y'].iloc[i] > 0 > df_ball_positions['delta_y'].iloc[ i + 1] positive_position_change = df_ball_positions['delta_y'].iloc[i] < 0 < df_ball_positions['delta_y'].iloc[ i + 1]
if negative_position_change or positive_position_change: change_count = 0 for change_frame in range(i + 1, i + int(minimum_change_frames_for_hit * 1.2) + 1): negative_position_change_following_frame = df_ball_positions['delta_y'].iloc[i] > 0 > \ df_ball_positions['delta_y'].iloc[change_frame] positive_position_change_following_frame = df_ball_positions['delta_y'].iloc[i] < 0 < \ df_ball_positions['delta_y'].iloc[change_frame]
if negative_position_change and negative_position_change_following_frame: change_count += 1 elif positive_position_change and positive_position_change_following_frame: change_count += 1
if change_count > minimum_change_frames_for_hit - 1: df_ball_positions.loc[df_ball_positions.index[i], 'ball_hit'] = 1
7.数据看板(stats dashboard)
确定击球帧后,通过定位两个击球帧之间,player和ball的状态变化量,就可以计算各种物理量了
(1)计算球的飞行时间
1 2 3 4
# Get ball shot time start_frame = ball_shot_frames[ball_shot_index] end_frame = ball_shot_frames[ball_shot_index + 1] ball_shot_time_in_seconds = (end_frame - start_frame) / constants.FPS
# Speed of the ball shot in km/h speed_of_ball_shot = distance_covered_by_ball_meters / ball_shot_time_in_seconds * 3.6
(4)谁击中的球
思路:球被击中那一刻,谁离球最近,就是击球者
1 2 3 4
# player who hit the ball player_positions = player_mini_court_detections[start_frame] ball_position = ball_mini_court_detections[start_frame][1] player_shot_ball = get_closest_player_by_point(player_positions, ball_position)