3.0 MiB
Widzenie komputerowe
07. Analiza wideo: przepływ optyczny, śledzenie obiektów [laboratoria]
Andrzej Wójtowicz (2021)
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 Lucasa–Kanade, 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)
[1;31m---------------------------------------------------------------------------[0m [1;31mValueError[0m Traceback (most recent call last) [1;32m~\AppData\Local\Temp\ipykernel_20516\999644078.py[0m in [0;36m<module>[1;34m[0m [1;32m----> 1[1;33m [0mIPython[0m[1;33m.[0m[0mdisplay[0m[1;33m.[0m[0mVideo[0m[1;33m([0m[1;34m"vid/gen-protest.mp4"[0m[1;33m,[0m [0mwidth[0m[1;33m=[0m[1;36m800[0m[1;33m,[0m [0membed[0m[1;33m=[0m[1;32mFalse[0m[1;33m)[0m[1;33m[0m[1;33m[0m[0m [0m [1;32mc:\Users\48516\anaconda3\envs\widzenie_komputerowe\lib\site-packages\IPython\core\display.py[0m in [0;36m__init__[1;34m(self, data, url, filename, embed, mimetype, width, height, html_attributes)[0m [0;32m 1408[0m [1;34m"Consider passing Video(url='...')"[0m[1;33m,[0m[1;33m[0m[1;33m[0m[0m [0;32m 1409[0m ]) [1;32m-> 1410[1;33m [1;32mraise[0m [0mValueError[0m[1;33m([0m[0mmsg[0m[1;33m)[0m[1;33m[0m[1;33m[0m[0m [0m[0;32m 1411[0m [1;33m[0m[0m [0;32m 1412[0m [0mself[0m[1;33m.[0m[0mmimetype[0m [1;33m=[0m [0mmimetype[0m[1;33m[0m[1;33m[0m[0m [1;31mValueError[0m: 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]);
[1;31m---------------------------------------------------------------------------[0m [1;31mAttributeError[0m Traceback (most recent call last) [1;32m~\AppData\Local\Temp\ipykernel_20516\3721097851.py[0m in [0;36m<module>[1;34m[0m [0;32m 4[0m [0mbbox[0m [1;33m=[0m [1;33m([0m[1;36m45[0m[1;33m,[0m [1;36m350[0m[1;33m,[0m [1;36m120[0m[1;33m,[0m [1;36m270[0m[1;33m)[0m[1;33m[0m[1;33m[0m[0m [0;32m 5[0m [1;33m[0m[0m [1;32m----> 6[1;33m [0mtracker[0m [1;33m=[0m [0mcv[0m[1;33m.[0m[0mlegacy[0m[1;33m.[0m[0mTrackerMIL_create[0m[1;33m([0m[1;33m)[0m[1;33m[0m[1;33m[0m[0m [0m[0;32m 7[0m [0mtracker[0m[1;33m.[0m[0minit[0m[1;33m([0m[0mframe[0m[1;33m,[0m [0mbbox[0m[1;33m)[0m[1;33m[0m[1;33m[0m[0m [0;32m 8[0m [1;33m[0m[0m [1;31mAttributeError[0m: 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()
[1;31m---------------------------------------------------------------------------[0m [1;31mNameError[0m Traceback (most recent call last) [1;32m~\AppData\Local\Temp\ipykernel_20516\4220022586.py[0m in [0;36m<module>[1;34m[0m [0;32m 9[0m [0mtimer[0m [1;33m=[0m [0mcv[0m[1;33m.[0m[0mgetTickCount[0m[1;33m([0m[1;33m)[0m[1;33m[0m[1;33m[0m[0m [0;32m 10[0m [1;33m[0m[0m [1;32m---> 11[1;33m [0mok[0m[1;33m,[0m [0mbbox[0m [1;33m=[0m [0mtracker[0m[1;33m.[0m[0mupdate[0m[1;33m([0m[0mframe[0m[1;33m)[0m[1;33m[0m[1;33m[0m[0m [0m[0;32m 12[0m [1;33m[0m[0m [0;32m 13[0m [0mfps[0m [1;33m=[0m [0mcv[0m[1;33m.[0m[0mgetTickFrequency[0m[1;33m([0m[1;33m)[0m [1;33m/[0m [1;33m([0m[0mcv[0m[1;33m.[0m[0mgetTickCount[0m[1;33m([0m[1;33m)[0m [1;33m-[0m [0mtimer[0m[1;33m)[0m[1;33m;[0m[1;33m[0m[1;33m[0m[0m [1;31mNameError[0m: 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)
[1;31m---------------------------------------------------------------------------[0m [1;31mAttributeError[0m Traceback (most recent call last) [1;32mc:\Develop\wmi\AITECH\sem2\wko\aitech-wko-pub\wko-07.ipynb Cell 37[0m in [0;36m<cell line: 7>[1;34m()[0m [0;32m <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>[0m bboxes [39m=[39m [([39m45[39m, [39m350[39m, [39m120[39m, [39m270[39m), ([39m755[39m, [39m350[39m, [39m120[39m, [39m270[39m)] [0;32m <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>[0m colors [39m=[39m [([39m0[39m, [39m0[39m, [39m255[39m), ([39m0[39m, [39m255[39m, [39m0[39m)] [1;32m----> <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>[0m multi_tracker [39m=[39m cv[39m.[39;49mlegacy[39m.[39mMultiTracker_create() [0;32m <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>[0m [39mfor[39;00m bbox [39min[39;00m bboxes: [0;32m <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>[0m multi_tracker[39m.[39madd(cv[39m.[39mlegacy[39m.[39mTrackerMIL_create(), frame, bbox) [1;31mAttributeError[0m: 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()
[1;31m---------------------------------------------------------------------------[0m [1;31mNameError[0m Traceback (most recent call last) [1;32mc:\Develop\wmi\AITECH\sem2\wko\aitech-wko-pub\wko-07.ipynb Cell 38[0m in [0;36m<cell line: 3>[1;34m()[0m [0;32m <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>[0m [39mbreak[39;00m [0;32m <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>[0m timer [39m=[39m cv[39m.[39mgetTickCount() [1;32m---> <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>[0m _, boxes [39m=[39m multi_tracker[39m.[39mupdate(frame) [0;32m <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>[0m [39mfor[39;00m i, bbox [39min[39;00m [39menumerate[39m(boxes): [0;32m <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>[0m pt_1 [39m=[39m ([39mint[39m(bbox[[39m0[39m]), [39mint[39m(bbox[[39m1[39m])) [1;31mNameError[0m: 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.
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)
[1;31m---------------------------------------------------------------------------[0m [1;31mNameError[0m Traceback (most recent call last) [1;32m~\AppData\Local\Temp\ipykernel_20516\3995343795.py[0m in [0;36m<module>[1;34m[0m [1;32m----> 1[1;33m [0mvideo[0m [1;33m=[0m [0mcv[0m[1;33m.[0m[0mVideoCapture[0m[1;33m([0m[1;34m"vid/football.mp4"[0m[1;33m)[0m[1;33m[0m[1;33m[0m[0m [0m[0;32m 2[0m [0m_[0m[1;33m,[0m [0mframe[0m [1;33m=[0m [0mvideo[0m[1;33m.[0m[0mread[0m[1;33m([0m[1;33m)[0m[1;33m[0m[1;33m[0m[0m [0;32m 3[0m [1;33m[0m[0m [0;32m 4[0m [0mtracker[0m [1;33m=[0m [0mcv[0m[1;33m.[0m[0mlegacy[0m[1;33m.[0m[0mTrackerMIL_create[0m[1;33m([0m[1;33m)[0m[1;33m[0m[1;33m[0m[0m [0;32m 5[0m [0mtracker[0m[1;33m.[0m[0minit[0m[1;33m([0m[0mframe[0m[1;33m,[0m [0mbbox[0m[1;33m)[0m[1;33m[0m[1;33m[0m[0m [1;31mNameError[0m: name 'cv' is not defined