s470623-wko/wko-04.ipynb

399 lines
16 KiB
Plaintext
Raw Normal View History

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