## 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 aż 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 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

**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)
    są w odległości ≤ 30 od (100, 150).
    Oblicza ich średnią → przesuwa okno NA TĘ ŚREDNIĄ.
    Powtarza aż 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" tą 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 (2015–2018); „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
