1-3 notebooks done

This commit is contained in:
s470623 2022-11-27 19:15:49 +01:00
commit 822d935cc0
93 changed files with 5934 additions and 0 deletions

20
.gitignore vendored Normal file
View File

@ -0,0 +1,20 @@
# ---> JupyterNotebooks
# gitignore template for Jupyter Notebooks
# website: http://jupyter.org/
.ipynb_checkpoints
*/.ipynb_checkpoints/*
# IPython
profile_default/
ipython_config.py
# Remove previous ipynb_checkpoints
# git rm -r .ipynb_checkpoints/
datasets/att_faces/
datasets/glasses/
datasets/INRIAPerson/
datasets/yaleextb/
vid/gen-*
dnn/

27
README.md Normal file
View File

@ -0,0 +1,27 @@
# Widzenie komputerowe materiały do zajęć
## Cele przedmiotu
Celem kursu jest nabycie umiejętności wykorzystywania klasycznych i współczesnych algorytmów z dziedziny widzenia komputerowego. Uczestnicy zajęć będą przetwarzać obrazy cyfrowe oraz materiały wideo w celu odwzorowania zadań, do których jest przystosowany system wzrokowy człowieka, np. takich jak rozpoznawanie i śledzenie obiektów. Kurs będzie opierał się na praktycznym wykorzystaniu biblioteki OpenCV.
## Wymagania wstępne w zakresie wiedzy, umiejętności oraz kompetencji społecznych
* Umiejętność programowania na poziomie inżyniera informatyki.
* Znajomość podstaw uczenia maszynowego.
## Oprogramowanie
* Ubuntu 20.04
* Python 3.7
* OpenCV 4.5.3
* FFmpeg 4.1.8
## Literatura
* L. Venturi, & K. Korda (2020). Hands-On Vision and Behavior for Self-Driving Cars. Packt Publishing.
* D.M. Escriva, & R. Laganiere (2019). OpenCV 4 Computer Vision Application Programming Cookbook (wyd. 4). Packt Publishing.
* A.F. Villan (2019). Mastering OpenCV 4 with Python. Packt Publishing.
* E.R. Davies (2017). Computer Vision: Principles, Algorithms, Applications, Learning (wyd. 5). Academic Press.
* R. Szeliski (2021). Computer Vision: Algorithms and Applications. Springer-Verlag.
* D. Fouhey, & J. Johnson (2021). EECS 442: Computer Vision. University of Michigan.
* S. Malick (2021). LearnOpenCV.

BIN
datasets/att_faces.zip Normal file

Binary file not shown.

BIN
datasets/glasses.zip Normal file

Binary file not shown.

Binary file not shown.

BIN
datasets/yaleextb.zip Normal file

Binary file not shown.

BIN
img/aitech-logotyp-1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
img/aitech-logotyp-2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

BIN
img/app-billboard.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 KiB

BIN
img/baboon-3d.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
img/baboon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

BIN
img/bakery.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 641 KiB

BIN
img/bear-mask.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
img/bear.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

BIN
img/billboards.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 KiB

BIN
img/binary-image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
img/blobs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

BIN
img/boat_1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 718 KiB

BIN
img/boat_2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 KiB

BIN
img/bologna-on-wall.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

BIN
img/bologna.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

BIN
img/book-python-cover.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
img/caribou.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

BIN
img/coins.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

BIN
img/coins_detected.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

BIN
img/document-alignment.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 706 KiB

BIN
img/flamingo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

BIN
img/football-multi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

BIN
img/grabcut-result.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 545 KiB

BIN
img/highgui-baboon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

BIN
img/highgui-canny.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
img/hsv-cylinder.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

BIN
img/hv-histograms.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

BIN
img/kitty.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
img/lena-grayscale.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
img/lena.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 KiB

BIN
img/lincoln-mask.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
img/lincoln.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
img/linux_foundation.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
img/man-at-desk-results.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 KiB

BIN
img/man-at-desk.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

BIN
img/man-with-pipe.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

BIN
img/man-without-pipe.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

BIN
img/messi5.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
img/mexico-meme.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

BIN
img/noisy-lena.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 592 KiB

BIN
img/panorama-manual.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

BIN
img/park-results.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 KiB

BIN
img/park-to-eq.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 KiB

BIN
img/park.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 KiB

BIN
img/parrot.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

BIN
img/pedestrians.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 KiB

BIN
img/people.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

BIN
img/pipe.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
img/poker-chips.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

BIN
img/rgb-colors.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
img/rgb-cube.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

BIN
img/road-lanes-detected.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 602 KiB

BIN
img/road-lanes.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

BIN
img/road-sign.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

BIN
img/runners.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 KiB

BIN
img/selfie-background.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 KiB

BIN
img/selfie-man.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

BIN
img/selfie-out.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 KiB

BIN
img/shapes.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

BIN
img/soyjaks-final.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 KiB

BIN
img/soyjaks.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 KiB

BIN
img/st-louis-arch-0.033.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 881 KiB

BIN
img/st-louis-arch-0.25.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
img/st-louis-arch-15.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

BIN
img/st-louis-arch-2.5.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

BIN
img/swimmingpool.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 906 KiB

BIN
img/text_no_ocr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

BIN
img/texture.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 507 KiB

BIN
img/x_small.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 943 B

BIN
vid/bike.mp4 Normal file

Binary file not shown.

BIN
vid/blinking-man.mp4 Normal file

Binary file not shown.

BIN
vid/blinking-woman1.mp4 Normal file

Binary file not shown.

BIN
vid/blinking-woman2.mp4 Normal file

Binary file not shown.

BIN
vid/football.mp4 Normal file

Binary file not shown.

BIN
vid/protest.mp4 Normal file

Binary file not shown.

1
wko-01.ipynb Normal file

File diff suppressed because one or more lines are too long

1
wko-02.ipynb Normal file

File diff suppressed because one or more lines are too long

1601
wko-03.ipynb Normal file

File diff suppressed because one or more lines are too long

398
wko-04.ipynb Normal file
View File

@ -0,0 +1,398 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "d7796c8b",
"metadata": {},
"source": [
"![Logo 1](img/aitech-logotyp-1.jpg)\n",
"<div class=\"alert alert-block alert-info\">\n",
"<h1> Widzenie komputerowe </h1>\n",
"<h2> 04. <i>Zaawansowane przetwarzanie obrazów i fotografia obliczeniowa</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": "1454f22a",
"metadata": {},
"source": [
"W poniższych materiałach krótko omówimy zagadnienia związane z fotografią obliczeniową, a większą część czasu pozostawimy na realizację zadania dotyczącego wykrywania linii.\n",
"\n",
"Na początku załadujmy niezbędne biblioteki."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "245f995f",
"metadata": {},
"outputs": [],
"source": [
"import cv2 as cv\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"%matplotlib inline"
]
},
{
"cell_type": "markdown",
"id": "58f244a1",
"metadata": {},
"source": [
"# Obrazy HDR\n",
"\n",
"Na poprzednich zajęciach widzieliśmy w jaki sposób można poprawić kontrast np. przy pomocy wyrównania histogramu. W niektórych przypadkach jest to wystarczająca operacja pozwalająca uzyskać zadowalający efekt. Niestety, jest też wiele przypadków gdy na potrzebę uchwycenia głównych elementów na zdjęciu obraz musi mieć mniejszą lub większą ekspozycję, co kończy się niedoświetleniem lub prześwietleniem obiektów znajdujących się np. na drugim planie. Zasadniczo w takiej sytuacji chcielibyśmy wyjść poza standardowy ograniczony zakres wartości 0-255 do jakiegoś szerszego zakresu, który z jednej strony pozwoliłby nam uchwycić więcej informacji znajdujących się na scenie, ale z drugiej strony musimy też pamiętać o ponownej konwersji do standardowego zakresu.\n",
"\n",
"Pokażemy tutaj w jaki sposób można wykorzystać technikę [*High dynamic range (HDR) imaging*](https://en.wikipedia.org/wiki/High-dynamic-range_imaging) w OpenCV do uzyskania obrazu o dużej rozpiętości tonalnej. Poniżej mamy kilka zdjęć tego samego obiektu (źródło: Wikipedia, [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/deed.en)), wykonanych z różnym czasem naświetlania, przez co możemy zauważyć, że część elementów jest niedoświetlona, a część prześwietlona:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "01f5a664",
"metadata": {},
"outputs": [],
"source": [
"exposure_times = np.array([ 1/30.0, 0.25, 2.5, 15.0 ], dtype=np.float32)\n",
" \n",
"f_names = [\"st-louis-arch-0.033.jpg\", \n",
" \"st-louis-arch-0.25.jpg\", \n",
" \"st-louis-arch-2.5.jpg\", \n",
" \"st-louis-arch-15.jpg\"]\n",
"\n",
"plt.figure(figsize=(20,5))\n",
"images = []\n",
"for i, f_name in enumerate(f_names):\n",
" image = cv.imread(f\"img/{f_name}\", cv.IMREAD_COLOR)\n",
" images.append(image)\n",
" \n",
" plt.subplot(141 + i)\n",
" plt.imshow(image[:,:,::-1])\n",
" plt.title(f\"{str(round(exposure_times[i], 3))} sec.\")"
]
},
{
"cell_type": "markdown",
"id": "9cb65411",
"metadata": {},
"source": [
"Szczegółowy opis dalszych kroków można znaleźć np. w rozdziale *10.1 Photometric calibration* i *10.2 High dynamic range imaging* książki R. Szeliski *Computer Vision* (2021) - tutaj ograniczymy się do technicznego rozwiązania kolejnych problemów.\n",
"\n",
"Pierwszym problemem, który musimy rozwiązać, jest wyrównanie/dopasowanie obrazów. Nawet jeśli zdjęcie jest robione ze statywu (lub gorzej - z ręki), to po nałożeniu zdjęć na siebie będą widoczne artefakty wynikające z wibracji i przesunięć. W OpenCV możemy to rozwiązać poprzez funkcję [`cv.createAlignMTB()`](https://docs.opencv.org/4.5.3/d7/db6/classcv_1_1AlignMTB.html), która tworzy tzw. bitmapy z progiem mediany (ang. *median threshold bitmaps*), w których zawartość obrazu jest obliczana przez przypisanie wartości 1 pikselom jaśniejszym niż mediana luminancji i 0 w przeciwnym wypadku."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7dda882f",
"metadata": {},
"outputs": [],
"source": [
"align_mtb = cv.createAlignMTB()\n",
"align_mtb.process(images, images)"
]
},
{
"cell_type": "markdown",
"id": "8873862f",
"metadata": {},
"source": [
"W dalszych krokach musimy:\n",
"\n",
"* oszacować radiometryczną funkcję odpowiedzi na podstawie wyrównanych obrazów,\n",
"* oszacować mapę radiacyjną, wybierając lub mieszając piksele z różnych ekspozycji,\n",
"* wykonać mapowanie tonalne wynikowego obrazu HDR z powrotem do postaci umożliwiającej jego normalne wyświetlanie."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bd00b46f",
"metadata": {},
"outputs": [],
"source": [
"calibrate_debevec = cv.createCalibrateDebevec()\n",
"response_debevec = calibrate_debevec.process(images, exposure_times)\n",
"merge_debevec = cv.createMergeDebevec()\n",
"hdr_debevec = merge_debevec.process(images, exposure_times, response_debevec)"
]
},
{
"cell_type": "markdown",
"id": "d6c1f8f3",
"metadata": {},
"source": [
"Teoretycznie moglibyśmy zapisać teraz wynikowy plik `.hdr` np. przy pomocy `cv.imwrite(\"hdr_debevec.hdr\", hdr_debevec)` i edytować dalej go w programie graficznym. W tym miejscu jednak wykonamy dalszą obróbkę przy pomocy OpenCV, tj. wykonamy mapowanie tonalne. Poniżej mamy zaprezentowane wyniki uzyskane metodą Reinharda i Mantiuka:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4a16094f",
"metadata": {},
"outputs": [],
"source": [
"tonemap_rReinhard = cv.createTonemapReinhard(1.5, 0,0,0)\n",
"ldr_reinhard = tonemap_rReinhard.process(hdr_debevec)\n",
"plt.imshow(ldr_reinhard[:,:,::-1]);"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "92cdbefa",
"metadata": {},
"outputs": [],
"source": [
"tonemap_mantiuk = cv.createTonemapMantiuk(2.2, 0.85, 1.2)\n",
"ldr_mantiuk = tonemap_mantiuk.process(hdr_debevec)\n",
"ldr_mantiuk = np.clip(3 * ldr_mantiuk, 0, 1)\n",
"plt.imshow(ldr_mantiuk[:,:,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "c4d83478",
"metadata": {},
"source": [
"# Gładkie klonowanie\n",
"\n",
"Na wcześniejszych zajęciach widzieliśmy, że możemy umieszczać elementy jednego obrazu w drugim obrazie np. poprzez wykorzystanie maski i kanału dotyczącego przezroczystości. Innym ciekawym podejściem jest praca na gradientach obrazu (zamiast na jego intensywności), co może dać równie interesujące, a czasem i bardziej realistyczne wyniki. W tzw. gładkim klonowaniu (ang. *seamless cloning*) intensywność obiektu docelowego będzie różna od intensywności obiektu źródłowego, natomiast gradienty będą podobne. Szczegóły omawianej metody znajdują się w artykule P. Perez et al. (2003) [*Poisson Image Editing*](https://www.irisa.fr/vista/Papers/2003_siggraph_perez.pdf).\n",
"\n",
"Poniżej mamy obraz basenu i kąpiących się w nim ludzi oraz niedźwiedzia, którego chcemy umieścić w basenie:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e0d74bf3",
"metadata": {},
"outputs": [],
"source": [
"swimmingpool = cv.imread(\"img/swimmingpool.jpg\", cv.IMREAD_COLOR)\n",
"bear = cv.imread(\"img/bear.jpg\", cv.IMREAD_COLOR)\n",
"bear_mask = cv.imread(\"img/bear-mask.jpg\", cv.IMREAD_GRAYSCALE)\n",
"\n",
"plt.figure(figsize=(20,5))\n",
"plt.subplot(131)\n",
"plt.imshow(swimmingpool[:,:,::-1])\n",
"plt.title(\"Swimming pool\")\n",
"plt.subplot(132)\n",
"plt.imshow(bear[:,:,::-1])\n",
"plt.title(\"Bear\")\n",
"plt.subplot(133)\n",
"plt.imshow(bear_mask[:,:], cmap='gray')\n",
"plt.title(\"Bear mask\");"
]
},
{
"cell_type": "markdown",
"id": "7654ece6",
"metadata": {},
"source": [
"Przy pomocy metody [`cv.seamlessClone()`](https://docs.opencv.org/4.5.3/df/da0/group__photo__clone.html#ga2bf426e4c93a6b1f21705513dfeca49d) wykonujemy gładkie klonowanie:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f6d3092b",
"metadata": {},
"outputs": [],
"source": [
"center = (250,700)\n",
"\n",
"swimmingpool_with_bear = cv.seamlessClone(bear, swimmingpool, bear_mask, center, cv.NORMAL_CLONE)\n",
"\n",
"plt.figure(figsize=(10,10))\n",
"plt.imshow(swimmingpool_with_bear[:,:,::-1])\n",
"plt.title(\"I think this might be photoshopped\");"
]
},
{
"cell_type": "markdown",
"id": "2337a4c3",
"metadata": {},
"source": [
"Spróbujmy teraz umieścić tekst na teksturze:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f61aaaaf",
"metadata": {},
"outputs": [],
"source": [
"texture = cv.imread(\"img/texture.jpg\", cv.IMREAD_COLOR)\n",
"bologna = cv.imread(\"img/bologna-on-wall.jpg\", cv.IMREAD_COLOR)\n",
"\n",
"plt.figure(figsize=(20,5))\n",
"plt.subplot(131)\n",
"plt.imshow(texture[:,:,::-1])\n",
"plt.title(\"Texture\")\n",
"plt.subplot(132)\n",
"plt.imshow(bologna[:,:,::-1])\n",
"plt.title(\"Bologna to clone\");"
]
},
{
"cell_type": "markdown",
"id": "99a99732",
"metadata": {},
"source": [
"W tym wypadku stworzenie maski może być problematyczne, zatem możemy pójść na skróty i przyjąć, że cały obraz stanowi maskę. Przy takim podejściu zobaczymy jednak, że opcja [`cv.NORMAL_CLONE`](https://docs.opencv.org/4.5.3/df/da0/group__photo__clone.html#gabfc4ba1d1fb873f2b56d34032f86c1d4) spowoduje mocne rozmazanie wokół wklejanej części, stąd też lepszym rozwiązaniem będzie opcja `cv.MIXED_CLONE`, co wynika z kombinacji gradientów z jednego i drugiego obrazu:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a64d730e",
"metadata": {},
"outputs": [],
"source": [
"mask = 255 * np.ones(bologna.shape, bologna.dtype)\n",
"\n",
"width, height, channels = texture.shape\n",
"center = (height//2, width//2)\n",
"\n",
"normal_clone = cv.seamlessClone(bologna, texture, mask, center, cv.NORMAL_CLONE)\n",
"mixed_clone = cv.seamlessClone(bologna, texture, mask, center, cv.MIXED_CLONE)\n",
"\n",
"plt.figure(figsize=[20,10])\n",
"plt.subplot(121)\n",
"plt.title(\"Normal clone\")\n",
"plt.imshow(normal_clone[:,:,::-1])\n",
"plt.subplot(122)\n",
"plt.title(\"Mixed clone\")\n",
"plt.imshow(mixed_clone[:,:,::-1])\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "b3df663d",
"metadata": {},
"source": [
"# Usuwanie niechcianych obiektów\n",
"\n",
"Spróbujemy teraz usunąć ze zdjęcia defekty, które często są domeną starych fotografii. Poniżej mamy zdjęcie prezydenta Lincolna z rysą na poziomie włosów; dodatkowo ręcznie ustaliliśmy maskę wskazującą miejsce występowania tej rysy:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "35d05105",
"metadata": {},
"outputs": [],
"source": [
"lincoln = cv.imread(\"img/lincoln.jpg\", cv.IMREAD_COLOR)\n",
"lincoln_mask = cv.imread(\"img/lincoln-mask.jpg\", cv.IMREAD_GRAYSCALE)\n",
"\n",
"plt.figure(figsize=(15,10))\n",
"plt.subplot(121)\n",
"plt.imshow(lincoln[:,:,::-1])\n",
"plt.title(\"Lincoln last photo\")\n",
"plt.subplot(122)\n",
"plt.imshow(lincoln_mask[:,:], cmap='gray')\n",
"plt.title(\"Mask\");"
]
},
{
"cell_type": "markdown",
"id": "ba912b4f",
"metadata": {},
"source": [
"Używając metody [`cv.inpaint()`](https://docs.opencv.org/4.5.3/d7/d8b/group__photo__inpaint.html#gaedd30dfa0214fec4c88138b51d678085) możemy przywrócić wybrany region w obrazie przy pomocy sąsiedztwa zadanego regionu. Metoda `cv.INPAINT_NS` implementuje podejście opisane w M. Bertalmio et al. (2001) [*Navier-Stokes, Fluid Dynamics, and Image and Video Inpainting*](https://www.math.ucla.edu/~bertozzi/papers/cvpr01.pdf), która ogólnie mówiąc ma za zadanie zachować gradienty (np. krawędzie) i rozprowadzać informacje o kolorach na płaskich przestrzeniach. Z kolei `cv.INPAINT_TELEA` implementuje metodę opisaną w A. Telea (2004) [*An image inpainting technique based on the fast marching method*](https://core.ac.uk/download/pdf/148284148.pdf). W obu przypadkach możemy zauważyć oczekiwane wypełnienie na poziomie włosów, aczkolwiek po prawej stronie jest widoczne pewne rozmazanie - być może należałoby poprawić/zawęzić wejściową maskę."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "52ff1513",
"metadata": {},
"outputs": [],
"source": [
"lincoln_inpainted_ns = cv.inpaint(src=lincoln, inpaintMask=lincoln_mask, inpaintRadius=5, flags=cv.INPAINT_NS)\n",
"lincoln_inpainted_t = cv.inpaint(src=lincoln, inpaintMask=lincoln_mask, inpaintRadius=5, flags=cv.INPAINT_TELEA)\n",
"\n",
"plt.figure(figsize=(15,10))\n",
"plt.subplot(131)\n",
"plt.imshow(lincoln[:,:,::-1])\n",
"plt.title(\"Lincoln last photo\")\n",
"plt.subplot(132)\n",
"plt.imshow(lincoln_inpainted_ns[:,:,::-1])\n",
"plt.title(\"Inpainted: Navier-Stokes\")\n",
"plt.subplot(133)\n",
"plt.imshow(lincoln_inpainted_t[:,:,::-1])\n",
"plt.title(\"Inpainted: Telea\");"
]
},
{
"cell_type": "markdown",
"id": "62854178",
"metadata": {},
"source": [
"# Transformata Hougha"
]
},
{
"cell_type": "markdown",
"id": "6cc98b55",
"metadata": {},
"source": [
"## Zadanie 1\n",
"\n",
"W poniższym zadaniu należy przygotować serię operacji, która pozwoli wykryć proste linie na obrazie `img/road-lanes.jpg` przy pomocy [transformaty Hougha](https://docs.opencv.org/4.5.3/d9/db0/tutorial_hough_lines.html) (wym. *hafa*), zaimplementowanej w funkcji [`cv.HoughLines()`](https://docs.opencv.org/4.5.3/dd/d1a/group__imgproc__feature.html#ga46b4e588934f6c8dfd509cc6e0e4545a) lub [`cv.HoughLinesP()`](https://docs.opencv.org/4.5.3/dd/d1a/group__imgproc__feature.html#ga8618180a5948286384e3b7ca02f6feeb). Możemy przyjąć, że przetwarzane zdjęcie jest pojedynczą klatką uzyskaną w kamery znajdującej się na jadącym samochodzie.\n",
"\n",
"Kolejne kroki mogą wyglądać następująco:\n",
"\n",
"1. progowanie obrazu w celu uzyskania pasów na drodze,\n",
"2. ograniczenie przetwarzanego obrazu do interesującego nas fragmentu (np. może to być traprez osadzony na dole ekranu zwężający się do środka - góra i boki nas za bardzo nie interesują),\n",
"3. wykrycie krawędzi,\n",
"4. wykrycie linii transformatą Hougha,\n",
"5. ekstrapolacja znalezionych wyżej linii,\n",
"6. naniesienie linii na obraz wejściowy."
]
},
{
"cell_type": "markdown",
"id": "63a04ced",
"metadata": {},
"source": [
"![Pasy wykryte na drodze](img/road-lanes-detected.png)"
]
}
],
"metadata": {
"author": "Andrzej Wójtowicz",
"email": "andre@amu.edu.pl",
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"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.8.10"
},
"subtitle": "04. Zaawansowane przetwarzanie obrazów i fotografia obliczeniowa [laboratoria]",
"title": "Widzenie komputerowe",
"year": "2021"
},
"nbformat": 4,
"nbformat_minor": 5
}

853
wko-05.ipynb Normal file
View File

@ -0,0 +1,853 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "94b34c51",
"metadata": {},
"source": [
"![Logo 1](img/aitech-logotyp-1.jpg)\n",
"<div class=\"alert alert-block alert-info\">\n",
"<h1> Widzenie komputerowe </h1>\n",
"<h2> 05. <i>Transformacje geometryczne i cechy obrazó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": "512b22b6",
"metadata": {},
"source": [
"W poniższych materiałach zobaczymy jak przy pomocy biblitoeki OpenCV realizować transformacje geometryczne obrazu, wyszukiwać \"ciekawe\" elementy obrazu oraz jak łączyć je z innymi podobnymi elementami na innych obrazach.\n",
"\n",
"Na początku załadujmy niezbędne biblioteki."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4c6170e3",
"metadata": {},
"outputs": [],
"source": [
"import cv2 as cv\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"%matplotlib inline"
]
},
{
"cell_type": "markdown",
"id": "2256ace3",
"metadata": {},
"source": [
"# Transformacje obrazu\n",
"\n",
"Zacznijmy od podstawowych przekształceń geometrycznych. Aby efekt poszczególnych operacji był widoczny, dodamy pustą przestrzeń wokół testowego obrazu, na którym będziemy pracować:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cb9554d1",
"metadata": {},
"outputs": [],
"source": [
"lena = cv.imread(\"img/lena.png\", cv.IMREAD_COLOR)\n",
"image = np.zeros((1024, 1024, 3), np.uint8)\n",
"\n",
"for i in range(3):\n",
" image[256:768, 256:768, i] = lena[:,:, i];\n",
"\n",
"plt.figure(figsize=(5,5))\n",
"plt.imshow(image[:,:,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "3ef7152b",
"metadata": {},
"source": [
"W OpenCV [transformacje afiniczne](https://en.wikipedia.org/wiki/Affine_transformation#Image_transformation) są wykonywane przy pomocy definicji macierzy 2x3 (zamiast bardziej klasycznej 3x3), która przekazywana jest do funkcji [`cv.warpAffine()`](https://docs.opencv.org/4.5.3/da/d54/group__imgproc__transform.html#ga0203d9ee5fcd28d40dbc4a1ea4451983). Poniżej mamy przykład translacji (przesunięcia) o 100 pikseli w prawo wzdłuż osi *x* i o 150 pikseli wzdłuż osi *y*:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "29e072c5",
"metadata": {},
"outputs": [],
"source": [
"mat = np.float32([\n",
" [1.0, 0.0, 100],\n",
" [0.0, 1.0, 150]\n",
"])\n",
"\n",
"image_translated = cv.warpAffine(image, mat, image.shape[0:2])\n",
"\n",
"plt.figure(figsize=(5,5))\n",
"plt.imshow(image_translated[:,:,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "c4df87c1",
"metadata": {},
"source": [
"Zobaczmy co się stanie po przeskalowaniu dwukrotnym wzdłuż osi *x*:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6c71222d",
"metadata": {},
"outputs": [],
"source": [
"mat = np.float32([\n",
" [2.0, 0.0, 0],\n",
" [0.0, 1.0, 0]\n",
"])\n",
"\n",
"image_scaled = cv.warpAffine(image, mat, image.shape[0:2])\n",
"\n",
"plt.figure(figsize=(5,5))\n",
"plt.imshow(image_scaled[:,:,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "2bdb4a28",
"metadata": {},
"source": [
"Nasz wyjściowy obraz \"wyszedł\" poza wymiary wejściowe, więc musimy podać w funkcji trochę większe rozmiary wynikowego obrazu (w tym wypadku chodzi o szerokość):"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b1b3f06e",
"metadata": {},
"outputs": [],
"source": [
"mat = np.float32([\n",
" [2.0, 0.0, 0],\n",
" [0.0, 1.0, 0]\n",
"])\n",
"\n",
"image_scaled = cv.warpAffine(image, mat, (2*image.shape[1], image.shape[0]))\n",
"\n",
"plt.figure(figsize=(10,10))\n",
"plt.imshow(image_scaled[:,:,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "f23fb260",
"metadata": {},
"source": [
"Poniżej mamy dwukrotne przeskalowanie w obu kierunkach (efekt jest zasadniczo widoczny patrząc na wynikowe skale obu osi):"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bfcf3049",
"metadata": {},
"outputs": [],
"source": [
"mat = np.float32([\n",
" [2.0, 0.0, 0],\n",
" [0.0, 2.0, 0]\n",
"])\n",
"\n",
"image_scaled = cv.warpAffine(image, mat, (2*image.shape[1], 2*image.shape[0]))\n",
"\n",
"plt.figure(figsize=(5,5))\n",
"plt.imshow(image_scaled[:,:,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "19370776",
"metadata": {},
"source": [
"W przypadku obrotu musimy pamiętać, że mamy inaczej zdefiniowany układ współrzędnych (oś *y*), więc uzyskujemy trochę inną formę macierzy obrotu:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b28b1582",
"metadata": {},
"outputs": [],
"source": [
"angle = 30 * np.pi / 180.0\n",
"\n",
"cos_theta = np.cos(angle)\n",
"sin_theta = np.sin(angle)\n",
"\n",
"mat = np.float32([\n",
" [ cos_theta, sin_theta, 0],\n",
" [-sin_theta, cos_theta, 0]\n",
"])\n",
"\n",
"image_rotated = cv.warpAffine(image, mat, image.shape[0:2])\n",
"\n",
"plt.figure(figsize=(5,5))\n",
"plt.imshow(image_rotated[:,:,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "396af8c3",
"metadata": {},
"source": [
"Powyżej mieliśmy obrót wokół środka układu, ale możemy też uzyskać obrót wokół wskazanego punktu, np. środka obrazu:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d6cca027",
"metadata": {},
"outputs": [],
"source": [
"angle = 30 * np.pi / 180.0\n",
"\n",
"cos_theta = np.cos(angle)\n",
"sin_theta = np.sin(angle)\n",
"\n",
"center_x = image.shape[0] / 2\n",
"center_y = image.shape[1] / 2\n",
"\n",
"t_x = (1 - cos_theta) * center_x - sin_theta * center_y\n",
"t_y = sin_theta * center_x + (1 - cos_theta) * center_y\n",
"\n",
"mat = np.float32([\n",
" [ cos_theta, sin_theta, t_x],\n",
" [-sin_theta, cos_theta, t_y]\n",
"])\n",
"\n",
"image_rotated = cv.warpAffine(image, mat, image.shape[0:2])\n",
"\n",
"plt.figure(figsize=(5,5))\n",
"plt.imshow(image_rotated[:,:,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "5501dcac",
"metadata": {},
"source": [
"Na szczęście zamiast samodzielnego liczenia macierzy obrotu można użyć funkcji [`cv.getRotationMatrix2D()`](https://docs.opencv.org/4.5.3/da/d54/group__imgproc__transform.html#gafbbc470ce83812914a70abfb604f4326) (możemy też jej użyć do skalowania):"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8b1f159c",
"metadata": {},
"outputs": [],
"source": [
"mat = cv.getRotationMatrix2D((center_x, center_y), 30, 1)\n",
"\n",
"image_rotated = cv.warpAffine(image, mat, image.shape[0:2])\n",
"\n",
"plt.figure(figsize=(5,5))\n",
"plt.imshow(image_rotated[:,:,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "0152bb4c",
"metadata": {},
"source": [
"Poniżej mamy przykład pochylenia:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7ebb76e4",
"metadata": {},
"outputs": [],
"source": [
"mat = np.float32([\n",
" [1.0, 0.1, 0],\n",
" [0.0, 1.0, 0]\n",
"])\n",
"\n",
"image_sheared = cv.warpAffine(image, mat, image.shape[0:2])\n",
"\n",
"plt.figure(figsize=(5,5))\n",
"plt.imshow(image_sheared[:,:,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "a101e5a6",
"metadata": {},
"source": [
"Jeżeli mamy kilka transformacji do wykonania, to bardziej efektywnym rozwiązaniem będzie uprzednie wymnożenie macierzy odpowiedzialnych za poszczególne transformacje (zamiast sukcesywnego wykonywania pojedynczych, izolowanych operacji):"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c914ddfd",
"metadata": {},
"outputs": [],
"source": [
"m_scale = np.float32([\n",
" [0.5, 0.0],\n",
" [0.0, 1.0]\n",
"])\n",
"\n",
"m_shear = np.float32([\n",
" [1.0, 0.1],\n",
" [0.0, 1.0]\n",
"])\n",
"\n",
"m_rotation = np.float32([\n",
" [ cos_theta, sin_theta],\n",
" [-sin_theta, cos_theta]\n",
"])\n",
"\n",
"v_translation = np.float32([\n",
" [150],\n",
" [90]\n",
"])\n",
"\n",
"mat_tmp = m_rotation @ m_shear @ m_scale\n",
"\n",
"mat_transformation = np.append(mat_tmp, v_translation, 1)\n",
"\n",
"image_transformed = cv.warpAffine(image, mat_transformation, image.shape[0:2])\n",
"\n",
"plt.figure(figsize=(5,5))\n",
"plt.imshow(image_transformed[:,:,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "68f6d1d0",
"metadata": {},
"source": [
"Dla punktów o konkretnych współrzędnych możemy też obliczyć ich docelowe współrzędne po transformacjach:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "753b1779",
"metadata": {},
"outputs": [],
"source": [
"src_points = np.float32([[50, 50], [50, 249], [249, 50], [249, 249]])\n",
"dst_points = (mat_tmp @ src_points.T + v_translation).T\n",
"\n",
"print(dst_points)"
]
},
{
"cell_type": "markdown",
"id": "12fe63be",
"metadata": {},
"source": [
"Mając co najmniej 3 punkty wejściowe i ich odpowiedniki wyjściowe, możemy oszacować macierz transformacji przy pomocy funkcji [`cv.estimateAffine2D()`](https://docs.opencv.org/4.5.3/d9/d0c/group__calib3d.html#ga27865b1d26bac9ce91efaee83e94d4dd):"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "244b0a24",
"metadata": {},
"outputs": [],
"source": [
"mat_estimated_1 = cv.estimateAffine2D(src_points[:3], dst_points[:3])[0]\n",
"print(\"Explicit matrix:\\n\\n\", mat_transformation)\n",
"print(\"\\nEstimated matrix:\\n\\n\", mat_estimated_1)"
]
},
{
"cell_type": "markdown",
"id": "967d8ae3",
"metadata": {},
"source": [
"Mając więcej punktów możemy spróbować uzyskać dokładniejsze oszacowanie:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "53e14d49",
"metadata": {},
"outputs": [],
"source": [
"mat_estimated_2 = cv.estimateAffine2D(src_points, dst_points)[0]\n",
"print(\"Explicit matrix:\\n\\n\", mat_transformation)\n",
"print(\"\\nEstimated matrix:\\n\\n\", mat_estimated_2)"
]
},
{
"cell_type": "markdown",
"id": "494b3706",
"metadata": {},
"source": [
"Załóżmy, że chcielibyśmy teraz uzyskać zmianę perspektywy, co w efekcie spowodowałoby, że nasz kwadratowy obraz zamieniłby się w trapez:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b98c1e5b",
"metadata": {},
"outputs": [],
"source": [
"image_to_transform = np.zeros(image.shape, dtype=np.float32)\n",
"dst_points = np.float32([[356, 256], [667, 256], [767, 767], [256, 767]])\n",
"cv.fillConvexPoly(image_to_transform, np.int32(dst_points), (0.8, 0.8, 0.5), cv.LINE_AA);\n",
"\n",
"plt.figure(figsize=[10,10])\n",
"plt.subplot(121)\n",
"plt.imshow(image[:,:,::-1])\n",
"plt.title('Original image')\n",
"\n",
"plt.subplot(122)\n",
"plt.imshow(image_to_transform[:,:,::-1])\n",
"plt.title('Destination plane');"
]
},
{
"cell_type": "markdown",
"id": "2313e3f4",
"metadata": {},
"source": [
"Niestety przy pomocy transformacji afinicznych [nie jesteśmy w stanie](https://stackoverflow.com/a/45644845) uzyskać zakładanego efektu:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f68a6095",
"metadata": {},
"outputs": [],
"source": [
"src_points = np.float32([[256, 256], [767, 256], [767, 767], [256, 767]])\n",
"mat_estimated = cv.estimateAffine2D(src_points, dst_points)[0]\n",
"\n",
"image_transformed_a = cv.warpAffine(image, mat_estimated, image.shape[0:2])\n",
"\n",
"plt.figure(figsize=[15,10])\n",
"plt.subplot(131)\n",
"plt.imshow(image[:,:,::-1])\n",
"plt.title('Original image')\n",
"plt.subplot(132)\n",
"plt.imshow(image_to_transform[:,:,::-1])\n",
"plt.title('Destination plane')\n",
"plt.subplot(133)\n",
"plt.imshow(image_transformed_a[:,:,::-1])\n",
"plt.title('Transformed image (affine)');"
]
},
{
"cell_type": "markdown",
"id": "66864db4",
"metadata": {},
"source": [
"Do uzyskania zakładanego efektu musimy znaleźć macierz [homografii](https://en.wikipedia.org/wiki/Homography) 3x3 przy pomocy co najmniej 4 punktów i funkcji [`cv.findHomography()`](https://docs.opencv.org/4.5.3/d9/d0c/group__calib3d.html#ga4abc2ece9fab9398f2e560d53c8c9780):"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "62324a22",
"metadata": {},
"outputs": [],
"source": [
"mat_h = cv.findHomography(src_points, dst_points)[0]\n",
"print(mat_h)"
]
},
{
"cell_type": "markdown",
"id": "df477c3a",
"metadata": {},
"source": [
"Przy pomocy funkcji [`cv.warpPerspective()`](https://docs.opencv.org/4.5.3/da/d54/group__imgproc__transform.html#gaf73673a7e8e18ec6963e3774e6a94b87) możemy dokonać teraz zmiany perspektywy:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6f804489",
"metadata": {},
"outputs": [],
"source": [
"image_transformed_h = cv.warpPerspective(image, mat_h, image.shape[0:2])\n",
"\n",
"plt.figure(figsize=[20,10])\n",
"plt.subplot(141)\n",
"plt.imshow(image[:,:,::-1])\n",
"plt.title('Original image')\n",
"plt.subplot(142)\n",
"plt.imshow(image_to_transform[:,:,::-1])\n",
"plt.title('Destination plane')\n",
"plt.subplot(143)\n",
"plt.imshow(image_transformed_a[:,:,::-1])\n",
"plt.title('Transformed image (affine)')\n",
"plt.subplot(144)\n",
"plt.imshow(image_transformed_h[:,:,::-1])\n",
"plt.title('Transformed image (homography)');"
]
},
{
"cell_type": "markdown",
"id": "5e60a8e1",
"metadata": {},
"source": [
"Poprzez znalezienie macierzy homografii i zmianę perspektywy możemy np. wyrównywać zdjęcia dokumentów w celu ich dalszej analizy. Poniżej mamy przykład z oznaczeniem okładki zeszytu:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1d02f81a",
"metadata": {},
"outputs": [],
"source": [
"book = cv.imread(\"img/caribou.jpg\", cv.IMREAD_COLOR)\n",
"image_h, image_w = book.shape[0:2]\n",
"\n",
"src_points = np.array([[210, 190], [380, 280], [290, 500], [85, 390]], dtype=float)\n",
"dst_points = np.array([[0, 0],[image_w-1, 0], [image_w-1, image_h-1], [0, image_h-1]], dtype=float)\n",
"\n",
"colors = [(0,0,255), (0,255,0), (255,0,0), (90,200,200)]\n",
"for (x,y), i in(zip(src_points, colors)):\n",
" book = cv.circle(book, (int(x),int(y)), 10, i, -1)\n",
"\n",
"mat = cv.findHomography(src_points, dst_points)[0]\n",
"\n",
"book_aligned = cv.warpPerspective(book, mat, (image_w, image_h))\n",
"\n",
"plt.figure(figsize=[10,10])\n",
"plt.subplot(121)\n",
"plt.imshow(book[:,:,::-1])\n",
"plt.title('Original image')\n",
"plt.subplot(122)\n",
"plt.imshow(book_aligned[:,:,::-1])\n",
"plt.title('Aligned image');"
]
},
{
"cell_type": "markdown",
"id": "062ebf74",
"metadata": {},
"source": [
"W przypadku dokumentów, możemy np. dysponować wzorem dokumentu do uzupełnienia oraz zdjęciem wypełnionego dokumentu. Po wyrówaniu zdjęcia moglibyśmy łatwiej analizować wypełnione pola:\n",
"\n",
"![Przykładowe wyrówanie dokumentu. Źródło: learnopencv.org](img/document-alignment.png)\n",
"\n",
"Cztery niezbędne punkty moglibyśmy wybierać ręcznie, jednak w przypadku masowego przetwarzania lepszym rozwiązaniem jest automatyczne znalezienie odpowiadających sobie punktów (niekoniecznie rogów dokumentu) na wzorcu i przetwarzanym zdjęciu. Poniżej zobaczymy jak można znaleźć i dopasować takie punkty."
]
},
{
"cell_type": "markdown",
"id": "c1692205",
"metadata": {},
"source": [
"# Punkty kluczowe\n",
"\n",
"Punkty kluczowe (ang. *keypoints*) są punktami na obrazie, o których możemy myśleć w kategoriach dobrze zdefiniowanych narożników, relatywnie odpornych na przekształcenia obrazu. Każdy punkt kluczowy ma przypisany descryptor tj. [wektor cech](https://stackoverflow.com/a/42116946).\n",
"\n",
"Wczytajmy przykładowy obraz, na którym postaramy się znaleźć takie punkty kluczowe:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8471d86c",
"metadata": {},
"outputs": [],
"source": [
"python_book = cv.imread(\"img/book-python-cover.jpg\", cv.IMREAD_COLOR)\n",
"python_book_gray = cv.cvtColor(python_book, cv.COLOR_BGR2GRAY)\n",
"\n",
"plt.figure(figsize=[10,10])\n",
"plt.subplot(121)\n",
"plt.imshow(python_book[:,:,::-1])\n",
"plt.title('Original image')\n",
"plt.subplot(122)\n",
"plt.imshow(python_book_gray, cmap='gray')\n",
"plt.title('Grayscale image');"
]
},
{
"cell_type": "markdown",
"id": "bed58e13",
"metadata": {},
"source": [
"Jednym z najpopularniejszych (i nieopatentowanych) algorytmów wykrywania i opisywania punktów kluczowych jest [ORB](https://docs.opencv.org/4.5.3/d1/d89/tutorial_py_orb.html) ([dokumentacja](https://docs.opencv.org/4.5.3/db/d95/classcv_1_1ORB.html)). Poniżej znajduje się wizualizacja wykrytych punktów kluczowych:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "be60009c",
"metadata": {},
"outputs": [],
"source": [
"orb_alg = cv.ORB_create()\n",
"\n",
"keypoints_1 = orb_alg.detect(python_book_gray, None)\n",
"\n",
"keypoints_1, descriptors_1 = orb_alg.compute(python_book_gray, keypoints_1)\n",
"\n",
"python_book_keypoints = cv.drawKeypoints(python_book, keypoints_1, None, color=(0,255,0), \n",
" flags=cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)\n",
"\n",
"plt.figure(figsize=(10,10))\n",
"plt.imshow(python_book_keypoints[:,:,::-1])\n",
"plt.title('Image keypoints');"
]
},
{
"cell_type": "markdown",
"id": "146432b4",
"metadata": {},
"source": [
"# Łączenie punktów kluczowych\n",
"\n",
"Załóżmy, że chcemy sprawdzić czy na danym zdjęciu znajduje się okładka powyższej książki:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9556cf9a",
"metadata": {},
"outputs": [],
"source": [
"book_in_hands = cv.imread(\"img/book-python-in-hands.jpg\", cv.IMREAD_COLOR)\n",
"book_in_hands_gray = cv.cvtColor(book_in_hands, cv.COLOR_BGR2GRAY)\n",
"\n",
"plt.figure(figsize=[10,10])\n",
"plt.subplot(121)\n",
"plt.imshow(book_in_hands[:,:,::-1])\n",
"plt.title('Original image')\n",
"plt.subplot(122)\n",
"plt.imshow(book_in_hands_gray, cmap='gray')\n",
"plt.title('Grayscale image');"
]
},
{
"cell_type": "markdown",
"id": "f4608507",
"metadata": {},
"source": [
"Na początku znajdźmy i opiszmy punkty kluczowe. Oba kroki możemy wykonać jednocześnie przy pomocy metody `detectAndCompute()`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "091c3868",
"metadata": {},
"outputs": [],
"source": [
"orb_alg = cv.ORB_create(nfeatures=1000)\n",
"\n",
"keypoints_1, descriptors_1 = orb_alg.detectAndCompute(python_book_gray, None)\n",
"keypoints_2, descriptors_2 = orb_alg.detectAndCompute(book_in_hands_gray, None)"
]
},
{
"cell_type": "markdown",
"id": "46df3c03",
"metadata": {},
"source": [
"Jeśli uda nam się połączyć odpowiadające sobie punkty kluczowe i wyliczyć macierz homografii, to z dość dużą dozą pewności będziemy mogli przyjąć, że na zdjęciu znajduje się szukany obiekt i będziemy mogli z grubsza wskazać gdzie on się znajduje. Punkty kluczowe są opisane przez wektory, więc np. moglibyśmy [*brute-forcem* i odległością Hamminga](https://docs.opencv.org/4.5.3/d3/da1/classcv_1_1BFMatcher.html) poszukać najlepszych odpowiedników. W poniższym przypadku pozostawimy 5% najlepszych dopasowań i zwizualizujemy te dopasowania:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9a660b70",
"metadata": {},
"outputs": [],
"source": [
"matcher = cv.DescriptorMatcher_create(cv.DESCRIPTOR_MATCHER_BRUTEFORCE_HAMMING)\n",
"matches = matcher.match(descriptors_1, descriptors_2, None)\n",
"\n",
"matches.sort(key=lambda x: x.distance, reverse=False) # sort by distance\n",
"\n",
"num_good_matches = int(len(matches) * 0.05) # save 5%\n",
"matches = matches[:num_good_matches]\n",
"\n",
"image_matched = cv.drawMatches(python_book, keypoints_1, book_in_hands, keypoints_2, matches, None)\n",
"plt.figure(figsize=(10,10))\n",
"plt.imshow(image_matched[...,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "d4d41002",
"metadata": {},
"source": [
"Jak widać część punktów jest niepoprawnie dopasowana. Na szczęście obliczenie macierzy homografii odbywa się przy pomocy algorytmu [RANSAC](https://en.wikipedia.org/wiki/Random_sample_consensus) (ang. *random sample consensus*), co pozwala wziąć pod uwagę, że pewne odzwierciedlenia są złe. Aby oznaczyć miejsce występowania obiektu, transformujemy współrzędne narożników źródłowego zdjęcia przy użyciu otrzymanej macierzy i funkcji [`cv.perspectiveTransform()`](https://docs.opencv.org/4.5.3/d2/de8/group__core__array.html#gad327659ac03e5fd6894b90025e6900a7):"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "635c29f3",
"metadata": {},
"outputs": [],
"source": [
"src_pts = np.float32([keypoints_1[m.queryIdx].pt for m in matches]).reshape(-1,1,2)\n",
"dst_pts = np.float32([keypoints_2[m.trainIdx].pt for m in matches]).reshape(-1,1,2)\n",
"\n",
"mat, mask = cv.findHomography(src_pts, dst_pts, cv.RANSAC, 5.0)\n",
"matches_mask = mask.ravel().tolist()\n",
"height, width, _ = python_book.shape\n",
"\n",
"pts = np.float32([[0,0], [0,height-1], [width-1,height-1], [width-1,0]]).reshape(-1,1,2)\n",
"\n",
"dst = cv.perspectiveTransform(pts, mat)\n",
"\n",
"book_in_hands_poly = cv.polylines(book_in_hands,\n",
" [np.int32(dst)], True, (0,0,255), \n",
" 10, cv.LINE_AA)\n",
" \n",
"draw_params = dict(matchColor=(0,255,0),\n",
" matchesMask=matches_mask, # draw only inliers\n",
" flags=2) # NOT_DRAW_SINGLE_POINTS\n",
"image_matched = cv.drawMatches(python_book, keypoints_1, \n",
" book_in_hands_poly, keypoints_2, \n",
" matches, None, **draw_params)\n",
"\n",
"plt.figure(figsize=(10,10))\n",
"plt.imshow(image_matched[...,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "c1580cf1",
"metadata": {},
"source": [
"# Zadanie 1\n",
"\n",
"Napisz interaktywną aplikację przy pomocy HighGUI, która pozwoli na obrazie `img/billboards.jpg` wybrać 4 punkty i umieścić w zaznaczonym obszarze wybrany obraz, np. `img/bakery.jpg`.\n",
"\n",
"![Aplikacja](img/app-billboard.png)"
]
},
{
"cell_type": "markdown",
"id": "e6b25af6",
"metadata": {},
"source": [
"# Zadanie 2\n",
"\n",
"Załóżmy, że mamy dwa zdjęcia, które chcielibyśmy połączyć w panoramę:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "becb6541",
"metadata": {},
"outputs": [],
"source": [
"boat_1 = cv.imread(\"img/boat_1.jpg\", cv.IMREAD_COLOR)\n",
"boat_2 = cv.imread(\"img/boat_2.jpg\", cv.IMREAD_COLOR)\n",
"\n",
"plt.figure(figsize=[10,10])\n",
"plt.subplot(121)\n",
"plt.imshow(boat_1[:,:,::-1])\n",
"plt.title('Image 1')\n",
"plt.subplot(122)\n",
"plt.imshow(boat_2[:,:,::-1])\n",
"plt.title('Image 2');"
]
},
{
"cell_type": "markdown",
"id": "35873eb0",
"metadata": {},
"source": [
"Standardowo w OpenCV panoramę tworzy się przy pomocy klasy [`Stitcher`](https://docs.opencv.org/4.5.3/d2/d8d/classcv_1_1Stitcher.html), której można podać listę zdjęć do połączenia:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3d35782b",
"metadata": {},
"outputs": [],
"source": [
"stitcher_alg = cv.Stitcher_create()\n",
"_, panorama = stitcher_alg.stitch([boat_1, boat_2])\n",
"plt.figure(figsize=[20,10]) \n",
"plt.imshow(panorama[:,:,::-1])\n",
"plt.title('Panorama');"
]
},
{
"cell_type": "markdown",
"id": "73464452",
"metadata": {},
"source": [
"Spróbuj zaimplementować samodzielne stworzenie panoramy. Poniższy efekt będzie wystarczający, aczkolwiek możesz również spróbować zniwelować widoczną różnicę w intensywnościach na granicy zdjęć."
]
},
{
"cell_type": "markdown",
"id": "965e871e",
"metadata": {},
"source": [
"![Panorama](img/panorama-manual.png)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a4b7e06a",
"metadata": {},
"outputs": [],
"source": [
"# miejsce na eksperymenty"
]
}
],
"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": "05. Transformacje geometryczne i cechy obrazów [laboratoria]",
"title": "Widzenie komputerowe",
"year": "2021"
},
"nbformat": 4,
"nbformat_minor": 5
}

620
wko-06.ipynb Normal file
View File

@ -0,0 +1,620 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "819ce420",
"metadata": {},
"source": [
"![Logo 1](img/aitech-logotyp-1.jpg)\n",
"<div class=\"alert alert-block alert-info\">\n",
"<h1> Widzenie komputerowe </h1>\n",
"<h2> 06. <i>Rozpoznawanie i segmentacja obrazó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": "a6dc5acc",
"metadata": {},
"source": [
"W poniższych materiałach zobaczymy w jaki sposób możemy klasycznym podejściem rozpoznawać ludzi na zdjęciach, a ponadto w jaki sposób szybko podzielić obraz na elementy znajdujące się na pierwszym planie i w tle obrazu.\n",
"\n",
"Na początku załadujmy niezbędne biblioteki."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e45bb312",
"metadata": {},
"outputs": [],
"source": [
"import cv2 as cv\n",
"import numpy as np\n",
"import sklearn.svm\n",
"import sklearn.metrics\n",
"import matplotlib.pyplot as plt\n",
"%matplotlib inline\n",
"import os\n",
"import random"
]
},
{
"cell_type": "markdown",
"id": "5b757675",
"metadata": {},
"source": [
"Naszym głównym celem będzie rozpoznawanie ludzi na zdjęciach przy pomocy klasycznej metody *histogram of oriented gradients* (HOG). Krótko mówiąc, dla danego zdjęcia chcemy uzyskać wektor cech, który będziemy mogli wykorzystać w klasyfikatorze SVM. Szczegóły znajdują się w *6.3.2 Pedestrian detection* R. Szeliski (2022) *Computer Vision: Algorithms and Applications*, natomiast tutaj zobrazujemy techniczne wykorzystanie tej metody.\n",
"\n",
"# Klasyfikacja obrazów przy użyciu HOG i SVM\n",
"\n",
"Spróbjemy zbudować klasyfikator, który wskazuje czy na zdjęciu znajduje się osoba z okularami czy bez okularów. Rozpakujmy zbiór danych, z którego będziemy korzystali:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7b953b82",
"metadata": {},
"outputs": [],
"source": [
"!cd datasets && unzip -qo glasses.zip"
]
},
{
"cell_type": "markdown",
"id": "f4a457f3",
"metadata": {},
"source": [
"Następnie wczytujemy dane i dzielimy je na dwa zbiory w proporcjach 80/20:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "737d95c1",
"metadata": {},
"outputs": [],
"source": [
"dataset_dir = \"datasets/glasses\"\n",
"images_0 = os.listdir(f\"{dataset_dir}/with\")\n",
"images_0 = [f\"{dataset_dir}/with/{x}\" for x in images_0]\n",
"images_1 = os.listdir(f\"{dataset_dir}/without\")\n",
"images_1 = [f\"{dataset_dir}/without/{x}\" for x in images_1]\n",
"images = images_0 + images_1\n",
"random.seed(1337)\n",
"random.shuffle(images)\n",
"\n",
"train_data = []\n",
"test_data = []\n",
"train_labels = []\n",
"test_labels = []\n",
"\n",
"splitval = int((1-0.2)*len(images))\n",
"\n",
"for x in images[:splitval]:\n",
" train_data.append(cv.imread(x, cv.IMREAD_COLOR))\n",
" train_labels.append(x.split(\"/\")[2])\n",
" \n",
"for x in images[splitval:]:\n",
" test_data.append(cv.imread(x, cv.IMREAD_COLOR))\n",
" test_labels.append(x.split(\"/\")[2])\n",
" \n",
"d_labels = {\"with\": 0, \"without\": 1}\n",
" \n",
"train_labels = np.array([d_labels[x] for x in train_labels])\n",
"test_labels = np.array([d_labels[x] for x in test_labels])\n",
"\n",
"print(f\"Train data: {len(train_data)}, test data: {len(test_data)}\")"
]
},
{
"cell_type": "markdown",
"id": "265147e3",
"metadata": {},
"source": [
"Poniżej znajduje się kilka przykładowych zdjęć."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e0595915",
"metadata": {},
"outputs": [],
"source": [
"plt.figure(figsize=(10,2))\n",
"for i in range(5):\n",
" plt.subplot(151 + i)\n",
" plt.imshow(train_data[i][:,:,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "e05e27e8",
"metadata": {},
"source": [
"Tworzymy deskryptor HOG przy pomocy funkcji [`cv.HOGDescriptor()`](https://docs.opencv.org/4.5.3/d5/d33/structcv_1_1HOGDescriptor.html). Metodą [`compute()`](https://docs.opencv.org/4.5.3/d5/d33/structcv_1_1HOGDescriptor.html#a38cd712cd5a6d9ed0344731fcd121e8b) tworzymy wektory cech, które posłużą nam jako dane wejściowe do klasyfikatora. Poniżej znajduje się również przykładowa konfiguracja deskryptora:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f21e8924",
"metadata": {},
"outputs": [],
"source": [
"hp_win_size = (96, 32)\n",
"hp_block_size = (8, 8)\n",
"hp_block_stride = (8, 8)\n",
"hp_cell_size = (4, 4)\n",
"hp_n_bins = 9\n",
"hp_deriv_aperture = 0\n",
"hp_win_sigma = 4.0\n",
"hp_histogram_norm_type = 1\n",
"hp_l2_hys_threshold = 0.2\n",
"hp_gamma_correction = True\n",
"hp_n_levels = 64\n",
"hp_signed_gradient = True\n",
"\n",
"hog_descriptor = cv.HOGDescriptor(\n",
" hp_win_size, hp_block_size, hp_block_stride, hp_cell_size, \n",
" hp_n_bins, hp_deriv_aperture, hp_win_sigma, \n",
" hp_histogram_norm_type, hp_l2_hys_threshold, \n",
" hp_gamma_correction, hp_n_levels, hp_signed_gradient)\n",
"\n",
"train_hog = np.vstack([hog_descriptor.compute(x).ravel() for x in train_data])\n",
"test_hog = np.vstack([hog_descriptor.compute(x).ravel() for x in test_data])"
]
},
{
"cell_type": "markdown",
"id": "755b8ebe",
"metadata": {},
"source": [
"Do klasyfikacji użyjemy klasyfikatora SVM. Możemy użyć implementacji znajdującej się w module [`cv.ml`](https://docs.opencv.org/4.5.3/d1/d2d/classcv_1_1ml_1_1SVM.html):"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b46783d4",
"metadata": {},
"outputs": [],
"source": [
"model = cv.ml.SVM_create()\n",
"model.setGamma(0.02)\n",
"model.setC(2.5)\n",
"model.setKernel(cv.ml.SVM_RBF)\n",
"model.setType(cv.ml.SVM_C_SVC)"
]
},
{
"cell_type": "markdown",
"id": "d8f47c54",
"metadata": {},
"source": [
"Trenujemy model:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "810f9a1e",
"metadata": {},
"outputs": [],
"source": [
"model.train(np.array(train_hog), cv.ml.ROW_SAMPLE, train_labels);"
]
},
{
"cell_type": "markdown",
"id": "69d39eee",
"metadata": {},
"source": [
"Sprawdzamy wynik na danych testowych:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "763b6dc7",
"metadata": {},
"outputs": [],
"source": [
"predictions = model.predict(test_hog)[1].ravel()\n",
"accuracy = (test_labels == predictions).mean()\n",
"print(f\"ACC: {accuracy * 100:.2f} %\")"
]
},
{
"cell_type": "markdown",
"id": "2dd04ec5",
"metadata": {},
"source": [
"Możemy również użyć implementacji klasyfikatora znajdującej się w bibliotece [`scikit-learn`](https://scikit-learn.org/stable/modules/generated/sklearn.svm.SVC.html):"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "13b7ba1c",
"metadata": {},
"outputs": [],
"source": [
"model = sklearn.svm.SVC(C=2.5, gamma=0.02, kernel='rbf')\n",
"model.fit(train_hog, train_labels)\n",
"\n",
"predictions = model.predict(test_hog)\n",
"accuracy = (test_labels == predictions).mean()\n",
"print(f\"ACC: {accuracy * 100:.2f} %\")"
]
},
{
"cell_type": "markdown",
"id": "2259c310",
"metadata": {},
"source": [
"# Rozpoznawanie ludzi\n",
"\n",
"Powyższą metodykę klasyfikcji możemy zastosować do rozpoznawania obiektów na zdjęciach, np. ludzi. W tym wypadku będziemy chcieli wskazać gdzie na zdjęciu znajduje się dany obiekt lub obiekty.\n",
"\n",
"Rozpocznijmy od rozpakowania zbioru danych:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d8497390",
"metadata": {},
"outputs": [],
"source": [
"!cd datasets && unzip -qo inria-person-sub.zip"
]
},
{
"cell_type": "markdown",
"id": "30374bad",
"metadata": {},
"source": [
"Wczytujemy dane, które są już podzielone na dwa zbiory:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "978d77cf",
"metadata": {},
"outputs": [],
"source": [
"dataset_dir = \"datasets/INRIAPerson\"\n",
"\n",
"images_train_0 = os.listdir(f\"{dataset_dir}/train_64x128_H96/negPatches\")\n",
"images_train_0 = [f\"{dataset_dir}/train_64x128_H96/negPatches/{x}\" for x in images_train_0]\n",
"images_train_1 = os.listdir(f\"{dataset_dir}/train_64x128_H96/posPatches\")\n",
"images_train_1 = [f\"{dataset_dir}/train_64x128_H96/posPatches/{x}\" for x in images_train_1]\n",
"\n",
"images_test_0 = os.listdir(f\"{dataset_dir}/test_64x128_H96/negPatches\")\n",
"images_test_0 = [f\"{dataset_dir}/test_64x128_H96/negPatches/{x}\" for x in images_test_0]\n",
"images_test_1 = os.listdir(f\"{dataset_dir}/test_64x128_H96/posPatches\")\n",
"images_test_1 = [f\"{dataset_dir}/test_64x128_H96/posPatches/{x}\" for x in images_test_1]\n",
"\n",
"train_data = []\n",
"test_data = []\n",
"train_labels = []\n",
"test_labels = []\n",
"\n",
"for x in images_train_0:\n",
" img = cv.imread(x, cv.IMREAD_COLOR)\n",
" if img is not None:\n",
" train_data.append(img)\n",
" train_labels.append(0)\n",
"\n",
"for x in images_train_1:\n",
" img = cv.imread(x, cv.IMREAD_COLOR)\n",
" if img is not None:\n",
" train_data.append(img)\n",
" train_labels.append(1)\n",
" \n",
"for x in images_test_0:\n",
" img = cv.imread(x, cv.IMREAD_COLOR)\n",
" if img is not None:\n",
" test_data.append(img)\n",
" test_labels.append(0)\n",
"\n",
"for x in images_test_1:\n",
" img = cv.imread(x, cv.IMREAD_COLOR)\n",
" if img is not None:\n",
" test_data.append(img)\n",
" test_labels.append(1)\n",
"\n",
"print(f\"Train data: {len(train_data)}, test data: {len(test_data)}\")"
]
},
{
"cell_type": "markdown",
"id": "9bf41d6e",
"metadata": {},
"source": [
"Poniżej znajduje się kilka przykładowych zdjęć ze zbioru:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f29d47c1",
"metadata": {},
"outputs": [],
"source": [
"plt.figure(figsize=(10,2))\n",
"for i in range(3):\n",
" plt.subplot(161 + i)\n",
" plt.imshow(train_data[i][:,:,::-1]);\n",
"for i in range(3):\n",
" plt.subplot(164 + i)\n",
" plt.imshow(train_data[-(i+1)][:,:,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "57cec468",
"metadata": {},
"source": [
"Tworzymy deskryptor i wektory cech:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d5248df2",
"metadata": {},
"outputs": [],
"source": [
"hp_win_size = (64, 128)\n",
"hp_block_size = (16, 16)\n",
"hp_block_stride = (8, 8)\n",
"hp_cell_size = (8, 8)\n",
"hp_n_bins = 9\n",
"hp_deriv_aperture = 1\n",
"hp_win_sigma = -1\n",
"hp_histogram_norm_type = 0\n",
"hp_l2_hys_threshold = 0.2\n",
"hp_gamma_correction = True\n",
"hp_n_levels = 64\n",
"hp_signed_gradient = False\n",
"\n",
"hog_descriptor = cv.HOGDescriptor(\n",
" hp_win_size, hp_block_size, hp_block_stride, hp_cell_size, \n",
" hp_n_bins, hp_deriv_aperture, hp_win_sigma, \n",
" hp_histogram_norm_type, hp_l2_hys_threshold, \n",
" hp_gamma_correction, hp_n_levels, hp_signed_gradient)\n",
"\n",
"train_hog = np.vstack([hog_descriptor.compute(x).ravel() for x in train_data])\n",
"test_hog = np.vstack([hog_descriptor.compute(x).ravel() for x in test_data])"
]
},
{
"cell_type": "markdown",
"id": "c6782aa9",
"metadata": {},
"source": [
"Następnie tworzymy klasyfikator:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8f6108ed",
"metadata": {},
"outputs": [],
"source": [
"model = cv.ml.SVM_create()\n",
"model.setGamma(0)\n",
"model.setC(0.01)\n",
"model.setKernel(cv.ml.SVM_LINEAR)\n",
"model.setType(cv.ml.SVM_C_SVC)\n",
"model.setTermCriteria((cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 1000, 1e-3))"
]
},
{
"cell_type": "markdown",
"id": "bbfbde58",
"metadata": {},
"source": [
"Uczymy model:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "afd0bbb4",
"metadata": {},
"outputs": [],
"source": [
"model.train(np.array(train_hog), cv.ml.ROW_SAMPLE, np.array(train_labels));"
]
},
{
"cell_type": "markdown",
"id": "09626eed",
"metadata": {},
"source": [
"Sprawdzamy jakość klasyfikacji:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fa3be6b6",
"metadata": {},
"outputs": [],
"source": [
"predictions = model.predict(test_hog)[1].ravel()\n",
"accuracy = (test_labels == predictions).mean()\n",
"print(f\"ACC: {accuracy * 100:.2f} %\")"
]
},
{
"cell_type": "markdown",
"id": "c6df6682",
"metadata": {},
"source": [
"Poniżej znajduje się podejście przy pomocy biblioteki *scikit-learn*:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7b3de8d1",
"metadata": {},
"outputs": [],
"source": [
"model2 = sklearn.svm.SVC(C=0.01, gamma='auto', kernel='linear', max_iter=1000)\n",
"model2.fit(train_hog, train_labels)\n",
"\n",
"predictions = model2.predict(test_hog)\n",
"accuracy = (test_labels == predictions).mean()\n",
"print(f\"Accuracy: {sklearn.metrics.accuracy_score(test_labels, predictions) * 100:.2f} %\")\n",
"print(f\"Precision: {sklearn.metrics.precision_score(test_labels, predictions) * 100:.2f} %\")\n",
"print(f\"Recall: {sklearn.metrics.recall_score(test_labels, predictions) * 100:.2f} %\")"
]
},
{
"cell_type": "markdown",
"id": "6e84c568",
"metadata": {},
"source": [
"Mając teraz wyuczony model, chcielibyśmy sprawdzić czy np. na zdjęciu `img/pedestrians.jpg` znajdują się ludzie, tak aby uzyskać ew. obramowania z ich występowaniem. W pierwszej kolejności w naszym deskryptorze HOG ustawiamy współczynniki klasfikatora SVM przy pomocy metody [`setSVMDetector()`](https://docs.opencv.org/4.5.3/d5/d33/structcv_1_1HOGDescriptor.html#a6de5ac55631eed51e36278cde3a2c159). Następnie przy pomocy metody [`detectMultiScale()`](https://docs.opencv.org/4.5.3/d5/d33/structcv_1_1HOGDescriptor.html#a91e56a2c317392e50fbaa2f5dc78d30b) znajdujemy wyszukiwane obiekty (ludzi) w różnych skalach."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d6458103",
"metadata": {},
"outputs": [],
"source": [
"image = cv.imread(\"img/pedestrians.jpg\", cv.IMREAD_COLOR)\n",
"scale = 600 / image.shape[0]\n",
"image = cv.resize(image, None, fx=scale, fy=scale)\n",
"\n",
"support_vectors = model.getSupportVectors()\n",
"rho, _, _ = model.getDecisionFunction(0)\n",
"detector = np.zeros(support_vectors.shape[1] + 1, dtype=support_vectors.dtype)\n",
"detector[:-1] = -support_vectors[:]\n",
"detector[-1] = rho\n",
"\n",
"hog_descriptor.setSVMDetector(detector)\n",
"\n",
"locations, weights = hog_descriptor.detectMultiScale(\n",
" image, winStride=(8, 8), padding=(32, 32), scale=1.05,\n",
" finalThreshold=2, hitThreshold=1.0)\n",
"\n",
"for location, weight in zip(locations, weights):\n",
" x1, y1, w, h = location\n",
" x2, y2 = x1 + w, y1 + h\n",
" cv.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), thickness=3, lineType=cv.LINE_AA)\n",
" cv.putText(image, f\"{weight[0]:.2f}\", (x1,y1), cv.FONT_HERSHEY_SIMPLEX, 1, (0,0,255), 2, cv.LINE_AA)\n",
"\n",
"plt.figure(figsize=(6,6))\n",
"plt.imshow(image[:,:,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "cd287c92",
"metadata": {},
"source": [
"Coś nam nawet udało się wykryć jak na tak niewielki zbiór danych uczących ;) Z drugiej strony, dwie osoby na pierwszym planie zostały pominięte, a osoba po prawej jest dyskusyjna jeśli chodzi o zakres oznaczenia.\n",
"\n",
"W OpenCV dostępny jest domyślny klasyfikator w funkcji [`HOGDescriptor_getDefaultPeopleDetector()`](https://docs.opencv.org/4.5.3/d5/d33/structcv_1_1HOGDescriptor.html#a9c7a0b2aa72cf39b4b32b3eddea78203) i poniżej możemy zobaczyć jak sobie radzi na badanym zdjęciu:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "57a745c9",
"metadata": {},
"outputs": [],
"source": [
"image = cv.imread(\"img/pedestrians.jpg\", cv.IMREAD_COLOR)\n",
"scale = 600 / image.shape[0]\n",
"image = cv.resize(image, None, fx=scale, fy=scale)\n",
"\n",
"hog_dflt_descriptor = cv.HOGDescriptor(\n",
" hp_win_size, hp_block_size, hp_block_stride, hp_cell_size, \n",
" hp_n_bins, hp_deriv_aperture, hp_win_sigma, \n",
" hp_histogram_norm_type, hp_l2_hys_threshold, \n",
" hp_gamma_correction, hp_n_levels, hp_signed_gradient)\n",
"\n",
"detector_dflt = cv.HOGDescriptor_getDefaultPeopleDetector()\n",
"hog_dflt_descriptor.setSVMDetector(detector_dflt)\n",
"\n",
"locations, weights = hog_dflt_descriptor.detectMultiScale(\n",
" image, winStride=(8, 8), padding=(32, 32), scale=1.05,\n",
" finalThreshold=2, hitThreshold=1.0)\n",
"\n",
"for location, weight in zip(locations, weights):\n",
" x1, y1, w, h = location\n",
" x2, y2 = x1 + w, y1 + h\n",
" cv.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), thickness=3, lineType=cv.LINE_AA)\n",
" cv.putText(image, f\"{weight[0]:.2f}\", (x1,y1), cv.FONT_HERSHEY_SIMPLEX, 1, (0,0,255), 2, cv.LINE_AA)\n",
"\n",
"plt.figure(figsize=(6,6))\n",
"plt.imshow(image[:,:,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "6c8cf915",
"metadata": {},
"source": [
"# Segmentacja obrazu metodą GrabCut\n",
"\n",
"## Zadanie 1\n",
"\n",
"W poniższym zadaniu użyjemy algorytmu [GrabCut](https://en.wikipedia.org/wiki/GrabCut), będącego interaktywną metodą segmentacji obrazu, dzielącą obraz na pierwszy i drugi plan. W OpenCV algorytm jest zaimplementowany w funkcji [`cv.grabCut()`](https://docs.opencv.org/4.5.3/d3/d47/group__imgproc__segmentation.html#ga909c1dda50efcbeaa3ce126be862b37f). Dodatkowe informacje o algorytmie znajdują się w [dokumentacji](https://docs.opencv.org/4.5.3/d8/d83/tutorial_py_grabcut.html).\n",
"\n",
"Przygotuj interaktywną aplikację, która wykorzystuje algorytm GrabCut. W aplikacji powinna być możliwość zaznaczenia początkowego prostokąta, a następnie elementy maski (zwróć uwagę z jakich elementów może składać się maska). Przykładowe działanie możesz zaprezentować na obrazie `img/messi5.jpg`."
]
},
{
"cell_type": "markdown",
"id": "35f22bca",
"metadata": {},
"source": [
"![GrabCut - wynik](img/grabcut-result.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": "06. Segmentacja i rozpoznawanie obrazów [laboratoria]",
"title": "Widzenie komputerowe",
"year": "2021"
},
"nbformat": 4,
"nbformat_minor": 5
}

595
wko-07.ipynb Normal file
View File

@ -0,0 +1,595 @@
{
"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
}

459
wko-08.ipynb Normal file
View File

@ -0,0 +1,459 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "909d3c02",
"metadata": {},
"source": [
"![Logo 1](img/aitech-logotyp-1.jpg)\n",
"<div class=\"alert alert-block alert-info\">\n",
"<h1> Widzenie komputerowe </h1>\n",
"<h2> 08. <i>Rozpoznawanie twarzy</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": "7a9fde6b",
"metadata": {},
"source": [
"W poniższych materiałach zaprezentujemy klasyczne metody rozpoznawania twarzy. Opisywane zagadnienia można odnaleźć w *5.2.3 Principal component analysis* R. Szeliski (2022) *Computer Vision: Algorithms and Applications* oraz [dokumentacji](https://docs.opencv.org/4.5.3/da/d60/tutorial_face_main.html).\n",
"\n",
"Na początku załadujmy niezbędne biblioteki."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1d86977a",
"metadata": {},
"outputs": [],
"source": [
"import cv2 as cv\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"%matplotlib inline\n",
"import sklearn.metrics\n",
"import ipywidgets\n",
"import os\n",
"import random"
]
},
{
"cell_type": "markdown",
"id": "c5a62135",
"metadata": {},
"source": [
"Rozpakujmy zbiór danych, na którym będziemy pracować:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0e0f1723",
"metadata": {},
"outputs": [],
"source": [
"!cd datasets && unzip -qo yaleextb.zip"
]
},
{
"cell_type": "markdown",
"id": "e6a0efb1",
"metadata": {},
"source": [
"Nasz zbiór zawiera po kilkadziesiąt zdjęć kilkudziesięciu osób, które zostały sfotografowane w różnych warunkach oświetlenia. Wczytane zdjęcia podzielimy na zbiór treningowy i testowy w stosunku 3/1 oraz wyświetlimy kilka przykładowych zdjęć:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7b775bbf",
"metadata": {},
"outputs": [],
"source": [
"dataset_dir = \"datasets/yaleextb\"\n",
"\n",
"img_data = []\n",
"img_labels = []\n",
"\n",
"images = os.listdir(dataset_dir)\n",
"\n",
"n_examples = 15\n",
"\n",
"for i in range(1, 40):\n",
" i_str = str(i).zfill(2)\n",
" images_p = [img for img in images if img.startswith(f\"yaleB{i_str}\")]\n",
" \n",
" for img in images_p[:n_examples]:\n",
" img_data.append(cv.imread(f\"{dataset_dir}/{img}\", cv.IMREAD_GRAYSCALE))\n",
" img_labels.append(i)\n",
"\n",
"random.seed(1337)\n",
"selector = random.choices([False, True], k=len(images), weights=[3, 1])\n",
"train_data = [x for x, y in zip(img_data, selector) if not y]\n",
"train_labels = [x for x, y in zip(img_labels, selector) if not y]\n",
"test_data = [x for x, y in zip(img_data, selector) if y]\n",
"test_labels = [x for x, y in zip(img_labels, selector) if y]\n",
"\n",
"plt.figure(figsize=(12,5))\n",
"for i in range(4):\n",
" plt.subplot(251 + i)\n",
" plt.imshow(train_data[i], cmap='gray');\n",
"for i in range(4):\n",
" plt.subplot(256 + i)\n",
" plt.imshow(train_data[-i-20], cmap='gray');"
]
},
{
"cell_type": "markdown",
"id": "6e315630",
"metadata": {},
"source": [
"Pierwszym modelem jest *Eigenfaces* zaimplementowany w [`EigenFaceRecognizer`](https://docs.opencv.org/4.5.3/dd/d7c/classcv_1_1face_1_1EigenFaceRecognizer.html). Główny pomysł polega na użyciu PCA do redukcji wymiarów. W naszym przykładzie zachowamy 60 wektorów własnych."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0473c8ae",
"metadata": {},
"outputs": [],
"source": [
"model = cv.face.EigenFaceRecognizer_create(60)\n",
"model.train(np.array(train_data), np.array(train_labels))"
]
},
{
"cell_type": "markdown",
"id": "7a753f2d",
"metadata": {},
"source": [
"Zachowane wektory własne możemy zwizualizować:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f797fe86",
"metadata": {},
"outputs": [],
"source": [
"img_shape = train_data[0].shape\n",
"plt.figure(figsize=(12,5))\n",
"for i in range(5):\n",
" e_v = model.getEigenVectors()[:,i]\n",
" e_v = np.reshape(e_v, img_shape)\n",
"\n",
" plt.subplot(151+i)\n",
" plt.imshow(e_v, cmap='gray');"
]
},
{
"cell_type": "markdown",
"id": "19545151",
"metadata": {},
"source": [
"Możemy zobaczyć jakie potencjalne twarze znajdują się w naszej przestrzeni. Do *uśrednionej* twarzy dodajemy kolejne wektory własne z odpowiednimi wagami. Poniżej mamy przykład wykorzystujący 6 wektorów:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5265f337",
"metadata": {},
"outputs": [],
"source": [
"mean = model.getMean()\n",
"W = model.getEigenVectors()\n",
"\n",
"def generate_face(**args):\n",
" img = mean.copy()\n",
" for i, k in enumerate(args.keys()):\n",
" img = np.add(img, W[:,i]*(10*args[k]))\n",
" \n",
" img = np.reshape(img, img_shape)\n",
" plt.figure(figsize=(5,5))\n",
" plt.imshow(img, cmap='gray')\n",
" plt.show()\n",
" \n",
"ipywidgets.interactive(generate_face, \n",
" w_0=ipywidgets.IntSlider(min=-128, max=128),\n",
" w_1=ipywidgets.IntSlider(min=-128, max=128),\n",
" w_2=ipywidgets.IntSlider(min=-128, max=128),\n",
" w_3=ipywidgets.IntSlider(min=-128, max=128),\n",
" w_4=ipywidgets.IntSlider(min=-128, max=128),\n",
" w_5=ipywidgets.IntSlider(min=-128, max=128))"
]
},
{
"cell_type": "markdown",
"id": "fd4bdce6",
"metadata": {},
"source": [
"Możemy teraz spróbować zrobić rekonstrukcję np. pierwszej twarzy ze zbioru treningowego. Pobieramy dla niej projekcje (wagi) z naszego modelu i podobnie jak wyżej wykorzystujemy uśrednioną twarz i wektory własne. Możemy zobaczyć, że użycie większej liczby wektorów powoduje zwiększenie precyzji rekonstrukcji:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2619c6f9",
"metadata": {},
"outputs": [],
"source": [
"pro = model.getProjections()[0]\n",
"\n",
"def reconstruct_face(k):\n",
" img = mean.copy()\n",
"\n",
" for i in range(k):\n",
" img = np.add(img, W[:,i]*pro[0,i])\n",
" \n",
" return img\n",
"\n",
"plt.figure(figsize=(12,6))\n",
"for i in range(6):\n",
" k = (i+1)*10\n",
" r_face = np.reshape(reconstruct_face(k), img_shape)\n",
" j = 0 if i <= 4 else 10\n",
" plt.subplot(151+i+100)\n",
" plt.imshow(r_face, cmap='gray')\n",
" plt.title(f\"k = {k}\")\n",
" \n",
"plt.subplot(257)\n",
"plt.imshow(train_data[0], cmap='gray');\n",
"plt.title(\"original\");"
]
},
{
"cell_type": "markdown",
"id": "ae87277a",
"metadata": {},
"source": [
"Spróbujmy teraz odnaleźć osobny znajdujące się na dwóch przykładowych obrazach ze zbioru testowego. Dla nieznanej twarzy obliczamy projekcje i szukamy metodą najbliższego sąsiada projekcji ze zbioru treningowego. Poniżej mamy przykład z poprawnym rozpoznaniem osoby oraz z niepoprawnym rozpoznaniem:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "828f3134",
"metadata": {},
"outputs": [],
"source": [
"def find_face(query_id):\n",
" query_face = test_data[query_id]\n",
" query_label = test_labels[query_id]\n",
"\n",
" x = np.reshape(query_face, mean.shape)\n",
" x_coeff = np.dot(x - mean, W)\n",
"\n",
" best_face = None\n",
" best_label = None\n",
" best_dist = float('inf')\n",
"\n",
" for i, p in enumerate(model.getProjections()):\n",
" dist = np.linalg.norm(np.reshape(p, 60) - np.reshape(x_coeff, 60))\n",
"\n",
" if dist < best_dist:\n",
" best_face = train_data[i]\n",
" best_label = train_labels[i]\n",
" best_dist = dist\n",
" \n",
" return query_face, query_label, best_face, best_label\n",
"\n",
"qf_1, ql_1, bf_1, bl_1 = find_face(45)\n",
"qf_2, ql_2, bf_2, bl_2 = find_face(10)\n",
"\n",
"plt.figure(figsize=(8,11))\n",
"plt.subplot(221)\n",
"plt.imshow(qf_1, cmap='gray')\n",
"plt.title(f\"Face 1: query label = {ql_1}\")\n",
"plt.subplot(222)\n",
"plt.imshow(bf_1, cmap='gray');\n",
"plt.title(f\"Face 1: best label = {bl_1}\")\n",
"plt.subplot(223)\n",
"plt.imshow(qf_2, cmap='gray')\n",
"plt.title(f\"Face 2: query label = {ql_2}\")\n",
"plt.subplot(224)\n",
"plt.imshow(bf_2, cmap='gray');\n",
"plt.title(f\"Face 2: best label = {bl_2}\");"
]
},
{
"cell_type": "markdown",
"id": "43f9a8e5",
"metadata": {},
"source": [
"Bardziej kompaktowe wykonanie predykcji możemy uzyskać poprzez metodę `predict()`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bf736bdd",
"metadata": {},
"outputs": [],
"source": [
"print(test_labels[45], model.predict(test_data[45])[0])\n",
"print(test_labels[10], model.predict(test_data[10])[0])"
]
},
{
"cell_type": "markdown",
"id": "eeaf62b5",
"metadata": {},
"source": [
"Jak widać poniżej, metoda ta nie uzyskuje szczególnie zadowalających wyników (generalnie słabo sobie radzi w sytuacji zmian oświetlenia):"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "12c65438",
"metadata": {},
"outputs": [],
"source": [
"predictions = []\n",
"for test_img in test_data:\n",
" p_label, p_conf = model.predict(test_img)\n",
" predictions.append(p_label)\n",
" \n",
"print(f\"Accuracy: {sklearn.metrics.accuracy_score(test_labels, predictions) * 100:.2f} %\")"
]
},
{
"cell_type": "markdown",
"id": "ea5d879b",
"metadata": {},
"source": [
"Poniżej krótko zaprezentujemy jeszcze dwa rozwinięcia tego algorytmu. Pierwszym z nich jest *Fisherfaces* zaimplementowany w [`FisherFaceRecognizer`](https://docs.opencv.org/4.5.3/d2/de9/classcv_1_1face_1_1FisherFaceRecognizer.html). Tym razem przy pomocy LDA chcemy dodatkowo uwzględnić rozrzut pomiędzy klasami (por. [przykład](https://sthalles.github.io/fisher-linear-discriminant/)). Poniżej tworzymy model z 40 komponentami:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4eb5b746",
"metadata": {},
"outputs": [],
"source": [
"model = cv.face.FisherFaceRecognizer_create(40)\n",
"model.train(np.array(train_data), np.array(train_labels))"
]
},
{
"cell_type": "markdown",
"id": "e9f334be",
"metadata": {},
"source": [
"Zauważmy, że uzyskujemy tutaj ponad dwukrotnie lepszy wynik:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "96faa192",
"metadata": {},
"outputs": [],
"source": [
"predictions = []\n",
"for test_img in test_data:\n",
" p_label, p_conf = model.predict(test_img)\n",
" predictions.append(p_label)\n",
" \n",
"print(f\"Accuracy: {sklearn.metrics.accuracy_score(test_labels, predictions) * 100:.2f} %\")"
]
},
{
"cell_type": "markdown",
"id": "02220e5f",
"metadata": {},
"source": [
"Dalszym rozwinięciem jest model *Local Binary Patterns Histograms* (LBPH) zaimplementowany w [`LBPHFaceRecognizer`](https://docs.opencv.org/4.5.3/df/d25/classcv_1_1face_1_1LBPHFaceRecognizer.html). W tym wypadku chcemy np. uwzględnić możliwość innego oświetlenia osób niż taki, który występuje w naszym zbiorze treningowym. Podobnie jak wcześniej zależy nam na redukcji wymiarów, ale tym razem uzyskamy to poprzez wyliczanie cech (progowanie) dla poszczególnych pikseli w zadanych regionach."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "61eeffdf",
"metadata": {},
"outputs": [],
"source": [
"model = cv.face.LBPHFaceRecognizer_create(radius=10, neighbors=10, grid_x=32, grid_y=32)\n",
"model.train(np.array(train_data), np.array(train_labels))"
]
},
{
"cell_type": "markdown",
"id": "0d64cb5a",
"metadata": {},
"source": [
"Uzyskany wynik jest o kilka punktów procentowy lepszy od poprzedniego modelu, jednak możemy zauważyć, że zmiana domyślnych parametrów na takie, które zwiększają precyzję, powoduje również zwiększenie czasu potrzebnego na wykonanie predykcji:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ca2e319d",
"metadata": {},
"outputs": [],
"source": [
"predictions = []\n",
"for test_img in test_data:\n",
" p_label, p_conf = model.predict(test_img)\n",
" predictions.append(p_label)\n",
" \n",
"print(f\"Accuracy: {sklearn.metrics.accuracy_score(test_labels, predictions) * 100:.2f} %\")"
]
},
{
"cell_type": "markdown",
"id": "00196405",
"metadata": {},
"source": [
"# Zadanie 1\n",
"\n",
"W katalogu `datasets` znajduje się zbiór zdjęć `att_faces`. Sprawdź jakiego typu są to zdjęcia oraz jak powyższe algorytmy działają na tym zbiorze."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "51b8a256",
"metadata": {},
"outputs": [],
"source": [
"# miejsce na eksperymenty"
]
}
],
"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": "08. Rozpoznawanie twarzy [laboratoria]",
"title": "Widzenie komputerowe",
"year": "2021"
},
"nbformat": 4,
"nbformat_minor": 5
}

519
wko-09.ipynb Normal file
View File

@ -0,0 +1,519 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "80377b3b",
"metadata": {},
"source": [
"![Logo 1](img/aitech-logotyp-1.jpg)\n",
"<div class=\"alert alert-block alert-info\">\n",
"<h1> Widzenie komputerowe </h1>\n",
"<h2> 09. <i>Metody głębokiego uczenia (1)</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": "07159136",
"metadata": {},
"source": [
"W poniższym materiale zobaczymy w jaki sposób korzystać z metod głębokiego uczenia sieci neuronowych w pakiecie OpenCV.\n",
"\n",
"Na początku załadujmy niezbędne biblioteki:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b2e906f0",
"metadata": {},
"outputs": [],
"source": [
"import cv2 as cv\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"%matplotlib inline"
]
},
{
"cell_type": "markdown",
"id": "f4348bc5",
"metadata": {},
"source": [
"OpenCV wspiera [wiele](https://github.com/opencv/opencv/wiki/Deep-Learning-in-OpenCV) bibliotek i modeli sieci neuronowych. Modele trenuje się poza OpenCV - bibliotekę wykorzystuje się tylko do predykcji, aczkolwiek sama w sobie ma całkiem sporo możliwych optymalizacji w porównaniu do źródłowych bibliotek neuronowych, więc predykcja może być tutaj faktycznie szybsza.\n",
"\n",
"Pliki z modelami i danymi pomocniczymi będziemy pobierali z sieci i będziemy je zapisywali w katalogu `dnn`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "42b85f55",
"metadata": {},
"outputs": [],
"source": [
"!mkdir -p dnn"
]
},
{
"cell_type": "markdown",
"id": "ac09b098",
"metadata": {},
"source": [
"# Klasyfikacja obrazów\n",
"\n",
"Spróbujemy wykorzystać sieć do klasyfikacji obrazów wyuczonej na zbiorze [ImageNet](https://www.image-net.org/). Pobierzmy plik zawierający opis 1000 możliwych klas:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "85b1b68c",
"metadata": {},
"outputs": [],
"source": [
"!wget -q --show-progress -O dnn/classification_classes_ILSVRC2012.txt https://raw.githubusercontent.com/opencv/opencv/master/samples/data/dnn/classification_classes_ILSVRC2012.txt "
]
},
{
"cell_type": "markdown",
"id": "fd0c577b",
"metadata": {},
"source": [
"Spójrzmy na pierwsze pięć klas w pliku:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fb0d0546",
"metadata": {},
"outputs": [],
"source": [
"with open('dnn/classification_classes_ILSVRC2012.txt', 'r') as f_fd:\n",
" classes = f_fd.read().splitlines()\n",
" \n",
"print(len(classes), classes[:5])"
]
},
{
"cell_type": "markdown",
"id": "5b0ee6ff",
"metadata": {},
"source": [
"Do klasyfikacji użyjemy sieci [DenseNet](https://arxiv.org/abs/1608.06993). Pobierzemy jedną z mniejszych [reimplementacji](https://github.com/shicai/DenseNet-Caffe), która jest hostowana m.in. na Google Drive (musimy doinstalować jeden pakiet):"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fb2bf2a1",
"metadata": {},
"outputs": [],
"source": [
"!pip3 install --user --disable-pip-version-check gdown"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "27996509",
"metadata": {},
"outputs": [],
"source": [
"import gdown\n",
"\n",
"url = 'https://drive.google.com/uc?id=0B7ubpZO7HnlCcHlfNmJkU2VPelE'\n",
"output = 'dnn/DenseNet_121.caffemodel'\n",
"gdown.download(url, output, quiet=False)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "648ec9c9",
"metadata": {},
"outputs": [],
"source": [
"!wget -q --show-progress -O dnn/DenseNet_121.prototxt https://raw.githubusercontent.com/shicai/DenseNet-Caffe/master/DenseNet_121.prototxt"
]
},
{
"cell_type": "markdown",
"id": "f7294c54",
"metadata": {},
"source": [
"Konkretne biblioteki neuronowe posiadają dedykowane funkcje do ładowania modeli, np. [`readNetFromCaffe()`](https://docs.opencv.org/4.5.3/d6/d0f/group__dnn.html#ga29d0ea5e52b1d1a6c2681e3f7d68473a) lub [`readNetFromTorch()`](https://docs.opencv.org/4.5.3/d6/d0f/group__dnn.html#ga65a1da76cb7d6852bdf7abbd96f19084), jednak można też użyć ogólnej [`readNet()`](https://docs.opencv.org/4.5.3/d6/d0f/group__dnn.html#ga3b34fe7a29494a6a4295c169a7d32422):"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6fd2d6b3",
"metadata": {},
"outputs": [],
"source": [
"model = cv.dnn.readNet(model='dnn/DenseNet_121.prototxt', config='dnn/DenseNet_121.caffemodel', framework='Caffe')"
]
},
{
"cell_type": "markdown",
"id": "fe22fd6f",
"metadata": {},
"source": [
"Spróbujemy sklasyfikować poniższy obraz:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6ace4606",
"metadata": {},
"outputs": [],
"source": [
"image = cv.imread('img/flamingo.jpg')\n",
"plt.figure(figsize=[5,5])\n",
"plt.imshow(image[:,:,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "e51db3ac",
"metadata": {},
"source": [
"Aby móc przepuścić obraz przez sieć musimy zmienić jego formę reprezentacji poprzez funkcję [`blobFromImage()`](https://docs.opencv.org/4.5.3/d6/d0f/group__dnn.html#ga29f34df9376379a603acd8df581ac8d7). Aby uzyskać sensowne dane musimy ustawić parametry dotyczące preprocessingu (informacje o tym są zawarte na [stronie modelu](https://github.com/shicai/DenseNet-Caffe)):"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d4e945ae",
"metadata": {},
"outputs": [],
"source": [
"image_blob = cv.dnn.blobFromImage(image=image, scalefactor=0.017, size=(224, 224), mean=(104, 117, 123), \n",
" swapRB=False, crop=False)"
]
},
{
"cell_type": "markdown",
"id": "625aebdd",
"metadata": {},
"source": [
"Ustawiamy dane wejściowe w naszej sieci i pobieramy obliczone wartości:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "753333a1",
"metadata": {},
"outputs": [],
"source": [
"model.setInput(image_blob)\n",
"outputs = model.forward()[0]"
]
},
{
"cell_type": "markdown",
"id": "34316ddb",
"metadata": {},
"source": [
"Wyliczamy która klasa jest najbardziej prawdopodobna:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "13423a6d",
"metadata": {},
"outputs": [],
"source": [
"outputs = outputs.reshape(1000, 1)\n",
"\n",
"label_id = np.argmax(outputs)\n",
"\n",
"probs = np.exp(outputs) / np.sum(np.exp(outputs))"
]
},
{
"cell_type": "markdown",
"id": "874c1b1d",
"metadata": {},
"source": [
"Wynik:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ec75a3c5",
"metadata": {},
"outputs": [],
"source": [
"plt.imshow(image[:,:,::-1])\n",
"plt.title(classes[label_id])\n",
"print(\"{:.2f} %\".format(np.max(probs) * 100.0))"
]
},
{
"cell_type": "markdown",
"id": "3808c42c",
"metadata": {},
"source": [
"# Wykrywanie twarzy\n",
"\n",
"Do wykrywania twarzy użyjemy sieci bazującej na [SSD](https://github.com/weiliu89/caffe/tree/ssd):"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3c0df387",
"metadata": {},
"outputs": [],
"source": [
"!wget -q --show-progress -O dnn/res10_300x300_ssd_iter_140000_fp16.caffemodel https://raw.githubusercontent.com/opencv/opencv_3rdparty/dnn_samples_face_detector_20180205_fp16/res10_300x300_ssd_iter_140000_fp16.caffemodel\n",
"!wget -q --show-progress -O dnn/res10_300x300_ssd_iter_140000_fp16.prototxt https://raw.githubusercontent.com/opencv/opencv/master/samples/dnn/face_detector/deploy.prototxt"
]
},
{
"cell_type": "markdown",
"id": "c6142f6e",
"metadata": {},
"source": [
"Ładujemy model:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "60d41efb",
"metadata": {},
"outputs": [],
"source": [
"model = cv.dnn.readNet(model='dnn/res10_300x300_ssd_iter_140000_fp16.prototxt', config='dnn/res10_300x300_ssd_iter_140000_fp16.caffemodel', framework='Caffe')"
]
},
{
"cell_type": "markdown",
"id": "ad612cc6",
"metadata": {},
"source": [
"Będziemy chcieli wykryć twarze na poniższym obrazie:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b404d8c4",
"metadata": {},
"outputs": [],
"source": [
"image = cv.imread('img/people.jpg')\n",
"plt.figure(figsize=[7,7])\n",
"plt.imshow(image[:,:,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "a77f8e64",
"metadata": {},
"source": [
"Znajdujemy twarze i oznaczamy je na zdjęciu (za próg przyjęliśmy 0.5; zob. informacje o [preprocessingu](https://github.com/opencv/opencv/tree/master/samples/dnn#face-detection)):"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1d16f230",
"metadata": {},
"outputs": [],
"source": [
"height, width, _ = image.shape\n",
"\n",
"image_blob = cv.dnn.blobFromImage(image, scalefactor=1.0, size=(300, 300), mean=[104, 177, 123], \n",
" swapRB=False, crop=False)\n",
"\n",
"model.setInput(image_blob)\n",
"\n",
"detections = model.forward()\n",
"\n",
"image_out = image.copy()\n",
"\n",
"for i in range(detections.shape[2]):\n",
" confidence = detections[0, 0, i, 2]\n",
" if confidence > 0.5:\n",
"\n",
" box = detections[0, 0, i, 3:7] * np.array([width, height, width, height])\n",
" (x1, y1, x2, y2) = box.astype('int')\n",
"\n",
" cv.rectangle(image_out, (x1, y1), (x2, y2), (0, 255, 0), 6)\n",
" label = '{:.3f}'.format(confidence)\n",
" label_size, base_line = cv.getTextSize(label, cv.FONT_HERSHEY_SIMPLEX, 3.0, 1)\n",
" cv.rectangle(image_out, (x1, y1 - label_size[1]), (x1 + label_size[0], y1 + base_line), \n",
" (255, 255, 255), cv.FILLED)\n",
" cv.putText(image_out, label, (x1, y1), cv.FONT_HERSHEY_SIMPLEX, 3.0, (0, 0, 0))\n",
" \n",
"plt.figure(figsize=[12,12])\n",
"plt.imshow(image_out[:,:,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "590841cd",
"metadata": {},
"source": [
"## Punkty charakterystyczne twarzy\n",
"\n",
"W OpenCV jest możliwość wykrywania punktów charakterystycznych twarzy (ang. *facial landmarks*). Użyjemy zaimplementowanego [modelu](http://www.jiansun.org/papers/CVPR14_FaceAlignment.pdf) podczas Google Summer of Code przy użyciu [`createFacemarkLBF()`](https://docs.opencv.org/4.5.3/d4/d48/namespacecv_1_1face.html#a0bec73a729ed878430c2feb9ce65bc2a):"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8534a399",
"metadata": {},
"outputs": [],
"source": [
"!wget -q --show-progress -O dnn/lbfmodel.yaml https://raw.githubusercontent.com/kurnianggoro/GSOC2017/master/data/lbfmodel.yaml"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c2971f10",
"metadata": {},
"outputs": [],
"source": [
"landmark_detector = cv.face.createFacemarkLBF()\n",
"landmark_detector.loadModel('dnn/lbfmodel.yaml')"
]
},
{
"cell_type": "markdown",
"id": "761dbc15",
"metadata": {},
"source": [
"Ograniczamy nasze poszukiwania do twarzy:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "39215601",
"metadata": {},
"outputs": [],
"source": [
"faces = []\n",
"\n",
"for detection in detections[0][0]:\n",
" if detection[2] >= 0.5:\n",
" left = detection[3] * width\n",
" top = detection[4] * height\n",
" right = detection[5] * width\n",
" bottom = detection[6] * height\n",
"\n",
" face_w = right - left\n",
" face_h = bottom - top\n",
"\n",
" face_roi = (left, top, face_w, face_h)\n",
" faces.append(face_roi)\n",
"\n",
"faces = np.array(faces).astype(int)\n",
"\n",
"_, landmarks_list = landmark_detector.fit(image, faces)"
]
},
{
"cell_type": "markdown",
"id": "56aa90c9",
"metadata": {},
"source": [
"Model generuje 68 punktów charakterycznych, które możemy zwizualizować:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6d3ab726",
"metadata": {},
"outputs": [],
"source": [
"image_display = image.copy()\n",
"landmarks = landmarks_list[0][0].astype(int)\n",
"\n",
"for idx, landmark in enumerate(landmarks):\n",
" cv.circle(image_display, landmark, 2, (0,255,255), -1)\n",
" cv.putText(image_display, str(idx), landmark, cv.FONT_HERSHEY_SIMPLEX, 0.35, (0, 255, 0), 1, \n",
" cv.LINE_AA)\n",
"\n",
"plt.figure(figsize=(10,10))\n",
"plt.imshow(image_display[700:1050,500:910,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "7cee8969",
"metadata": {},
"source": [
"Jeśli nie potrzebujemy numeracji, to możemy użyć prostszego podejścia, tj. funkcji [`drawFacemarks()`](https://docs.opencv.org/4.5.3/db/d7c/group__face.html#ga318d9669d5ed4dfc6ab9fae2715310f5):"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1039e253",
"metadata": {},
"outputs": [],
"source": [
"image_display = image.copy()\n",
"for landmarks_set in landmarks_list:\n",
" cv.face.drawFacemarks(image_display, landmarks_set, (0, 255, 0))\n",
"\n",
"plt.figure(figsize=(10,10))\n",
"plt.imshow(image_display[500:1050,500:1610,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "db16a1bf",
"metadata": {},
"source": [
"# Zadanie 1\n",
"\n",
"W katalogu `vid` znajdują się filmy `blinking-*.mp4`. Napisz program do wykrywania mrugnięć. Opcjonalnie możesz użyć *eye aspect ratio* z [tego artykułu](http://vision.fe.uni-lj.si/cvww2016/proceedings/papers/05.pdf) lub zaproponować własne rozwiązanie."
]
}
],
"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": "09. Wykrywanie i rozpoznawanie tekstu [laboratoria]",
"title": "Widzenie komputerowe",
"year": "2021"
},
"nbformat": 4,
"nbformat_minor": 5
}

840
wko-10.ipynb Normal file
View File

@ -0,0 +1,840 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "3c8a4b52",
"metadata": {},
"source": [
"![Logo 1](img/aitech-logotyp-1.jpg)\n",
"<div class=\"alert alert-block alert-info\">\n",
"<h1> Widzenie komputerowe </h1>\n",
"<h2> 10. <i>Metody głębokiego uczenia (2)</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": "783d6d64",
"metadata": {},
"source": [
"W poniższym materiale zobaczymy w jaki sposób korzystać z wytrenowanych modeli sieci neuronowych w zagadnieniach związanych z wykrywaniem wielu obiektów, szacowaniem pozy człowieka, wykrywaniem i rozpoznawaniem tekstu oraz super rozdzielczością.\n",
"\n",
"Uwaga: realizacja poniższych treści będzie wymagała pobrania ok. 700 MB danych.\n",
"\n",
"Na początku załadujmy niezbędne biblioteki:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ef18510f",
"metadata": {},
"outputs": [],
"source": [
"import cv2 as cv\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"%matplotlib inline"
]
},
{
"cell_type": "markdown",
"id": "e45afc56",
"metadata": {},
"source": [
"Pobrane pliki będziemy zapisywać w katalogu `dnn`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7aac31ef",
"metadata": {},
"outputs": [],
"source": [
"!mkdir -p dnn"
]
},
{
"cell_type": "markdown",
"id": "f792eb4f",
"metadata": {},
"source": [
"# Wykrywanie obiektów\n",
"\n",
"## SSD\n",
"\n",
"W poprzednich materiałach korzystaliśmy z [SSD](https://arxiv.org/pdf/1512.02325.pdf) do wykrywania wielu twarzy na zdjęciu. W poniższym przykładzie możemy zobaczyć użycie do wykrywania wielu obiektów - sieć została wytrenowana na zbiorze [Common Objects in Context](https://cocodataset.org/) (COCO). Użyjemy modelu dostępnego dla frameworku [Tensorflow](https://github.com/tensorflow/models/tree/master/research/object_detection) (inne modele możemy znaleźć w [Detection Model Zoo](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/tf1_detection_zoo.md)):"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "aa10b6fa",
"metadata": {},
"outputs": [],
"source": [
"!wget -q --show-progress -O dnn/ssd_mobilenet_v2_coco_2018_03_29.tar.gz http://download.tensorflow.org/models/object_detection/ssd_mobilenet_v2_coco_2018_03_29.tar.gz\n",
"!cd dnn && tar xzf ssd_mobilenet_v2_coco_2018_03_29.tar.gz && rm ssd_mobilenet_v2_coco_2018_03_29.tar.gz"
]
},
{
"cell_type": "markdown",
"id": "99ec1efa",
"metadata": {},
"source": [
"Pobraliśmy model i generujemy konfigurację:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "eac9a8da",
"metadata": {},
"outputs": [],
"source": [
"!wget -q --show-progress -O dnn/ssd_mobilenet_v2_coco_2018_03_29/tf_text_graph_ssd.py https://raw.githubusercontent.com/opencv/opencv/4.5.3/samples/dnn/tf_text_graph_ssd.py\n",
"!wget -q --show-progress -O dnn/ssd_mobilenet_v2_coco_2018_03_29/tf_text_graph_common.py https://raw.githubusercontent.com/opencv/opencv/4.5.3/samples/dnn/tf_text_graph_common.py\n",
"!cd dnn/ssd_mobilenet_v2_coco_2018_03_29 && python3 tf_text_graph_ssd.py --input frozen_inference_graph.pb --output net.pbtxt --config pipeline.config"
]
},
{
"cell_type": "markdown",
"id": "232e2987",
"metadata": {},
"source": [
"Wczytujemy model:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9b4180e5",
"metadata": {},
"outputs": [],
"source": [
"model = cv.dnn.readNetFromTensorflow(\"dnn/ssd_mobilenet_v2_coco_2018_03_29/frozen_inference_graph.pb\",\n",
" \"dnn/ssd_mobilenet_v2_coco_2018_03_29/net.pbtxt\")"
]
},
{
"cell_type": "markdown",
"id": "0bbfd2a4",
"metadata": {},
"source": [
"Pobieramy i wczytujemy etykiety klas obiektów:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "17335a42",
"metadata": {},
"outputs": [],
"source": [
"!wget -q -O - https://raw.githubusercontent.com/tensorflow/models/master/research/object_detection/data/mscoco_complete_label_map.pbtxt | grep display_name | grep -o '\".*\"' | tr -d '\"' > dnn/ssd_mobilenet_v2_coco_2018_03_29/coco-labels.txt"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "662e1a33",
"metadata": {},
"outputs": [],
"source": [
"with open('dnn/ssd_mobilenet_v2_coco_2018_03_29/coco-labels.txt', 'r') as f_fd:\n",
" classes = f_fd.read().splitlines()\n",
" \n",
"print(len(classes), classes[:5])"
]
},
{
"cell_type": "markdown",
"id": "94cace8a",
"metadata": {},
"source": [
"Spróbujemy sprawdzić jakie obiekty znajdują się na poniższym zdjęciu:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "91834aba",
"metadata": {},
"outputs": [],
"source": [
"image = cv.imread('img/messi5.jpg')\n",
"plt.figure(figsize=[7,7])\n",
"plt.imshow(image[:,:,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "43774ae3",
"metadata": {},
"source": [
"Sieć zwraca nam listę obiektów z oznaczeniem współrzędnych na zdjęciu oraz identyfikatorem obiektu (ustawiliśmy próg odcięcia na 0.5):"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "84652c91",
"metadata": {},
"outputs": [],
"source": [
"height, width, _ = image.shape\n",
"\n",
"image_blob = cv.dnn.blobFromImage(image=image, scalefactor=1, size=(300, 300), mean=(0,0,0), \n",
" swapRB=True, crop=False)\n",
"\n",
"model.setInput(image_blob)\n",
"detections = model.forward()\n",
"\n",
"image_out = image.copy()\n",
"\n",
"for i in range(detections.shape[2]):\n",
" confidence = detections[0, 0, i, 2]\n",
" if confidence > 0.5:\n",
"\n",
" box = detections[0, 0, i, 3:7] * np.array([width, height, width, height])\n",
" (x1, y1, x2, y2) = box.astype('int')\n",
" \n",
" class_id = int(detections[0, 0, i, 1])\n",
"\n",
" cv.rectangle(image_out, (x1, y1), (x2, y2), (0, 255, 0), 6)\n",
" label = '{:} ({:.3f})'.format(classes[class_id], confidence)\n",
" label_size, base_line = cv.getTextSize(label, cv.FONT_HERSHEY_SIMPLEX, 0.65, 1)\n",
" cv.rectangle(image_out, (x1, y1 - label_size[1]), (x1 + label_size[0], y1 + base_line), \n",
" (255, 255, 255), cv.FILLED)\n",
" cv.putText(image_out, label, (x1, y1), cv.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 0))\n",
" \n",
"plt.figure(figsize=[12,12])\n",
"plt.imshow(image_out[:,:,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "3fa16e91",
"metadata": {},
"source": [
"## YOLOv4"
]
},
{
"cell_type": "markdown",
"id": "27ce3522",
"metadata": {},
"source": [
"Innym popularnym modelem do wykrywania obiektów jest [You Only Look Once](https://github.com/AlexeyAB/darknet) (YOLO). Porównując YOLO do innych sieci, model ten nie analizuje poszczególnych regionów, ale patrzy na obraz całościowo, co w pewien sposób stanowi balans między szybkością a precyzją. Ze względu na tę cechę model ten dobrze nadaje się do wykrywania obiektów w czasie rzeczywistym. Model powinien dobrze sobie radzić gdy zostanie mu przedstawiona nieznana wcześniej reprezentacja obiektu (np. zacieniony) lub gdy obiekt znajduje się w otoczeniu innych nieoczekiwanych obiektów.\n",
"\n",
"YOLO jest dostępne w kilku wersjach, natomiast my sprawdzimy jak sobie radzi wersja kompaktowa:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4c3e7fb1",
"metadata": {},
"outputs": [],
"source": [
"!mkdir -p dnn/yolo_v4_tiny\n",
"!wget -q --show-progress -O dnn/yolo_v4_tiny/yolov4-tiny.weights https://github.com/AlexeyAB/darknet/releases/download/yolov4/yolov4-tiny.weights\n",
"!wget -q --show-progress -O dnn/yolo_v4_tiny/yolov4-tiny.cfg https://raw.githubusercontent.com/AlexeyAB/darknet/master/cfg/yolov4-tiny.cfg\n",
"!wget -q --show-progress -O dnn/yolo_v4_tiny/coco.names https://raw.githubusercontent.com/AlexeyAB/darknet/master/cfg/coco.names"
]
},
{
"cell_type": "markdown",
"id": "9497b09c",
"metadata": {},
"source": [
"Wczytujemy model:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e8cc6a3a",
"metadata": {},
"outputs": [],
"source": [
"model = cv.dnn.readNetFromDarknet(\"dnn/yolo_v4_tiny/yolov4-tiny.cfg\", \n",
" \"dnn/yolo_v4_tiny/yolov4-tiny.weights\")"
]
},
{
"cell_type": "markdown",
"id": "df331450",
"metadata": {},
"source": [
"Wczytujemy etykiety obiektów:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8f01d354",
"metadata": {},
"outputs": [],
"source": [
"with open('dnn/yolo_v4_tiny/coco.names', 'r') as f_fd:\n",
" classes = f_fd.read().splitlines()\n",
" \n",
"print(len(classes), classes[:5])"
]
},
{
"cell_type": "markdown",
"id": "3fc5e3fc",
"metadata": {},
"source": [
"Przetestujemy działanie na poniższym zdjęciu:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "df65dee0",
"metadata": {},
"outputs": [],
"source": [
"image = cv.imread('img/pedestrians.jpg')\n",
"plt.figure(figsize=[7,7])\n",
"plt.imshow(image[:,:,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "9fbb6325",
"metadata": {},
"source": [
"Podczas korzystania z tego modelu musimy się zmierzyć z kilkoma subtelnościami. Model wykorzystuje framework Darknet, więc musimy wskazać, że chodzi nam o predykcje pochodzące z ostatniej warstwy. Dodatkowo mamy kilka progów odcięcia do zdefiniowania, tj. miarę obiektowości (*objectness*), pewności (*confidence*) oraz tłumienia niemaksymalnego aby ograniczyć występowanie nakładających się na siebie ramek z wykrytymi obiektami (por. [`cv.dnn.NMSBoxes()`](https://docs.opencv.org/4.5.3/d6/d0f/group__dnn.html#ga9d118d70a1659af729d01b10233213ee)). Poniżej mamy wynik działania:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d8450888",
"metadata": {},
"outputs": [],
"source": [
"height, width, _ = image.shape\n",
"\n",
"image_blob = cv.dnn.blobFromImage(image=image, scalefactor=1/255, size=(416, 416), mean=(0,0,0), \n",
" swapRB=True, crop=False)\n",
"\n",
"model.setInput(image_blob)\n",
"detections = model.forward([model.getLayerNames()[i[0] - 1] for i in model.getUnconnectedOutLayers()])\n",
"\n",
"image_out = image.copy()\n",
"\n",
"class_ids = []\n",
"confidences = []\n",
"boxes = []\n",
"\n",
"for out in detections:\n",
" for detection in out:\n",
" if detection[4] > 0.5: # objectness thr.\n",
" scores = detection[5:]\n",
" class_id = np.argmax(scores)\n",
" confidence = scores[class_id]\n",
" if confidence > 0.5: # confidence thr.\n",
" center_x = int(detection[0] * width)\n",
" center_y = int(detection[1] * height)\n",
" b_width = int(detection[2] * width)\n",
" b_height = int(detection[3] * height)\n",
"\n",
" b_left = int(center_x - b_width / 2)\n",
" b_top = int(center_y - b_height / 2)\n",
" class_ids.append(class_id)\n",
" confidences.append(float(confidence))\n",
" boxes.append([b_left, b_top, b_width, b_height])\n",
"\n",
"indices = cv.dnn.NMSBoxes(boxes, confidences, score_threshold=0.5, nms_threshold=0.5)\n",
"for i in indices:\n",
" idx = i[0]\n",
" box = boxes[idx]\n",
" x1 = box[0]\n",
" y1 = box[1]\n",
" x2 = box[0] + box[2]\n",
" y2 = box[1] + box[3]\n",
" cv.rectangle(image_out, (x1, y1), (x2, y2), (0, 255, 0), 6)\n",
" label = '{:} ({:.3f})'.format(classes[class_ids[idx]], confidences[idx])\n",
" \n",
" label_size, base_line = cv.getTextSize(label, cv.FONT_HERSHEY_SIMPLEX, 0.65, 1)\n",
" cv.rectangle(image_out, (x1, y1 - label_size[1]), (x1 + label_size[0], y1 + base_line), \n",
" (255, 255, 255), cv.FILLED)\n",
" cv.putText(image_out, label, (x1, y1), cv.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 0))\n",
" \n",
"plt.figure(figsize=[12,12])\n",
"plt.imshow(image_out[:,:,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "41e32b8e",
"metadata": {},
"source": [
"# Szacowanie pozy człowieka\n",
"\n",
"Kolejnym interesującym zagadnieniem jest szacowanie pozy człowieka (ang. *human pose estimation*) na podstawie zdjęcia. Celem jest tutaj wykrycie charakterystycznych punktów orientacyjnych, które mogą potem zostać wykorzystane np. treningu sportowego, kontroli gestów, korekcji postawy, itp. W tym celu wykorzystamy [OpenPose](https://github.com/CMU-Perceptual-Computing-Lab/openpose)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6a3fedf2",
"metadata": {},
"outputs": [],
"source": [
"!mkdir -p dnn/openpose\n",
"!wget -q --show-progress -O dnn/openpose/pose_iter_160000.caffemodel http://posefs1.perception.cs.cmu.edu/Users/tsimon/Projects/coco/data/models/mpi/pose_iter_160000.caffemodel\n",
"!wget -q --show-progress -O dnn/openpose/pose_deploy_linevec_faster_4_stages.prototxt https://raw.githubusercontent.com/CMU-Perceptual-Computing-Lab/openpose/master/models/pose/mpi/pose_deploy_linevec_faster_4_stages.prototxt"
]
},
{
"cell_type": "markdown",
"id": "851c965b",
"metadata": {},
"source": [
"Wczytujemy model:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d55edcb8",
"metadata": {},
"outputs": [],
"source": [
"model = cv.dnn.readNetFromCaffe(\"dnn/openpose/pose_deploy_linevec_faster_4_stages.prototxt\",\n",
" \"dnn/openpose/pose_iter_160000.caffemodel\")"
]
},
{
"cell_type": "markdown",
"id": "c2c701c3",
"metadata": {},
"source": [
"Będziemy chcieli przeanalizować poniższe zdjęcie:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "aeaed6eb",
"metadata": {},
"outputs": [],
"source": [
"image = cv.imread(\"img/messi5.jpg\")\n",
"plt.figure(figsize=[7,7])\n",
"plt.imshow(image[:,:,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "00a42dd5",
"metadata": {},
"source": [
"Zdefinujemy poniżej połączenia pomiędzy 15 punktami orientacyjnymi:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "894acae5",
"metadata": {},
"outputs": [],
"source": [
"pose_points_n = 15\n",
"pose_pairs = [[0,1], [1,2], [2,3], [3,4], [1,5], [5,6], [6,7], [1,14], [14,8], [8,9], [9,10], [14,11], [11,12], [12,13]]"
]
},
{
"cell_type": "markdown",
"id": "5a8a5028",
"metadata": {},
"source": [
"W wyniku otrzymujemy mapy prawodpodobieństwa występowania danego punktu orientacyjnego:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "24ca95c6",
"metadata": {},
"outputs": [],
"source": [
"height, width, _ = image.shape\n",
"\n",
"image_blob = cv.dnn.blobFromImage(image, 1.0/255, (368, 368), (0, 0, 0), swapRB=False, crop=False)\n",
"model.setInput(image_blob)\n",
"\n",
"output = model.forward()\n",
"\n",
"plt.figure(figsize=(20,3))\n",
"for i in range(pose_points_n):\n",
" prob_map = output[0, i, :, :]\n",
" disp_map = cv.resize(prob_map, (width, height), cv.INTER_LINEAR)\n",
" plt.subplot(2, 8, i+1)\n",
" plt.axis('off')\n",
" plt.imshow(disp_map, cmap='jet', vmin=0, vmax=1)"
]
},
{
"cell_type": "markdown",
"id": "c8be6dc1",
"metadata": {},
"source": [
"Przeskalowujemy wyniki do rozmiarów obrazu wejściowego i przy pomocy [`cv.minMaxLoc()`](https://docs.opencv.org/4.5.3/d2/de8/group__core__array.html#gab473bf2eb6d14ff97e89b355dac20707) znajdujemy wartość maksymalną (dodatkowo sprawdzamy czy wartość prawdopodobieństwa jest odpowiednio duża):"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a3163987",
"metadata": {},
"outputs": [],
"source": [
"scale_x = width / output.shape[3]\n",
"scale_y = height / output.shape[2]\n",
"\n",
"points = []\n",
"\n",
"for i in range(pose_points_n):\n",
" prob_map = output[0, i, :, :]\n",
" \n",
" _, prob, _, point = cv.minMaxLoc(prob_map)\n",
" \n",
" x = scale_x * point[0]\n",
" y = scale_y * point[1]\n",
"\n",
" if prob > 0.1: # thr.\n",
" points.append((int(x), int(y)))\n",
" else:\n",
" points.append(None)"
]
},
{
"cell_type": "markdown",
"id": "f1f8cac3",
"metadata": {},
"source": [
"Możemy teraz nanieść punkty na obraz i połączyć je w szkielet"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fcbda6c6",
"metadata": {},
"outputs": [],
"source": [
"image_points = image.copy()\n",
"image_skeleton = image.copy()\n",
"\n",
"for i, p in enumerate(points):\n",
" cv.circle(image_points, p, 8, (255, 255, 0), thickness=-1, lineType=cv.FILLED)\n",
" cv.putText(image_points, \"{}\".format(i), p, cv.FONT_HERSHEY_SIMPLEX, 1, (0,255,255), 2, lineType=cv.LINE_AA)\n",
"\n",
"\n",
"for pair in pose_pairs:\n",
" part_a = pair[0]\n",
" part_b = pair[1]\n",
"\n",
" if points[part_a] and points[part_b]:\n",
" cv.line(image_skeleton, points[part_a], points[part_b], (0, 255, 255), 4)\n",
" cv.circle(image_skeleton, points[part_a], 7, (255, 255, 0), thickness=-1, lineType=cv.FILLED)\n",
"\n",
"plt.figure(figsize=(20,20))\n",
"plt.subplot(121)\n",
"plt.imshow(image_points[:,:,::-1])\n",
"plt.subplot(122)\n",
"plt.imshow(image_skeleton[:,:,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "ea3421fd",
"metadata": {},
"source": [
"# Wykrywanie i rozpoznawanie tekstu\n",
"\n",
"W kolejnym przykładzie zobaczymy jak możemy wykryć na zdjęciu tekst przy pomocy [DB](https://github.com/MhLiao/DB) oraz rozpoznać go przy pomocy [CRNN](https://arxiv.org/pdf/1507.05717.pdf)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5ef81ed2",
"metadata": {},
"outputs": [],
"source": [
"import gdown\n",
"\n",
"for url, output in [('https://drive.google.com/uc?export=dowload&id=19YWhArrNccaoSza0CfkXlA8im4-lAGsR', 'dnn/DB_TD500_resnet50.onnx'), \n",
" ('https://drive.google.com/uc?export=dowload&id=12diBsVJrS9ZEl6BNUiRp9s0xPALBS7kt', 'dnn/crnn_cs.onnx'),\n",
" ('https://drive.google.com/uc?export=dowload&id=1oKXxXKusquimp7XY1mFvj9nwLzldVgBR', 'dnn/alphabet_94.txt')]:\n",
" gdown.download(url, output, quiet=False)"
]
},
{
"cell_type": "markdown",
"id": "72721bc5",
"metadata": {},
"source": [
"Będziemy pracować na poniższym zdjęciu:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "86e3f889",
"metadata": {},
"outputs": [],
"source": [
"image = cv.imread('img/road-sign.jpg')\n",
"\n",
"plt.figure(figsize=(5,7))\n",
"plt.imshow(image[:,:,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "ec7d3ce4",
"metadata": {},
"source": [
"Wczytujemy obsługiwany alfabet:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5d27f129",
"metadata": {},
"outputs": [],
"source": [
"with open('dnn/alphabet_94.txt', 'r') as f_fd:\n",
" alphabet = f_fd.read().splitlines()\n",
" \n",
"print(len(alphabet), alphabet[:15])"
]
},
{
"cell_type": "markdown",
"id": "d3373c60",
"metadata": {},
"source": [
"OpenCV posiada gotowe API dla sieci DB poprzez [`cv.dnn_TextDetectionModel_DB()`](https://docs.opencv.org/4.5.3/db/d0f/classcv_1_1dnn_1_1TextDetectionModel__DB.html):"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b3c3bfc2",
"metadata": {},
"outputs": [],
"source": [
"text_detector = cv.dnn_TextDetectionModel_DB(\"dnn/DB_TD500_resnet50.onnx\")\n",
"\n",
"text_detector.setBinaryThreshold(0.4).setPolygonThreshold(0.5)\n",
"text_detector.setInputParams(scale=1.0/255, size=(640, 640), \n",
" mean=(122.67891434, 116.66876762, 104.00698793), swapRB=True)"
]
},
{
"cell_type": "markdown",
"id": "31300a5f",
"metadata": {},
"source": [
"W wyniku otrzymujemy ramki, na których występuje tekst (choć jak widzimy, są też wyniki fałszywie pozytywne):"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d14502d4",
"metadata": {},
"outputs": [],
"source": [
"boxes, confs = text_detector.detect(image)\n",
"\n",
"image_out = image.copy()\n",
"\n",
"cv.polylines(image_out, boxes, True, (255, 0, 255), 4)\n",
"\n",
"plt.figure(figsize=(5,7))\n",
"plt.imshow(image_out[:,:,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "3c3eae71",
"metadata": {},
"source": [
"W kolejnym kroku przygotowujemy model do rozpoznawania tekstu przy pomocy [`cv.dnn_TextRecognitionModel()`](https://docs.opencv.org/4.5.3/de/dee/classcv_1_1dnn_1_1TextRecognitionModel.html):"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d6b29f6a",
"metadata": {},
"outputs": [],
"source": [
"text_recognizer = cv.dnn_TextRecognitionModel(\"dnn/crnn_cs.onnx\")\n",
"text_recognizer.setDecodeType(\"CTC-greedy\")\n",
"text_recognizer.setVocabulary(alphabet)\n",
"text_recognizer.setInputParams(scale=1/127.5, size=(100, 32), mean=(127.5, 127.5, 127.5), swapRB=True)"
]
},
{
"cell_type": "markdown",
"id": "a17f6437",
"metadata": {},
"source": [
"Każdą wykrytą ramkę rzutujemy na rozmiar 100x32 i wykrywamy tekst:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d6909f83",
"metadata": {},
"outputs": [],
"source": [
"for box in boxes:\n",
" vertices = np.asarray(box).astype(np.float32)\n",
" output_size = (100, 32)\n",
" target_vertices = np.array([\n",
" [0, output_size[1] - 1],\n",
" [0, 0],\n",
" [output_size[0] - 1, 0],\n",
" [output_size[0] - 1, output_size[1] - 1]],\n",
" dtype=\"float32\")\n",
" rotation_matrix = cv.getPerspectiveTransform(vertices, target_vertices)\n",
" cropped_roi = cv.warpPerspective(image, rotation_matrix, output_size)\n",
" \n",
" result = text_recognizer.recognize(cropped_roi)\n",
" print(result)"
]
},
{
"cell_type": "markdown",
"id": "e0b4b3c0",
"metadata": {},
"source": [
"# Super rozdzielczość\n",
"\n",
"Podczas zwiększania rozdzielczości brakujące piksele muszą być w jakiś sposób interpolowane. Przy niewielkich powiększeniach zwykle wystarczą nam tradycyjne metody, jednak jeśli pracujemy z obrazem w niskiej rozdzielczości i chcemy go znacząco powiększyć, to chcielibyśmy również uzyskać wysoką jakość np. poprzez uwzględnienie informacji z otoczenia pikseli. Problematyka ta dotyczy zagadnienia super rozdzielczości (ang. *super-resolution*).\n",
"\n",
"W [artykule](https://arxiv.org/pdf/1902.06068.pdf) z 2020 r. możemy znaleźć porównanie dostępnych w tamtym czasie modeli (zob. wykres na str. 15); np. możemy zobaczyć, że model [EDSR](https://github.com/Saafke/EDSR_Tensorflow) radzi sobie całkiem nieźle, aczkolwiek kosztem sporego narzutu obliczeniowego (por. również benchmarki [OpenCV](https://github.com/opencv/opencv_contrib/blob/master/modules/dnn_superres/README.md)). Przetestujemy EDSR na powiększeniu 4-krotnym:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6b9f6be9",
"metadata": {},
"outputs": [],
"source": [
"!wget -q --show-progress -O dnn/EDSR_x4.pb https://raw.githubusercontent.com/Saafke/EDSR_Tensorflow/master/models/EDSR_x4.pb"
]
},
{
"cell_type": "markdown",
"id": "92f09e43",
"metadata": {},
"source": [
"Przy pomocy [`cv.dnn_superres.DnnSuperResImpl_create()`](https://docs.opencv.org/4.5.3/d8/d11/classcv_1_1dnn__superres_1_1DnnSuperResImpl.html) przygotowujemy model:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "368ca179",
"metadata": {},
"outputs": [],
"source": [
"sr = cv.dnn_superres.DnnSuperResImpl_create()\n",
"sr.readModel('dnn/EDSR_x4.pb')\n",
"sr.setModel('edsr', 4)"
]
},
{
"cell_type": "markdown",
"id": "9e0169f3",
"metadata": {},
"source": [
"Następnie zwiększy rozdzielczość zadanego obrazu (operacja może zająć trochę czasu):"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "45c89529",
"metadata": {},
"outputs": [],
"source": [
"image = cv.imread('img/parrot.jpg')\n",
"\n",
"image_EDSR = sr.upsample(image)\n",
"\n",
"plt.figure(figsize=(25,25))\n",
"plt.subplot(211)\n",
"plt.imshow(image[:,:,::-1])\n",
"plt.subplot(212)\n",
"plt.imshow(image_EDSR[:,:,::-1]);"
]
},
{
"cell_type": "markdown",
"id": "3c157587",
"metadata": {},
"source": [
"# Zadanie 1\n",
"\n",
"Przy pomocy biblioteki [MediaPipe](https://google.github.io/mediapipe/solutions/selfie_segmentation.html) dokonaj podmień tło w selfie `img/selfie-man.jpg` na `img/selfie-background.jpg` (możesz również odbić obraz w poziomie)."
]
},
{
"cell_type": "markdown",
"id": "d6116f61",
"metadata": {},
"source": [
"![Wynik działania programu](img/selfie-out.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": "10. Metody głębokiego uczenia w widzeniu komputerowym [laboratoria]",
"title": "Widzenie komputerowe",
"year": "2021"
},
"nbformat": 4,
"nbformat_minor": 5
}