praca_magisterska/pytania/questions/pytanie_23.md

565 lines
34 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## PYTANIE 23: Segmentacja obrazu
**Problem, strategie klasyczne i sieci neuronowe.**
---
### Tło pojęciowe — słowniczek
**Obraz cyfrowy (digital image)** — macierz pikseli. Obraz 1920×1080 = ~2 mln pikseli. Każdy piksel ma wartość (grayscale: 0-255) lub kanały RGB (3 × 0-255). Segmentacja operuje na tej macierzy.
**Piksel (pixel)** — najmniejsza jednostka obrazu. „Picture element." Segmentacja = przypisanie etykiety KAŻDEMU pikselowi.
**Segmentacja obrazu (image segmentation)** — podział obrazu na regiony, gdzie każdy piksel dostaje etykietę klasy (np. „samochód", „droga", „niebo"). Różni się od klasyfikacji (cały obraz → 1 etykieta) i detekcji (bounding box + etykieta).
**Czy naprawdę KAŻDY piksel?** Tak, w semantic segmentation wynik to mapa o IDENTYCZNYM rozmiarze jak obraz wejściowy. Obraz 640×480 → mapa 640×480, w której KAŻDY z 307 200 pikseli ma etykietę klasy. Żaden piksel nie jest pominięty. Nawet piksele „tła" dostają etykietę (np. klasa „background" lub „void"). W instance segmentation dodatkowo piksele tego samego obiektu dostają ten sam ID instancji.
Obraz wejściowy: 640 × 480 pikseli (RGB, 3 kanały)
Mapa segmentacji: 640 × 480 pikseli (1 kanał — numer klasy)
Piksel (100, 200): RGB=(134, 178, 210) → klasa 3 ("niebo")
Piksel (320, 400): RGB=(82, 79, 73) → klasa 7 ("droga")
KAŻDY piksel ma etykietę — nawet ten "nudny" fragment tła.
---
**Over-segmentation (nad-segmentacja)** — sytuacja, gdy algorytm segmentacji generuje ZBYT WIELE regionów — więcej niż jest obiektów/klas na obrazie. Jeden obiekt zostaje podzielony na kilka-kilkadziesiąt fragmentów. Problem typowy dla metod klasycznych (watershed, region growing).
Obraz: jeden kubek na stole
Idealna segmentacja: 2 regiony (kubek, tło)
Over-segmentation: 47 regionów! (kubek podzielony na 12 kawałków,
stół na 20, tło na 15)
Dlaczego to się dzieje?
- Watershed: każde lokalne minimum jasności → osobny region → setki regionów
- Region Growing: drobne różnice w intensywności → osobne regiony
- Szum (noise) w obrazie → fałszywe granice
Jak sobie z tym radzić?
- **Markers/seeds:** zamiast automatycznych minimów → podaj ręczne punkty startowe
- **Superpixels:** celowa nad-segmentacja na ~100-500 jednorodnych "superpikseli"
(np. SLIC), potem GRUPOWANIE superpikseli w klasy → szybsze i stabilniejsze
- **Hierarchiczne:** wielopoziomowa segmentacja → scalanie regionów bottom-up
- **Deep learning:** sieci neuronowe uczą się "co jest obiektem" z danych → nie mają
problemu z over-segmentation (bo wiedzą, że kubek to jeden obiekt)
**Under-segmentation (pod-segmentacja)** — przeciwieństwo: zbyt mało regionów, różne obiekty zlane w jeden region. Mniej typowy problem.
---
**Typy segmentacji:**
**Semantic segmentation** — każdy piksel → klasa, ale NIE rozróżnia instancji. Wszystkie samochody = jedna klasa „samochód".
[samochód][samochód][droga][droga][pieszo][niebo]
Dwa samochody = ta sama etykieta "samochód"
**Instance segmentation** — rozróżnia instancje tego samego obiektu. Samochód#1 i Samochód#2 mają różne etykiety.
**Panoptic segmentation** — łączy semantic + instance. Obiekty „things" (samochody, ludzie) mają instancje; „stuff" (niebo, droga) — tylko klasy.
---
#### Pojęcia kluczowe dla progowania i Otsu
**Wariancja (variance, σ²)** — miara tego, jak bardzo wartości RÓŻNIĄ SIĘ od swojej średniej. Im większa wariancja, tym bardziej „rozrzucone" są dane. Wzór: σ² = Σ(xᵢ - μ)² / n, gdzie μ to średnia.
Przykład 1 — MAŁA wariancja (dane skupione):
wartości: [48, 50, 52, 49, 51] średnia μ = 50
σ² = ((48-50)² + (50-50)² + (52-50)² + (49-50)² + (51-50)²) / 5
= (4 + 0 + 4 + 1 + 1) / 5 = 2.0
Przykład 2 — DUŻA wariancja (dane rozrzucone):
wartości: [10, 90, 30, 80, 50] średnia μ = 52
σ² = ((10-52)² + (90-52)² + (30-52)² + (80-52)² + (50-52)²) / 5
= (1764 + 1444 + 484 + 784 + 4) / 5 = 896.0
Mała σ² = punkty blisko średniej = dane JEDNORODNE
Duża σ² = punkty daleko od średniej = dane RÓŻNORODNE
**Wewnątrzklasowa (within-class)** — „wewnątrz klasy" oznacza, że mierzymy wariancję OSOBNO dla każdej grupy (klasy), a potem ważymy wynik proporcją pikseli w grupie. Jeśli klasa 0 ma piksele [30, 50, 45] a klasa 1 ma piksele [180, 200, 190], to σ²_wewnątrz = (udział_kl0 × σ²_kl0) + (udział_kl1 × σ²_kl1).
**Wariancja wewnątrzklasowa (within-class variance)** — obliczasz wariancję KAŻDEJ klasy osobno, ważysz przez udział pikseli w tej klasie, sumujesz. Jeśli σ²_wewnątrz jest MAŁA → klasy są „jednorodne" (piksele w klasie 0 mają podobne jasności, piksele w klasie 1 też).
**Co to znaczy „klasy jednorodne"?** — jednorodna klasa to taka, w której WSZYSTKIE piksele mają podobne wartości. Np. klasa „tło" ma jasności [195, 200, 198, 205] → jednorodna (σ² mała). Klasa mieszająca tło i obiekt [30, 200, 50, 190] → niejednorodna (σ² duża). Otsu szuka progu T, który daje NAJBARDZIEJ jednorodne klasy.
**Histogram bimodalny (bimodal histogram)** — histogram z DWOMA wyraźnymi „garbami" (pikami). „Bi" = dwa, „modal" = moda (najczęstsza wartość). Typowy dla obrazów z jednym obiektem na tle — garb 1 odpowiada ciemnym pikselom (obiekt), garb 2 jasnym (tło). Otsu działa TYLKO gdy histogram jest bimodalny — bo szuka progu MIĘDZY garbami.
Garb 1 (ciemne~60): piksele obiektu
Garb 2 (jasne~190): piksele tła
Dolina między garbami → tu Otsu stawia próg T!
Gdyby histogram miał JEDEN garb (unimodalny) → brak naturalnego
podziału → Otsu wybierze losowy próg → słaby wynik.
![Histogram bimodalny, wariancja wewnątrzklasowa i jednorodność klas — Otsu](img/q23_otsu_bimodal.png)
---
**Thresholding (progowanie)** — najprostsza metoda segmentacji. Pomysł: każdy piksel ma wartość jasności (0=czarny, 255=biały). Wybierz PRÓG T: piksel > T → klasa 1 (obiekt), piksel ≤ T → klasa 0 (tło). Działa lepiej niż się wydaje na prostych obrazach (tekst na kartce, RTG, dokumenty).
Obraz (jasność pikseli): [50][200][180][30][220][190]
Próg T=128:
50 ≤ 128 → 0 (tło)
200 > 128 → 1 (obiekt)
180 > 128 → 1
30 ≤ 128 → 0
Wynik: [ 0 ][ 1 ][ 1 ][ 0][ 1 ][ 1 ]
Problem: JAK wybrać T? Ręcznie → subiektywne. Rozwiązanie → Otsu.
Mnemonik: „PRÓG na bramce" — jak bramkarz, przepuszcza piksele jaśniejsze od T,
blokuje ciemniejsze.
**Otsu** — automatyczny dobór progu. Algorytm: przetestuj WSZYSTKIE progi T=0..255, dla każdego oblicz wariancję wewnątrzklasową (jak „różnorodne" są piksele w klasie 0 i klasie 1). Wybierz T minimalizujące tę wariancję = klasy jak najbardziej jednorodne. Złożoność: O(n·L) gdzie n=piksele, L=poziomy jasności (256). Ograniczenie: działa TYLKO dla 2 klas i zakłada bimodalny histogram jasności (dwa „garby"). Patrz diagram powyżej.
Pseudokod Otsu:
best_T = 0
min_var = ∞
for T in 0..255:
c0 = piksele z jasność ≤ T
c1 = piksele z jasność > T
w0 = len(c0) / len(all_pixels)
w1 = len(c1) / len(all_pixels)
var = w0 * variance(c0) + w1 * variance(c1)
if var < min_var:
min_var = var
best_T = T
return best_T
Mnemonik: AUTO-bramkarz Otsu" sam sprawdza 256 progów i wybiera najlepszy.
---
#### Pojęcia kluczowe dla Region Growing
**Region Growing (rozrastanie regionu)** zaczynasz od jednego piksela ziarna" (seed) wybranego ręcznie lub automatycznie. Sprawdzasz sąsiadów: jeśli sąsiad jest PODOBNY (np. |jasność_sąsiada - jasność_regionu| < próg), dodaj go do regionu. Powtarzaj nie ma więcej podobnych sąsiadów. Następnie nowy seed nowy region.
**Dlaczego seed „ręcznie LUB automatycznie"?** to dwa różne scenariusze użycia:
RĘCZNY seed:
- Użytkownik klika myszką na obraz: tu jest obiekt"
- Użycie: segmentacja interaktywna (Photoshop magic wand",
narzędzia medyczne do zaznaczania guzów na RTG)
- Zaleta: precyzyjny, użytkownik wie co chce segmentować
- Wada: wymaga człowieka nie skaluje się do 10 000 obrazów
AUTOMATYCZNY seed metody:
1. Siatka (grid): seed co N pikseli (np. co 50 px na obrazie 500×500 100 seedów)
2. Lokalne ekstrema histogramu: znajdź najczęstszą jasność seed tam
3. Losowanie: wylosuj K punktów jako seedy
4. Analiza gradientu: piksele w płaskich" regionach (brak krawędzi) dobre seedy
Dlaczego OR a nie AND?
Bo to ALTERNATYWNE podejścia albo człowiek wybiera (mało i precyzyjnie),
albo algorytm wybiera (dużo i szybko, ale mniej precyzyjnie).
![Region Growing: seed ręczny vs automatyczny, krok po kroku, fale BFS](img/q23_region_growing.png)
Pseudokod Region Growing:
region = {seed}
queue = [seed]
while queue not empty:
pixel = queue.pop()
for neighbor in pixel.neighbors(): # 4 lub 8 sąsiadów
if neighbor not visited AND similar(neighbor, region):
region.add(neighbor)
queue.append(neighbor)
Mnemonik: PLAMA atramentu" seed to kropla atramentu na papierze,
rozlewa się na podobne (jasne) miejsca, zatrzymuje się na granicach.
---
#### Pojęcia kluczowe dla Watershed
**Watershed (metoda zlewiska)** traktuje obraz jak mapę topograficzną: wartość jasności piksela = wysokość terenu. Ciemne piksele = doliny, jasne = szczyty. Algorytm zalewa" mapę wodą od najniższych punktów (minimów). Gdy woda z dwóch dolin się spotyka tam jest GRANICA segmentu (grań).
![Watershed: obraz jako mapa topograficzna, zalewanie, over-segmentation i marker-controlled watershed](img/q23_watershed.png)
Algorytm:
1. Zamień obraz na mapę wysokości" (jasność = wysokość)
2. Znajdź wszystkie lokalne minima (najciemniejsze punkty)
3. Zalewaj" od minimów woda rośnie równomiernie
4. Gdy woda z dwóch dolin się spotyka postaw TAMĘ (granicę segmentu)
5. Kontynuuj cały obraz zalany
Problem: MASYWNA over-segmentation każde lokalne minimum (nawet szum!) osobna dolina
Rozwiązanie: marker-controlled watershed użytkownik podaje markery (seedy),
zalewamy TYLKO od tych markerów
Mnemonik: ZALEWANIE terenu" wyobraź sobie model terenu z plasteliny w wannie.
Powoli nalewasz wodę doliny się wypełniają granie gór = granice segmentów.
---
#### Pojęcia kluczowe dla Mean Shift
**Okno (window) / jądro (kernel)** w kontekście Mean Shift to koło (lub kula w wielowymiarowej przestrzeni) o ustalonej szerokości (bandwidth = promień h) wokół aktualnego punktu. Wewnątrz okna algorytm oblicza średnią ważoną" pozycji pikseli. Okno = jądro to synonim. Nazwa jądro" pochodzi od estymacji jądrowej gęstości (kernel density estimation, KDE).
Okno o promieniu h = 30 wokół punktu (100, 150):
Bierze WSZYSTKIE piksele, których cechy (jasność, x, y)
w odległości 30 od (100, 150).
Oblicza ich średnią przesuwa okno NA ŚREDNIĄ.
Powtarza okno się zatrzyma" (przesunięcie < ε).
**Najwyższa gęstość (density peak)** punkt w przestrzeni cech, gdzie jest NAJWIĘKSZE skupisko pikseli. Jak najwyższy szczyt góry w 3D. Mean Shift = „przesuń w kierunku średniej" iteracyjnie zbliża się do szczytu gęstości.
**Przestrzeń cech (feature space)** każdy piksel jest opisany nie tylko pozycją (x, y) ale też cechami koloru (jasność, R, G, B). Przestrzeń cech to przestrzeń wielowymiarowa, np. (R, G, B, x, y) = 5 wymiarów. Piksele o podobnych kolorach i blisko siebie będą blisko w przestrzeni cech tworzą klastry (skupiska).
Piksel A: (x=100, y=200, R=30, G=25, B=35) punkt w 5D
Piksel B: (x=102, y=201, R=32, G=27, B=33) BLISKO A w 5D
Piksel C: (x=105, y=198, R=200, G=210, B=220) DALEKO od A w 5D (inny kolor!)
A i B w jednym segmencie, C w innym
**Dlaczego Mean Shift NIE wymaga podania liczby segmentów?** W K-means musisz podać K=3 (trzy klastry) ZANIM uruchomisz algorytm. Mean Shift działa inaczej: każdy piksel startuje i toczy się" do najbliższego szczytu gęstości. Ile jest szczytów = tyle segmentów. Algorytm sam ODKRYWA liczbę klastrów. Parametrem jest tylko bandwidth (szerokość okna h): duże h mało szczytów mało segmentów; małe h dużo szczytów dużo segmentów.
![Mean Shift: przestrzeń cech, jądro przesuwane do max gęstości, dlaczego bez K](img/q23_mean_shift.png)
Pseudokod Mean Shift:
for each pixel p:
x = p.features # np. (R, G, B, pos_x, pos_y)
repeat:
window = all pixels within distance h from x
x_new = weighted_mean(window)
if |x_new - x| < epsilon:
break
x = x_new
p.cluster = x # zbieżny punkt = ID klastra
Mnemonik: KULKI toczą się do dołków" rozsyp kulki na nierównym stole,
każda toczy się do najbliższego zagłębienia. Ile dołków = tyle segmentów.
---
#### Pojęcia kluczowe dla Normalized Cuts
**Cięcie grafu (graph cut)** graf to zbiór węzłów (pikseli) połączonych krawędziami (z wagami = podobieństwo). Ciąć graf" to znaleźć LINIĘ dzielącą węzły na grupy, tak aby krawędzie przecięte" linią miały niską wagę (= łączyły niepodobne piksele), a krawędzie wewnątrz grup miały wysoką wagę (= łączyły podobne piksele).
**Jak szukamy cięcia?** Naiwnie: sprawdź WSZYSTKIE możliwe podziały wykładnicza złożoność. Normalized Cuts zamienia problem na rozwiązanie problemu wartości własnych" (eigenvalue problem) macierzy Laplacianu grafu. Drugi najmniejszy wektor własny wskazuje, które piksele należą do grupy A (wartości dodatnie) a które do B (wartości ujemne).
**Dlaczego „znormalizowane" (normalized)?** Zwykłe cięcie (min-cut) ma wadę: preferuje odcinanie MALUTKICH grup (1 piksel odcięty = małe cięcie). Normalizowanie dzieli koszt cięcia przez rozmiar grup duże, zrównoważone segmenty.
![Normalized Cuts: obraz jako graf, cięcie, algorytm krok po kroku](img/q23_normalized_cuts.png)
Pseudokod Normalized Cuts (uproszczony):
# 1. Zbuduj macierz podobieństwa W
for each pair of pixels (i, j):
W[i,j] = exp(-|color_i - color_j|^2 / sigma^2) # jeśli sąsiedzi
W[i,j] = 0 # jeśli odlegli
# 2. Macierz stopni D
D = diag(sum(W, axis=1)) # D[i,i] = suma wiersza i
# 3. Rozwiąż problem wartości własnych
(D - W) * y = lambda * D * y
# Weź DRUGI najm. wektor własny y (pierwszy = trywialny)
# 4. Podziel piksele
segment_A = {i : y[i] > 0}
segment_B = {i : y[i] <= 0}
Mnemonik: „CIĘCIE sznurków" — piksele połączone sznurkami (mocne = podobne).
Tnij SŁABE sznurki → dwie grupy. Normalizacja = nie odcinaj samotnych pikseli.
---
#### Pojęcia kluczowe dla sieci neuronowych
**ReLU (Rectified Linear Unit)** — najpopularniejsza funkcja aktywacji w sieciach neuronowych. Wzór: ReLU(x) = max(0, x). Jeśli wejście jest ujemne → wynik = 0 (neuron „milczy"). Jeśli wejście jest dodatnie → wynik = x (neuron „przepuszcza" sygnał bez zmiany). Prosta, ale bardzo skuteczna — szybsza od starszych funkcji (sigmoid, tanh), bo nie wymaga obliczania exp().
ReLU(-3) = max(0, -3) = 0 ← neuron „wyłączony"
ReLU(0) = max(0, 0) = 0 ← na granicy
ReLU(2.5) = max(0, 2.5) = 2.5 ← neuron „włączony", przekazuje 2.5
Dlaczego nie po prostu f(x) = x (bez progu)?
Bo liniowość → cała sieć = jedna warstwa liniowa (tracisz głębokość).
ReLU jest NIELINIOWA (ma „zakręt" w 0) → pozwala sieci uczyć się
skomplikowanych wzorców.
![ReLU: wykres funkcji, dlaczego ReLU, przykład numeryczny](img/q23_relu.png)
**Iloczyn skalarny (dot product)** — operacja na dwóch wektorach (listach liczb) dająca JEDNĄ liczbę. Mnożysz odpowiednie elementy parami i sumujesz wyniki. W CNN konwolucja = iloczyn skalarny filtra × fragment obrazu. Duży wynik = wektory „podobne" (filtr pasuje do fragmentu).
a = [1, 3, -2] b = [4, -1, 5]
a · b = 1·4 + 3·(-1) + (-2)·5 = 4 - 3 - 10 = -9
W konwolucji:
filtr = [-1, 0, 1, -1, 0, 1, -1, 0, 1] (spłaszczony 3×3)
fragment = [50, 50, 200, 50, 50, 200, 50, 50, 200]
dot = (-1)·50 + 0·50 + 1·200 + ... = 450 → duży = krawędź!
![Iloczyn skalarny: definicja, geometryczna interpretacja, użycie w konwolucji](img/q23_dot_product.png)
---
**Warstwa Fully Connected (FC, gęsta, dense)** — warstwa, w której KAŻDY neuron jest połączony z KAŻDYM wejściem. Obraz 7×7×512 (po konwolucjach) = 25 088 wartości. FC z 4096 neuronami = 25 088 × 4 096 = **~103 miliony wag**. Wady: (1) wymaga STAŁEGO rozmiaru wejścia (zawsze 7×7×512), (2) traci informację GDZIE coś jest (spłaszcza przestrzeń na wektor 1D).
**Konwolucja (convolution)** — operacja przesuwania małego filtra (np. 3×3) po obrazie. W każdej pozycji oblicza iloczyn skalarny filtra × fragment obrazu → jedną liczbę. TE SAME wagi filtra użyte w KAŻDEJ pozycji → dzielenie parametrów. Zachowuje informację przestrzenną (GDZIE coś jest).
**Conv 1×1 (konwolucja punktowa)** — filtr o rozmiarze 1×1 pikseli. „Patrzy" na JEDEN piksel, ale WSZYSTKIE kanały (np. 512). Działa jak FC, ale OSOBNO dla KAŻDEGO piksela → zachowuje mapę H×W. FCN zamienia FC na Conv 1×1: zamiast spłaszczyć 7×7×512 → 25 088 → FC, robi Conv1×1 na KAŻDYM z 7×7 pikseli × 512 kanałów → mapa 7×7×C (C = liczba klas).
**Jak FCN zamienia FC na Conv 1×1?** — Klasyczny CNN: ostatnia mapa cech 7×7×512 → FLATTEN → wektor 25 088 → FC → 1000 klas → „to jest kot". FCN: ostatnia mapa cech H×W×512 → Conv1×1(512→C) → mapa H×W×C → upsample do pełnej rozdzielczości. Kluczowa różnica: NIE spłaszczamy → możemy przetwarzać obraz o DOWOLNYM rozmiarze.
**Skip connections z encodera** — w encoder-decoder encoder zmniejsza obraz (pooling): 224→112→56→28→14. W tym procesie traci DETALE przestrzenne (dokładne krawędzie). Skip connections = „drogi na skróty" — cechy z wczesnych warstw encodera (pełne detali) są przekazywane WPROST do odpowiednich warstw decodera. Decoder wie CO i GDZIE.
![FCN: warstwa FC vs Conv 1×1, konwolucja, skip connections](img/q23_fc_vs_conv1x1.png)
---
**U-Net — dlaczego kształt „U"?** — Narysuj architekturę: encoder zmniejsza rozdzielczość (bloki idą w DÓŁ po lewej stronie), bottleneck jest na dole, decoder zwiększa rozdzielczość (bloki idą W GÓRĘ po prawej stronie). Wizualnie tworzy literę „U". „Encoder schodzi w dół" = każda warstwa encodera ma MNIEJSZĄ rozdzielczość (224→112→56→28), wizualizowane jako bloki o malejącym rozmiarze ułożone jeden pod drugim.
**Concatenation (konkatenacja, złączenie)** — operacja „sklejania" dwóch tensorów wzdłuż osi kanałów. Jeśli encoder na poziomie 2 daje mapę 128×128×64 kanałów, a decoder na poziomie 2 daje mapę 128×128×64 kanałów, to concatenation = 128×128×**128** kanałów (64+64). Różni się od DODAWANIA (addition), które daje 128×128×64 (element-wise sum). Concatenation zachowuje WIĘCEJ informacji — sieć sama wybiera, które kanały wykorzystać.
Dodawanie (ResNet-style):
encoder [a, b, c] + decoder [x, y, z] = [a+x, b+y, c+z] → 3 kanały
Concatenation (U-Net-style):
encoder [a, b, c] ++ decoder [x, y, z] = [a, b, c, x, y, z] → 6 kanałów!
→ więcej informacji, sieć sama zdecyduje co ważne
![U-Net: architektura w kształcie U, skip connections z concatenation, encoder ↓ decoder ↑](img/q23_unet_arch.png)
Mnemonik U-Net: „Litera U — w dół i w górę" — encoder schodzi ↓ (zmniejsza),
decoder wraca ↑ (zwiększa), między nimi mosty (skip = concat).
---
**Receptive field (pole widzenia, pole recepcyjne)** — ile pikseli WEJŚCIOWYCH wpływa na JEDEN piksel wyjściowy. Konwolucja 3×3 → RF = 3×3. Dwie konwolucje 3×3 pod rząd → RF = 5×5 (druga widzi 3×3 fragmenty, z których każdy widział 3×3 → efektywnie 5×5). Większe RF = neuron widzi większy kontekst = lepiej rozumie co to za piksel.
**Dlaczego większe RF jest lepsze?** — Pojedynczy piksel o jasności 150 może być fragmentem nieba LUB samochodu. Patrząc na otoczenie 3×3 → nadal nie wiesz. Patrząc na otoczenie 50×50 → widzisz budynki obok → „to droga!". Segmentacja wymaga KONTEKSTU globalnego.
**Rate (współczynnik dylatacji)** — parametr atrous (dilated) convolution. Rate=1 = zwykła konwolucja (filtr dotyka sąsiadów). Rate=2 = filtr próbkuje co DRUGI piksel → RF rośnie z 3×3 do 5×5 przy TYCH SAMYCH 9 wagach. Rate=3 → RF = 7×7. Większy kontekst za darmo (bez dodatkowych parametrów).
**Global Average Pooling (GAP)** — operacja redukcji: mapa cech H×W×C → 1×1×C. Dla KAŻDEGO kanału oblicza ŚREDNIĄ ze wszystkich H×W pikseli. Wynik: jeden wektor o wymiarze C, reprezentujący „średnią informację" z całego obrazu. RF = nieskończone (cały obraz). Używane w ASPP DeepLab jako jedna z równoległych gałęzi.
Mapa cech 7×7×512:
Kanał 0: macierz 7×7 wartości → średnia → jedna liczba
Kanał 1: macierz 7×7 wartości → średnia → jedna liczba
...
Kanał 511: macierz 7×7 wartości → średnia → jedna liczba
Wynik: wektor [avg₀, avg₁, ..., avg₅₁₁] → 1×1×512
![Receptive field: zwykła vs dilated konwolucja, rate, global average pooling](img/q23_receptive_field.png)
---
**Transformer** — architektura sieci neuronowej zaproponowana w 2017 (Vaswani et al., „Attention Is All You Need"). Oryginalnie dla NLP (tłumaczenie), od 2020 (ViT — Vision Transformer) stosowana w wizji komputerowej. Kluczowy mechanizm: **self-attention** — każdy element (piksel/token) „pyta" WSZYSTKIE inne elementy: „jak bardzo jesteś ze mną powiązany?". Każdy element tworzy trzy wektory: Q (Query — czego szukam?), K (Key — co oferuję), V (Value — moja wartość). Attention = softmax(Q·Kᵀ / √d) · V. Koszt: O(n²) pamięci (n = liczba elementów).
**SOTA (State Of The Art)** — najlepszy znany wynik na danym benchmarku (zbiorze testowym) w danym momencie. Np. „Mask2Former osiąga mIoU 57.8% na ADE20K — to aktualny SOTA". SOTA ciągle się zmienia — każdy nowy paper może pobić poprzedni rekord.
![Transformer: CNN lokalny vs Transformer globalny, self-attention Q/K/V, SOTA](img/q23_transformer_attention.png)
---
**mIoU (mean Intersection over Union)** — standardowa metryka segmentacji. Dla każdej klasy: IoU = (piksele poprawne ∩ ground truth) / (piksele poprawne ground truth). Potem średnia z klas.
Klasa "samochód": predykcja=100 pikseli, GT=120, wspólne=80
IoU = 80 / (100+120-80) = 80/140 = 0.571 = 57.1%
**Dice Loss** — funkcja kosztu powiązana z IoU: 2·|A∩B| / (|A|+|B|). Popularna w segmentacji medycznej (dobrze radzi sobie z class imbalance).
**Focal Loss** — modyfikacja cross-entropy redukująca wpływ łatwych przykładów, skupiająca uczenie na trudnych. Kluczowa przy class imbalance (np. 99% tła, 1% obiekt).
---
### Problem: czym jest segmentacja obrazu?
Segmentacja obrazu to **przypisanie etykiety klasy KAŻDEMU pikselowi** obrazu. Wynik: mapa segmentacji o tym samym rozmiarze co obraz wejściowy, gdzie każdy piksel ma etykietę (np. „samochód", „droga", „niebo").
Wejście: obraz 640×480 (RGB) = 307 200 pikseli
Wynik: mapa 640×480, każdy piksel → etykieta = 307 200 etykiet
Obraz: [niebo niebo niebo niebo]
[niebo drzewo drzewo niebo]
[droga droga samochód droga]
[droga droga droga droga]
**Czym segmentacja NIE jest:**
Zadanie Wynik Granulacja
──────────────────────────────────────────────────────────────
Klasyfikacja 1 etykieta na cały obraz obraz
Detekcja bounding box + klasa prostokąt
Segmentacja etykieta per piksel piksel
**3 warianty segmentacji:**
![Typy segmentacji obrazu](img/segmentation_types.png)
| Wariant | Co robi | Przykład |
|---------|---------|----------|
| **Semantic** | klasa per piksel, bez rozróżniania instancji | wszystkie samochody = „samochód" |
| **Instance** | rozróżnia instancje tego samego obiektu | samochód#1, samochód#2 |
| **Panoptic** | semantic + instance razem | „stuff" (niebo) + „things" (samochód#1, #2) |
---
### Strategie klasyczne
Metody niewymagające uczenia maszynowego — oparte na ręcznie zdefiniowanych regułach (próg, podobieństwo, struktura grafu).
| Metoda | Idea | Wada | Złożoność | Mnemonik |
|--------|------|------|-----------|----------|
| **Thresholding** | piksel > T → klasa 1, else → klasa 0 | tylko 2 klasy, proste sceny | O(n) | „PRÓG na bramce" |
| **Otsu** | automatyczny próg (min wariancja wewnątrzklasowa) | j.w. ale dobiera T sam | O(n·L) | „AUTO-bramkarz" |
| **Region Growing** | dodawaj sąsiednie piksele o podobnej wartości | over-segmentation, zależy od seeda | O(n) | „PLAMA atramentu" |
| **Watershed** | obraz = mapa wysokości, granice = granie gór | over-segmentation | O(n log n) | „ZALEWANIE terenu" |
| **Mean Shift** | iteracyjnie przesuwaj jądro do max gęstości | wolny | O(n²) | „KULKI toczą się" |
| **Normalized Cuts** | piksele = węzły grafu, minimalizuj znormalizowane cięcie | bardzo wolny | O(n³) | „CIĘCIE sznurków" |
#### DIY Przykład — Thresholding (Otsu) krok po kroku
Poniższy diagram pokazuje CAŁY pipeline progowania Otsu od obrazu wejściowego do wyniku. Obraz syntetyczny 64×64 z ciemnym kołem na jasnym tle — typowy przypadek bimodalny.
![DIY Thresholding + Otsu: obraz → histogram bimodalny → progowanie → szukanie min σ² → pseudokod → wynik](img/q23_diy_thresholding.png)
Pseudokod Otsu (Python-style):
best_T, min_var = 0, float('inf')
for T in range(256):
c0 = pixels[pixels <= T] # piksele ciemne
c1 = pixels[pixels > T] # piksele jasne
if len(c0) == 0 or len(c1) == 0:
continue
w0 = len(c0) / len(pixels) # udział klasy 0
w1 = len(c1) / len(pixels) # udział klasy 1
var = w0 * variance(c0) + w1 * variance(c1) # σ² wewnątrzklasowa
if var < min_var:
min_var = var
best_T = T
# best_T = optymalny próg (np. 128)
result = (pixels > best_T).astype(int) # binaryzacja
**Wspólna wada klasycznych metod:** wymagają ręcznego doboru parametrów (próg, seed, kernel), nie uczą się cech z danych, słabe na złożonych obrazach naturalnych.
---
### Sieci neuronowe (deep learning)
Metody uczące się automatycznie rozpoznawać cechy z danych treningowych. Wszystkie oparte na architekturze **encoder-decoder** z wariacjami.
**Wspólna idea encoder-decoder:**
Encoder: obraz [224×224] → [112] → [56] → [28] → [14] (wyciąga CECHY)
Decoder: cechy [14] → [28] → [56] → [112] → [224×224] (odtwarza MAPĘ)
bottleneck
| Sieć | Rok | Kluczowa innowacja | Use case | Mnemonik |
|------|-----|-------------------|----------|----------|
| **FCN** | 2015 | w pełni konwolucyjna + skip connections | pierwsza end-to-end | „FC → Conv 1×1" |
| **U-Net** | 2015 | U-shape + skip concat + data augmentation | segmentacja medyczna | „Litera U + mosty" |
| **DeepLab v3+** | 2018 | atrous (dilated) conv + ASPP | general-purpose | „DZIURY w filtrze" |
| **SegFormer** | 2021 | transformer encoder (self-attention) | SOTA lightweight | „WSZYSCY ze WSZYSTKIMI" |
| **Mask2Former** | 2022 | masked attention + unified architecture | SOTA universal | „WSZYSCY ze WSZYSTKIMI" |
**FCN (Fully Convolutional Network):**
Mnemonik: „FC → Conv 1×1 = otwieramy bramkę dla DOWOLNEGO rozmiaru"
Zwykły CNN: Conv → Conv → Pool → ... → FC → FC → "kot"
FCN: Conv → Conv → Pool → ... → Conv1×1 → Upsample → mapa pikseli
Innowacja: zamiana FC na Conv1×1 → wejście dowolnego rozmiaru
Skip connections: łączą cechy z encodera → zachowują detale przestrzenne
**U-Net:**
Mnemonik: „Litera U + mosty" — schodzisz w dół, wracasz w górę,
po drodze mosty (skip connections z concat) przenoszą detale.
Encoder (↓) Decoder (↑)
[64]────skip────→[64] ← skip connections = concatenation
[128]───skip───→[128] (przenosi detale z encodera do decodera)
[256]──skip──→[256]
[512]─skip─→[512]
[1024] ← bottleneck
Dlaczego medycyna? Działa dobrze z MAŁYMI zbiorami danych (data augmentation)
**DeepLab v3+:**
Mnemonik: „DZIURY w filtrze" — filtr dosłownie ma dziury (à trous),
przez co widzi dalej bez dodatkowych parametrów.
Zwykła konwolucja 3×3: [x][x][x] receptive field = 3
Dilated (rate=2): [x][ ][x][ ][x] receptive field = 5, te same parametry!
ASPP: równolegle rate=6,12,18 → multi-scale features → łączenie
Efekt: widzi kontekst globalny BEZ zwiększania parametrów
**Transformery (SegFormer, Mask2Former):**
Mnemonik: „WSZYSCY ze WSZYSTKIMI" — każdy piksel rozmawia z KAŻDYM innym.
CNN: filtr 3×3 widzi LOKALNY kontekst (sąsiadów)
Transformer: self-attention widzi CAŁY obraz naraz
Cena: O(n²) pamięci (n = piksele), ale lepsze wyniki
#### DIY Przykład — U-Net krok po kroku
Poniższy diagram pokazuje CAŁY pipeline U-Net od obrazu wejściowego do mapy segmentacji. Obraz syntetyczny 64×64 z dwoma obiektami (koła) na jasnym tle.
![DIY U-Net: obraz → encoder zmniejsza → bottleneck → decoder zwiększa + skip → mapa segmentacji → pseudokod](img/q23_diy_unet.png)
Pseudokod U-Net (PyTorch-style):
# ENCODER — zmniejsza rozdzielczość, wyciąga cechy
e1 = conv_block(input, filters=64) # [64×64×64]
e2 = conv_block(maxpool(e1), filters=128) # [32×32×128]
e3 = conv_block(maxpool(e2), filters=256) # [16×16×256]
# BOTTLENECK — najgłębsza warstwa
b = conv_block(maxpool(e3), filters=512) # [8×8×512]
# DECODER — zwiększa rozdzielczość + skip connections (concat!)
d3 = conv_block(concat(upconv(b), e3), filters=256) # [16×16×256]
d2 = conv_block(concat(upconv(d3), e2), filters=128) # [32×32×128]
d1 = conv_block(concat(upconv(d2), e1), filters=64) # [64×64×64]
# WYNIK — Conv 1×1 → mapa klas
output = conv_1x1(d1, n_classes=3) # [64×64×3] → argmax → [64×64] etykiety
---
### Metryki i funkcje kosztu
| Metryka/Loss | Wzór | Kiedy użyć |
|-------------|------|------------|
| **mIoU** | mean(IoU per klasa) | standardowy benchmark |
| **Pixel Accuracy** | poprawne / wszystkie | prosta, ale zła przy class imbalance |
| **Dice Loss** | 1 - 2·\|A∩B\| / (\|A\|+\|B\|) | segmentacja medyczna |
| **Focal Loss** | -α(1-p)^γ · log(p) | class imbalance (99% tła) |
### Etymologia
**Segmentacja** — łac. „segmentum" = odcięty kawałek; podział obrazu na regiony. **Otsu** — Nobuyuki Otsu (1979); automatyczny dobór progu. **Watershed** — metafora: woda spływająca z grani do dolin (z geografii). **U-Net** — Ronneberger et al. (Freiburg, 2015); „U" od kształtu architektury. **FCN** — Fully Convolutional Network (Long, Shelhamer, Darrell, 2015). **DeepLab** — Google (20152018); „Atrous" z fr. „à trous" = „z dziurami" (dilated convolutions). **mIoU** — mean Intersection over Union.
### Jak zapamiętać
**Super-mnemonik na kolejność algorytmów:**
„Turyści Oglądają Rzekę, Wodospad, Morze, Nurt — Fotografują Uroczy Dwór Tajemnic"
Klasyczne: Thresholding → Otsu → Region growing → Watershed → Mean shift → Normalized cuts
Neuronowe: FCN → U-Net → DeepLab → Transformer
![Mnemoniki: karty z algorytmami segmentacji i ich skojarzeniami](img/q23_mnemonics.png)
**Mnemoniki per algorytm — STRATEGIE KLASYCZNE:**
| Algorytm | Mnemonik | Skojarzenie |
|----------|----------|-------------|
| **Thresholding** | „PRÓG na bramce" | Bramkarz przepuszcza piksele > T, blokuje ≤ T |
| **Otsu** | „AUTO-bramkarz" | Sam sprawdza 256 progów, wybiera najlepszy (min σ²) |
| **Region Growing** | „PLAMA atramentu" | Kropla atramentu rozlewa się na podobne piksele (BFS) |
| **Watershed** | „ZALEWANIE terenu" | Woda zalewa doliny, granie gór = granice segmentów |
| **Mean Shift** | „KULKI toczą się do dołków" | Każda kulka → max gęstości, ile dołków = tyle segmentów |
| **Normalized Cuts** | „CIĘCIE sznurków" | Tnij słabe sznurki (krawędzie grafu), zachowaj silne |
**Mnemoniki per algorytm — SIECI NEURONOWE:**
| Sieć | Mnemonik | Skojarzenie |
|------|----------|-------------|
| **FCN** | „FC → Conv 1×1" | Otwiera bramkę dla dowolnego rozmiaru wejścia |
| **U-Net** | „Litera U + mosty" | Schodzisz ↓, wracasz ↑, mosty (skip concat) przenoszą detale |
| **DeepLab** | „DZIURY w filtrze" | Filtr ma dziury (à trous) → widzi dalej bez dodatkowych wag |
| **Transformer** | „WSZYSCY ze WSZYSTKIMI" | Każdy piksel pyta każdy inny (self-attention, O(n²)) |
**Mnemoniki per metrykę:**
- **mIoU** = „Nakładka / Suma" → intersection / union, uśrednione per klasa
- **Dice** = „Dwie nakładki / Razem" → 2·|A∩B| / (|A|+|B|)
- **Focal** = „Fokus na TRUDNYCH" → trudne piksele ważą więcej