s470623-wko/wko-07.ipynb
2022-11-27 19:15:49 +01:00

596 lines
17 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"cells": [
{
"cell_type": "markdown",
"id": "73e26798",
"metadata": {},
"source": [
"![Logo 1](img/aitech-logotyp-1.jpg)\n",
"<div class=\"alert alert-block alert-info\">\n",
"<h1> Widzenie komputerowe </h1>\n",
"<h2> 07. <i>Analiza wideo: przepływ optyczny, śledzenie obiektów</i> [laboratoria]</h2> \n",
"<h3>Andrzej Wójtowicz (2021)</h3>\n",
"</div>\n",
"\n",
"![Logo 2](img/aitech-logotyp-2.jpg)"
]
},
{
"cell_type": "markdown",
"id": "f124f5cd",
"metadata": {},
"source": [
"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.\n",
"\n",
"Na początku załadujmy niezbędne biblioteki."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ed69629c",
"metadata": {},
"outputs": [],
"source": [
"import cv2 as cv\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"%matplotlib inline\n",
"import IPython.display"
]
},
{
"cell_type": "markdown",
"id": "f9ef8d67",
"metadata": {},
"source": [
"# Przepływ optyczny\n",
"\n",
"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:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1629fc29",
"metadata": {},
"outputs": [],
"source": [
"IPython.display.Video(\"vid/bike.mp4\", width=800)"
]
},
{
"cell_type": "markdown",
"id": "aa176c9a",
"metadata": {},
"source": [
"Załadujmy film:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2463bd1d",
"metadata": {},
"outputs": [],
"source": [
"bike = cv.VideoCapture(\"vid/bike.mp4\")"
]
},
{
"cell_type": "markdown",
"id": "dde041a3",
"metadata": {},
"source": [
"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()`](https://docs.opencv.org/4.5.3/dd/d1a/group__imgproc__feature.html#ga1d6bb77486c8f92d79c8793ad995d541):"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "36492aa6",
"metadata": {},
"outputs": [],
"source": [
"corners_num = 100\n",
"corners_colors = np.random.randint(0, 255, (corners_num, 3))\n",
"\n",
"_, frame_1 = bike.read()\n",
"frame_1_gray = cv.cvtColor(frame_1, cv.COLOR_BGR2GRAY)\n",
"keypoints_1 = cv.goodFeaturesToTrack(\n",
" frame_1_gray, mask=None, maxCorners=corners_num,\n",
" qualityLevel=0.3, minDistance=7, blockSize=7)\n",
"\n",
"mask = np.zeros_like(frame_1)\n",
"count = 0"
]
},
{
"cell_type": "markdown",
"id": "028dded7",
"metadata": {},
"source": [
"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()`](https://docs.opencv.org/4.5.3/dc/d6b/group__video__track.html#ga473e4b886d0bcc6b65831eb88ed93323):"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "14b62820",
"metadata": {},
"outputs": [],
"source": [
"while True:\n",
" _, frame_2 = bike.read()\n",
" frame_2_gray = cv.cvtColor(frame_2, cv.COLOR_BGR2GRAY)\n",
" count += 1\n",
"\n",
" keypoints_2, status, _ = cv.calcOpticalFlowPyrLK(\n",
" frame_1_gray, frame_2_gray, keypoints_1, None, winSize=(15, 15),\n",
" maxLevel=2, criteria=(cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 0.03))\n",
"\n",
" keypoints_2_good = keypoints_2[status==1]\n",
" keypoints_1_good = keypoints_1[status==1]\n",
" \n",
" for i, (kp2, kp1) in enumerate(zip(keypoints_2_good, keypoints_1_good)):\n",
" a, b = kp2.ravel()\n",
" a, b = int(a), int(b)\n",
" c, d = kp1.ravel()\n",
" c, d = int(c), int(d)\n",
" cv.line(mask, (a, b), (c, d), corners_colors[i].tolist(), 8, cv.LINE_AA)\n",
" cv.circle(frame_2, (a ,b), 9, corners_colors[i].tolist(), -1)\n",
" \n",
" display_frame = cv.add(frame_2, mask)\n",
" if count % 5 == 0:\n",
" plt.figure(figsize=(7,7))\n",
" plt.imshow(display_frame[:,:,::-1])\n",
" if count > 40:\n",
" break\n",
"\n",
" frame_1_gray = frame_2_gray.copy()\n",
" keypoints_1 = keypoints_2_good.reshape(-1,1,2)\n",
" \n",
"bike.release()"
]
},
{
"cell_type": "markdown",
"id": "d8c01f59",
"metadata": {},
"source": [
"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."
]
},
{
"cell_type": "markdown",
"id": "879a813e",
"metadata": {},
"source": [
"## Stabilizacja obrazu\n",
"\n",
"Spróbujemy wykorzystać przepływ optyczny do stablizacji cyfrowej filmu nakręconego z ręki:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d8686953",
"metadata": {},
"outputs": [],
"source": [
"IPython.display.Video(\"vid/protest.mp4\", width=800)"
]
},
{
"cell_type": "markdown",
"id": "c3541db4",
"metadata": {},
"source": [
"Załadujmy film oraz przygotujmy film wyjściowy, który będziemy wyświetlać obok oryginalnego, tak aby móc porównać otrzymane wyniki:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0b18aad1",
"metadata": {},
"outputs": [],
"source": [
"cap = cv.VideoCapture(\"vid/protest.mp4\")\n",
"n_frames = int(cap.get(cv.CAP_PROP_FRAME_COUNT))\n",
"width = int(cap.get(cv.CAP_PROP_FRAME_WIDTH)) \n",
"height = int(cap.get(cv.CAP_PROP_FRAME_HEIGHT))\n",
"fps = cap.get(cv.CAP_PROP_FPS)\n",
"\n",
"out = cv.VideoWriter('vid/gen-protest.avi', cv.VideoWriter_fourcc(*'MJPG'), fps, (width*2, height))"
]
},
{
"cell_type": "markdown",
"id": "27355bd0",
"metadata": {},
"source": [
"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()`](https://docs.opencv.org/4.5.3/d9/d0c/group__calib3d.html#gad767faff73e9cbd8b9d92b955b50062d) możemy oszacować transformacje (translację oraz obrót), które nastapiły między następującymi po sobie klatkami:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c00b1e9d",
"metadata": {},
"outputs": [],
"source": [
"_, prev = cap.read()\n",
"prev_gray = cv.cvtColor(prev, cv.COLOR_BGR2GRAY)\n",
"\n",
"transforms = np.zeros((n_frames-1, 3), np.float32)\n",
"\n",
"for i in range(n_frames-2):\n",
" prev_pts = cv.goodFeaturesToTrack(prev_gray, maxCorners=200,\n",
" qualityLevel=0.01, minDistance=30, blockSize=3)\n",
" \n",
" success, curr = cap.read() \n",
" if not success: \n",
" break\n",
" curr_gray = cv.cvtColor(curr, cv.COLOR_BGR2GRAY) \n",
" \n",
" curr_pts, status, _ = cv.calcOpticalFlowPyrLK(prev_gray, curr_gray, prev_pts, None)\n",
" \n",
" idx = np.where(status==1)[0]\n",
" prev_pts = prev_pts[idx]\n",
" curr_pts = curr_pts[idx]\n",
" \n",
" mat, _ = cv.estimateAffinePartial2D(prev_pts, curr_pts)\n",
" # traslation\n",
" dx = mat[0,2]\n",
" dy = mat[1,2]\n",
" # rotation angle\n",
" da = np.arctan2(mat[1,0], mat[0,0])\n",
" \n",
" transforms[i] = [dx,dy,da]\n",
" \n",
" prev_gray = curr_gray"
]
},
{
"cell_type": "markdown",
"id": "ba9fce7d",
"metadata": {},
"source": [
"Przygotujemy też kilka funkcji pomocniczych. Posiadając serię transformacji wygładzimy ich poszczególne komponenty przy pomocy średniej kroczącej."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0fd89a26",
"metadata": {},
"outputs": [],
"source": [
"def moving_average(values, radius): \n",
" window_size = 2 * radius + 1 \n",
" mask = np.ones(window_size)/window_size \n",
"\n",
" values_padded = np.lib.pad(values, (radius, radius), 'edge') \n",
" values_smoothed = np.convolve(values_padded, mask, mode='same') \n",
" \n",
" return values_smoothed[radius:-radius] # remove padding\n",
"\n",
"def smooth(trajectory, radius=50): \n",
" smoothed_trajectory = np.copy(trajectory) \n",
" for i in range(smoothed_trajectory.shape[1]):\n",
" smoothed_trajectory[:,i] = moving_average(trajectory[:,i], radius)\n",
"\n",
" return smoothed_trajectory"
]
},
{
"cell_type": "markdown",
"id": "9f4d1df0",
"metadata": {},
"source": [
"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:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "efb52c4d",
"metadata": {},
"outputs": [],
"source": [
"trajectory = np.cumsum(transforms, axis=0)\n",
"smoothed_trajectory = smooth(trajectory) \n",
"\n",
"difference = smoothed_trajectory - trajectory\n",
"transforms_smooth = transforms + difference"
]
},
{
"cell_type": "markdown",
"id": "0ef3b313",
"metadata": {},
"source": [
"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:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b8d4528b",
"metadata": {},
"outputs": [],
"source": [
"cap.set(cv.CAP_PROP_POS_FRAMES, 0) # back to first frame\n",
"\n",
"for i in range(n_frames-2):\n",
" success, frame = cap.read() \n",
" if not success:\n",
" break\n",
"\n",
" dx = transforms_smooth[i,0]\n",
" dy = transforms_smooth[i,1]\n",
" da = transforms_smooth[i,2]\n",
"\n",
" mat = np.zeros((2,3), np.float32)\n",
" mat[0,0] = np.cos(da)\n",
" mat[0,1] = -np.sin(da)\n",
" mat[1,0] = np.sin(da)\n",
" mat[1,1] = np.cos(da)\n",
" mat[0,2] = dx\n",
" mat[1,2] = dy\n",
"\n",
" frame_stabilized = cv.warpAffine(frame, mat, (width, height))\n",
" \n",
" mat = cv.getRotationMatrix2D((width/2, height/2), 0, 1.1)\n",
" frame_stabilized = cv.warpAffine(frame_stabilized, mat, (width, height))\n",
"\n",
" frame_out = cv.hconcat([frame, frame_stabilized]) # frame by frame\n",
" \n",
" out.write(frame_out)\n",
" \n",
"out.release()"
]
},
{
"cell_type": "markdown",
"id": "c204ec3a",
"metadata": {},
"source": [
"Na potrzeby wyświetlenie wynikowego filmu w przeglądarce, użyjemy kodeka H264:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4b58bce1",
"metadata": {},
"outputs": [],
"source": [
"!ffmpeg -y -hide_banner -loglevel warning -nostats -i vid/gen-protest.avi -vcodec libx264 vid/gen-protest.mp4"
]
},
{
"cell_type": "markdown",
"id": "fce1d5cd",
"metadata": {},
"source": [
"Wynikowy film:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "041e29a5",
"metadata": {},
"outputs": [],
"source": [
"IPython.display.Video(\"vid/gen-protest.mp4\", width=800)"
]
},
{
"cell_type": "markdown",
"id": "5fd935d2",
"metadata": {},
"source": [
"# Śledzenie obiektów"
]
},
{
"cell_type": "markdown",
"id": "dbeb1ae1",
"metadata": {},
"source": [
"Załóżmy, że chcemy na poniższym filmie śledzić przemieszczanie się piłkarek:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7a31e78d",
"metadata": {},
"outputs": [],
"source": [
"IPython.display.Video(\"vid/football.mp4\", width=800)"
]
},
{
"cell_type": "markdown",
"id": "2fc45895",
"metadata": {},
"source": [
"Biblioteka OpenCV posiada [kilka algorytmów](https://docs.opencv.org/4.5.3/dc/d6b/group__tracking__legacy.html) pozwalających na śledzenie obiektów. Poniżej użyjemy algorytmu [*Multiple Instance Learning*](https://docs.opencv.org/4.5.3/d9/dbc/classcv_1_1legacy_1_1TrackerMIL.html):"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a35e3ad7",
"metadata": {},
"outputs": [],
"source": [
"video = cv.VideoCapture(\"vid/football.mp4\")\n",
"_, frame = video.read()\n",
"\n",
"bbox = (45, 350, 120, 270)\n",
"\n",
"tracker = cv.legacy.TrackerMIL_create()\n",
"tracker.init(frame, bbox)\n",
"\n",
"pt_1 = (int(bbox[0]), int(bbox[1]))\n",
"pt_2 = (int(bbox[0] + bbox[2]), int(bbox[1] + bbox[3]))\n",
"cv.rectangle(frame, pt_1, pt_2, (0, 0, 255), 4, cv.LINE_8)\n",
"\n",
"plt.figure(figsize=(7,7))\n",
"plt.imshow(frame[:,:,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "934dde10",
"metadata": {},
"source": [
"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:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b7650ee1",
"metadata": {},
"outputs": [],
"source": [
"count = 50\n",
"\n",
"while count > 0:\n",
"\n",
" ok, frame = video.read()\n",
" if not ok:\n",
" break\n",
"\n",
" timer = cv.getTickCount()\n",
" \n",
" ok, bbox = tracker.update(frame)\n",
" \n",
" fps = cv.getTickFrequency() / (cv.getTickCount() - timer);\n",
"\n",
" if ok:\n",
" pt_1 = (int(bbox[0]), int(bbox[1]))\n",
" pt_2 = (int(bbox[0] + bbox[2]), int(bbox[1] + bbox[3]))\n",
" cv.rectangle(frame, pt_1, pt_2, (0,0,255), 4, cv.LINE_8)\n",
" else :\n",
" cv.putText(frame, \"Tracking failure\", (20, 180), \n",
" cv.FONT_HERSHEY_SIMPLEX, 2, (0,0,255), cv.LINE_AA)\n",
"\n",
" cv.putText(frame, \"FPS : \" + str(int(fps)), (20,50), \n",
" cv.FONT_HERSHEY_SIMPLEX, 2, (0,0,255), cv.LINE_AA)\n",
"\n",
" if count % 10 == 0:\n",
" plt.figure(figsize=(7,7))\n",
" plt.imshow(frame[:,:,::-1])\n",
" count -= 1\n",
"\n",
"video.release()"
]
},
{
"cell_type": "markdown",
"id": "bf17ff66",
"metadata": {},
"source": [
"Istnieje też możliwość jednoczesnego śledzenia kilku obiektów:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d2e56fa9",
"metadata": {},
"outputs": [],
"source": [
"video = cv.VideoCapture(\"vid/football.mp4\")\n",
"_, frame = video.read()\n",
"\n",
"bboxes = [(45, 350, 120, 270), (755, 350, 120, 270)]\n",
"colors = [(0, 0, 255), (0, 255, 0)]\n",
"\n",
"multi_tracker = cv.legacy.MultiTracker_create()\n",
"\n",
"for bbox in bboxes:\n",
" multi_tracker.add(cv.legacy.TrackerMIL_create(), frame, bbox)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9f989b8a",
"metadata": {},
"outputs": [],
"source": [
"count = 50\n",
"\n",
"while count > 0:\n",
"\n",
" ok, frame = video.read()\n",
" if not ok:\n",
" break\n",
"\n",
" timer = cv.getTickCount()\n",
" \n",
" _, boxes = multi_tracker.update(frame)\n",
" \n",
" for i, bbox in enumerate(boxes):\n",
" pt_1 = (int(bbox[0]), int(bbox[1]))\n",
" pt_2 = (int(bbox[0] + bbox[2]), int(bbox[1] + bbox[3]))\n",
" cv.rectangle(frame, pt_1, pt_2, colors[i], 4, cv.LINE_8)\n",
"\n",
" if count % 10 == 0:\n",
" plt.figure(figsize=(7,7))\n",
" plt.imshow(frame[:,:,::-1])\n",
" count -= 1\n",
"\n",
"video.release()"
]
},
{
"cell_type": "markdown",
"id": "b1d84a3a",
"metadata": {},
"source": [
"# Zadanie 1\n",
"\n",
"Dla filmu `vid/football.mp4` porównaj jakość śledzenia dla dostępnych algorytmów. Wyniki zapisz na jednym filmie.\n",
"\n",
"![Porówanie algorytmów śledzenia obiektów](img/football-multi.png)"
]
}
],
"metadata": {
"author": "Andrzej Wójtowicz",
"email": "andre@amu.edu.pl",
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"lang": "pl",
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.3"
},
"subtitle": "07. Analiza wideo [laboratoria]",
"title": "Widzenie komputerowe",
"year": "2021"
},
"nbformat": 4,
"nbformat_minor": 5
}