Zephyrnet-logo

Analyseer en visualiseer gebeurtenissen met meerdere camera's met behulp van Amazon SageMaker Studio Lab

Datum:

De National Football League (NFL) is een van de meest populaire sportcompetities in de Verenigde Staten en dat is het ook de meest waardevolle sportcompetitie ter wereld. De NFL, BioCore en AWS zetten zich in om het menselijk begrip rond de diagnose, preventie en behandeling van sportgerelateerde blessures te vergroten om het voetbalspel veiliger te maken. Meer informatie over de inspanningen op het gebied van gezondheid en veiligheid van NFL-spelers is beschikbaar op de NFL-website.

De AWS professionele services team werkt samen met de NFL en Biocore om op machine learning (ML) gebaseerde oplossingen te bieden voor het identificeren van helmeffecten van gamebeelden met behulp van computer vision (CV) -technieken. Omdat er voor elke game meerdere camerabeelden beschikbaar zijn, hebben we oplossingen ontwikkeld om de impact van de helm uit elk van deze weergaven te identificeren en de resultaten van de impact van de helm samen te voegen.

De motivatie achter het gebruik van meerdere camerabeelden komt voort uit de beperking van informatie wanneer de impactgebeurtenissen met slechts één beeld worden vastgelegd. Met slechts één perspectief kunnen sommige spelers elkaar afsluiten of worden geblokkeerd door andere objecten op het veld. Door meer perspectieven toe te voegen, kan ons ML-systeem daarom meer effecten identificeren die niet zichtbaar zijn in één enkele weergave. Om de resultaten van ons fusieproces te laten zien en hoe het team visualisatietools gebruikt om de prestaties van het model te helpen evalueren, hebben we een codebase ontwikkeld om de resultaten van de detectie van meerdere weergaven visueel te overlappen. Dit proces helpt bij het identificeren van het werkelijke aantal effecten dat individuele spelers ervaren door dubbele effecten te verwijderen die in meerdere weergaven zijn gedetecteerd.

In dit bericht gebruiken we de openbaar beschikbare dataset van de NFL - Impact Detection Kaggle-competitie en laat resultaten zien voor het samenvoegen van twee weergaven. De dataset bevat helmgrenzen bij elk frame en impactlabels in elke video. We richten ons met name op het ontdubbelen en visualiseren van video's met de ID 57583_000082 in endzone- en zijlijnweergaven. U kunt de downloaden endzone- en zijlijnvideo's, en ook de grondwaarheidslabels.

Voorwaarden

De oplossing vereist het volgende:

Ga aan de slag op SageMaker Studio Lab en installeer de vereiste pakketten

U kunt het notitieblok uitvoeren vanuit het GitHub repository of van SageMaker Studio Lab. In dit bericht voeren we de notebook uit vanuit een SageMaker Studio Lab-omgeving. We kiezen voor SageMaker Studio Lab omdat het gratis is, krachtige CPU- en GPU-gebruikerssessies biedt en 15 GB permanente opslag die uw omgeving automatisch opslaat, zodat u verder kunt gaan waar u was gebleven. Om SageMaker Studio Lab te gebruiken, een nieuw account aanvragen en instellen. Nadat het account is goedgekeurd, voert u de volgende stappen uit:

  1. Bezoek de aws-samples GitHub-opslagplaats.
  2. In het README sectie, kies StudioLab openen.

sagemaker-studio-knop

Dit leidt u om naar uw SageMaker Studio Lab-omgeving.

  1. Selecteer uw CPU-rekentype en kies vervolgens Looptijd starten.
  2. Nadat de runtime is gestart, kiest u Kopiëren naar project, waarmee een nieuw venster wordt geopend met de Jupyter Lab-omgeving.

Nu ben je klaar om de notebook te gebruiken!

  1. Openen fuse_and_visualize_multiview_impacts.ipynb en volg de instructies in het notitieboek.

De eerste cel in de notebook installeert de benodigde Python-pakketten zoals panda's en OpenCV:

%pip install pandas
%pip install opencv-contrib-python-headless

Importeer alle benodigde Python-pakketten en stel panda-opties in voor een betere visualisatie-ervaring:

import os
import cv2
import pandas as pd
import numpy as np
pd.set_option('mode.chained_assignment', None)

We gebruiken panda's voor het opnemen en analyseren van het CSV-bestand met de geannoteerde begrenzingsvakken voor helmen en effecten. We gebruiken NumPy voornamelijk voor het manipuleren van arrays en matrices. We gebruiken OpenCV voor het lezen, schrijven en manipuleren van afbeeldingsgegevens in Python.

Bereid de gegevens voor door de resultaten van twee weergaven samen te voegen

Om de twee perspectieven samen te smelten, gebruiken we de train_labels.csv van de Kaggle-competitie als voorbeeld omdat het grondwaarheidseffecten bevat van zowel de endzone als de zijlijn. De volgende functie neemt de invoergegevensset en voert een gefuseerd gegevensframe uit dat wordt ontdubbeld voor alle toneelstukken in de invoergegevensset:

def prep_data(df): df['game_play'] = df['gameKey'].astype('str') + '_' + df['playID'].astype('str').str.zfill(6) return df def dedup_view(df, windows): # define view df = df.sort_values(by='frame') view_columns = ['frame', 'left', 'width', 'top', 'height', 'video'] common_columns = ['game_play', 'label', 'view', 'impactType'] label_cleaned = df[view_columns + common_columns] # rename columns sideline_column_rename = {col: 'Sideline_' + col for col in view_columns} endzone_column_rename = {col: 'Endzone_' + col for col in view_columns} sideline_columns = list(sideline_column_rename.values()) # create two dataframes, one for sideline, one for endzone label_endzone = label_cleaned.query('view == "Endzone"') label_endzone.rename(columns=endzone_column_rename, inplace=True) label_sideline = label_cleaned.query('view == "Sideline"') label_sideline.rename(columns=sideline_column_rename, inplace=True) # prepare sideline labels label_sideline['is_dup'] = False for columns in sideline_columns: label_endzone[columns] = np.nan label_endzone['is_dup'] = False # iterrate endzone rows to find matches and dedup for index, row in label_endzone.iterrows(): player = row['label'] frame = row['Endzone_frame'] impact_type = row['impactType'] sideline_row = label_sideline[(label_sideline['label'] == player) & ((label_sideline['Sideline_frame'] >= frame - windows // 2) & (label_sideline['Sideline_frame'] <= frame + windows // 2 + 1)) & (label_sideline['is_dup'] == False) & (label_sideline['impactType'] == impact_type)] if len(sideline_row) > 0: sideline_index = sideline_row.index[0] label_sideline['is_dup'].loc[sideline_index] = True for col in sideline_columns: label_endzone[col].loc[index] = sideline_row.iloc[0][col] label_endzone['is_dup'].loc[index] = True # calculate overlap perc not_dup_sideline = label_sideline[label_sideline['is_dup'] == False] final_output = pd.concat([not_dup_sideline, label_endzone]) return final_output def fuse_df(raw_df, windows): outputs = [] all_game_play = raw_df['game_play'].unique() for game_play in all_game_play: df = raw_df.query('game_play ==@game_play') output = dedup_view(df, windows) outputs.append(output) output_df = pd.concat(outputs) output_df['gameKey'] = output_df['game_play'].apply(lambda x: x.split('_')[0]).map(int) output_df['playID'] = output_df['game_play'].apply(lambda x: x.split('_')[1]).map(int) return output_df

Om de functie uit te voeren, voeren we het volgende codeblok uit om de locatie van het train_labels.csv gegevens en voer vervolgens gegevensvoorbereiding uit om een ​​extra kolom toe te voegen en alleen de impactrijen te extraheren. Nadat we de functie hebben uitgevoerd, slaan we de uitvoer op in een dataframe-variabele genaamd fused_df.

# read the annotated impact data from train_labels.csv
ground_truth = pd.read_csv('train_labels.csv') # prepare game_play column using pipe(prep_data) function in pandas then filter the dataframe for just rows with impacts
ground_truth = ground_truth.pipe(prep_data).query('impact == 1') # loop over all the unique game_plays and deduplicate the impact results from sideline and endzone
fused_df = fuse_df(ground_truth, windows=30)

De volgende schermafbeelding toont de grondwaarheid.

De volgende schermafbeelding toont de voorbeelden van gefuseerde dataframes.

Grafiek en videocode

Nadat we de impactresultaten hebben gefuseerd, gebruiken we de gegenereerde fused_df om de resultaten over onze endzone- en zijlijnvideo's te leggen en de twee weergaven samen te voegen. We gebruiken hiervoor de volgende functie, en de benodigde invoer zijn de paden naar de endzone-video, zijlijnvideo, fused_df dataframe en het uiteindelijke uitvoerpad voor de nieuw gegenereerde video. De functies die in deze sectie worden gebruikt, worden beschreven in de markdown-sectie van de notebook die wordt gebruikt in SageMaker Studio Lab.

def get_video_and_metadata(vid_path): vid = cv2.VideoCapture(vid_path) total_frame_number = vid.get(cv2.CAP_PROP_FRAME_COUNT) width = int(vid.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(vid.get(cv2.CAP_PROP_FRAME_HEIGHT)) fps = vid.get(cv2.CAP_PROP_FPS) return vid, total_frame_number, width, height, fps def overlay_impacts(frame, fused_df, game_key, play_id, frame_cnt, h1): # look for duplicates duplicates = fused_df.query(f"gameKey == {int(game_key)} and playID == {int(play_id)} and is_dup == True and Sideline_frame == @frame_cnt") frame_has_impact = False if len(duplicates) > 0: for duplicate in duplicates.itertuples(index=False): if frame_cnt == duplicate.Sideline_frame: frame_has_impact = True if frame_has_impact: cv2.rectangle(frame, #frame to be edited (int(duplicate.Sideline_left), int(duplicate.Sideline_top)), #(x,y) of top left corner (int(duplicate.Sideline_left) + int(duplicate.Sideline_width), int(duplicate.Sideline_top) + int(duplicate.Sideline_height)), #(x,y) of bottom right corner (0,0,255), #RED boxes thickness=3) cv2.rectangle(frame, #frame to be edited (int(duplicate.Endzone_left), int(duplicate.Endzone_top)+ h1), #(x,y) of top left corner (int(duplicate.Endzone_left) + int(duplicate.Endzone_width), int(duplicate.Endzone_top) + int(duplicate.Endzone_height) + h1), #(x,y) of bottom right corner (0,0,255), #RED boxes thickness=3) cv2.line(frame, #frame to be edited (int(duplicate.Sideline_left), int(duplicate.Sideline_top)), #(x,y) of point 1 in a line (int(duplicate.Endzone_left), int(duplicate.Endzone_top) + h1), #(x,y) of point 2 in a line (255, 255, 255), # WHITE lines thickness=4) else: # if no duplicates, look for sideline then endzone and add to the view sl_impacts = fused_df.query(f"gameKey == {int(game_key)} and playID == {int(play_id)} and is_dup == False and view == 'Sideline' and Sideline_frame == @frame_cnt") if len(sl_impacts) > 0: for impact in sl_impacts.itertuples(index=False): if frame_cnt == impact.Sideline_frame: frame_has_impact = True if frame_has_impact: cv2.rectangle(frame, #frame to be edited (int(impact.Sideline_left), int(impact.Sideline_top)), #(x,y) of top left corner (int(impact.Sideline_left) + int(impact.Sideline_width), int(impact.Sideline_top) + int(impact.Sideline_height)), #(x,y) of bottom right corner (0, 255, 255), #YELLOW BOXES thickness=3) ez_impacts = fused_df.query(f"gameKey == {int(game_key)} and playID == {int(play_id)} and is_dup == False and view == 'Endzone' and Endzone_frame == @frame_cnt") if len(ez_impacts) > 0: for impact in ez_impacts.itertuples(index=False): if frame_cnt == impact.Endzone_frame: frame_has_impact = True if frame_has_impact: cv2.rectangle(frame, #frame to be edited (int(impact.Endzone_left), int(impact.Endzone_top)+ h1), #(x,y) of top left corner (int(impact.Endzone_left) + int(impact.Endzone_width), int(impact.Endzone_top) + int(impact.Endzone_height) + h1 ), #(x,y) of bottom right corner (0, 255, 255), #YELLOW BOXES thickness=3) return frame, frame_has_impact def generate_impact_video(ez_vid_path:str, sl_vid_path:str, fused_df:pd.DataFrame, output_path:str, freeze_impacts=True): #define video codec to be used for VIDEO_CODEC = "MP4V" # parse game_key and play_id information from the name of the files game_key = os.path.basename(ez_vid_path).split('_')[0] # parse game_key play_id = os.path.basename(ez_vid_path).split('_')[1] # parse play_id # get metadata such as total frame number, width, height and frames per second (FPS) from endzone (ez) and sideline (sl) videos ez_vid, ez_total_frame_number, ez_width, ez_height, ez_fps = get_video_and_metadata(ez_vid_path) sl_vid, sl_total_frame_number, sl_width, sl_height, sl_fps = get_video_and_metadata(sl_vid_path) # define a video writer for the output video output_video = cv2.VideoWriter(output_path, #output file name cv2.VideoWriter_fourcc(*VIDEO_CODEC), #Video codec ez_fps, #frames per second in the output video (ez_width, ez_height+sl_height)) # frame size with stacking video vertically # find shorter video and use the total frame number from the shorter video for the output video total_frame_number = int(min(ez_total_frame_number, sl_total_frame_number)) # iterate through each frame from endzone and sideline for frame_cnt in range(total_frame_number): frame_has_impact = False frame_near_impact = False # reading frames from both endzone and sideline ez_ret, ez_frame = ez_vid.read() sl_ret, sl_frame = sl_vid.read() # creating strings to be added to the output frames img_name = f"Game key: {game_key}, Play ID: {play_id}, Frame: {frame_cnt}" video_frame = f'{game_key}_{play_id}_{frame_cnt}' if ez_ret == True and sl_ret == True: h, w, c = ez_frame.shape h1,w1,c1 = sl_frame.shape if h != h1 or w != w1: # resize images if they're different ez_frame = cv2.resize(ez_frame,(w1,h1)) frame = np.concatenate((sl_frame, ez_frame), axis=0) # stack the frames vertically frame, frame_has_impact = overlay_impacts(frame, fused_df, game_key, play_id, frame_cnt, h1) cv2.putText(frame, #image frame to be modified img_name, #string to be inserted (30, 30), #(x,y) location of the string cv2.FONT_HERSHEY_SIMPLEX, #font 1, #scale (255, 255, 255), #WHITE letters thickness=2) cv2.putText(frame, #image frame to be modified str(frame_cnt), #frame count string to be inserted (w1-75, h1-20), #(x,y) location of the string in the top view cv2.FONT_HERSHEY_SIMPLEX, #font 1, #scale (255, 255, 255), # WHITE letters thickness=2) cv2.putText(frame, #image frame to be modified str(frame_cnt), #frame count string to be inserted (w1-75, h1+h-20), #(x,y) location of the string in the bottom view cv2.FONT_HERSHEY_SIMPLEX, #font 1, #scale (255, 255, 255), # WHITE letters thickness=2) output_video.write(frame) # Freeze for 60 frames on impacts if frame_has_impact and freeze_impacts: for _ in range(60): output_video.write(frame) else: break frame_cnt += 1 output_video.release() return

Om deze functies uit te voeren, kunnen we een invoer leveren zoals weergegeven in de volgende code, die een video genereert met de naam output.mp4:

generate_impact_video('57583_000082_Endzone.mp4', '57583_000082_Sideline.mp4', fused_df, 'output.mp4')

Dit genereert een video zoals getoond in het volgende voorbeeld, waarbij de rode kaders impacts zijn die gevonden worden in zowel de endzone- als zijlijnweergaven, en de gele selectiekaders effecten zijn die gevonden worden in slechts één weergave in de endzone of zijlijn.

Conclusie

In dit bericht hebben we laten zien hoe de NFL-, Biocore- en de AWS ProServe-teams samenwerken om impactdetectie te verbeteren door resultaten uit meerdere weergaven samen te voegen. Hierdoor kunnen de teams debuggen en visualiseren hoe het model kwalitatief presteert. Dit proces kan eenvoudig worden opgeschaald naar drie of meer weergaven; in onze projecten hebben we tot zeven verschillende weergaven gebruikt. Het kan moeilijk zijn om helminslagen te detecteren door video's vanuit slechts één weergave te bekijken, maar door inslagen vanuit meerdere weergaven te detecteren en de resultaten samen te voegen, kunnen we de prestaties van ons model verbeteren.

Om met deze oplossing te experimenteren, gaat u naar de aws-samples GitHub-opslagplaats en verwijzen naar de fuse_and_visualize_multiview_impacts.ipynb notitieboekje. Soortgelijke technieken kunnen ook worden toegepast op andere industrieën, zoals productie, detailhandel en beveiliging, waar meerdere weergaven het ML-systeem ten goede zou komen om doelen beter te identificeren met een meer uitgebreide weergave.

Ga voor meer informatie over de gezondheid en veiligheid van NFL-spelers naar de NFL-website en NFL uitgelegd: innovatie op het gebied van gezondheid en veiligheid van spelers.


Over de auteurs

Chris Boomhower is een Machine Learning Engineer bij AWS Professional Services. Chris heeft meer dan 6 jaar ervaring met het ontwikkelen van begeleide en onbewaakte Machine Learning-oplossingen in verschillende sectoren. Tegenwoordig besteedt hij het grootste deel van zijn tijd aan het helpen van klanten in de sport-, gezondheidszorg- en landbouwsector bij het ontwerpen en bouwen van schaalbare, end-to-end, Machine Learning-oplossingen.

Ben Fenker is een Senior Data Scientist in AWS Professional Services en heeft klanten geholpen bij het bouwen en implementeren van ML-oplossingen in sectoren variërend van sport tot gezondheidszorg tot productie. Hij heeft een Ph.D. in natuurkunde van Texas A&M University en 6 jaar ervaring in de industrie. Ben houdt van honkbal, lezen en het opvoeden van zijn kinderen.

Sam Huddleston is een Principal Data Scientist bij Biocore LLC, die fungeert als Technology Lead voor het Digital Athlete-programma van de NFL. Biocore is een team van ingenieurs van wereldklasse, gevestigd in Charlottesville, Virginia, dat onderzoek, testen, expertise op het gebied van biomechanica, modellering en andere technische diensten levert aan klanten die zich toeleggen op het begrijpen en verminderen van letsel.

Jarvis Lee is een Senior Data Scientist bij AWS Professional Services. Hij werkt al meer dan vijf jaar bij AWS en werkt samen met klanten aan problemen met machine learning en computervisie. Naast zijn werk fietst hij graag.

Tyler Mullenbach is de Global Practice Lead voor ML met AWS Professional Services. Hij is verantwoordelijk voor het sturen van de strategische richting van ML voor professionele services en ervoor te zorgen dat klanten transformatieve zakelijke prestaties realiseren door de toepassing van ML-technologieën.

Kevin Lied is Data Scientist bij AWS Professional Services. Hij is gepromoveerd in de biofysica en heeft meer dan 5 jaar ervaring in de sector in het bouwen van oplossingen voor computervisie en machine learning.

Betty Zhang is een datawetenschapper met 10 jaar ervaring in data en technologie. Haar passie is het bouwen van innovatieve machine learning-oplossingen om transformationele veranderingen voor bedrijven te stimuleren. In haar vrije tijd houdt ze van reizen, lezen en leren over nieuwe technologieën.

spot_img

Laatste intelligentie

spot_img