s470623-wko/wko-05.ipynb

854 lines
26 KiB
Plaintext
Raw Normal View History

2022-11-27 19:15:49 +01:00
{
"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
}