praca_magisterska/pytania/questions/pytanie_23.md

565 lines
34 KiB
Markdown
Raw Normal View History

## 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.
---
2026-02-21 19:51:31 +01:00
#### 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.
2026-02-21 19:51:31 +01:00
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.
2026-02-21 19:51:31 +01:00
**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
2026-02-21 19:51:31 +01:00
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)
2026-02-21 19:51:31 +01:00
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ń).
2026-02-21 19:51:31 +01:00
![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 aż 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
2026-02-21 19:51:31 +01:00
**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).
2026-02-21 19:51:31 +01:00
Okno o promieniu h = 30 wokół punktu (100, 150):
Bierze WSZYSTKIE piksele, których cechy (jasność, x, y)
są w odległości ≤ 30 od (100, 150).
Oblicza ich średnią → przesuwa okno NA TĘ ŚREDNIĄ.
Powtarza aż okno się „zatrzyma" (przesunięcie < ε).
2026-02-21 19:51:31 +01:00
**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.
2026-02-21 19:51:31 +01:00
**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).
2026-02-21 19:51:31 +01:00
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.
---
2026-02-21 19:51:31 +01:00
#### 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" tą linią miały niską wagę (= łączyły niepodobne piksele), a krawędzie wewnątrz grup miały wysoką wagę (= łączyły podobne piksele).
2026-02-21 19:51:31 +01:00
**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).
2026-02-21 19:51:31 +01:00
**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.
2026-02-21 19:51:31 +01:00
![Normalized Cuts: obraz jako graf, cięcie, algorytm krok po kroku](img/q23_normalized_cuts.png)
2026-02-21 19:51:31 +01:00
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
2026-02-21 19:51:31 +01:00
# 2. Macierz stopni D
D = diag(sum(W, axis=1)) # D[i,i] = suma wiersza i
2026-02-21 19:51:31 +01:00
# 3. Rozwiąż problem wartości własnych
(D - W) * y = lambda * D * y
# Weź DRUGI najm. wektor własny y (pierwszy = trywialny)
2026-02-21 19:51:31 +01:00
# 4. Podziel piksele
segment_A = {i : y[i] > 0}
segment_B = {i : y[i] <= 0}
2026-02-21 19:51:31 +01:00
Mnemonik: „CIĘCIE sznurków" — piksele połączone sznurkami (mocne = podobne).
Tnij SŁABE sznurki → dwie grupy. Normalizacja = nie odcinaj samotnych pikseli.
2026-02-21 19:51:31 +01:00
---
#### 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().
2026-02-21 19:51:31 +01:00
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
2026-02-21 19:51:31 +01:00
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.
2026-02-21 19:51:31 +01:00
![ReLU: wykres funkcji, dlaczego ReLU, przykład numeryczny](img/q23_relu.png)
2026-02-21 19:51:31 +01:00
**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).
2026-02-21 19:51:31 +01:00
a = [1, 3, -2] b = [4, -1, 5]
a · b = 1·4 + 3·(-1) + (-2)·5 = 4 - 3 - 10 = -9
2026-02-21 19:51:31 +01:00
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ź!
2026-02-21 19:51:31 +01:00
![Iloczyn skalarny: definicja, geometryczna interpretacja, użycie w konwolucji](img/q23_dot_product.png)
---
2026-02-21 19:51:31 +01:00
**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).
2026-02-21 19:51:31 +01:00
**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).
2026-02-21 19:51:31 +01:00
**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).
2026-02-21 19:51:31 +01:00
**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.
2026-02-21 19:51:31 +01:00
**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.
2026-02-21 19:51:31 +01:00
![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).
2026-02-21 19:51:31 +01:00
| 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
2026-02-21 19:51:31 +01:00
| 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):**
2026-02-21 19:51:31 +01:00
Mnemonik: „FC → Conv 1×1 = otwieramy bramkę dla DOWOLNEGO rozmiaru"
Zwykły CNN: Conv → Conv → Pool → ... → FC → FC → "kot"
2026-02-21 19:51:31 +01:00
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:**
2026-02-21 19:51:31 +01:00
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+:**
2026-02-21 19:51:31 +01:00
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):**
2026-02-21 19:51:31 +01:00
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
2026-02-21 19:51:31 +01:00
#### 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ć
2026-02-21 19:51:31 +01:00
**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