wko/wko-07.ipynb
2023-01-16 09:35:16 +01:00

3.0 MiB
Raw Blame History

Logo 1

Widzenie komputerowe

07. Analiza wideo: przepływ optyczny, śledzenie obiektów [laboratoria]

Andrzej Wójtowicz (2021)

Logo 2

W poniższych materiałach zobaczymy w jaki sposób możemy przy pomocy przepływu optycznego dokonać stabilizacji obrazu oraz w jaki sposób śledzić obiekty znajdujące się na filmie.

Na początku załadujmy niezbędne biblioteki.

import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
import IPython.display

Przepływ optyczny

Naszym celem będzie znalezienie na poniższym filmie punktów kluczowych, które pozwolą nam w jakiś sposób sprawdzić jak przemieszcza się rowerzystka:

IPython.display.Video("vid/bike.mp4", width=800)

Załadujmy film:

bike = cv.VideoCapture("vid/bike.mp4")

Przy pomocy algorytmu Shi-Tomasi (rozwinięcie metody Harrisa) możemy znaleźć narożniki, które dobrze nadają się do śledzenia. W OpenCV algorytm jest zaimplementowany w funkcji cv.goodFeaturesToTrack():

corners_num = 100
corners_colors = np.random.randint(0, 255, (corners_num, 3))

_, frame_1 = bike.read()
frame_1_gray = cv.cvtColor(frame_1, cv.COLOR_BGR2GRAY)
keypoints_1 = cv.goodFeaturesToTrack(
    frame_1_gray, mask=None, maxCorners=corners_num,
    qualityLevel=0.3, minDistance=7, blockSize=7)

mask = np.zeros_like(frame_1)
count = 0

Aby sprawdzić w jaki sposób punkty przemieszczają się pomiędzy kolejnymi klatkami filmu, wykorzystamy algorytm LucasaKanade, który jest zaimplementowany w funkcji cv.calcOpticalFlowPyrLK():

while True:
    _, frame_2 = bike.read()
    frame_2_gray = cv.cvtColor(frame_2, cv.COLOR_BGR2GRAY)
    count += 1

    keypoints_2, status, _ = cv.calcOpticalFlowPyrLK(
        frame_1_gray, frame_2_gray, keypoints_1, None, winSize=(15, 15),
        maxLevel=2, criteria=(cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 0.03))

    keypoints_2_good = keypoints_2[status==1]
    keypoints_1_good = keypoints_1[status==1]
    
    for i, (kp2, kp1) in enumerate(zip(keypoints_2_good, keypoints_1_good)):
        a, b = kp2.ravel()
        a, b = int(a), int(b)
        c, d = kp1.ravel()
        c, d = int(c), int(d)
        cv.line(mask, (a, b), (c, d), corners_colors[i].tolist(), 8, cv.LINE_AA)
        cv.circle(frame_2, (a ,b), 9, corners_colors[i].tolist(), -1)
    
    display_frame = cv.add(frame_2, mask)
    if count % 5 == 0:
        plt.figure(figsize=(7,7))
        plt.imshow(display_frame[:,:,::-1])
    if count > 40:
        break

    frame_1_gray = frame_2_gray.copy()
    keypoints_1 = keypoints_2_good.reshape(-1,1,2)
    
bike.release()

Możemy zauważyć, że część punktów kluczowych została wykryta poza głównym śledzonym obiektem, jednak mimo wszystko jesteśmy w stanie określić główny ruch przemieszczającego się obiektu.

Stabilizacja obrazu

Spróbujemy wykorzystać przepływ optyczny do stablizacji cyfrowej filmu nakręconego z ręki:

IPython.display.Video("vid/protest.mp4", width=800)

Załadujmy film oraz przygotujmy film wyjściowy, który będziemy wyświetlać obok oryginalnego, tak aby móc porównać otrzymane wyniki:

cap = cv.VideoCapture("vid/protest.mp4")
n_frames = int(cap.get(cv.CAP_PROP_FRAME_COUNT))
width  = int(cap.get(cv.CAP_PROP_FRAME_WIDTH)) 
height = int(cap.get(cv.CAP_PROP_FRAME_HEIGHT))
fps = cap.get(cv.CAP_PROP_FPS)

out = cv.VideoWriter('vid/gen-protest.avi', cv.VideoWriter_fourcc(*'MJPG'), fps, (width*2, height))

Pomiędzy poszczególnymi klatkami filmu znajdujemy punkty kluczowe i śledzimy w jaki sposób się one przemieściły. Na tej podstawie przy pomocy cv.estimateAffinePartial2D() możemy oszacować transformacje (translację oraz obrót), które nastapiły między następującymi po sobie klatkami:

_, prev = cap.read()
prev_gray = cv.cvtColor(prev, cv.COLOR_BGR2GRAY)

transforms = np.zeros((n_frames-1, 3), np.float32)

for i in range(n_frames-2):
    prev_pts = cv.goodFeaturesToTrack(prev_gray, maxCorners=200,
                                      qualityLevel=0.01, minDistance=30, blockSize=3)
      
    success, curr = cap.read() 
    if not success: 
        break
    curr_gray = cv.cvtColor(curr, cv.COLOR_BGR2GRAY) 
    
    curr_pts, status, _ = cv.calcOpticalFlowPyrLK(prev_gray, curr_gray, prev_pts, None)
    
    idx = np.where(status==1)[0]
    prev_pts = prev_pts[idx]
    curr_pts = curr_pts[idx]
    
    mat, _ = cv.estimateAffinePartial2D(prev_pts, curr_pts)
    # traslation
    dx = mat[0,2]
    dy = mat[1,2]
    # rotation angle
    da = np.arctan2(mat[1,0], mat[0,0])
    
    transforms[i] = [dx,dy,da]
      
    prev_gray = curr_gray

Przygotujemy też kilka funkcji pomocniczych. Posiadając serię transformacji wygładzimy ich poszczególne komponenty przy pomocy średniej kroczącej.

def moving_average(values, radius): 
    window_size = 2 * radius + 1    
    mask = np.ones(window_size)/window_size 

    values_padded = np.lib.pad(values, (radius, radius), 'edge') 
    values_smoothed = np.convolve(values_padded, mask, mode='same') 
     
    return values_smoothed[radius:-radius] # remove padding

def smooth(trajectory, radius=50): 
    smoothed_trajectory = np.copy(trajectory) 
    for i in range(smoothed_trajectory.shape[1]):
        smoothed_trajectory[:,i] = moving_average(trajectory[:,i], radius)

    return smoothed_trajectory

Możemy teraz policzyć jakie mieliśmy transformacje względem początku filmu, wygładzić je poprzez średnią kroczącą, a następnie nanieść wynikowe różnice na poszczególne transformacje:

trajectory = np.cumsum(transforms, axis=0)
smoothed_trajectory = smooth(trajectory) 

difference = smoothed_trajectory - trajectory
transforms_smooth = transforms + difference

Ostatecznie na podstawie wygładzonych transformacji dostosowujemy poszczególne klatki filmu. Dodatkowo poprzez ustabilizowanie obrazu mogą pojawić się czarne obramowania na wynikowym obrazie, zatem poprzez niewielkie powiększenie obrazu zniwelujemy ten efekt:

cap.set(cv.CAP_PROP_POS_FRAMES, 0) # back to first frame

for i in range(n_frames-2):
    success, frame = cap.read() 
    if not success:
        break

    dx = transforms_smooth[i,0]
    dy = transforms_smooth[i,1]
    da = transforms_smooth[i,2]

    mat = np.zeros((2,3), np.float32)
    mat[0,0] = np.cos(da)
    mat[0,1] = -np.sin(da)
    mat[1,0] = np.sin(da)
    mat[1,1] = np.cos(da)
    mat[0,2] = dx
    mat[1,2] = dy

    frame_stabilized = cv.warpAffine(frame, mat, (width, height))
    
    mat = cv.getRotationMatrix2D((width/2, height/2), 0, 1.1)
    frame_stabilized = cv.warpAffine(frame_stabilized, mat, (width, height))

    frame_out = cv.hconcat([frame, frame_stabilized]) # frame by frame
    
    out.write(frame_out)
    
out.release()

Na potrzeby wyświetlenie wynikowego filmu w przeglądarce, użyjemy kodeka H264:

!ffmpeg -y -hide_banner -loglevel warning -nostats -i vid/gen-protest.avi -vcodec libx264 vid/gen-protest.mp4
'ffmpeg' is not recognized as an internal or external command,
operable program or batch file.

Wynikowy film:

IPython.display.Video("vid/gen-protest.mp4", width=800, embed=False)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
~\AppData\Local\Temp\ipykernel_20516\999644078.py in <module>
----> 1 IPython.display.Video("vid/gen-protest.mp4", width=800, embed=False)

c:\Users\48516\anaconda3\envs\widzenie_komputerowe\lib\site-packages\IPython\core\display.py in __init__(self, data, url, filename, embed, mimetype, width, height, html_attributes)
   1408                 "Consider passing Video(url='...')",
   1409             ])
-> 1410             raise ValueError(msg)
   1411 
   1412         self.mimetype = mimetype

ValueError: To embed videos, you must pass embed=True (this may make your notebook files huge)
Consider passing Video(url='...')

Śledzenie obiektów

Załóżmy, że chcemy na poniższym filmie śledzić przemieszczanie się piłkarek:

IPython.display.Video("vid/football.mp4", width=800)

Biblioteka OpenCV posiada kilka algorytmów pozwalających na śledzenie obiektów. Poniżej użyjemy algorytmu _Multiple Instance Learning:

video = cv.VideoCapture("vid/football.mp4")
_, frame = video.read()

bbox = (45, 350, 120, 270)

tracker = cv.legacy.TrackerMIL_create()
tracker.init(frame, bbox)

pt_1 = (int(bbox[0]), int(bbox[1]))
pt_2 = (int(bbox[0] + bbox[2]), int(bbox[1] + bbox[3]))
cv.rectangle(frame, pt_1, pt_2, (0, 0, 255), 4, cv.LINE_8)

plt.figure(figsize=(7,7))
plt.imshow(frame[:,:,::-1]);
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
~\AppData\Local\Temp\ipykernel_20516\3721097851.py in <module>
      4 bbox = (45, 350, 120, 270)
      5 
----> 6 tracker = cv.legacy.TrackerMIL_create()
      7 tracker.init(frame, bbox)
      8 

AttributeError: module 'cv2' has no attribute 'legacy'

Możemy sprawdzić wyniki pomiędzy poszczególnymi klatkami, jednak tutaj na potrzeby prezentacji dodajmy odstęp co 10 klatek aby można było zauwazyć ruch. Dodatkowo możemy sprawdzić względną prędkość działania algorytmu:

import cv2 
print(cv2. __version__) 
4.6.0
import cv2 
print(cv2. __version__) 
4.6.0
count = 50

while count > 0:

    ok, frame = video.read()
    if not ok:
        break

    timer = cv.getTickCount()
    
    ok, bbox = tracker.update(frame)
    
    fps = cv.getTickFrequency() / (cv.getTickCount() - timer);

    if ok:
        pt_1 = (int(bbox[0]), int(bbox[1]))
        pt_2 = (int(bbox[0] + bbox[2]), int(bbox[1] + bbox[3]))
        cv.rectangle(frame, pt_1, pt_2, (0,0,255), 4, cv.LINE_8)
    else :
        cv.putText(frame, "Tracking failure", (20, 180), 
                    cv.FONT_HERSHEY_SIMPLEX, 2, (0,0,255), cv.LINE_AA)

    cv.putText(frame, "FPS : " + str(int(fps)), (20,50), 
               cv.FONT_HERSHEY_SIMPLEX, 2, (0,0,255), cv.LINE_AA)

    if count % 10 == 0:
        plt.figure(figsize=(7,7))
        plt.imshow(frame[:,:,::-1])
    count -= 1

video.release()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
~\AppData\Local\Temp\ipykernel_20516\4220022586.py in <module>
      9     timer = cv.getTickCount()
     10 
---> 11     ok, bbox = tracker.update(frame)
     12 
     13     fps = cv.getTickFrequency() / (cv.getTickCount() - timer);

NameError: name 'tracker' is not defined

Istnieje też możliwość jednoczesnego śledzenia kilku obiektów:

video = cv.VideoCapture("vid/football.mp4")
_, frame = video.read()

bboxes = [(45, 350, 120, 270), (755, 350, 120, 270)]
colors = [(0, 0, 255), (0, 255, 0)]

multi_tracker = cv.legacy.MultiTracker_create()

for bbox in bboxes:
    multi_tracker.add(cv.legacy.TrackerMIL_create(), frame, bbox)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
c:\Develop\wmi\AITECH\sem2\wko\aitech-wko-pub\wko-07.ipynb Cell 37 in <cell line: 7>()
      <a href='vscode-notebook-cell:/c%3A/Develop/wmi/AITECH/sem2/wko/aitech-wko-pub/wko-07.ipynb#X51sZmlsZQ%3D%3D?line=3'>4</a> bboxes = [(45, 350, 120, 270), (755, 350, 120, 270)]
      <a href='vscode-notebook-cell:/c%3A/Develop/wmi/AITECH/sem2/wko/aitech-wko-pub/wko-07.ipynb#X51sZmlsZQ%3D%3D?line=4'>5</a> colors = [(0, 0, 255), (0, 255, 0)]
----> <a href='vscode-notebook-cell:/c%3A/Develop/wmi/AITECH/sem2/wko/aitech-wko-pub/wko-07.ipynb#X51sZmlsZQ%3D%3D?line=6'>7</a> multi_tracker = cv.legacy.MultiTracker_create()
      <a href='vscode-notebook-cell:/c%3A/Develop/wmi/AITECH/sem2/wko/aitech-wko-pub/wko-07.ipynb#X51sZmlsZQ%3D%3D?line=8'>9</a> for bbox in bboxes:
     <a href='vscode-notebook-cell:/c%3A/Develop/wmi/AITECH/sem2/wko/aitech-wko-pub/wko-07.ipynb#X51sZmlsZQ%3D%3D?line=9'>10</a>     multi_tracker.add(cv.legacy.TrackerMIL_create(), frame, bbox)

AttributeError: module 'cv2' has no attribute 'legacy'
count = 50

while count > 0:

    ok, frame = video.read()
    if not ok:
        break

    timer = cv.getTickCount()
    
    _, boxes = multi_tracker.update(frame)
    
    for i, bbox in enumerate(boxes):
        pt_1 = (int(bbox[0]), int(bbox[1]))
        pt_2 = (int(bbox[0] + bbox[2]), int(bbox[1] + bbox[3]))
        cv.rectangle(frame, pt_1, pt_2, colors[i], 4, cv.LINE_8)

    if count % 10 == 0:
        plt.figure(figsize=(7,7))
        plt.imshow(frame[:,:,::-1])
    count -= 1

video.release()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
c:\Develop\wmi\AITECH\sem2\wko\aitech-wko-pub\wko-07.ipynb Cell 38 in <cell line: 3>()
      <a href='vscode-notebook-cell:/c%3A/Develop/wmi/AITECH/sem2/wko/aitech-wko-pub/wko-07.ipynb#X52sZmlsZQ%3D%3D?line=6'>7</a>     break
      <a href='vscode-notebook-cell:/c%3A/Develop/wmi/AITECH/sem2/wko/aitech-wko-pub/wko-07.ipynb#X52sZmlsZQ%3D%3D?line=8'>9</a> timer = cv.getTickCount()
---> <a href='vscode-notebook-cell:/c%3A/Develop/wmi/AITECH/sem2/wko/aitech-wko-pub/wko-07.ipynb#X52sZmlsZQ%3D%3D?line=10'>11</a> _, boxes = multi_tracker.update(frame)
     <a href='vscode-notebook-cell:/c%3A/Develop/wmi/AITECH/sem2/wko/aitech-wko-pub/wko-07.ipynb#X52sZmlsZQ%3D%3D?line=12'>13</a> for i, bbox in enumerate(boxes):
     <a href='vscode-notebook-cell:/c%3A/Develop/wmi/AITECH/sem2/wko/aitech-wko-pub/wko-07.ipynb#X52sZmlsZQ%3D%3D?line=13'>14</a>     pt_1 = (int(bbox[0]), int(bbox[1]))

NameError: name 'multi_tracker' is not defined

Zadanie 1

Dla filmu vid/football.mp4 porównaj jakość śledzenia dla dostępnych algorytmów. Wyniki zapisz na jednym filmie.

Porówanie algorytmów śledzenia obiektów

bbox = (45, 350, 120, 270)


def draw_first_rectangle(frame):
    pt_1 = (int(bbox[0]), int(bbox[1]))
    pt_2 = (int(bbox[0] + bbox[2]), int(bbox[1] + bbox[3]))
    cv.rectangle(frame, pt_1, pt_2, (0, 0, 255), 4, cv.LINE_8)

    plt.figure(figsize=(7,7))
    plt.imshow(frame[:,:,::-1])


def track_object(video, tracker):

    count = 50

    while count > 0:

        ok, frame = video.read()
        if not ok:
            break

        timer = cv.getTickCount()
        
        ok, bbox = tracker.update(frame)
        
        fps = cv.getTickFrequency() / (cv.getTickCount() - timer);

        if ok:
            pt_1 = (int(bbox[0]), int(bbox[1]))
            pt_2 = (int(bbox[0] + bbox[2]), int(bbox[1] + bbox[3]))
            cv.rectangle(frame, pt_1, pt_2, (0,0,255), 4, cv.LINE_8)
        else :
            cv.putText(frame, "Tracking failure", (20, 180), 
                        cv.FONT_HERSHEY_SIMPLEX, 2, (0,0,255), cv.LINE_AA)

        cv.putText(frame, "FPS : " + str(int(fps)), (20,50), 
                cv.FONT_HERSHEY_SIMPLEX, 2, (0,0,255), cv.LINE_AA)

        if count % 10 == 0:
            plt.figure(figsize=(7,7))
            plt.imshow(frame[:,:,::-1])
        count -= 1

    video.release()
video = cv.VideoCapture("vid/football.mp4")
_, frame = video.read()

tracker = cv.legacy.TrackerMIL_create()
tracker.init(frame, bbox)

draw_first_rectangle(frame)
track_object(video, tracker)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
~\AppData\Local\Temp\ipykernel_20516\3995343795.py in <module>
----> 1 video = cv.VideoCapture("vid/football.mp4")
      2 _, frame = video.read()
      3 
      4 tracker = cv.legacy.TrackerMIL_create()
      5 tracker.init(frame, bbox)

NameError: name 'cv' is not defined