widzenie-komputerowe-MP/wko-01.ipynb

6.0 MiB
Raw Permalink Blame History

Logo 1

Widzenie komputerowe

01. Wprowadzenie do widzenia komputerowego [laboratoria]

Andrzej Wójtowicz (2021)

Logo 2

Biblioteka OpenCV

Podczas zajęć będziemy poruszali zagadnienia związane z widzeniem komputerowym (ang. _computer vision, CV). Tę tematykę możemy traktować jako rozwinięcie, czy też bardziej zaawansowaną formę prztwarzania obrazów (ang. image processing, IP), gdzie tym razem będziemy starali się wyciągnąć pewną bardziej zaawansowaną wiedzę płynącą z obrazów statycznych lub wideo (cf. dyskusja na Artificial Intelligence Stack Exchange). Przedmiot ma formę laboratoryjną, zatem główną dyskusję dotyczącą zakresu obu dziedzin zostawimy w ramach dodatkowej literatury uzupełniającej.

Standardem dla algorytmów z dziedzin IP/CV jest biblioteka OpenCV, która implementuje wiele z tych algorytmów oraz jest aktywnie rozwijana przez społeczność. Sama biblioteka posiada interfejsy do wielu języków programowania, natomiast my skupimy się na języku Python, który będzie dla nas idealny na potrzeby intensywnego prototypowania. Dokumentację online będzie głównie prowadziła do języka C++, ponieważ nie ma dedykowanej online dla Pythona, ale argumenty funkcji i metod są analogiczne.

Początkowe zajęcia będą głównie dotyczyły zagadnień IP, tak aby zapoznać się z biblioteką OpenCV, a dalsze zajęcia będą już związane z CV.

Instalacja

Materiały do zajęć Jupyter Notebook są tworzone na serwerze JupyterHub z kernelem Python 3. Pominiemy tutaj tworzenie wirtualnego środowiska, jednak należy mieć na uwadze, że poniższe polecenia mogą być też przydatne podczas próby uruchomienia notebooków lub programów na własnym komputerze.

Poniższe polecenie wyświetla używaną wersję Pythona:

import platform
print(platform.python_version())
3.9.2

Niezbędne moduły zainstalujemy poprzez menadżer pip. Sama biblioteka OpenCV, abstrahując od np. paczek debianowych, posiada 4 możliwe opcje instalacji. My zainstalujemy pełną wersję tej biblioteki, a dodatkowo doinstalujemy pakiety związane m.in. wyświetlaniem grafiki oraz uczeniem maszynowym.

!pip3 install --user --disable-pip-version-check opencv-contrib-python==4.5.3.56 numpy scipy matplotlib scikit-learn
Collecting opencv-contrib-python==4.5.3.56
  Downloading opencv_contrib_python-4.5.3.56-cp39-cp39-manylinux2014_x86_64.whl (56.1 MB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 56.1/56.1 MB 13.1 MB/s eta 0:00:0000:0100:01
[?25hRequirement already satisfied: numpy in /usr/lib/python3/dist-packages (1.19.5)
Requirement already satisfied: scipy in /usr/lib/python3/dist-packages (1.6.0)
Requirement already satisfied: matplotlib in /usr/lib/python3/dist-packages (3.3.4)
Requirement already satisfied: scikit-learn in /usr/lib/python3/dist-packages (0.23.2)
Installing collected packages: opencv-contrib-python
Successfully installed opencv-contrib-python-4.5.3.56

Sama biblioteka posiada też własne moduły związane z wyświetlaniem grafiki (moduł _HighGUI) oraz z uczeniem maszynowym (moduł ml), jednak my raczej nie będziemy z nich korzystać podczas zajęć (aczkolwiek mogą być przydatne podczas realizacji projektu).

Wszystkie moduły, algorytmy i zmienne są dostępne z poziomu modułu cv2:

import cv2 as cv

Poniższym poleceniem możemy również sprawdzić wersję zainstalowanej biblioteki:

print(cv.__version__)
4.5.3

Obrazy jako tablice

Obraz możemy traktować jako standardową tablicę NumPy zawierającą dane dotyczące danych pikseli. Im większa liczba pikseli w obrazie, tym większa jest jego rozdzielczość. Intuicyjnie można przyjąć, że piksele są niewielkimi blokami informacji ułożonymi w postaci siatki dwuwymiarowej, a głębokość piksela odnosi się do informacji o kolorze. Aby obraz mógł być przetworzony przez komputer, to taki obraz musi zostać przekonwertowany na postać binarną. Kolor obrazu można obliczyć w następujący sposób:

Liczba kolorów (odcieni) = 2_bpp, gdzie bpp oznacza bity na piksel.

Większa liczba bitów na piksel daje więcej możliwych kolorów na obrazach.

Bity na piksel Liczba kolorów
1 21 = 2
2 22 = 4
3 23 = 8
4 24 = 16
8 28 = 256
16 216 = 65536

Spójrzmy na trzy typowe reprezentacje obrazów.

Obraz czarno-biały

Obraz binarny składa się z 1 bita na piksel, a więc może mieć tylko dwa możliwe kolory, tj. czarny lub biały. Kolor czarny jest reprezentowany przez wartość 0, a 1 oznacza biel (czasem w użyciu są reprezentacje, które mają odwrotne wartości czerni i bieli).

Obraz czarno-biały jako tablica bitów

Obraz w skali odcieni szarości

Obraz w skali szarości składa się z 8 bitów na piksel. Oznacza to, że może mieć 256 różnych odcieni, przy czym 0 pikseli będzie reprezentować kolor czarny, a 255 oznacza biel.

Przykładowy obraz w skali odcieni szarości

Obraz kolorowy

Kolorowe obrazy w standardowej formie są reprezentowane jako połączenie barwy czerwonej, niebieskiej i zielonej - wszystkie inne kolory można uzyskać, mieszając te podstawowe kolory we właściwych proporcjach.

Składowe RGB

Kolorowy obraz składa się również z 8 bitów na piksel, z tym że na taki obraz składają się 3 kanały (czerwony, zielony i niebieski). W rezultacie 256 różnych natężeń danego koloru podstawowego można przedstawić za pomocą 0 oznaczającego najmniej intensywny i 255 najbardziej intensywny. Dla poniższego obrazu pawiana:

Obraz pawiana

podstawowe parametry dotyczące wymiarów prezentują się następująco:

Shape
(288, 289, 3)
288: Pixel height (wysokość w pikselach)
289: Pixel width (szerokość w pikselach)
3: color channel (liczba kanałów)

Taki obraz możemy reprezentować w postaci trójwymiarowej tablicy:

Kanały RGB obrazu pawiana

Wczytywanie obrazów

Przy użyciu funkcji cv.imread() możemy odczytać obraz. Pierwszy argument to lokalizacja pliku. Obraz powinien znajdować się w katalogu roboczym lub powinna zostać podana ścieżka bezwzględna do pliku.

Drugi argument (opcjonalny) jest flagą oznaczającą w jaki sposób obraz powinien zostać wczytany:

Zamiast trzech powyższych flag można alternatywnie przekazać odpowiednio 1, 0 lub -1.

image = cv.imread("img/baboon.png", cv.IMREAD_COLOR)

Sprawdźmy typ zmiennej:

print(type(image))
<class 'numpy.ndarray'>

Sprawdźmy kształt tablicy:

print(image.shape)
(288, 289, 3)

Typ danych tablicy:

print(image.dtype)
uint8

Jak widać, poszczególne piksele na kanałach są 8-bitowymi liczbami całkowitymi bez znaku.

Wyświetlanie obrazów przy pomocy Matplotlib

W notebooku Jupyter najwygodniej wyświetla się obrazy przy pomocy biblioteki Matplotlib, a dokładniej modułu pyplot:

import matplotlib.pyplot as plt
import matplotlib
%matplotlib inline

Przed wyświetleniem obrazu musimy dokonać drobnej konwersji obrazu. OpenCV domyślnie wczytuje obraz w formacie BGR, natomiast Matplotlib pracuje na obrazie w formacie RGB. Do konwersji użyjemy funkcji cv.cvtColor(), której pierwszym argumentem jest konwertowany obraz, a drugim sposób konwersji (w tym wypadku definiowany przez stałą cv.COLOR_BGR2RGB). _Nota bene, czasami lepiej jest operować w przestrzeni barw HSV niż RGB.

image2 = cv.cvtColor(image, cv.COLOR_BGR2RGB)

Po przekonwertowaniu obrazu możemy wyświetlić go przy pomocy pyplot. Użyjemy do tego funkcji matplotlib.pyplot.imshow():

plt.imshow(image2)
<matplotlib.image.AxesImage at 0x7feb8400cf10>

Zwróćmy uwagę, że piksel o współrzędnych (0, 0) znajduje się w lewym górnym rogu.

Można też obejść się bez konwersji i po prostu odwrócić w locie kanały w tablicy numpy:

plt.imshow(image[..., ::-1])
<matplotlib.image.AxesImage at 0x7feb7a6fe8b0>

Sprawdźmy kolejny plik:

x_small = cv.imread("img/x_small.jpg", cv.IMREAD_GRAYSCALE)
plt.imshow(x_small, cmap = 'gray');

Wczytaliśmy obraz w skali odcieni szarości. Podczas wyświetlania obrazu możemy ustawić mapę kolorów (parametr cmap). Dodatkowo, jeśli na końcu polecenia damy średnik (;), to w wynikowej komórce notebooka nie będzie się wyświetlał zwracany typ.

Czasami warto również wyświetlić pasek z informacją o tym jaka wartość odpowiada za dany odcień:

plt.imshow(x_small, cmap = 'gray');
plt.colorbar();

Proste operacje

Możemy sprawdzić, że obraz jest w istocie tablicą NumPy o wartościach w zakresie od 0 do 255:

print(x_small)
[[  0   0   0   0   0   0   1  10   6   1]
 [  0   9  71  39   4   2  33 100  54   8]
 [  1  70 198 173  71  58 158 202 133  25]
 [  1  25 150 208 201 203 221 172  57   3]
 [  0   2  43 186 243 245 187  53   4   0]
 [  0   4  90 207 231 236 189  87   9   0]
 [  0  37 160 210 137 156 209 195  63   2]
 [  0  28 112 114  23  28 128 185  71   3]
 [  0   4  25  15   2   1  13  58  10   0]
 [  0   0   0   0   0   0   0   0   0   0]]

Wracając do samej biblioteki Matplotlib, możemy również np. sterować rozmiarem wyświetlanego obrazu oraz nadać mu tytuł:

plt.figure(figsize = [6, 6])
plt.imshow(x_small, cmap = 'gray')
plt.title('Small X', fontsize = 20);

Niektóre parametry Matlplotlib (np. figsize) możemy ustawić na domyślne przy pomocy matplotlib.rcParams.

Podczas wczytywania obrazów zawierających przezroczystość należy zwrócić uwagę na sposób wczytywania obrazu:

img_lfl = cv.imread("img/linux_foundation.png", cv.IMREAD_COLOR)
plt.imshow(img_lfl);

Mamy tutaj obraz 4-kanałowy, przez co musimy go wczytać jako cv.IMREAD_UNCHANGED, no i dodatkowo odpowiednio go przekonwertować:

img_lfl = cv.imread("img/linux_foundation.png", cv.IMREAD_UNCHANGED)
plt.imshow(cv.cvtColor(img_lfl, cv.COLOR_BGRA2RGBA));

Możemy również podzielić obraz na poszczególne kanały i ew. ponownie je połączyć, odpowiednio przy pomocy funkcji cv.split() i cv.merge():

b, g, r = cv.split(image)
plt.imshow(b, cmap = 'gray')
plt.title('Blue channel', fontsize = 20);
restored_image = cv.merge((r,g,b))
plt.imshow(restored_image);

Dostęp do poszczególnych pikseli odbywa się tak jak w macierzy, czyli podając wiersz i kolumnę. Poniżej możemy zobaczyć zmianę piksela na współrzędnych (1, 1):

plt.imshow(x_small, cmap = 'gray');
x_small[1, 1] = 255
plt.imshow(x_small, cmap = 'gray');

Przycinanie odbywa się przez znany w Pythonie tzw. _slicing, czyli wycinki:

plt.imshow(x_small[3:8, 2:], cmap = 'gray');

Możemy również ustawić kolor dla kilku kanałów jednocześnie dla zadanego regionu. Wykonamy to na kopii obrazu:

image_edited = image.copy()
image_edited[100:200, 150:250] = (128, 0, 128)
plt.imshow(image_edited[..., ::-1]);

Przeskalowanie obrazu odbywa się przez cv.resize(), w której albo podajemy dokładne docelowe wymiary, albo podajemy współczynniki skalowania na osiach _x i y; możemy też uwzględnić odpowiednią metodę interpolacji. Matplotlib nie wyświetli nam wprost powiększonych obrazków, ale będziemy mogli zauważyć zmianę poprzez np. poprzez zmianę zakresów skali osi x i y:

x_small_resized_1 = cv.resize(x_small, (40, 40))
plt.imshow(x_small_resized_1, cmap='gray');
x_small_resized_2 = cv.resize(x_small, None, fx=3, fy=4, interpolation=cv.INTER_CUBIC)
plt.imshow(x_small_resized_2, cmap='gray');

Obrót obrazu dokonywany jest przez funkcję cv.flip(), w której podajemy według której osi ma nastąpić obrót (0: _x, 1: y, -1: obie).

image_flipped = cv.flip(image, 0)
plt.imshow(image_flipped[..., ::-1]);

Może pojawić się potrzeba utworzenia nowych obrazów, więc najczęściej używa się do tego biblioteki NumPy:

import numpy as np

Poniższe wywołania pokazują kilka wariantów. Możemy utworzyć pustą trójwymiarową macierz wypełnioną zerami przy pomocy numpy.zeros():

image_empty = np.zeros((50, 100, 3), dtype='uint8')
plt.imshow(image_empty);

Jeżeli chcemy ustawić jakąś początkową wartość, to możemy przeskalować tablicę jedynek uzyskaną z numpy.ones():

image_empty = 128*np.ones((50, 100, 3), dtype='uint8')
plt.imshow(image_empty)
<matplotlib.image.AxesImage at 0x7feb7a4f0970>

Jeżeli nowy obraz powinien mieć takie same wymiary jak inny obraz, to możemy do tej operacji użyć numpy.ones_like():

empty_from_other = 255*np.ones_like(image)
plt.imshow(empty_from_other);

Czasami może być przydatne utworzenie maski zawierającej informację o tym czy np. dany piksel zawiera wartość w danym zakresie. Przy pomocy cv.inRange() możemy sprawdzić zakresy dla trzech kanałów; przy okazji możemy zobaczyć jak zgrupować kilka obrazów jednocześnie (por. matplotlib.pyplot.subplot()):

mask = cv.inRange(image, (0,100,150), (100,150,200))

plt.figure(figsize=[10,10])
plt.subplot(121)
plt.imshow(image[..., ::-1])
plt.title("Baboon")
plt.subplot(122)
plt.imshow(mask, cmap='gray')
plt.title("Mask");

Standardowo operujemy w zakresie liczb całkowitych [0; 255], przez co jeśli w wyniku jakiejś operacji mielibyśmy wykroczyć poza zakres, to skończy się to albo operacją modulo albo np. uzyskaniem wartości ujemnych, co ostatecznie przełoży się na błędny wynik.

Załóżmy, że chcemy podbić kontrast o 50%. Przemnożenie obrazu o wartość 1.5 zwróci nam tablicę o wartościach liczb rzeczywistych:

new_image = image * 1.5
print(new_image.dtype)
float64

W takim wypadku możemy naiwnie spróbować przekonwertować to ponownie do liczb całkowitych, ale wartości powyżej 255 zostaną obliczone według operacji modulo, przez co wynik będzie niezadowalający:

new_image = np.uint8(new_image)
plt.imshow(new_image[..., ::-1]);

Rozwiązaniem jest przycięcie wartości przy użyciu numpy.clip() do zakresu [0; 255] jeśli operujemy na liczbach całkowitych, z ew. znormalizowaniem wartości do zakresu [0; 1] jeśli chcemy pracować na liczbach rzeczywistych:

new_image = image * 1.5
clipped_new_image = np.clip(new_image, 0, 255)
clipped_new_image = np.uint8(clipped_new_image)
plt.imshow(clipped_new_image[..., ::-1]);
new_image = (image * 1.5)/255
clipped_new_image = np.clip(new_image, 0, 1)
plt.imshow(clipped_new_image[..., ::-1]);

Jeśli zwiększymy jasność i potencjalnie wyjdziemy poza zakres [0; 255], to ponownie wartości zostaną przekręcone:

new_image = image + 50
plt.imshow(new_image[..., ::-1]);

Z drugiej strony należy mieć też na uwadze pewne subtelne konwersje, które mogą odbywać się niejawnie. Poniżej mamy operację, która powoduje użycie danych typu int16, przez co następuje niejawna konwersja i przycięcie (jest o tym informacja w ostrzeżeniu):

new_image = image + (-50)
plt.imshow(new_image[..., ::-1]);
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).

Rozwiązaniem powyższych problemów może być w przypadku zmiany jasności użycie funkcji cv.add() i cv.substract(), a w przypadku zmiany kontrastu użycie cv.multiply(), konwersja do typu np. float64 i powrót do uint8 z przycięciem wartości.

matrix = np.ones(image.shape, dtype='uint8') * 50

image_brighter = cv.add(image, matrix)
plt.imshow(image_brighter[:, :, ::-1]);
image_darker   = cv.subtract(image, matrix)
plt.imshow(image_darker[:, :, ::-1]);
matrix = np.ones(image.shape, dtype = 'float64')

image_lower   = np.uint8(cv.multiply(np.float64(image), matrix, scale = 0.8))
plt.imshow(image_lower[:, :, ::-1]);
image_higher  = np.uint8(np.clip(cv.multiply(np.float64(image), matrix, scale = 1.2) , 0, 255))
plt.imshow(image_higher[:, :, ::-1]);

Zapisywanie obrazów

Funkcja cv.imwrite() zapisuje obraz do pliku. Pierwszy argument to nazwa pliku, drugi argument to obraz, który chcemy zapisać. Np. poniższe polecenie spowodowałoby zapisanie obrazu w formacie PNG w katalogu roboczym:

cv.imwrite('pawian.png', image)

Anotowanie

Sprawdźmy w jaki sposób do pustego obrazu dodać kilka obiektów geometrycznych. Użyjemy do tego funkcji cv.line(), cv.circle() , cv.rectangle(), cv.ellipse(), cv.polylines() i cv.putText(). Funkcje te będą nadpisywały wejściowy obraz.

W powyższych funkcjach pojawiają się wspólne argumenty:

  • img - obraz, na którym będziemy umieszczać obiekty,
  • color - kolor obiektu podany w formacie jako krotka (_tuple), np. (255, 0, 0); w przypadku obrazów w skali odcieni szarości wystarczy podać wartość skalarną,
  • thickness - grubość linii, okręgu, itp.; w przypadku wartości -1 w przypadku zamkniętych figur jak okrąg, obiekt zostanie wypełniony wewnątrz wskazanym kolorem; domyślna wartość to 1,
  • lineType : rodzaj linii, np. 8-sąsiedztwo, antyaliasing, itp.; domyślnie jest 8-sąsiedztwo, natomiast wartość cv.LINE_AA włącza antyaliasing.

Na początku zaimportujemy bibliotekę NumPy i przy pomocy funkcji numpy.zeros() utworzymy pusty 3-warstwowy czarny obraz o rozmiarze 512x512.

import numpy as np

img = np.zeros((512,512,3), np.uint8)

plt.imshow(img)
<matplotlib.image.AxesImage at 0x7feb7a67b460>

W dalszej części operowali na obrazie w formacie RGB.

Linie

Aby narysować linię, musimy podać początkowe i końcowe współrzędne linii. Przy pomocy cv.line() narysujemy na obrazie czerwoną linię od lewego górnego rogu do prawego dolnego rogu o grubości 5 pikseli.

cv.line(img, (0,0), (511,511), (255,0,0), 5)

plt.imshow(img)
<matplotlib.image.AxesImage at 0x7feb7a443b20>

Prostokąty

Aby narysować prostokąt, potrzebujemy lewego górnego rogu i prawego dolnego rogu prostokąta. Tym razem przy pomocy cv.rectangle() narysujemy zielony prostokąt w prawym górnym rogu obrazu.

cv.rectangle(img, (384,0), (510,128), (0,255,0), 3)

plt.imshow(img)
<matplotlib.image.AxesImage at 0x7feb7a2f18b0>

Okręgi

Aby narysować okrąg, potrzebujemy jego współrzędnych środka i promienia. Przy pomocy cv.circle() narysujemy okrąg wewnątrz prostokąta narysowanego powyżej.

cv.circle(img, (447,63), 63, (0,0,255), -1)

plt.imshow(img)
<matplotlib.image.AxesImage at 0x7feb7a688910>

Elipsy

Aby narysować elipsę, musimy przekazać kilka argumentów. Jednym argumentem jest położenie środka (x, y). Następnym argumentem jest długość dwóch osi (długość głównej osi i mniejsza długość osi). Kolejny parametr to kąt obrotu elipsy w kierunku przeciwnym do ruchu wskazówek zegara. Kolejne dwa argumenty oznaczają początek i koniec łuku elipsy mierzonego zgodnie z ruchem wskazówek zegara od głównej osi. tj. podanie wartości 0 i 360 daje pełną elipsę. Aby uzyskać więcej informacji, sprawdź dokumentację cv.ellipse(). Poniższy kod rysuje pół elipsy na środku obrazu.

cv.ellipse(img, (256,256), (100,50), 0, 0, 180, 255, -1)

plt.imshow(img)
<matplotlib.image.AxesImage at 0x7feb7a453eb0>

Poligony

Aby narysować wielokąt, najpierw potrzebujemy współrzędnych wierzchołków. Ustawmy te punkty w tablicy o wymiarach liczba_wierszy x 1 x 2, gdzie liczba_wierszy to liczba wierzchołków i powinna być typu int32. Tutaj narysujemy mały wielokąt z czterema wierzchołkami w kolorze jasnoniebieskim.

pts = np.array([[10,5], [20,30], [70,20], [50,10]], np.int32)
pts = pts.reshape((-1, 1, 2))
cv.polylines(img, [pts], True, (0,255,255))

plt.imshow(img)
<matplotlib.image.AxesImage at 0x7feb7a49ab20>

Wielokąt może wydawać się przerywany, ale jeśli powiększylibyśmy wynikowy obraz, to byśmy zobaczyli, że jest on zamknięty.

Jeśli trzecim argumentem cv.polylines() jest False, otrzymamy niedomknięty poligon.

Funkcja cv.polylines() może być używana do rysowania wielu linii. Wystarczy utworzyć listę wszystkich linii, które chcemy narysować i przekazać je do funkcji. Wszystkie linie zostaną narysowane osobno. Jest to znacznie szybszy sposób na narysowanie grupy linii niż wywołanie osobno cv.line() dla każdej linii.

Tekst

Aby umieścić tekst na obrazie, musimy określić następujące rzeczy:

  • dane tekstowe, które chcemy napisać,
  • współrzędne miejsca, w którym chcemy umieścić tekst (np. lewy dolny róg, w którym zacznie się tekst),
  • typ czcionki (sprawdź dokumentację cv.putText() dla obsługiwanych czcionek),
  • rozmiar czcionki,
  • pozostałe rzeczy takie jak kolor, grubość, typ linii, itp.; dla uzyskania lepszego wynikowego wyglądu zaleca się lineType = cv.LINE_AA.

Na naszym obrazie umieścimy tekst w kolorze białym.

cv.putText(img, 'OpenCV', (10,500), cv.FONT_HERSHEY_SIMPLEX, 4, (255,255,255), 2, cv.LINE_AA)

plt.imshow(img)
<matplotlib.image.AxesImage at 0x7feb7a56fb50>

Czasami potrzebujemy jednak dokładnych informacji o wynikowym tekście, tak aby np. odpowiednio go umiejscowić lub umieścić pod nim kontrastowe tło. Do uzyskania tych informacji pomocne są funkcje cv.getTextSize() i cv.getFontScaleFromHeight().

Wyświetlanie obrazów poza Jupyterem

HighGUI

W OpenCV obraz standardowo wyświetla się przy pomocy modułu HighGUI. Przy użyciu funkcji cv.imshow() możemy wyświetlić obraz w oknie. Okno automatycznie dopasowuje się do rozmiaru obrazu.

Pierwszy argument to nazwa okna. Drugi argument to nasz obraz. Możemy utworzyć dowolną liczbę okien, ale z różnymi nazwami okien.

cv.imshow('image', image)
cv.waitKey(0)

Pod Windowsem wyświetli nam się wskazany przez nas obraz, natomiast pod Linuxem mechanizm HighGUI przy wyświetlaniu obrazu udostępnia trochę więcej opcji i informacji o wyświetlanym obrazie.

Obraz pawiana wyświetlony przy pomocy HighGUI

Zwróćmy uwagę, że obraz pojawia się dopiero po poleceniu cv.waitKey(0) oraz będzie oczekiwał na wciśnięcie klawisza.

cv.waitKey() jest funkcją związaną z klawiaturą. Jej argumentem jest czas w milisekundach. Funkcja czeka przez określony czas na każde zdarzenie klawiatury. Jeśli w tym czasie zostanie naciśnięty dowolny klawisz, program przejdzie do dalszych operacji. Jeśli jako argument zostanie przekazane 0, to funkcja czeka bez końca na uderzenie klawisza klawiatury. Można również ustawić wykrywanie konkretnych klawiszy, takich jak naciśnięcie klawisza <A>, itp. Poza przetwarzaniem zdarzeń klawiatury, ta funkcja przetwarza również wiele innych zdarzeń GUI, więc musisz użyć jej przy wyświetleniu obrazu.

Klikając na obraz i wciskając <Enter>, konsola zwróci informację liczbową o wciśniętym klawiszu, a ponadto obsługa programu wróci z powrotem do konsoli. Wydając poniższe polecenie usuniemy wszystkie okna:

cv.destroyAllWindows()

cv.destroyAllWindows() usuwa wszystkie utworzone przez nas okna, a jeśli chcemy usunąć konkretne okno, należy użyć funkcji cv.destroyWindow(), w której jako argument podajemy dokładną nazwę okna.

Istnieje specjalny przypadek, w którym możemy utworzyć okno i później załadować do niego obraz. W takim przypadku określamy czy rozmiar okna jest zmienny, czy nie. Robi się to za pomocą funkcji cv.namedWindow(). Domyślna flaga to cv.WINDOW_AUTOSIZE, ale jeśli określimy flagę jako cv.WINDOW_NORMAL, to możemy zmienić rozmiar okna. Będzie to pomocne, gdy obraz jest zbyt duży:

cv.namedWindow('image', cv.WINDOW_NORMAL)
cv.imshow('image', image)
cv.waitKey(0)
cv.destroyAllWindows()

Interfejs HighGUI czasami pomaga w debugowaniu, ale trzeba mieć też świadomość, że nie jest on specjalnie rozbudowany i zasadniczo nie służy do budowania zaawansowanych interfejsów graficznych. HighGUI obsługuje sygnały idące nie tylko z klawiatury, ale również myszy, a ponadto pozawala na umieszczenie np. sliderów. Krótkie przykłady znajdują się w samouczkach opisujących cv.setMouseCallback() oraz cv.getTrackbarPos() i cv.createTrackbar().

Matplotlib

Po przekonwertowaniu obrazu do formatu RGB wyświetlamy go przy pomocy pyplot. Używamy przy tym funkcji matplotlib.pyplot.imshow() oraz matplotlib.pyplot.show():

plt.imshow(image2)
plt.show()

Funkcja matplotlib.pyplot.show() posiada opcjonalny argument block (domyślna wartość True), która kontroluje czy funkcja jest blokująca:

plt.imshow(image2)
plt.show(False)

lub krócej:

plt.imshow(image2)
plt.show(0)

Uwaga: jeżeli w skrypcie ustawimy argument block na False, to po uruchomieniu skryptu najprawdopodobniej nie zobaczymy wynikowego obrazu/diagramu, ponieważ skrypt zacznie wykonywać dalsze polecenia i np. zakończy działanie programu. Ustawienie argumentu block na False najlepiej sprawdza się podczas pracy/eksperymentowania w konsoli interpretera Pythona.

Moduły

Lista modułów OpenCV jest dość długa i zawiera pakiety algorytmów m.in. dla przetwarzania obrazów, uczenia maszynowego, głębokich sieci neuronowych, fotografii obliczeniowej, dedykowane rozwiązania dla CUDA, przepływ optyczny, operacje dla obrazów w logice rozmytej, przetwarzania RGB-D i wiele, wiele innych. Z poziomu Pythona dostęp do poszczególnych algorytmów odbywa się bezpośrednio przez moduł cv2 (u nas alias cv). Część klas i funkcji ma przedrostek oznaczający moduł, np. cv.bgsegm_BackgroundSubtractorCNT(...) oznacza klasę BackgroundSubtractorCNT z modułu bgsegm zawierającym udoskonalone metody segmentacji pomiędzy tłem a pierwszym planem.

Ćwiczenie 1

Wczytaj plik img/soyjaks.jpg i spróbuj odtworzyć poniższy obrazek.

# miejsce na eksperymenty

Two Soyjaks Pointing

Ćwiczenie 2

Załaduj obrazy img/pipe.png oraz img/man-without-pipe.png i wykonaj operacje tak, aby uzyskać poniższy obraz.

# miejsce na eksperymenty

Człowiek z fajką

Co dalej?

Przygotuj środowisko pracy instalując wygodne dla Ciebie IDE, np. PyCharm (są darmowe studenckie licencje), Visual Studio z dodatkiem Python Tools lub Visual Studio Code. Sprawdź czy możesz zaimportować OpenCV, ew. stwórz wirtualne środowisko i doinstaluj niezbędne paczki.