Использование моделей компьютерного зрения с открытым исходным кодом для создания лучших моментов в баскетболе

Мы живем в мире быстрого потребления контента, во главе которого стоят TikTok, Snapchat, Instagram, Twitter, Facebook, Youtube и другие.

Молодые болельщики осваивают новые способы взаимодействия с лигами, при этом все меньше внимания уделяется просмотру игр в прямом эфире.

Согласно опросу Variety Intelligence Platform, среди американских спортивных болельщиков в возрасте от 18 до 34 лет 58 % болельщиков MLB, 54 % болельщиков NBA и 48 % болельщиков NFL говорят, что предпочитают смотреть лучшие моменты, а не полные игры. »).

Будучи преданным поклонником НБА, живущим на полпути от США, где эти игры проходят посреди ночи, нет большего потребителя этих ярких моментов, чем я.

Не нуждаясь в дополнительной мотивации, я решил создать автоматизированный процесс для создания лучших моментов игр NBA.

Что

Цель этого проекта состояла в том, чтобы создать эти основные моменты с использованием технологий с полностью открытым исходным кодом и максимально упростить их.

Как

Есть много способов попытаться найти точки интереса в играх — анализ звука, обнаружение движения и т. д. Есть компании, которые делают бизнес именно на этом, используя сложные модели для определения точек интереса и создания ярких клипов. Но эти сложные исходные данные и модели не обязательно дают более точные результаты. Вместо этого я решил положиться на устойчивый и четкий сигнал — отчет о каждой игре с отметкой времени, соответствующий игровым часам. Так просто, но так точно. Хотя это решение не обязательно будет работать в любом виде спорта, в НБА (и в баскетболе в целом) часы священны… так что надежность очень высока при минимальных усилиях или вообще без них, а уровень вычислений очень низок.

Это решение состоит из трех частей:

  1. Создание простого парсера play-by-play для получения данных об игре, для которой мы создаем хайлайт.
  2. Использование модели Tesseract OCR с открытым исходным кодом для поиска текущих игровых часов и четверти в игровом фильме.
  3. Сравнивая часы и четверть часа, которые мы извлекли из кадров, с нашими данными воспроизведения.

…и если у нас есть совпадение — вуаля! У нас есть изюминка!

Теперь давайте перейдем к техническим аспектам.

Этот проект был написан на Python, но его можно легко воспроизвести на любом языке.
Прежде чем мы начнем, вот библиотеки, которые использовались в этом проекте:

import pandas as pd
import pytesseract
import cv2
from moviepy.editor import *
import json
import requests

Создание пошагового парсера

Play-by-play предоставляет расшифровку игры в формате отдельных событий.

Некоторые примеры данных, которые можно найти в данных play-by-play:

  • время владения (на игровых часах)
  • квартал, в котором произошло владение
  • игрок, инициировавший владение мячом (в случае перехвата или подбора при защите)
  • игрок соперника, инициировавший владение мячом (в случае пропущенного броска или передачи мяча), включая местоположение на площадке, с которого был сделан бросок, и некоторые другие уникальные идентификаторы, которые мы используем для классификации типа владения мячом.

Данные, которые мы извлекаем, взяты из официальных API НБА (data.nba.net и cdn.nba.com). Данные хранятся в формате JSON. Чтобы получить данные в более традиционном формате, таком как кадр данных, нам просто нужно запустить несколько строк кода.

В первой части представлены игры, в которые играли в указанную дату:

jsn = f"https://data.nba.net/10s/prod/v1/{date}/scoreboard.json"
page = requests.get(jsn)
j = json.loads(page.content)

Затем мы проверяем, что введенные команды действительно играли в указанную дату. Если да, то мы берем идентификатор игры и вставляем его в следующий API.

Мы извлекаем данные и загружаем их в pandas dataframe:

raw_game = f'https://cdn.nba.com/static/json/liveData/playbyplay/playbyplay_{game_id}.json'
page = requests.get(raw_game)
j = json.loads(page.content)
df = pd.DataFrame(j['game']['actions'])

Результаты выглядят примерно так:

Отсюда мы можем фильтровать любые интересующие нас игры (корзины, перехваты, блоки и т. д.). В нашем случае я беру только забитые мячи и исключаю штрафные броски.

ndf = df[['clock', 'period', 'description', 'teamTricode', 'shotResult', 'actionType']]
ndf = ndf[ndf['shotResult'] == 'Made']
ndf = ndf[ndf['actionType'] != 'freethrow']

Теперь, когда у нас есть набор данных обо всех точках интереса в игре, пришло время найти их в реальных игровых кадрах.

Обработка игрового ролика

Следующим шагом будет обработка игрового фильма в кадры и манипулирование ими с помощью модуля OpenCV.

cap = cv2.VideoCapture(video_path)
ret, frame = cap.read()
while cap.isOpened():
…

Затем каждый кадр проходит ряд манипуляций (предварительной обработки), чтобы подготовить его к распознаванию (подробнее об распознавании).

Шаги:

  1. Обрежьте исходный кадр и оставьте только нижнюю треть, где находятся игровые часы (чтобы не тратить время на чтение всего кадра).
  2. Преобразуйте изображение в оттенки серого.
  3. Преобразуйте изображение в градациях серого в черно-белое.
  4. Примените Размытие по Гауссу к черно-белому изображению.
height, width = frame.shape
crop_img = frame[height-(height/3):height, 0:width]
gray = cv2.cvtColor(crop_img, cv2.COLOR_BGR2GRAY)
bw = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
blurred = cv2.GaussianBlur(bw, (5, 5), 0)

Тессеракт

Tesseract — это движок оптического распознавания символов с открытым исходным кодом. Это самая популярная и качественная OCR-библиотека.

OCR использует искусственный интеллект для поиска текста и распознавания изображений.

Tesseract находит шаблоны в пикселях, буквах, словах и предложениях.

Он имеет возможность распознавать более 100 языков из коробки и может быть обучен для распознавания других языков.

Таким образом, в основном движок ищет и извлекает текст из изображений.

В этом проекте мы используем python-tesseract (pytesseract), который является оболочкой python для механизма Google Tesseract-OCR.

Существует 13 режимов настройки тессеракта. Мы будем использовать режим 11: Разреженный текст. Найдите как можно больше текста в произвольном порядке.

Реализация Tesseract

Следующим шагом будет прогон нашего обработанного кадра через движок tesseract.

data = pytesseract.image_to_string(blurred, lang='eng', config=' - psm 11')

Теперь, когда у нас есть весь текст во фрейме, мы можем искать нужную информацию — то есть текущий квартал и игровые часы.

Продолжая пример выше:

Четверть – «1-я», а игровые часы – «7:32». Однако в выводе Tesseract обнаружено «ist». Цифру «1» ошибочно приняли за букву «i».

Итак, следующий шаг — создать карту распространенных ошибок для каждого квартала.

firstQ  = ['1st', 'ist', 'ast']
secondQ = ['2nd', '2n', 'znd']
thirdQ  = ['3rd', '3r', '3r0', '3ro', '37d', '3fd', '31d']
fourth  = ['4th', '4t', '47h', '41h', '4h']

Сопоставление данных по ходу игры с данными из видеозаписи игры

Давайте подытожим то, что у нас есть на данный момент: мы создали пошаговый парсер, обработали игровые кадры и прогнали их через движок Tesseract, чтобы извлечь текст из кадра.

Теперь пришло время объединить точки данных и найти основные моменты.

Мы делаем это, обрабатывая каждый кадр видеозаписи игры, как обсуждалось выше, и проверяя, совпадает ли извлеченная комбинация четверти и игровых часов с четвертью и игровыми часами основного момента.

Если у нас есть совпадение, мы сохраняем номер кадра.

curr_frame = cap.get(cv2.CAP_PROP_POS_FRAMES)
frame_loc.append(curr_frame)

Соединяем кадры воедино

Как только мы закончим просматривать все кадры в игре и у нас будет список всех кадров, в которых произошли блики, все, что нам нужно сделать, это собрать их воедино.

Мы сделаем это с помощью модуля moviepy.

Moviepy позволяет нам вырезать подклипы из полных видео. Чтобы увидеть всю игру, которая привела к блику, мы берем несколько секунд до и пару секунд после той секунды, в которой произошел хайлайт.

clips = []
for frame in highlight_frames:
clip_name = video.subclip(round(frame/fps) - 4, round(frame/fps)+ 2)
clips.append(clip_name)
final_clip = concatenate_videoclips(clips)
final_clip.write_videofile(output_path,
                           codec='libx264',
                           audio_codec='aac',
                           fps=fps)

Пример выделения выходных данных

Теперь, когда мы закончили создание бликов, давайте посмотрим на результат….

Повышение эффективности

Это базовый сценарий для поиска точек интереса и создания клипов с яркими моментами, но он далеко не эффективен.

Эффективность можно легко повысить различными способами, вот некоторые из них:

  • Отсутствие проверки каждого кадра — переход на одну секунду вместо проверки каждого кадра уменьшает количество обрабатываемых кадров в двухчасовой игре (60 кадров в секунду) с 430 000 до 7 200.
  • Определение координат игровых часов и запуск только этой области через движок OCR сократит время работы.
  • Ищите изменения в счете вместо игровых часов (если вы хотите, чтобы забивались только корзины)
  • Использование многопроцессорности для параллельной работы и ускорения процесса, а также использование графического процессора.

Подводя итог

Это всего лишь небольшой пример того, как можно использовать технологии с открытым исходным кодом для создания невероятных продуктов.

На этом пока все — надеюсь, вам было интересно, и, пожалуйста, дайте мне знать, если у вас есть какие-либо вопросы или комментарии!