Python plug-in for GIMP doing morphological operations such as white top-hat and black top-hat on the chosen photo.
2. Cel projektu
4. Funkcje
Na wstępie chciałbym wytłumaczyć po krótce terminologię oraz techniczny aspekt projektu, których w dalszej części dokumentu nie będę już poruszać.
Wtyczka Top-Hat jest rozszerzeniem funkcjonalności programu GIMP i operuje, jak sama nazwa wskazuje, na transformacji Top-Hat. Transformacja ta w morfologii matematycznej i wizji komputerowej jest dzialaniem mającym na celu wyciągnięcie, tudzież wyeksponowanie/wyekstraktowanie szczegółów i detali na danym zdjęciu.
Transformację Top-Hat dzielimy na:
- White Top-Hat (dalej WTH), służący do wyeksponowania elementów jaśniejszych, niż ich otoczenie (sąsiedztwo), np. gwiazdy na nocnym niebie, światła pojazdów poruszających się po zmroku;
- Black Top-Hat (dalej BTH), służący do wyeksponowania elementów ciemniejszych, niż ich otoczenie (sąsiedztwo), np. plamy na Słońcu, kratery na Księżycu, tętnice i żyły po podaniu pacjentowi kontrastu.
Obie transformacje są bardzo zbliżone, mimo, że odwrotne w skutkach. Efekt końcowy WTH uzyskuje się jako różnica wartości pikseli (od 0 do 255) zdjęcia wejściowego (oryginalnego) i jego "otwarcia", natomiast efekt BTH to różnica pikseli między "zamknięciem", a zdjęciem wejściowym (oryginalnym). Wspomniane "otwarcie" to określenie używane do opisania efektu wykonania kolejno po sobie operacji morfologicznej erozji i dylatacji, a "zamknięcie" to wykonanie kolejno dylatacji i erozji. Dylatacja (inaczej rozszerzenie) polega na przechodzeniu po pikselach i przypisywaniu im najwyższej wartości, jaka występuje w ich sąsiedztwie. Erozja (inaczej zwężanie) polega na przechodzeniu po pikselach i przypisywaniu im najmniejszej wartości, występującej w ich sąsiedztwie. Sąsiedztwo piksela definiuje element strukturalny, przykładowo sąsiedztwo czterospójne (von Neumanna) mówi, że sąsiadami piksela o współrzędnych (x, y) są piksele (x-1, y), (x+1, y), (x, y-1), (x, y+1). W poniższym projekcie wykorzystane zostało sąsiedztwo ośmiospójne (Moore'a), dodające do sąsiedztwa piksele (x-1, y-1), (x+1, y-1), (x-1, y+1), (x+1, y+1).
Jaki wpływ ma kształt i rozmiar elementu strukturalnego na końcowy efekt? Im większy element strukturalny, tym większe odchylenia od normy można wyekstraktować - zwracane końcowo elementy, są zawsze mniejsze od elementru strukturalnego.
Celem projektu była implementacja wtyczki do programu GIMP rozszerzającej funkcjonalność o filtry wykorzystujące transformacje Top-Hat z wykorzystaniem operatorów morfologii matematycznej, takich jak erozja, dylatacja, otwarcie i zamknięcie.
Podstawowy przebieg scenariusza:
- Przejść do folderu plug-ins (ścieżka to C:\Users<nazwa>\AppData\Roaming\GIMP\2.10\plug-ins)
- Przenieść do folderu pliki "black_tophat.py" oraz "white_tophat.py"
- Po uruchomieniu programu GIMP będzie pokazane między innymi ładowanie zainstalowanych wtyczek
Alternatywny przebieg scenariusza – Nie wyświetla się folder AppData:
- Kliknąć zakładkę „Widok” w eksploratorze plików
- Kliknąć w kategorię „Pokazywanie/ukrywanie”
- Zaznaczyć opcję „Ukryte elementy”
- Przejść do folderu plug-ins (ścieżka to C:\Users<nazwa>\AppData\Roaming\GIMP\2.10\plug-ins)
- Przenieść do niego pliki „black tophat.py” oraz „white tophat.py”
- Po uruchomieniu programu GIMP będzie pokazane między innymi ładowanie zainstalowanych wtyczek
Podstawowy przebieg scenariusza:
- Uruchomić program GIMP
- Załadować zdjęcie
- Kliknąć zakładkę „Filters”
- Najechać na kategorię Top-Hat
- Kliknąć na daną wtyczkę
- Wtyczka zostanie uruchomiona, a efekt jej działania można zobaczyć w oknie „Layers”
Alternatywny przebieg scenariusza – Wtyczki nie można kliknąć:
- Kliknąć prawym przyciskiem myszy na załadowanym zdjęciu
- Wybrać kategorię „Image”
- Wybrać kategorię „Mode”
- Zaznaczyć opcję „RGB”
- Kliknąć zakładkę „Filters”
- Najechać na kategorię Top-Hat
- Kliknąć na daną wtyczkę
- Wtyczka zostanie uruchomiona, a efekt jej działania można zobaczyć w oknie „Layers”
Podstawowy przebieg scenariusza:
- Dla przetestowania wtyczki White Top-Hat załadować obraz „test whitehat”, a dla przetestowania wtyczki Black Top-Hat załadować obraz „test blackhat”
- Uruchomić wtyczkę, dla której wybrano obraz testowy
- W oknie „Layers” ostatnia nadrzędna warstwa jest rezultatem. Po użyciu wtyczki White Top-Hat, na obrazie zostaną podkreślone wszystkie jaśniejsze od swojego otoczenia zbiory pikseli, które „nie wypełniły” elementu strukturalnego (kwadrat 3 piksele x 3 piksele). Po użyciu wtyczki Black Top-Hat, na obrazie zostaną uwidocznione (zaznaczone białymi pikselami) wszystkie ciemniejsze od swojego otoczenia zbiory pikseli, które „nie wypełniły” elementu strukturalnego (kwadrat 3 piksele x 3 piksele).
Adnotacja: rezultaty zdjęć są identyczne, gdyż obrazy testowe mają odwrotne wartości pikseli względem siebie i finalnie, kiedy White Top-Hat zaznacza na biało regiony jasne, a Black Top-Hat zaznacza na biało regiony ciemne, otrzymujemy ten sam obraz wynikowy. Należy zwrócić uwagę na zniknięcie dwóch regionów w kształcie kwadratu (o wymiarach 3 piksele na 3 piksele) na lewej połowie obrazów. Dzieje się tak, gdyż regiony te w całości „wypełniły” element strukturalny (cały kwadrat był biały na obrazie testowym Whitehat, lub czarny na obrazie Blackhat). Nie jest to matematyczny opis zachodzących operacji, ale pozwala zrozumieć zasadę działania transformacji.
Główna funkcja, z której wywoływane są pozostałe funkcje potrzebne do przeprowadzenia transformacji black top-hat. Argumentami tej funkcji są:
- image - obiekt będący zdjęciem wejściowym,
- layer - warstwa podstawowa, na której jest oryginalne zdjęcie,
- bytes_pp - zmienna reprezentująca wartość bajtów pamięci potrzebnych do alokacji danych jednego piksela (dla zdjęcia RGB są to 3 bajty, dla zdjęcia w odcieniach szarości jest to 1 bajt, wartość domyślna ustawiona na 3 bajty),
- source_pixels - macierz pikseli zdjęcia wejściowego/oryginalnego w reprezentacji dwuwymiarowej tablicy.
Główna funkcja, z której wywoływane są pozostałe funkcje potrzebne do przeprowadzenia transformacji white top-hat. Argumentami tej funkcji są:
- image - obiekt będący zdjęciem wejściowym,
- layer - warstwa podstawowa, na której jest oryginalne zdjęcie,
- bytes_pp - zmienna reprezentująca wartość bajtów pamięci potrzebnych do alokacji danych jednego piksela (dla zdjęcia RGB są to 3 bajty, dla zdjęcia w odcieniach szarości jest to 1 bajt, wartość domyślna ustawiona na 3 bajty),
- source_pixels - macierz pikseli zdjęcia wejściowego/oryginalnego w reprezentacji dwuwymiarowej tablicy.
Funkcja zwracająca nową macierz pikseli zdjęcia w odcieniach szarości. Funkcja konwertuje macierz pikseli oryginalnego zdjęcia na macierz pikseli zdjęcia w odcieniach szarości, wykorzystując do tego algorytm nadający wagę 0.3 kolorowi czerwonemu, 0.59 kolorowi zielonemu i 0.11 kolorowi niebieskiemu. Z braku możliwości przekonwertowania zdjęcia RGB na Greyscale w trakcie działania wtyczki, optymalizując zajęcie pamięci (piksel w formacie RGB zajmuje 3 bajty, a w Greyscale 1 bajt), zdjęcie po konwersji na skalę szarości nadal posiada piksele zapisane na 3 bajtach, z których każdy bajt posiada tę samą wartość (wynikającą z sum iloczynów wag i kolorów). Na koniec macierz zostaje skopiowana do nowej warstwy reprezentującej pierwszy etap obróbki zdjęcia. Argumentami tej funkcji są:
- img - obiekt będący zdjęciem wejściowym,
- layer - warstwa podstawowa, na której jest oryginalne zdjęcie,
- bytes_per_pixel - zmienna reprezentująca wartość bajtów pamięci potrzebnych do alokacji danych jednego piksela (dla zdjęcia RGB są to 3 bajty, dla zdjęcia w odcieniach szarości jest to 1 bajt, wartość domyślna ustawiona na 3 bajty),
- source_pixels - macierz pikseli zdjęcia wejściowego/oryginalnego w reprezentacji dwuwymiarowej tablicy.
Funkcja zwracająca nową macierz pikseli zdjęcia po wykonaniu operacji dylatacji. Funkcja konwertuje macierz pikseli zdjęcia w odcieniach szarości na macierz pikseli zdjęcia po przeprowadzeniu dylatacji, z wykorzystaniem elementru strukturalnego o sąsiedztwie ośmiospójnym. Zarówno zdjęcie przed jak i po operacji dylatacji posiada piksele zapisane na 3 bajtach, z których każdy bajt piksela posiada tę samą wartość skali szarości (reprezentacja skali szarości na macierzy RGB pikseli). Na koniec, macierz zostaje skopiowana do nowej warstwy reprezentującej kolejny etap obróbki zdjęcia. Argumentami tej funkcji są:
- img - obiekt będący zdjęciem wejściowym,
- layer - warstwa podstawowa, na której jest oryginalne zdjęcie,
- bytes_per_pixel - zmienna reprezentująca wartość bajtów pamięci potrzebnych do alokacji danych jednego piksela (dla zdjęcia RGB są to 3 bajty, dla zdjęcia w odcieniach szarości jest to 1 bajt, wartość domyślna ustawiona na 3 bajty),
- greyscale_pixels - macierz wejściowa pikseli zdjęcia w skali szarości.
Funkcja zwracająca nową macierz pikseli zdjęcia po wykonaniu operacji erozji. Funkcja konwertuje macierz pikseli zdjęcia po operacji dylatacji na macierz pikseli zdjęcia po przeprowadzeniu erozji, z wykorzystaniem elementru strukturalnego o sąsiedztwie ośmiospójnym. Zarówno zdjęcie przed jak i po operacji erozji posiada piksele zapisane na 3 bajtach, z których każdy bajt piksela posiada tę samą wartość skali szarości (reprezentacja skali szarości na macierzy RGB pikseli). Na koniec, macierz zostaje skopiowana do nowej warstwy reprezentującej kolejny etap obróbki zdjęcia. Argumentami tej funkcji są:
- img - obiekt będący zdjęciem wejściowym,
- layer - warstwa podstawowa, na której jest oryginalne zdjęcie,
- bytes_per_pixel - zmienna reprezentująca wartość bajtów pamięci potrzebnych do alokacji danych jednego piksela (dla zdjęcia RGB są to 3 bajty, dla zdjęcia w odcieniach szarości jest to 1 bajt, wartość domyślna ustawiona na 3 bajty),
- dilatation_pixels - macierz wejściowa pikseli zdjęcia po przeprowadzeniu operacji dylatacji.
Funkcja przyjmująca po przecinku kolejne informacje o metadanych wtyczki. Kolejność wprowadzanych argumentów powinna być następująca: nazwa programowa wtyczki, nazwa wtyczki widziana po najechaniu na nią kursorem, opis wtyczki widziany po najechaniu na nią kursorem, autor wtyczki, informacja o autorze, rok powstania, ścieżka wtyczki (w drzewie zakładek) mogąca ustalać nowe nazwy kategorii i podkategorii, przekazywane argumenty w postaci metadanych, przekazywane wartości zmiennych wcześniejszych argumentów, nazwa pod jaką będzie widoczna wtyczka podczas ładowania programu GIMP.
W trakcie realizacji projektu miałem okazję poszerzyć swoją wiedzę z zakresu obróbki cyfrowej obrazów, a w szczególności poznać zasady działania i algorytmy realizujące operatory morfologiczne. Zagadnienie było dla mnie ciekawe i nie uważam tego czasu jako zmarnowanego. Do zgłębiania wiedzy w tej dziedzinie zachęca mnie dodatkowo idea „wolnego oprogramowania”, jaką kierują się twórcy programu GIMP. Bardzo zainspirowało mnie udostępnienie społeczności tak dopracowanego narzędzia, jakim jest GIMP, za darmo. Fakt, że stosunkowo mało jest w internecie informacji na temat pisania skryptów do tego programu, a w szczególności tych zaawansowanych, naprawdę działa zachęcająco, gdyż można być pierwszą osobą implementującą daną funkcjonalność w postaci wtyczek.
Ulepszeniem mającym znaczny wpływ na działanie programu byłoby znalezienie sposobu na zmianę typu warstw z wejściowego RGB, na Greyscale (czego nie udało mi się zaimplementować przez niewystarczającą wiedzę o metadanych przechowywanych przez GIMP w swojej bazie danych procedur). W ten sposób można ograniczyć o 66% ilość alokowanej pamięci w trakcie programu, gdyż piksele typu Greyscale potrzebują 1 bajtu pamięci, a piksele typu RGB potrzebują 3 bajtów pamięci. Kolejnym potencjalnym ulepszeniem wtyczki byłoby przepisanie jej na kod napisany w języku C. W trakcie realizacji projektu napotkałem w większości przestarzałą dokumentację opisującą pisanie wtyczek w języku C. Nie wiem, czy był tam wyjaśniony problem braku dostępu do metadanych w bazie danych procedur programu GIMP, ale patrząc na zainteresowanie frazy „GIMP” w ujęciu czasowym w wyszukiwarce Google, z pewnością 15 lat temu społeczność korzystająca z programu GIMP była żywsza i częściej poruszano tematykę z nim związaną na forach internetowych. Jest to pewnie spowodowane tym, że oprogramowanie Open Source jest obecnie uważane bezpodstawnie za „mniej profesjonalne” zarówno przez firmy, jak i przez ich klientów.