tennis_analysis

repository·main·Indexed 21 days ago

https://github.com/abdullahtarek/tennis_analysis

A computer vision project for analyzing tennis matches from video. It utilizes YOLO v8, CNNs, and PyTorch to automate the detection of players, the ball, and court keypoints. The pipeline calculates performance metrics such as player speed, ball shot speed, and total shot counts, and includes tools for coordinate transformation via a MiniCourt representation and custom model training for ball detection and court keypoint extraction.

Tokens
5.6K
Snippets
16
Records
20
Agent score
75%

What's inside tennis_analysis

  1. Overview of Tennis Analysis capabilities

    main

    The Tennis Analysis project processes video footage to extract performance metrics for tennis players. It uses a combination of computer vision models to:

    1. Detect Players and Ball: Uses YOLO v8 and a fine-tuned YOLO model to track players and the tennis ball.
    2. Extract Court Keypoints: Uses CNNs to identify key points on the tennis court.
    3. Measure Metrics: Calculates player speed, ball shot speed, and the total number of shots.
  2. Train custom tennis models

    main

    If you need to retrain the detection or keypoint models, the project provides Jupyter notebooks for training:

    • Tennis ball detector (YOLO): Use training/tennis_ball_detector_training.ipynb
    • Tennis court keypoints (PyTorch): Use training/tennis_court_keypoints_training.ipynb
  3. Install requirements for Tennis Analysis

    main

    To run the tennis analysis project, ensure you have Python 3.8 installed and the following libraries available in your environment:

    • python3.8
    • ultralytics (for YOLO models)
    • pytorch
    • pandas
    • numpy
    • opencv
    pip install ultralytics torch pandas numpy opencv-python
  4. Prepare the Tennis Court Keypoints Dataset

    main

    To train the model, you must first download and extract the dataset. The dataset contains images and a JSON file mapping image IDs to keypoint coordinates.

    Note: The provided wget command contains specific session cookies and headers for Google Drive; you may need to update these if the download link expires or if you are using a different authentication method.

    # Download the dataset
    !wget --header="..." "https://drive.usercontent.google.com/download?id=1lhAaeQCmk2y440PmagA0KmIVBIysVMwu&..." -c -O 'tennis_court_det_dataset.zip'
    
    # Unzip the dataset
    !unzip tennis_court_det_dataset.zip
  5. Run the tennis analysis pipeline

    main

    The main.py script serves as the primary entrypoint for the tennis analysis pipeline. It orchestrates the following workflow:

    1. Video Ingestion: Reads video frames using read_video.
    2. Detection & Tracking: Uses PlayerTracker and BallTracker to detect and track players and the ball. It supports loading detections from stubs (e.g., .pkl files) to skip detection steps during development.
    3. Court Analysis: Uses CourtLineDetector to identify court keypoints.
    4. Player Filtering: Filters detected players based on court keypoints using player_tracker.choose_and_filter_players.
    5. Coordinate Transformation: Uses MiniCourt to convert bounding box detections into normalized mini-court coordinates.
    6. Shot & Speed Analysis: Identifies ball shots, calculates ball speed (km/h), and determines which player hit the ball.
    7. Visualization: Draws bounding boxes, court keypoints, mini-court overlays, and player statistics onto the video frames.
    8. Output: Saves the processed video using save_video.
    # To run the full pipeline, ensure you have the required models in your models/ directory
    # and input videos in input_videos/
    
    from main import main
    
    if __name__ == "__main__":
        main()
  6. Detect ball hits in trajectory data

    main

    A ball hit is identified by detecting rapid changes in the vertical direction (sign changes in delta_y) that persist over a specific number of frames.

    1. Calculate delta_y using the difference of the smoothed mid_y_rolling_mean.
    2. Iterate through the frames to find points where the direction of vertical movement flips (positive to negative or vice versa).
    3. Verify if these direction changes occur frequently enough within a window defined by minimum_change_frames_for_hit to qualify as a hit.
    4. Mark the hit frame in the ball_hit column with a value of 1.
    # Calculate vertical velocity/change
    df_ball_positions['delta_y'] = df_ball_positions['mid_y_rolling_mean'].diff()
    
    # Initialize hit column
    df_ball_positions['ball_hit'] = 0
    
    # Detection logic
    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 and df_ball_positions['delta_y'].iloc[i+1] < 0
        positive_position_change = df_ball_positions['delta_y'].iloc[i] < 0 and df_ball_positions['delta_y'].iloc[i+1] > 0
    
        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 and df_ball_positions['delta_y'].iloc[change_frame] < 0
                positive_position_change_following_frame = df_ball_positions['delta_y'].iloc[i] < 0 and df_ball_positions['delta_y'].iloc[change_frame] > 0
    
                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.iloc[i, df_ball_positions.columns.get_loc('ball_hit')] = 1
    
    # Retrieve indices of detected hits
    frame_nums_with_ball_hits = df_ball_positions[df_ball_positions['ball_hit'] == 1].index.tolist()
  7. Download the tennis ball detection dataset from Roboflow

    main

    Use the roboflow Python package to download the specific version of the tennis ball detection dataset in YOLOv5 format. You will need a Roboflow API key to authenticate.

    from roboflow import Roboflow
    rf = Roboflow(api_key="YOUR_API_KEY")
    project = rf.workspace("viren-dhanwani").project("tennis-ball-detection")
    version = project.version(6)
    dataset = version.download("yolov5")
    from roboflow import Roboflow
    rf = Roboflow(api_key="3205MH29k2z3u5Ejc3HU")
    project = rf.workspace("viren-dhanwani").project("tennis-ball-detection")
    version = project.version(6)
    dataset = version.download("yolov5")
  8. Train the keypoint detection model

    main

    Training is performed using Mean Squared Error (MSELoss) and the Adam optimizer. The training loop iterates through the DataLoader, performing forward passes, calculating loss, and updating weights via backpropagation.

    After training, the model weights can be saved using torch.save.

    import torch
    
    # Setup
    criterion = torch.nn.MSELoss()
    optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)
    epochs = 20
    
    # Training Loop
    for epoch in range(epochs):
        for i, (imgs, kps) in enumerate(train_loader):
            imgs = imgs.to(device)
            kps = kps.to(device)
    
            optimizer.zero_grad()
            outputs = model(imgs)
            loss = criterion(outputs, kps)
            loss.backward()
            optimizer.step()
    
            if i % 10 == 0:
                print(f"Epoch {epoch}, iter {i}, loss: {loss.item()}")
    
    # Save the trained model
    torch.save(model.state_dict(), "keypoints_model.pth")
  9. Train the tennis ball detector using YOLO

    main

    Once the dataset is downloaded and organized, you can initiate training using the YOLO CLI. The training command requires the task type (detect), the mode (train), the pre-trained model weights, the path to the data.yaml file provided by the dataset, the number of epochs, and the image size (imgsz).

    !yolo task=detect mode=train model=yolov5l6u.pt data={dataset.location}/data.yaml epochs=100 imgsz=640