PyTorch, kompüter görmə texnikası və Convolutional Neural Network (CNN) istifadə edərək, oyunçuları, komandaları və əsas performans statistikasını izləyən bir model üzərində işlədim.
İndiki vaxtda istədiyim qədər xokkey oynamıram, amma uşaqlıqdan bu mənim bir parçam olub. Bu yaxınlarda Limada keçirilən ilk Buzüstü Xokkey Turnirində (3-ə 3) hakimlər cədvəlinə kömək etmək və bəzi statistikaları saxlamaq şansım oldu. Bu hadisə Peru Inline Hokkey Assosiasiyasının (APHL) fövqəladə səyi və qonaqların mehriban səfərini əhatə etmişdir. Dostluq Liqası. AI twist əlavə etmək üçün istifadə etdim PyTorch, kompüter görmə texnika və a Convolutional Neyron Network (CNN) oyunçuları və komandaları izləyən və bəzi əsas performans statistikalarını toplayan model yaratmaq.
Bu məqalənin məqsədi a sürətli bələdçi modelin dizaynı və tətbiqi. Modelin hələ də bəzi incə tənzimləmələrə ehtiyacı olsa da, ümid edirəm ki, bu, hər kəsə idmana tətbiq edilən maraqlı kompüter görmə dünyasına özlərini tanıtmağa kömək edə bilər. etiraf etmək və təşəkkür etmək istərdim Peru Xətti Xokkey Assosiasiyası (APHL) Mənə bu layihə üçün turnirin 40 saniyəlik video nümunəsindən istifadə etməyə icazə verdiyinə görə (video daxiletmə nümunəsini burada tapa bilərsiniz layihənin GitHub deposu).
Memarlıq
Layihəyə davam etməzdən əvvəl, işləyə biləcəyim və “təkəri yenidən ixtira etməkdən” qaça biləcəyim bir baza tapmaq üçün bir az tez araşdırma apardım. Oyunçuları izləmək üçün kompüter görmə qabiliyyətinin istifadəsi baxımından futbolda çox maraqlı işlərin olduğunu aşkar etdim (təəccüblü deyil, dünyanın ən populyar komanda idman növüdür). Bununla belə, buz xokkeyi üçün çoxlu resurs tapmadım. Roboflow Özünüzü öyrətmək üçün bəzi maraqlı əvvəlcədən hazırlanmış modellər və məlumat dəstləri var, lakin hosted modellə işləmək bəzi gecikmə problemlərini təqdim etdi ki, bunları daha sonra izah edəcəyəm. Sonda mən futbol materialından video kadrları oxumaq və fərdi trek identifikatorlarını əldə etmək üçün istifadə etdim, əsas prinsiplərə və izləmə metodu yanaşmasına əməl etdim. bu dərslik (Əgər bəzi əsas kompüter görmə üsullarını daha yaxşı başa düşməkdə maraqlısınızsa, dərsliyin ən azı ilk saat yarımını izləməyi təklif edirəm).
İzləmə identifikatorları əhatə olunduqda, mən öz yolumu qurdum. Bu məqaləni nəzərdən keçirərkən, layihənin sadə obyekt aşkarlama tapşırığından oyunçuları, komandaları tam aşkarlayan və bəzi əsas performans göstəricilərini təqdim edən modelə necə təkamül etdiyini görəcəyik. (01-dən 08-ə qədər nümunə kliplər, müəllifin öz yaradıcılığı).
İzləmə Mexanizmi
İzləmə mexanizmi modelin əsasını təşkil edir. O, videoda aşkar edilmiş hər bir obyektin eyniləşdirilməsini və unikal identifikator təyin edilməsini təmin edir və bu eyniliyi hər bir çərçivədə saxlayır. İzləmə mexanizminin əsas komponentləri bunlardır:
- YOLO (Yalnız bir dəfə baxırsan): Bu, 2015-ci ildə qəzetdə təqdim edilmiş güclü real vaxt obyekt aşkarlama alqoritmidir.Siz yalnız bir dəfə baxırsınız: vahid, real vaxtda obyektin aşkarlanması”. Təxminən 80 əvvəlcədən hazırlanmış dərsləri aşkar etməkdə sürəti və çox yönlü olması ilə seçilir (qeyd etmək vacibdir ki, o, həmçinin xüsusi obyektləri aşkar etmək üçün xüsusi verilənlər dəstləri üzərində də öyrədilə bilər). İstifadə vəziyyətimiz üçün Ultralytics tərəfindən əvvəlki YOLO versiyaları əsasında qurulmuş kompüter görmə modeli olan YOLOv8x-ə etibar edəcəyik. Siz onu yükləyə bilərsiniz burada.
- ByteTrack Tracker: ByteTrack-i başa düşmək üçün biz MOT (Çoxlu Obyekt İzləmə) anlayışını başa düşməliyik ki, bu da video ardıcıllığında zamanla birdən çox obyektin hərəkətini izləməyi və cari çərçivədə aşkar edilmiş həmin obyektləri əvvəlki kadrlardakı müvafiq obyektlərlə əlaqələndirməyi nəzərdə tutur. Bunu həyata keçirmək üçün ByteTrack-dən istifadə edəcəyik (qəzədə 2021-ci ildə təqdim edilmişdir “ByteTrack: Hər Aşkarlama Qutunu əlaqələndirməklə Çox Obyekt İzləmə”). ByteTrack izləyicisini tətbiq etmək və aşkar edilmiş obyektlərə trek identifikatorları təyin etmək üçün biz Python-un nəzarət kitabxanasına etibar edəcəyik.
- OpenCV: Python-da müxtəlif kompüter görmə tapşırıqları üçün tanınmış kitabxanadır. İstifadə vəziyyətimiz üçün etibar edəcəyik OpenCV hər aşkar edilmiş obyekt üçün məhdudlaşdırıcı qutuları və mətni olan video çərçivələri vizuallaşdırmaq və şərh etmək.
İzləmə mexanizmimizi qurmaq üçün bu ilk iki addımdan başlayacağıq:
- Obyektləri (bizim vəziyyətimizdə oyunçular) aşkar etmək və unikal trek identifikatorları təyin etmək üçün YOLO modelinin ByteTrack ilə yerləşdirilməsi.
- Obyekt izlərini turşu (pkl) faylında saxlamaq üçün lüğətin işə salınması. Bu, kodu hər dəfə işə saldığımız zaman video kadr-kadr obyekt aşkarlama prosesinin həyata keçirilməsinin qarşısını almaq və əhəmiyyətli vaxta qənaət etmək üçün çox faydalı olacaq.
Növbəti addım üçün bunlar bizə lazım olacaq Python paketləridir:
pip install ultralytics
pip install supervision
pip install opencv-python
Sonra biz kitabxanalarımızı və nümunə video faylımız və turşu faylımız üçün yolu müəyyən edəcəyik (əgər o varsa, əgər yoxdursa, kod birini yaradacaq və onu eyni yolda saxlayacaq):
#**********************************LIBRARIES*********************************#
from ultralytics import YOLO
import supervision as sv
import pickle
import os
import cv2# INPUT-video file
video_path='D:/PYTHON/video_input.mp4'
# OUTPUT-Video File
output_video_path='D:/PYTHON/output_video.mp4'
# PICKLE FILE (IF AVAILABLE LOADS IT IF NOT, SAVES IT IN THIS PATH)
pickle_path='D:/PYTHON/stubs/track_stubs.pkl'
İndi davam edək və izləmə mexanizmimizi müəyyən edək (video daxiletmə nümunəsini burada tapa bilərsiniz layihənin GitHub deposu):
#*********************************TRACKING MECHANISM**************************#
class HockeyAnalyzer:
def __init__(self, model_path):
self.model=YOLO(model_path)
self.tracker=sv.ByteTrack() def detect_frames(self, frames):
batch_size=20
detections=[]
for i in range(0, len(frames), batch_size):
detections_batch=self.model.predict(frames[i:i+batch_size], conf=0.1)
detections +=detections_batch
return detections
#********LOAD TRACKS FROM FILE OR DETECT OBJECTS-SAVES PICKLE FILE************#
def get_object_tracks(self, frames, read_from_stub=False, stub_path=None):
if read_from_stub and stub_path is not None and os.path.exists(stub_path):
with open(stub_path, 'rb') as f:
tracks=pickle.load(f)
return tracks
detections=self.detect_frames(frames)
tracks={"person": []}
for frame_num, detection in enumerate(detections):
cls_names=detection.names
cls_names_inv={v: k for k, v in cls_names.items()}
# Tracking Mechanism
detection_supervision=sv.Detections.from_ultralytics(detection)
detection_with_tracks=self.tracker.update_with_detections(detection_supervision)
tracks["person"].append({})
for frame_detection in detection_with_tracks:
bbox=frame_detection[0].tolist()
cls_id=frame_detection[3]
track_id=frame_detection[4]
if cls_id==cls_names_inv.get('person', None):
tracks["person"][frame_num][track_id]={"bbox": bbox}
for frame_detection in detection_supervision:
bbox=frame_detection[0].tolist()
cls_id=frame_detection[3]
if stub_path is not None:
with open(stub_path, 'wb') as f:
pickle.dump(tracks, f)
return tracks
#***********************BOUNDING BOXES AND TRACK-IDs**************************#
def draw_annotations(self, video_frames, tracks):
output_video_frames=[]
for frame_num, frame in enumerate(video_frames):
frame=frame.copy()
player_dict=tracks["person"][frame_num]
# Draw Players
for track_id, player in player_dict.items():
color=player.get("team_color", (0, 0, 255))
bbox=player["bbox"]
x1, y1, x2, y2=map(int, bbox)
# Bounding boxes
cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
# Track_id
cv2.putText(frame, str(track_id), (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, color, 2)
output_video_frames.append(frame)
return output_video_frames
Metod YOLO modelini və ByteTrack izləyicisini işə salmaqla başlayır. Daha sonra, hər bir çərçivədə obyektləri aşkar etmək və toplamaq üçün YOLO modelindən istifadə edərək, hər bir çərçivə 20 dəstdə işlənir. Turşu faylı yolunda mövcuddursa, o, fayldan parçaları əvvəlcədən hesablayır. Turşu faylı mövcud deyilsə (siz kodu ilk dəfə işlədirsiniz və ya əvvəlki turşu faylını silmisiniz)the almaq_object_tracks hər bir aşkarlamanı ByteTrack üçün tələb olunan formata çevirir, bu aşkarlamalarla izləyicini yeniləyir və izləmə məlumatını təyin olunmuş yolda yeni turşu faylında saxlayır. Nəhayət, hər bir çərçivə üzərində təkrarlamalar edilir, aşkar edilmiş hər biri üçün məhdudlaşdırıcı qutular və trek identifikatorları çəkilir. obyekt.
İzləyicini icra etmək və məhdudlaşdırıcı qutular və trek identifikatorları ilə yeni çıxış videosunu saxlamaq üçün aşağıdakı koddan istifadə edə bilərsiniz:
#*************** EXECUTES TRACKING MECHANISM AND OUTPUT VIDEO****************## Read the video frames
video_frames=[]
cap=cv2.VideoCapture(video_path)
while cap.isOpened():
ret, frame=cap.read()
if not ret:
break
video_frames.append(frame)
cap.release()
#********************* EXECUTE TRACKING METHOD WITH YOLO**********************#
tracker=HockeyAnalyzer('D:/PYTHON/yolov8x.pt')
tracks=tracker.get_object_tracks(video_frames, read_from_stub=True, stub_path=pickle_path)
annotated_frames=tracker.draw_annotations(video_frames, tracks)
#*********************** SAVES VIDEO FILE ************************************#
fourcc=cv2.VideoWriter_fourcc(*'mp4v')
height, width, _=annotated_frames[0].shape
out=cv2.VideoWriter(output_video_path, fourcc, 30, (width, height))
for frame in annotated_frames:
out.write(frame)
out.release()
Kodunuzdakı hər şey düzgün işləyirsə, göstərilənə bənzər bir video çıxışı gözləməlisiniz Nümunə klip 01.
İPUCU №01: Hesablama gücünüzü qiymətləndirməyin! Kodu ilk dəfə işə salarkən, hesablama qabiliyyətinizdən asılı olaraq çərçivənin işlənməsinin bir qədər vaxt alacağını gözləyin. Mənim üçün yalnız bir CPU quraşdırmasından istifadə edərək 45 ilə 50 dəqiqə çəkdi (CUDA-nı seçim kimi nəzərdən keçirin). YOLOv8x izləmə mexanizmi güclü olsa da, əhəmiyyətli hesablama resursları tələb edir (bəzən yaddaşım 99%-ə çatırdı, barmaqlarımın üstündən xətt çəkmirdi!🙄). YOLO-nun bu versiyası ilə bağlı problemlərlə qarşılaşsanız, daha yüngül modellər mövcuddur Ultralytics ‘GitHub dəqiqlik və hesablama qabiliyyətini balanslaşdırmaq.
Buz Meydanı
İlk addımdan gördüyünüz kimi, bizim bəzi çətinliklərimiz var. Birincisi, gözlənildiyi kimi, model bütün hərəkət edən obyektləri götürür; oyunçular, hakimlər, hətta meydançanın xaricində olanlar. İkincisi, bu qırmızı məhdudlaşdırıcı qutular izləmə oyunçularını bir qədər qeyri-müəyyən edə bilər və təqdimat üçün çox səliqəli deyil. Bu bölmədə biz aşkarlama imkanlarımızı yalnız meydança daxilində olan obyektlərə yönəltməyə diqqət yetirəcəyik. Üstəlik, daha aydın görünməni təmin edərək, bu məhdudlaşdırıcı qutuları aşağıda ellipslərlə əvəz edəcəyik.
Əvvəlcə qutuların istifadəsindən ellipslərin istifadəsinə keçək. Bunu yerinə yetirmək üçün sadəcə olaraq mövcud kodumuzda etiketlərin və sərhəd qutuları metodunun üstünə yeni bir üsul əlavə edəcəyik:
#************ Design of Ellipse for tracking players instead of Bounding boxes**************# def draw_ellipse(self, frame, bbox, color, track_id=None, team=None):
y2=int(bbox[3])
x_center=(int(bbox[0]) + int(bbox[2])) // 2
width=int(bbox[2]) - int(bbox[0])
color=(255, 0, 0)
text_color=(255, 255, 255)
cv2.ellipse(
frame,
center=(x_center, y2),
axes=(int(width) // 2, int(0.35 * width)),
angle=0.0,
startAngle=-45,
endAngle=235,
color=color,
thickness=2,
lineType=cv2.LINE_4
)
if track_id is not None:
rectangle_width=40
rectangle_height=20
x1_rect=x_center - rectangle_width // 2
x2_rect=x_center + rectangle_width // 2
y1_rect=(y2 - rectangle_height // 2) + 15
y2_rect=(y2 + rectangle_height // 2) + 15
cv2.rectangle(frame,
(int(x1_rect), int(y1_rect)),
(int(x2_rect), int(y2_rect)),
color,
cv2.FILLED)
x1_text=x1_rect + 12
if track_id> 99:
x1_text -=10
font_scale=0.4
cv2.putText(
frame,
f"{track_id}",
(int(x1_text), int(y1_rect + 15)),
cv2.FONT_HERSHEY_SIMPLEX,
font_scale,
text_color,
thickness=2
)
return frame
Biz həmçinin məhdudlaşdırıcı qutuları və identifikatorları ellips metoduna çağırışla əvəz etməklə annotasiya addımını yeniləməli olacağıq:
#***********************BOUNDING BOXES AND TRACK-IDs**************************# def draw_annotations(self, video_frames, tracks):
output_video_frames=[]
for frame_num, frame in enumerate(video_frames):
frame=frame.copy()
player_dict=tracks["person"][frame_num]
# Draw Players
for track_id, player in player_dict.items():
bbox=player["bbox"]
# Draw ellipse and tracking IDs
self.draw_ellipse(frame, bbox, (0, 255, 0), track_id)
x1, y1, x2, y2=map(int, bbox)
output_video_frames.append(frame)
return output_video_frames
Bu dəyişikliklərlə çıxış videonuz aşağıda göstərildiyi kimi çox səliqəli görünməlidir Nümunə klip 02.
İndi meydançanın sərhədləri ilə işləmək üçün kompüter görmə qabiliyyətinə dair bəzi əsas biliklərə sahib olmalıyıq. İstifadə vəziyyətimizdə biz 720p (1280×720 piksel) formatı ilə işləyirik, yəni emal etdiyimiz hər bir çərçivə və ya təsvirin 1280 piksel (en) ilə 720 piksel (hündürlük) ölçüləri var.
720p (1280×720 piksel) formatı ilə işləmək nə deməkdir? Bu o deməkdir ki, şəkil üfüqi olaraq 1280 piksel və şaquli olaraq 720 pikseldən ibarətdir. Bu formatda koordinatlar şəklin yuxarı sol küncündə (0, 0) ilə başlayır, sağa hərəkət etdikcə x koordinatı, aşağıya doğru hərəkət etdikcə y koordinatı artır. Bu koordinatlar şəkilin yuxarı sol küncü üçün (x1, y1) və qutunun aşağı sağ küncü üçün (x2, y2) işarələrindən istifadə etmək kimi xüsusi sahələri qeyd etmək üçün istifadə olunur. Bunu anlamaq bizə məsafələri və sürətləri ölçməyə və təhlilimizi videoda hara yönəltmək istədiyimizə qərar verməyə kömək edəcək.
Beləliklə, biz aşağıdakı koddan istifadə edərək çərçivənin sərhədlərini yaşıl xətlərlə qeyd etməyə başlayacağıq:
#********************* Border Definition for Frame***********************
import cv2video_path='D:/PYTHON/video_input.mp4'
cap=cv2.VideoCapture(video_path)
#**************Read, Define and Draw corners of the frame****************
ret, frame=cap.read()
bottom_left=(0, 720)
bottom_right=(1280, 720)
upper_left=(0, 0)
upper_right=(1280, 0)
cv2.line(frame, bottom_left, bottom_right, (0, 255, 0), 2)
cv2.line(frame, bottom_left, upper_left, (0, 255, 0), 2)
cv2.line(frame, bottom_right, upper_right, (0, 255, 0), 2)
cv2.line(frame, upper_left, upper_right, (0, 255, 0), 2)
#*******************Save the frame with marked corners*********************
output_image_path='rink_area_marked_VALIDATION.png'
cv2.imwrite(output_image_path, frame)
print("Rink area saved:", output_image_path)
Nəticə (a)-da göstərildiyi kimi yaşıl düzbucaqlı olmalıdır Nümunə klip 03. Lakin meydançada yalnız hərəkət edən obyektləri izləmək üçün (b) -dəkinə daha çox oxşar bir sərhədləşdirmə lazımdır.
(b) hüququ əldə etmək, modelinizə ən uyğun olan sərhədləri tapana qədər müxtəlif koordinatları sınaqdan keçirdiyiniz iterativ sınaq və səhv prosesi kimidir. Başlanğıcda meydançanın sərhədlərini tam olaraq uyğunlaşdırmağı qarşıma məqsəd qoymuşdum. Bununla belə, izləmə sistemi kənarların yaxınlığında mübarizə apardı. Dəqiqliyi artırmaq üçün kənarda olanlar istisna olmaqla, meydançadakı bütün izləmə obyektlərinin tutulmasını təmin etmək üçün sərhədləri bir qədər genişləndirdim. (b) bəndində göstərilən nəticə əldə edə biləcəyim ən yaxşı nəticə idi (hələ daha yaxşı ssenarilər işlədə bilərsiniz) bu koordinatlarla müəyyən edilir:
- Aşağı Sol Künc: (-450, 710)
- Aşağı sağ künc: (2030, 710)
- Yuxarı Sol Künc: (352, 61)
- Yuxarı sağ künc: (948, 61)
Nəhayət, biz iki əlavə sahə müəyyənləşdirəcəyik: ohücum zonaları həm ağ, həm də sarı komandalar üçün (hər komanda qol vurmağı hədəflədiyi yer). Bu, bizə rəqib zonasında hər bir komanda üçün bəzi əsas mövqe statistikası və təzyiq göstəricilərini toplamağa imkan verəcək.
#**************YELLOW TEAM OFFENSIVE ZONE****************
Bottom Left Corner: (-450, 710)
Bottom Right Corner: (2030, 710)
Upper Left Corner: (200, 150)
Upper Right Corner: (1160, 150)#**************WHITE TEAM OFFENSIVE ZONE****************
Bottom Left Corner: (180, 150)
Bottom Right Corner: (1100, 150)
Upper Left Corner: (352, 61)
Upper Right Corner: (900, 61)
Bu koordinatları hələlik bir kənara qoyacağıq və növbəti mərhələdə hər bir komandanı necə təsnif edəcəyimizi izah edəcəyik. Sonra hamısını orijinal izləmə metodumuza birləşdirəcəyik.
Komanda Proqnozu üçün Dərin Öyrənmədən istifadə
Buraxılışından 80 ildən çox vaxt keçir “Sinir Fəaliyyətində İmmanent İdeyaların Məntiqi Hesablaması”, 1943-cü ildə Warren McCulloch və Walter Pitts tərəfindən yazılmış və erkən neyron şəbəkə tədqiqatları üçün möhkəm zəmin yaradan məqalə. Daha sonra, 1957-ci ildə sadələşdirilmiş neyronun riyazi modeli (girişlərin qəbulu, bu girişlərə çəkilərin tətbiqi, onların yekunlaşdırılması və ikili nəticənin çıxarılması) ilhamlanmışdır Mark I-i qurmaq üçün Frank Rosenblatt. Bu a konsepsiyasını nümayiş etdirmək üçün nəzərdə tutulmuş ilk aparat tətbiqi idi perseptron, ikili təsnifatlar etmək üçün verilənlərdən öyrənməyə qadir olan neyron şəbəkə modeli. O vaxtdan bəri, kompüterləri bizim kimi düşünməyə məcbur etmək axtarışları səngimir. Bu Neyron Şəbəkələrə ilk dərin dalışınızdırsa və ya biliklərinizi təzələmək və gücləndirmək istəyirsinizsə, bunu oxumağı məsləhət görürəm. Shreya Raonun məqalə silsiləsi dərin öyrənmə üçün əla başlanğıc nöqtəsi kimi. Bundan əlavə, siz mənim hekayələr kolleksiyama daxil ola bilərsiniz (müxtəlif ianəçilər) ki, mən bura toplaşmışamvə faydalı hesab edə biləcəyiniz.
Niyə Convolutional Neyron Network (CNN) seçirsiniz? Düzünü desəm, bu mənim ilk seçimim deyildi. Əvvəlcə bir model qurmağa çalışdım Eniş AIbulud yerləşdirilməsi üçün istifadəçi dostu platforma və API vasitəsilə Python bağlantısı. Bununla belə, gecikmə problemləri ortaya çıxdı (1000-dən çox irəli onlayn emal etmək üçün). Oxşar gecikmə problemləri əvvəlcədən öyrədilmiş modellərdə baş verdi Roboflow, onların keyfiyyətli məlumat dəstlərinə və əvvəlcədən öyrədilmiş modellərinə baxmayaraq. Yerli olaraq həyata keçirməyin zəruriliyini dərk edərək, komanda və hakimin aşkarlanması üçün forma rənglərini təsnif etmək üçün MSE əsaslı metodu sınadım. Son həll kimi səslənsə də, aşağı dəqiqlik göstərdi. Günlərlə sınaq və səhvdən sonra CNN-lərə keçdim. Müxtəlif dərin öyrənmə yanaşmaları arasında CNN-lər LSTM və ya RNN-dən fərqli olaraq obyektlərin aşkarlanması üçün yaxşı uyğun gəlir, hansı ki, dil transkripsiyası və ya tərcümə kimi ardıcıl məlumatlar üçün daha uyğundur.
Koda keçməzdən əvvəl onun arxitekturasına dair bəzi əsas anlayışları əhatə edək:
- Öyrənmək üçün nümunə verilənlər toplusu: Verilənlər dəsti üç sinifə bölünür: Hakim, Komanda_Səfər (Ağ maykalı oyunçular) və Komanda_Ev (Sarı formalı oyunçular). Hər bir sinfin nümunəsi iki dəstə bölündü: təlim məlumatları və doğrulama məlumatları. Təlim məlumatları CNN tərəfindən hər bir iterasiyada (Dövr) bir çox təbəqədə nümunələri “öyrənmək” üçün istifadə olunacaq. Doğrulama məlumatları hər iterasiyanın sonunda modelin performansını qiymətləndirmək və onun yeni məlumatlara nə dərəcədə ümumiləşdirilməsini ölçmək üçün istifadə olunacaq. Nümunə verilənlər bazasını yaratmaq çox çətin deyildi; videodan hər sinifdən nümunə şəkilləri kəsmək və onları alt kataloqlarda təşkil etmək mənə təxminən 30-40 dəqiqə çəkdi. Mən burada tapa biləcəyiniz təxminən 90 təsvirdən ibarət nümunə verilənlər toplusunu yaratmağı bacardım layihənin GitHub deposu.
- Model necə öyrənir? Giriş məlumatları neyron şəbəkəsinin hər bir təbəqəsi ilə hərəkət edir və bu, proqnoz vermək üçün bir və ya bir neçə təbəqəni birləşdirə bilər. Hər bir təbəqə proqnoz vermək və ya verilənlərə dəyişiklik etmək üçün məlumatları emal edən aktivləşdirmə funksiyasından istifadə edir. Bu təbəqələr arasındakı hər bir əlaqənin çəkisi var və bu, bir təbəqənin çıxışının digərinə nə qədər təsir etdiyini müəyyən edir. Məqsəd nəticələri proqnozlaşdırarkən səhvləri minimuma endirən bu çəkilərin düzgün birləşməsini tapmaqdır. Geri yayılma və itki funksiyası adlanan proses vasitəsilə model səhvləri azaltmaq və dəqiqliyi artırmaq üçün bu çəkiləri tənzimləyir. Bu proses an adlanan yerdə təkrarlanır Epoch (irəli ötürmə + geri yayılma)model səhvlərindən öyrəndikcə hər dövrədə proqnozlar verməkdə daha yaxşı olur.
- Aktivləşdirmə funksiyası: Daha əvvəl qeyd edildiyi kimi, aktivləşdirmə funksiyası modelin öyrənmə prosesində mühüm rol oynayır. Mən seçdim ReLU (Düzlənmiş Xətti Vahid) çünki hesablama baxımından səmərəli olması və yoxa çıxan qradiyent problemi adlanan şeyi yumşaltması ilə tanınır. (çox qatlı şəbəkələr effektiv öyrənməni dayandıra bilər). ReLU yaxşı işləsə də, digər funksiyalar kimi sigmoid, balıqlıvə ya çırpınmaq şəbəkənin nə qədər mürəkkəb olmasından asılı olaraq onların istifadələri də var.
- Dövrlər: Dövrlərin düzgün sayının təyin edilməsi təcrübədən ibarətdir. Məlumat dəstinin mürəkkəbliyi, CNN modelinizin arxitekturası və hesablama resursları kimi amilləri nəzərə almalısınız. Əksər hallarda, hər bir iterasiyada modelin performansını izləmək və həddən artıq uyğunlaşmanın qarşısını almaq üçün təkmilləşdirmələr minimal olduqda, təlimi dayandırmaq daha yaxşıdır. Kiçik təlim məlumat dəstimi nəzərə alaraq, Baza olaraq 10 epoxdan başlamağa qərar verdim. Bununla belə, metrik performans və doğrulama nəticələrinə əsaslanan digər ssenarilərdə düzəlişlər lazım ola bilər.
- Adam (Adaptiv Moment Təxmini): Nəhayət, məqsəd proqnozlaşdırılan və həqiqi nəticələr arasındakı səhvi azaltmaqdır. Daha əvvəl qeyd edildiyi kimi, zamanla proqnozları yaxşılaşdırmaq üçün neyron şəbəkə çəkilərini tənzimləmək və yeniləməklə geri yayılma burada əsas rol oynayır. Geri yayılma itki funksiyasından gradientlərə əsaslanan çəki yeniləmələrini idarə edərkən, Adam alqoritmi səhv və ya itki funksiyasını tədricən minimuma endirmək üçün öyrənmə sürətini dinamik şəkildə tənzimləməklə bu prosesi təkmilləşdirir. Başqa sözlə, o, modelin nə qədər tez öyrəndiyini dəqiqləşdirir.
CNN modelimizi işlətmək üçün bizə aşağıdakı Python paketləri lazım olacaq:
pip install torch torchvision
pip install matplotlib
pip install scikit-learn
İpucu-02: PyTorch-un düzgün quraşdırıldığından əmin olun. Bütün alətlərim Anaconda mühitində qurulub və mən PyTorch-u quraşdıranda əvvəlcə onun düzgün qurulduğu görünürdü. Bununla belə, bəzi kitabxanaları işləyərkən bəzi problemlər yarandı. Başlanğıcda bunun kod olduğunu düşündüm, lakin bir neçə düzəlişdən və uğursuzluqdan sonra Anaconda-nı yenidən quraşdırmalı və təmiz mühitdə PyTorch quraşdırmalı oldum və bununla da problem həll olundu!
Sonra biz kitabxanalarımızı və nümunə verilənlər bazamızın yolunu müəyyən edəcəyik:
# ************CONVOLUTIONAL NEURAL NETWORK-THREE CLASSES DETECTION**************************
# REFEREE
# WHITE TEAM (Team_away)
# YELLOW TEAM (Team_home)import os
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import torchvision.transforms as transforms
import torchvision.datasets as datasets
from torch.utils.data import DataLoader
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
import matplotlib.pyplot as plt
#Training and Validation Datasets
#Download the teams_sample_dataset file from the project's GitHub repository
data_dir='D:/PYTHON/teams_sample_dataset'
Əvvəlcə hər bir şəklin bərabər ölçüdə olmasını təmin edəcəyik (ölçünü 150×150 pikselə qədər dəyişdirin), sonra onu kodun başa düşəcəyi formata çevirəcəyik (PyTorch-da giriş məlumatları adətən Tensor obyektləri kimi təqdim olunur). Nəhayət, modelin işləməsini asanlaşdırmaq (normallaşdırmaq) üçün rəngləri tənzimləyəcəyik və şəkilləri yükləmək üçün prosedur quracağıq. Bu addımlar birlikdə şəkilləri hazırlamağa və onları təşkil etməyə kömək edir ki, model onlardan effektiv şəkildə öyrənməyə başlasın, məlumat formatının səbəb olduğu sapmalardan qaçsın.
#******************************Data transformation***********************************
transform=transforms.Compose([
transforms.Resize((150, 150)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
])# Load dataset
train_dataset=datasets.ImageFolder(os.path.join(data_dir, 'train'), transform=transform)
val_dataset=datasets.ImageFolder(os.path.join(data_dir, 'val'), transform=transform)
train_loader=DataLoader(train_dataset, batch_size=32, shuffle=True)
val_loader=DataLoader(val_dataset, batch_size=32, shuffle=False)
Sonra CNN-in arxitekturasını müəyyən edəcəyik:
#********************************CNN Model Architecture**************************************
class CNNModel(nn.Module):
def __init__(self):
super(CNNModel, self).__init__()
self.conv1=nn.Conv2d(3, 32, kernel_size=3, padding=1)
self.pool=nn.MaxPool2d(kernel_size=2, stride=2, padding=0)
self.conv2=nn.Conv2d(32, 64, kernel_size=3, padding=1)
self.conv3=nn.Conv2d(64, 128, kernel_size=3, padding=1)
self.fc1=nn.Linear(128 * 18 * 18, 512)
self.dropout=nn.Dropout(0.5)
self.fc2=nn.Linear(512, 3) #Three Classes (Referee, Team_away,Team_home) def forward(self, x):
x=self.pool(F.relu(self.conv1(x)))
x=self.pool(F.relu(self.conv2(x)))
x=self.pool(F.relu(self.conv3(x)))
x=x.view(-1, 128 * 18 * 18)
x=F.relu(self.fc1(x))
x=self.dropout(x)
x=self.fc2(x)
return x
Görəcəksiniz ki, CNN modelimizin üç qatı var (conv1, conv2, conv3). Məlumat aktivləşdirmə funksiyasının (ReLU) tətbiq olunduğu konvolyusiya qatında (conv) başlayır. Bu funksiya şəbəkəyə verilənlərdəki mürəkkəb nümunələri və əlaqələri öyrənməyə imkan verir. Bundan sonra birləşən təbəqə aktivləşdirilir. Max Pooling nədir? Bu, effektiv təlimə kömək edən və yaddaş resurslarını optimallaşdıran mühüm xüsusiyyətləri saxlayaraq şəkil ölçüsünü azaldan texnikadır. Bu proses conv1-dən conv3-ə qədər təkrarlanır. Nəhayət, məlumatlar yekun təsnifat (və ya qərar qəbulu) üçün tam əlaqəli təbəqələrdən (fc1, fc2) keçir.
Növbəti addım olaraq, modelimizi işə salırıq, itki funksiyası kimi kateqoriyalar arası entropiyanı konfiqurasiya edirik (ümumiyyətlə təsnifat tapşırıqları üçün istifadə olunur), və Adamı optimallaşdırıcımız kimi təyin edin. Daha əvvəl qeyd edildiyi kimi, biz modelimizi 10 dövrdən ibarət tam dövr ərzində icra edəcəyik.
#********************************CNN TRAINING**********************************************# Model-loss function-optimizer
model=CNNModel()
criterion=nn.CrossEntropyLoss()
optimizer=optim.Adam(model.parameters(), lr=0.001)
#*********************************Training*************************************************
num_epochs=10
train_losses, val_losses=[], []
for epoch in range(num_epochs):
model.train()
running_loss=0.0
for inputs, labels in train_loader:
optimizer.zero_grad()
outputs=model(inputs)
labels=labels.type(torch.LongTensor)
loss=criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss +=loss.item()
train_losses.append(running_loss / len(train_loader))
model.eval()
val_loss=0.0
all_labels=[]
all_preds=[]
with torch.no_grad():
for inputs, labels in val_loader:
outputs=model(inputs)
labels=labels.type(torch.LongTensor)
loss=criterion(outputs, labels)
val_loss +=loss.item()
_, preds=torch.max(outputs, 1)
all_labels.extend(labels.tolist())
all_preds.extend(preds.tolist())
Performansı izləmək üçün biz təlimin gedişatını izləmək, doğrulama ölçülərini çap etmək və onların qrafikini çəkmək üçün bəzi kodlar əlavə edəcəyik. Nəhayət, modeli olaraq qeyd edirik hockey_team_classifier.pth seçdiyiniz müəyyən bir yolda.
#********************************METRICS & PERFORMANCE************************************ val_losses.append(val_loss / len(val_loader))
val_accuracy=accuracy_score(all_labels, all_preds)
val_precision=precision_score(all_labels, all_preds, average='macro', zero_division=1)
val_recall=recall_score(all_labels, all_preds, average='macro', zero_division=1)
val_f1=f1_score(all_labels, all_preds, average='macro', zero_division=1)
print(f"Epoch [{epoch + 1}/{num_epochs}], "
f"Loss: {train_losses[-1]:.4f}, "
f"Val Loss: {val_losses[-1]:.4f}, "
f"Val Acc: {val_accuracy:.2%}, "
f"Val Precision: {val_precision:.4f}, "
f"Val Recall: {val_recall:.4f}, "
f"Val F1 Score: {val_f1:.4f}")
#*******************************SHOW METRICS & PERFORMANCE**********************************
plt.plot(train_losses, label='Train Loss')
plt.plot(val_losses, label='Validation Loss')
plt.legend()
plt.show()
# SAVE THE MODEL FOR THE GH_CV_track_teams CODE
torch.save(model.state_dict(), 'D:/PYTHON/hockey_team_classifier.pth')
Bundan əlavə, yuxarıda təsvir edilən bütün addımları yerinə yetirdikdən sonra “pth” faylınızla yanaşı (tam kodu burada tapa bilərsiniz layihənin GitHub deposuaşağıdakı kimi bir nəticə görməyinizi gözləməlisiniz (metrikalar bir qədər fərqli ola bilər):
#**************CNN PERFORMANCE ACROSS TRAINING EPOCHS************************Epoch [1/10], Loss: 1.5346, Val Loss: 1.2339, Val Acc: 47.37%, Val Precision: 0.7172, Val Recall: 0.5641, Val F1 Score: 0.4167
Epoch [2/10], Loss: 1.1473, Val Loss: 1.1664, Val Acc: 55.26%, Val Precision: 0.6965, Val Recall: 0.6296, Val F1 Score: 0.4600
Epoch [3/10], Loss: 1.0139, Val Loss: 0.9512, Val Acc: 57.89%, Val Precision: 0.6054, Val Recall: 0.6054, Val F1 Score: 0.5909
Epoch [4/10], Loss: 0.8937, Val Loss: 0.8242, Val Acc: 60.53%, Val Precision: 0.7222, Val Recall: 0.5645, Val F1 Score: 0.5538
Epoch [5/10], Loss: 0.7936, Val Loss: 0.7177, Val Acc: 63.16%, Val Precision: 0.6667, Val Recall: 0.6309, Val F1 Score: 0.6419
Epoch [6/10], Loss: 0.6871, Val Loss: 0.7782, Val Acc: 68.42%, Val Precision: 0.6936, Val Recall: 0.7128, Val F1 Score: 0.6781
Epoch [7/10], Loss: 0.6276, Val Loss: 0.5684, Val Acc: 78.95%, Val Precision: 0.8449, Val Recall: 0.7523, Val F1 Score: 0.7589
Epoch [8/10], Loss: 0.4198, Val Loss: 0.5613, Val Acc: 86.84%, Val Precision: 0.8736, Val Recall: 0.8958, Val F1 Score: 0.8653
Epoch [9/10], Loss: 0.3959, Val Loss: 0.3824, Val Acc: 92.11%, Val Precision: 0.9333, Val Recall: 0.9213, Val F1 Score: 0.9243
Epoch [10/10], Loss: 0.2509, Val Loss: 0.2651, Val Acc: 97.37%, Val Precision: 0.9762, Val Recall: 0.9792, Val F1 Score: 0.9769
10 dövrü bitirdikdən sonra CNN modeli performans göstəricilərində yaxşılaşma göstərir. İlkin olaraq, Epoch 1-də model 1,5346 təlim itkisi və 47,37% doğrulama dəqiqliyi ilə başlayır. Bu ilkin məqamı necə başa düşməliyik?
Dəqiqlik təsnifat performansını qiymətləndirmək üçün ən ümumi ölçülərdən biridir. Bizim vəziyyətimizdə bu, düzgün proqnozlaşdırılan siniflərin cəmindən nisbətini təmsil edir. Bununla belə, tək yüksək dəqiqlik ümumi model performansına zəmanət vermir; Siz hələ də xüsusi siniflər üçün zəif proqnozlara sahib ola bilərsiniz (ilkin sınaqlarda yaşadığım kimi). ilə bağlı məşq itkisi, o, modelin giriş məlumatlarını düzgün etiketlərlə əlaqələndirməyi nə dərəcədə effektiv öyrəndiyini ölçür. Təsnifat funksiyasından istifadə etdiyimiz üçün, Çarpaz entropiya itkisi proqnozlaşdırılan sinif ehtimalları ilə faktiki etiketlər arasındakı fərqi kəmiyyətlə müəyyən edir. 1.5346 kimi başlanğıc dəyəri proqnozlaşdırılan və faktiki siniflər arasında əhəmiyyətli fərqləri göstərir; ideal olaraq, təlim irəlilədikcə bu dəyər 0-a yaxınlaşmalıdır. Dövrlər irəlilədikcə biz təlim itkisinin əhəmiyyətli dərəcədə azaldığını və doğrulama dəqiqliyində artım müşahidə edirik. Son dövrə qədər təlim və təsdiqləmə itkisi müvafiq olaraq 0,2509 və 0,2651 minimumlarına çatır.
CNN modelimizi sınamaq üçün biz oyunçu təsvirlərindən bir nümunə seçə və onun proqnozlaşdırma qabiliyyətini qiymətləndirə bilərik. Sınaq üçün aşağıdakı kodu işlədə və istifadə edə bilərsiniz validation_dataset qovluğu içində layihənin GitHub deposu.
# *************TEST CNN MODEL WITH SAMPLE DATASET***************************import os
import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision.transforms as transforms
from PIL import Image
# SAMPLE DATASET FOR VALIDATION
test_dir='D:/PYTHON/validation_dataset'
# CNN MODEL FOR TEAM PREDICTIONS
class CNNModel(nn.Module):
def __init__(self):
super(CNNModel, self).__init__()
self.conv1=nn.Conv2d(3, 32, kernel_size=3, padding=1)
self.pool=nn.MaxPool2d(kernel_size=2, stride=2, padding=0)
self.conv2=nn.Conv2d(32, 64, kernel_size=3, padding=1)
self.conv3=nn.Conv2d(64, 128, kernel_size=3, padding=1)
self.fc1=nn.Linear(128 * 18 * 18, 512)
self.dropout=nn.Dropout(0.5)
self.fc2=nn.Linear(512, 3)
def forward(self, x):
x=self.pool(F.relu(self.conv1(x)))
x=self.pool(F.relu(self.conv2(x)))
x=self.pool(F.relu(self.conv3(x)))
x=x.view(-1, 128 * 18 * 18)
x=F.relu(self.fc1(x))
x=self.dropout(x)
x=self.fc2(x)
return x
# CNN MODEL PREVIOUSLY SAVED
model=CNNModel()
model.load_state_dict(torch.load('D:/PYTHON/hockey_team_classifier.pth'))
model.eval()
transform=transforms.Compose([
transforms.Resize((150, 150)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
])
#******************ITERATION ON SAMPLE IMAGES-ACCURACY TEST*****************************
class_names=['team_referee', 'team_away', 'team_home']
def predict_image(image_path, model, transform):
# LOADS DATASET
image=Image.open(image_path)
image=transform(image).unsqueeze(0)
# MAKES PREDICTIONS
with torch.no_grad():
output=model(image)
_, predicted=torch.max(output, 1)
team=class_names[predicted.item()]
return team
for image_name in os.listdir(test_dir):
image_path=os.path.join(test_dir, image_name)
if os.path.isfile(image_path):
predicted_team=predict_image(image_path, model, transform)
print(f'Image {image_name}: The player belongs to {predicted_team}')
Çıxış bu kimi görünməlidir:
# *************CNN MODEL TEST - OUTPUT ***********************************#Image Away_image04.jpg: The player belongs to team_away
Image Away_image12.jpg: The player belongs to team_away
Image Away_image14.jpg: The player belongs to team_away
Image Home_image07.jpg: The player belongs to team_home
Image Home_image13.jpg: The player belongs to team_home
Image Home_image16.jpg: The player belongs to team_home
Image Referee_image04.jpg: The player belongs to team_referee
Image Referee_image09.jpg: The player belongs to team_referee
Image Referee_image10.jpg: The player belongs to team_referee
Image Referee_image11.jpg: The player belongs to team_referee
Göründüyü kimi, model komandaları müəyyən etmək və hakimi komanda oyunçusu kimi istisna etməkdə kifayət qədər yaxşı bacarıq nümayiş etdirir.
İpucu # 03: CNN dizayn prosesi zamanı öyrəndiyim bir şey odur ki, mürəkkəblik əlavə etmək həmişə performansı yaxşılaşdırmır. Başlanğıcda, mən daha dərin modellər (daha əyri laylar) və oyunçuların formasının tanınmasını artırmaq üçün rəng əsaslı artırma ilə sınaqdan keçirdim. Bununla belə, kiçik verilənlər bazamda ümumiləşdirilə bilən xüsusiyyətləri öyrənməkdənsə, həddindən artıq uyğunlaşma ilə qarşılaşdım (bütün şəkillər ağ komanda oyunçuları və ya hakimlər kimi proqnozlaşdırılırdı). İşdən çıxma və toplu işlərin normallaşdırılması kimi tənzimləmə üsulları da vacibdir; onlar təlim zamanı məhdudiyyətlər qoymağa kömək edir, modelin yeni məlumatlara yaxşı ümumiləşdirilməsini təmin edir. Daha az bəzən nəticə baxımından daha çox məna verə bilər😁.
HƏMİNİ BİR YERDƏ QOYARAQ
Hamısını bir araya gətirmək əvvəllər təsvir edilən izləmə mexanizmimizə bəzi düzəlişlər tələb edəcək. Budur yenilənmiş kodun addım-addım bölgüsü.
Birincisi, biz lazım olan kitabxanaları və yolları quracağıq. Qeyd edək ki, turşu faylımız və CNN modelimiz üçün yollar indi müəyyən edilib. Bu dəfə, turşu faylı yolda tapılmazsa, kod xəta verəcək. Lazım gələrsə, turşu faylını yaratmaq üçün əvvəlki koddan istifadə edin və video analizini yerinə yetirmək üçün bu yenilənmiş versiyadan istifadə edin:
import cv2
import numpy as np
from ultralytics import YOLO
import pickle
import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision.transforms as transforms
from PIL import Image# MODEL INPUTS
model_path='D:/PYTHON/yolov8x.pt'
video_path='D:/PYTHON/video_input.mp4'
output_path='D:/PYTHON/output_video.mp4'
tracks_path='D:/PYTHON/stubs/track_stubs.pkl'
classifier_path='D:/PYTHON/hockey_team_classifier.pth'
Sonra, biz modelləri yükləyəcəyik, meydançanın koordinatlarını təyin edəcəyik və əvvəllər etdiyimiz kimi, hər bir çərçivədə 20-lik partiyalarda obyektlərin aşkarlanması prosesinə başlayacağıq. Qeyd edək ki, hələlik təhlili meydançaya yönəltmək üçün yalnız meydança sərhədlərindən istifadə edəcəyik. Məqalənin son addımlarında, performans statistikasını daxil edərkən, hücum zonasının koordinatlarından istifadə edəcəyik.
#*************************** Loads models and rink coordinates********************#
class_names=['Referee', 'Tm_white', 'Tm_yellow']class HockeyAnalyzer:
def __init__(self, model_path, classifier_path):
self.model=YOLO(model_path)
self.classifier=self.load_classifier(classifier_path)
self.transform=transforms.Compose([
transforms.Resize((150, 150)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
])
self.rink_coordinates=np.array([[-450, 710], [2030, 710], [948, 61], [352, 61]])
self.zone_white=[(180, 150), (1100, 150), (900, 61), (352, 61)]
self.zone_yellow=[(-450, 710), (2030, 710), (1160, 150), (200, 150)]
#******************** Detect objects in each frame **********************************#
def detect_frames(self, frames):
batch_size=20
detections=[]
for i in range(0, len(frames), batch_size):
detections_batch=self.model.predict(frames[i:i+batch_size], conf=0.1)
detections +=detections_batch
return detections
Sonra, hər bir oyunçunun komandasını proqnozlaşdırmaq üçün prosesi əlavə edəcəyik:
#*********************** Loads CNN Model**********************************************# def load_classifier(self, classifier_path):
model=CNNModel()
model.load_state_dict(torch.load(classifier_path, map_location=torch.device('cpu')))
model.eval()
return model
def predict_team(self, image):
with torch.no_grad():
output=self.classifier(image)
_, predicted=torch.max(output, 1)
predicted_index=predicted.item()
team=class_names[predicted_index]
return team
Növbəti addım olaraq, məhdudlaşdırıcı qutulardan ellipsə keçmək üçün əvvəllər təsvir edilən metodu əlavə edəcəyik:
#************ Ellipse for tracking players instead of Bounding boxes*******************#
def draw_ellipse(self, frame, bbox, color, track_id=None, team=None):
y2=int(bbox[3])
x_center=(int(bbox[0]) + int(bbox[2])) // 2
width=int(bbox[2]) - int(bbox[0]) if team=='Referee':
color=(0, 255, 255)
text_color=(0, 0, 0)
else:
color=(255, 0, 0)
text_color=(255, 255, 255)
cv2.ellipse(
frame,
center=(x_center, y2),
axes=(int(width) // 2, int(0.35 * width)),
angle=0.0,
startAngle=-45,
endAngle=235,
color=color,
thickness=2,
lineType=cv2.LINE_4
)
if track_id is not None:
rectangle_width=40
rectangle_height=20
x1_rect=x_center - rectangle_width // 2
x2_rect=x_center + rectangle_width // 2
y1_rect=(y2 - rectangle_height // 2) + 15
y2_rect=(y2 + rectangle_height // 2) + 15
cv2.rectangle(frame,
(int(x1_rect), int(y1_rect)),
(int(x2_rect), int(y2_rect)),
color,
cv2.FILLED)
x1_text=x1_rect + 12
if track_id> 99:
x1_text -=10
font_scale=0.4
cv2.putText(
frame,
f"{track_id}",
(int(x1_text), int(y1_rect + 15)),
cv2.FONT_HERSHEY_SIMPLEX,
font_scale,
text_color,
thickness=2
)
return frame
İndi turşu faylını oxumaq, analizi əvvəllər müəyyən etdiyimiz meydança sərhədləri daxilində daraltmaq və hər bir oyunçunun komandasını müəyyən etmək və etiketlər əlavə etmək üçün CNN modelinə zəng etməkdən ibarət analizatoru əlavə etməyin vaxtıdır. Qeyd edək ki, biz hakimləri fərqli rənglə etiketləmək və onların ellipslərinin rəngini də dəyişmək funksiyasını daxil edirik. Kod işlənmiş çərçivələrin çıxış videosuna yazılması ilə başa çatır.
#******************* Loads Tracked Data (pickle file )**********************************# def analyze_video(self, video_path, output_path, tracks_path):
with open(tracks_path, 'rb') as f:
tracks=pickle.load(f)
cap=cv2.VideoCapture(video_path)
if not cap.isOpened():
print("Error: Could not open video.")
return
fps=cap.get(cv2.CAP_PROP_FPS)
frame_width=int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
frame_height=int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fourcc=cv2.VideoWriter_fourcc(*'XVID')
out=cv2.VideoWriter(output_path, fourcc, fps, (frame_width, frame_height))
frame_num=0
while cap.isOpened():
ret, frame=cap.read()
if not ret:
break
#***********Checks if the player falls within the rink area**********************************#
mask=np.zeros(frame.shape[:2], dtype=np.uint8)
cv2.fillConvexPoly(mask, self.rink_coordinates, 1)
mask=mask.astype(bool)
# Draw rink area
#cv2.polylines(frame, [self.rink_coordinates], isClosed=True, color=(0, 255, 0), thickness=2)
# Get tracks from frame
player_dict=tracks["person"][frame_num]
for track_id, player in player_dict.items():
bbox=player["bbox"]
# Check if the player is within the Rink Area
x_center=int((bbox[0] + bbox[2]) / 2)
y_center=int((bbox[1] + bbox[3]) / 2)
if not mask[y_center, x_center]:
continue
#**********************************Team Prediction********************************************#
x1, y1, x2, y2=map(int, bbox)
cropped_image=frame[y1:y2, x1:x2]
cropped_pil_image=Image.fromarray(cv2.cvtColor(cropped_image, cv2.COLOR_BGR2RGB))
transformed_image=self.transform(cropped_pil_image).unsqueeze(0)
team=self.predict_team(transformed_image)
#************ Ellipse for tracked players and labels******************************************#
self.draw_ellipse(frame, bbox, (0, 255, 0), track_id, team)
font_scale=1
text_offset=-20
if team=='Referee':
rectangle_width=60
rectangle_height=25
x1_rect=x1
x2_rect=x1 + rectangle_width
y1_rect=y1 - 30
y2_rect=y1 - 5
# Different setup for Referee
cv2.rectangle(frame,
(int(x1_rect), int(y1_rect)),
(int(x2_rect), int(y2_rect)),
(0, 0, 0),
cv2.FILLED)
text_color=(255, 255, 255)
else:
if team=='Tm_white':
text_color=(255, 215, 0) # White Team: Blue labels
else:
text_color=(0, 255, 255) # Yellow Team: Yellow labels
# Draw Team labels
cv2.putText(
frame,
team,
(int(x1), int(y1) + text_offset),
cv2.FONT_HERSHEY_PLAIN,
font_scale,
text_color,
thickness=2
)
# Write output video
out.write(frame)
frame_num +=1
cap.release()
out.release()
Nəhayət, CNN-nin arxitekturasını əlavə edirik (CNN dizayn prosesində müəyyən edilir) və Xokkey analizatorunu icra edirik:
#**********************CNN Model Architecture ******************************#
class CNNModel(nn.Module):
def __init__(self):
super(CNNModel, self).__init__()
self.conv1=nn.Conv2d(3, 32, kernel_size=3, padding=1)
self.pool=nn.MaxPool2d(kernel_size=2, stride=2, padding=0)
self.conv2=nn.Conv2d(32, 64, kernel_size=3, padding=1)
self.conv3=nn.Conv2d(64, 128, kernel_size=3, padding=1)
self.fc1=nn.Linear(128 * 18 * 18, 512)
self.dropout=nn.Dropout(0.5)
self.fc2=nn.Linear(512, len(class_names)) def forward(self, x):
x=self.pool(F.relu(self.conv1(x)))
x=self.pool(F.relu(self.conv2(x)))
x=self.pool(F.relu(self.conv3(x)))
x=x.view(-1, 128 * 18 * 18)
x=F.relu(self.fc1(x))
x=self.dropout(x)
x=self.fc2(x)
return x
#*********Execute HockeyAnalyzer/classifier and Save Output************#
analyzer=HockeyAnalyzer(model_path, classifier_path)
analyzer.analyze_video(video_path, output_path, tracks_path)
Bütün addımları yerinə yetirdikdən sonra video çıxışınız belə görünməlidir:
Qeyd edək ki, bu son yeniləmədə obyekt aşkarlamaları yalnız buz meydançası daxilindədir və komandalar, eləcə də hakim fərqlənir. CNN modeli hələ də incə tənzimləmə tələb etsə də və bəzən bəzi oyunçularla sabitliyini itirsə də, video boyu əsasən etibarlı və dəqiq olaraq qalır.
SÜRƏT, MƏSAFƏ VƏ HÜCUM TƏZİQİ
Komandaları və oyunçuları izləmək qabiliyyəti performansın ölçülməsi üçün istilik xəritələrinin yaradılması, sürət və məsafənin təhlili, zonaya giriş və ya çıxışlar kimi hərəkətləri izləmək və ətraflı oyunçu göstəricilərinə daxil olmaq kimi maraqlı imkanlar açır. Bunun dadına baxa bilmək üçün üç performans göstəricisi əlavə edəcəyik: oyunçu başına orta sürətkonki sürmə məsafə qət etdi hər komanda tərəfindən və ohücum təzyiqi (hər komandanın rəqib zonasında keçirdiyi məsafənin faizi kimi ölçülür). Daha ətraflı statistikanı sizə buraxacağam!
Buz meydançasının koordinatlarını piksel əsaslı ölçmələrdən təxmini metrlərə uyğunlaşdırmağa başlayırıq. Bu tənzimləmə bizə məlumatları piksellərlə deyil, metrlərlə oxumağa imkan verir. Videoda görünən buz meydançasının real dünya ölçüləri təqribən 15mx30m-dir (eni 15 metr və hündürlüyü 30 metr). Bu çevrilməni asanlaşdırmaq üçün biz yığışdırmaq üçün bir üsul təqdim edirik rt piksel koordinatları metrə. Meydançanın faktiki ölçülərini təyin etməklə və onun künclərinin piksel koordinatlarından istifadə etməklə (soldan sağa və yuxarıdan aşağıya) biz çevrilmə əmsallarını əldə edirik. Bu amillər məsafələri metrlə və sürəti saniyədə metrlə qiymətləndirmək prosesimizi dəstəkləyəcək. (Kəşf edə və tətbiq edə biləcəyiniz başqa bir maraqlı texnika Perspektiv Çevrilmədir)
#*********************Loads models and rink coordinates*****************#
class_names=['Referee', 'Tm_white', 'Tm_yellow']class HockeyAnalyzer:
def __init__(self, model_path, classifier_path):
*
*
*
*
*
*
self.pixel_to_meter_conversion() #
#***********Pixel-based measurements to meters***************************#
def pixel_to_meter_conversion(self):
#Rink real dimensions in meters
rink_width_m=15
rink_height_m=30
#Pixel coordinates for rink dimensions
left_pixel, right_pixel=self.rink_coordinates[0][0], self.rink_coordinates[1][0]
top_pixel, bottom_pixel=self.rink_coordinates[2][1], self.rink_coordinates[0][1]
#Conversion factors
self.pixels_per_meter_x=(right_pixel - left_pixel) / rink_width_m
self.pixels_per_meter_y=(bottom_pixel - top_pixel) / rink_height_m
def convert_pixels_to_meters(self, distance_pixels):
#Convert pixels to meters
return distance_pixels / self.pixels_per_meter_x, distance_pixels / self.pixels_per_meter_y
İndi biz hazırıq saniyədə metrlə ölçülən hər bir oyunçuya sürət əlavə edin. Bunu etmək üçün üç dəyişiklik etməliyik. Birincisi, adlı boş lüğəti işə salın əvvəlki_vəzifələr içində Xokkey Analizator sinfi oyunçuların indiki və əvvəlki mövqelərini müqayisə etməyə kömək etmək. Eynilə, biz də yaradacağıqeam_stats daha vizuallaşdırma üçün hər bir komandanın statistikasını saxlamaq üçün struktur.
Sonra a əlavə edəcəyik sürət üsulu oyunçuların sürətini saniyədə piksellə qiymətləndirmək və sonra onu saniyədə metrə çevirmək üçün çevirmə əmsalından (əvvəllər izah edilmiş) istifadə etmək. Nəhayət, dən təhlil_video metodu, biz yeni sürət metodumuzu çağıracağıq və sürəti izlənilən hər bir obyektə (oyunçular və hakim) əlavə edəcəyik. Dəyişikliklər belə görünür:
#*********************Loads models and rink coordinates*****************#
class_names=['Referee', 'Tm_white', 'Tm_yellow']class HockeyAnalyzer:
def __init__(self, model_path, classifier_path):
*
*
*
*
*
*
*
self.pixel_to_meter_conversion()
self.previous_positions={} # self.team_stats={
'Tm_white': {'distance': 0, 'speed': [], 'count': 0, 'offensive_pressure': 0},
'Tm_yellow': {'distance': 0, 'speed': [], 'count': 0, 'offensive_pressure': 0}
} #
#**************** Speed: meters per second********************************#
def calculate_speed(self, track_id, x_center, y_center, fps):
current_position=(x_center, y_center)
if track_id in self.previous_positions:
prev_position=self.previous_positions[track_id]
distance_pixels=np.linalg.norm(np.array(current_position) - np.array(prev_position))
distance_meters_x, distance_meters_y=self.convert_pixels_to_meters(distance_pixels)
speed_meters_per_second=(distance_meters_x**2 + distance_meters_y**2)**0.5 * fps
else:
speed_meters_per_second=0
self.previous_positions[track_id]=current_position
return speed_meters_per_second
#******************* Loads Tracked Data (pickle file )**********************************#
def analyze_video(self, video_path, output_path, tracks_path):
with open(tracks_path, 'rb') as f:
tracks=pickle.load(f)
*
*
*
*
*
*
*
*
# Draw Team label
cv2.putText(
frame,
team,
(int(x1), int(y1) + text_offset),
cv2.FONT_HERSHEY_PLAIN,
font_scale,
text_color,
thickness=2
)
#**************Add these lines of code --->:
speed=self.calculate_speed(track_id, x_center, y_center, fps)
# Speed label
speed_font_scale=0.8
speed_y_position=int(y1) + 20
if speed_y_position> int(y1) - 5:
speed_y_position=int(y1) - 5
cv2.putText(
frame,
f"Speed: {speed:.2f} m/s",
(int(x1), speed_y_position),
cv2.FONT_HERSHEY_PLAIN,
speed_font_scale,
text_color,
thickness=2
)
# Write output video
out.write(frame)
frame_num +=1
cap.release()
out.release()
Bu yeni kod sətirlərini əlavə etməkdə çətinlik çəkirsinizsə, hər zaman ziyarət edə bilərsiniz layihənin GitHub deposu, burada tam inteqrasiya edilmiş kodu tapa bilərsiniz. Bu nöqtədə video çıxışınız bu kimi görünməlidir (hər bir oyunçunun etiketinə sürətin əlavə olunduğuna diqqət yetirin):
Nəhayət, hər bir komanda üçün bir oyunçuya düşən orta sürəti, qət edilən məsafə və rəqib zonasında hücum təzyiqi kimi digər göstəriciləri izləyə biləcəyimiz bir statistik lövhə əlavə edək.
Biz artıq hücum zonalarını müəyyən etmişik və onları kodumuza inteqrasiya etmişik. İndi hər bir oyunçunun rəqibinin zonasına nə qədər tez-tez daxil olduğunu izləməliyik. Buna nail olmaq üçün istifadə edərək bir metod tətbiq edəcəyik şüa tökmə alqoritmi. Bu alqoritm oyunçunun mövqeyinin ağ və ya sarı komandanın hücum zonasında olub olmadığını yoxlayır. O, oyunçudan hədəf zonasına xəyali bir xətt çəkərək işləyir. Xətt bir sərhədi keçərsə, oyunçu içəridədir, daha çox keçərsə (bizim vəziyyətimizdə dörd sərhəddən ikisi), oyunçu kənardadır. Daha sonra kod hər izlənən obyektin zona statusunu müəyyən etmək üçün bütün videonu skan edir.
#************ Locate player's position in Target Zone***********************# def is_inside_zone(self, position, zone):
x, y=position
n=len(zone)
inside=False
p1x, p1y=zone[0]
for i in range(n + 1):
p2x, p2y=zone[i % n]
if y> min(p1y, p2y):
if y if x if p1y !=p2y:
xinters=(y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x
if p1x==p2x or x inside=not inside
p1x, p1y=p2x, p2y
return inside
İndi biz göstərən metodu əlavə etməklə performans göstəricilərini idarə edəcəyik orta oyunçu sürətiümumi məsafə qət etdivə hücum təzyiqi (rəqib zonasında sərf olunan vaxtın faizi) hər bir komanda üçün cədvəl formatında. OpenCV-dən istifadə edərək, biz bu ölçüləri videonun üzərindəki cədvələ formatlayacağıq və oyun zamanı real vaxt statistikasını saxlamaq üçün dinamik yeniləmə mexanizmini birləşdirəcəyik.
#*******************************Performance metrics*********************************************#
def draw_stats(self, frame):
avg_speed_white=np.mean(self.team_stats['Tm_white']['speed']) if self.team_stats['Tm_white']['count']> 0 else 0
avg_speed_yellow=np.mean(self.team_stats['Tm_yellow']['speed']) if self.team_stats['Tm_yellow']['count']> 0 else 0
distance_white=self.team_stats['Tm_white']['distance']
distance_yellow=self.team_stats['Tm_yellow']['distance'] offensive_pressure_white=self.team_stats['Tm_white'].get('offensive_pressure', 0)
offensive_pressure_yellow=self.team_stats['Tm_yellow'].get('offensive_pressure', 0)
Pressure_ratio_W=offensive_pressure_white/distance_white *100 if self.team_stats['Tm_white']['distance']> 0 else 0
Pressure_ratio_Y=offensive_pressure_yellow/distance_yellow *100 if self.team_stats['Tm_yellow']['distance']> 0 else 0
table=[
["", "Away_White", "Home_Yellow"],
["Average SpeednPlayer", f"{avg_speed_white:.2f} m/s", f"{avg_speed_yellow:.2f} m/s"],
["DistancenCovered", f"{distance_white:.2f} m", f"{distance_yellow:.2f} m"],
["OffensivenPressure %", f"{Pressure_ratio_W:.2f} %", f"{Pressure_ratio_Y:.2f} %"],
]
text_color=(0, 0, 0)
start_x, start_y=10, 590
row_height=30 # Manage Height between rows
column_width=150 # Manage Width between rows
font_scale=1
def put_multiline_text(frame, text, position, font, font_scale, color, thickness, line_type, line_spacing=1.0):
y0, dy=position[1], int(font_scale * 20 * line_spacing) # Adjust line spacing here
for i, line in enumerate(text.split('n')):
y=y0 + i * dy
cv2.putText(frame, line, (position[0], y), font, font_scale, color, thickness, line_type)
for i, row in enumerate(table):
for j, text in enumerate(row):
if i in [1,2, 3]:
put_multiline_text(
frame,
text,
(start_x + j * column_width, start_y + i * row_height),
cv2.FONT_HERSHEY_PLAIN,
font_scale,
text_color,
1,
cv2.LINE_AA,
line_spacing=0.8
)
else:
cv2.putText(
frame,
text,
(start_x + j * column_width, start_y + i * row_height),
cv2.FONT_HERSHEY_PLAIN,
font_scale,
text_color,
1,
cv2.LINE_AA,
)
#****************** Track and update game stats****************************************#
def update_team_stats(self, team, speed, distance, position):
if team in self.team_stats:
self.team_stats[team]['speed'].append(speed)
self.team_stats[team]['distance'] +=distance
self.team_stats[team]['count'] +=1
if team=='Tm_white':
if self.is_inside_zone(position, self.zone_white):
self.team_stats[team]['offensive_pressure'] +=distance
elif team=='Tm_yellow':
if self.is_inside_zone(position, self.zone_yellow):
self.team_stats[team]['offensive_pressure'] +=distance
Statistikanın videoda görünməsi üçün biz bu metodu çağırmalıyıq analiz_video metodu, sürət etiketi müəyyən edildikdən sonra və çıxış videosu emal edilməzdən əvvəl bu əlavə kod sətirlərini əlavə etməyinizə əmin olun:
*
*
*
*
*
*
*
#Speed label
speed_font_scale=0.8
speed_y_position=int(y1) + 20
if speed_y_position> int(y1) - 5:
speed_y_position=int(y1) - 5 cv2.putText(
frame,
f"Speed: {speed:.2f} m/s",
(int(x1), speed_y_position),
cv2.FONT_HERSHEY_PLAIN,
speed_font_scale,
text_color,
thickness=2
)
#**************Add these lines of code--->:
distance=speed / fps
position=(x_center, y_center)
self.update_team_stats(team, speed, distance, position)
# Write output video
out.write(frame)
frame_num +=1
Hər bir oyunçunun qət etdiyi metrlərlə məsafə onların sürətini (saniyədə metrlə ölçülür) kadr sürətinə (saniyədə kadr) bölməklə hesablanır. Bu hesablama bizə hər bir oyunçunun videodakı hər kadr dəyişikliyi arasında nə qədər hərəkət etdiyini təxmin etməyə imkan verir. Hər şey yaxşı olarsa, son video çıxışınız belə görünməlidir:
Mülahizələr və gələcək işlər
Bu model, buz xokkey oyununda (və ya hər hansı komanda idmanında) oyunçuları izləmək üçün kompüter görmə qabiliyyətindən istifadə etməklə əldə edilə bilən əsas quraşdırmadır. Bununla belə, onu təkmilləşdirmək və yeni imkanlar əlavə etmək üçün edilə bilən bir çox incə tənzimləmə var. Növbəti 2.0 versiyası üçün üzərində işlədiyim bir neçə ideyanı nəzərdən keçirə bilərsiniz:
Şaybaya əməl etməyin çətinliyi: Kameranızın hansı istiqamətə baxdığından və ayırdetmə qabiliyyətindən asılı olaraq, futbol və ya basketbol topu ilə müqayisədə onun ölçüsünü nəzərə alaraq şaybanı izləmək çətindir. Ancaq buna nail olsanız, performansı izləmək üçün maraqlı imkanlar açılır, məsələn, sahiblik vaxtı ölçüləri, qol imkanları və ya atış məlumatları. Bu, həmçinin fərdi çıxışlara da aiddir; buzlu xokkeydə oyunçular digər komanda idman növləri ilə müqayisədə əhəmiyyətli dərəcədə tez-tez dəyişir, ona görə də bir dövr ərzində hər bir oyunçunun performansını izləmək çətin olur.
Resursları hesablayın, Oh, niyə hesablayın! Bütün kodu CPU tənzimləməsində işlətdim, lakin problemlərlə üzləşdim (bəzən mavi ekranlarla nəticələnir 😥) dizayn prosesi zamanı yaddaşın tükənməsinə görə (CUDA quraşdırmasından istifadə etməyi düşünün). Bizim nümunə videomuz təxminən 40 saniyə uzunluğunda və ilkin olaraq 5 MB ölçüsündədir, lakin modeli işə saldıqdan sonra çıxış 34 MB-a qədər artır. Tam 20 dəqiqəlik oyun dövrü üçün ölçüsü təsəvvür edin. Beləliklə, miqyasını artırarkən hesablama resurslarını və yaddaşı nəzərə almalısınız.
MLOpları qiymətləndirməyin: Sürətlə yerləşdirmək və genişləndirmək üçün bizə səmərəli, tez-tez icranı dəstəkləyən və etibarlı Maşın Öyrənmə boru kəmərlərinə ehtiyacımız var. Bu, a Davamlı İnteqrasiya-Yerləşdirmə-Təlim yanaşması. İstifadə vəziyyətimiz xüsusi bir ssenari üçün qurulmuşdur, lakin kamera istiqaməti və ya forma rəngləri kimi şərtlər dəyişsə nə etməli? Genişləndirmək üçün biz CI/CD/CT zehniyyətini qəbul etməliyik.
Ümid edirəm ki, bu kompüter görmə layihəsini maraqlı tapdınız, tam koda daxil ola bilərsiniz bu GitHub deposu. Bölgədə inline və buz xokkeyinin inkişafına dəstək olmaq istəyirsinizsə, izləyin APHL (gənc oyunçular və ilk rəsmi xokkey meydançamızın tikintisi üzərində işləyənlər üçün bağışlamaq istədiyiniz istifadə olunmuş avadanlıqlara həmişə ehtiyacımız var)və bütün dünyada izləyin və dəstəkləyin Dostluq Liqası.
Mən bir şey əldən verdim? Təklifləriniz həmişə qəbul olunur. Söhbəti davam etdirək!