mirror of
https://github.com/kuhyx/testsAndMisc-archive.git
synced 2026-07-04 13:03:15 +02:00
feat: shell scripts
This commit is contained in:
parent
592a50f1d2
commit
c544aae70c
1
python_pkg/praca_magisterska_video/__init__.py
Normal file
1
python_pkg/praca_magisterska_video/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Thesis video visualization scripts."""
|
||||||
563
python_pkg/praca_magisterska_video/answers/pytanie_23.md
Normal file
563
python_pkg/praca_magisterska_video/answers/pytanie_23.md
Normal file
@ -0,0 +1,563 @@
|
|||||||
|
## 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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**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).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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ń).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**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ź!
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**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
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**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:**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
| 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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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 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
|
||||||
1045
python_pkg/praca_magisterska_video/answers/pytanie_24.md
Normal file
1045
python_pkg/praca_magisterska_video/answers/pytanie_24.md
Normal file
File diff suppressed because it is too large
Load Diff
530
python_pkg/praca_magisterska_video/visualize_q02.py
Normal file
530
python_pkg/praca_magisterska_video/visualize_q02.py
Normal file
@ -0,0 +1,530 @@
|
|||||||
|
"""MoviePy visualization for PYTANIE 2: Shortest path algorithms.
|
||||||
|
|
||||||
|
Creates an animated video walking through Dijkstra, Bellman-Ford, and A*
|
||||||
|
on a small example graph, rendering each algorithm step by step.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
os.environ["FFMPEG_BINARY"] = "/usr/bin/ffmpeg"
|
||||||
|
|
||||||
|
from moviepy import (
|
||||||
|
ColorClip,
|
||||||
|
CompositeVideoClip,
|
||||||
|
TextClip,
|
||||||
|
VideoClip,
|
||||||
|
concatenate_videoclips,
|
||||||
|
)
|
||||||
|
from moviepy.video.fx import FadeIn, FadeOut
|
||||||
|
|
||||||
|
# ── Constants ─────────────────────────────────────────────────────
|
||||||
|
W, H = 1280, 720
|
||||||
|
FPS = 24
|
||||||
|
STEP_DUR = 8.0
|
||||||
|
HEADER_DUR = 5.0
|
||||||
|
FONT_B = "/usr/share/fonts/TTF/DejaVuSans-Bold.ttf"
|
||||||
|
FONT_R = "/usr/share/fonts/TTF/DejaVuSans.ttf"
|
||||||
|
OUTPUT_DIR = Path(__file__).resolve().parent / "videos"
|
||||||
|
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
OUTPUT = str(OUTPUT_DIR / "q02_shortest_path.mp4")
|
||||||
|
|
||||||
|
# Graph definition
|
||||||
|
NODE_POS = {"S": (250, 280), "A": (550, 180), "B": (550, 450), "C": (850, 320)}
|
||||||
|
EDGES_DIJKSTRA = [
|
||||||
|
("S", "A", 2),
|
||||||
|
("S", "B", 5),
|
||||||
|
("A", "C", 3),
|
||||||
|
("B", "A", 1),
|
||||||
|
("B", "C", 6),
|
||||||
|
]
|
||||||
|
EDGES_BF = [("S", "A", 2), ("A", "C", 3), ("S", "B", 5), ("B", "A", -4)]
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
BG = (20, 20, 40)
|
||||||
|
COL_DEFAULT = (70, 130, 200)
|
||||||
|
COL_CURRENT = (255, 200, 50)
|
||||||
|
COL_VISITED = (80, 200, 100)
|
||||||
|
COL_EDGE = (100, 100, 130)
|
||||||
|
COL_EDGE_ACT = (255, 100, 80)
|
||||||
|
INF = "inf"
|
||||||
|
|
||||||
|
|
||||||
|
def _tc(**kwargs: object) -> TextClip:
|
||||||
|
"""TextClip wrapper that adds enough bottom margin to prevent clipping."""
|
||||||
|
fs = kwargs.get("font_size", 24)
|
||||||
|
m = int(fs) // 3 + 2
|
||||||
|
kwargs["margin"] = (0, m)
|
||||||
|
return TextClip(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def _make_header(
|
||||||
|
title: str, subtitle: str, duration: float = HEADER_DUR
|
||||||
|
) -> CompositeVideoClip:
|
||||||
|
bg = ColorClip(size=(W, H), color=BG).with_duration(duration)
|
||||||
|
t = (
|
||||||
|
_tc(
|
||||||
|
text=title,
|
||||||
|
font_size=52,
|
||||||
|
color="white",
|
||||||
|
font=FONT_B,
|
||||||
|
)
|
||||||
|
.with_duration(duration)
|
||||||
|
.with_position(("center", 250))
|
||||||
|
)
|
||||||
|
s = (
|
||||||
|
_tc(
|
||||||
|
text=subtitle,
|
||||||
|
font_size=28,
|
||||||
|
color="#AABBCC",
|
||||||
|
font=FONT_R,
|
||||||
|
)
|
||||||
|
.with_duration(duration)
|
||||||
|
.with_position(("center", 340))
|
||||||
|
)
|
||||||
|
return CompositeVideoClip([bg, t, s], size=(W, H)).with_effects(
|
||||||
|
[FadeIn(0.5), FadeOut(0.5)]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _draw_circle(
|
||||||
|
frame: np.ndarray, cx: int, cy: int, r: int, color: tuple[int, ...]
|
||||||
|
) -> None:
|
||||||
|
yy, xx = np.ogrid[:H, :W]
|
||||||
|
mask = ((xx - cx) ** 2 + (yy - cy) ** 2) <= r**2
|
||||||
|
frame[mask] = color
|
||||||
|
|
||||||
|
|
||||||
|
def _draw_line(
|
||||||
|
frame: np.ndarray,
|
||||||
|
x1: int,
|
||||||
|
y1: int,
|
||||||
|
x2: int,
|
||||||
|
y2: int,
|
||||||
|
color: tuple[int, ...],
|
||||||
|
thickness: int = 2,
|
||||||
|
) -> None:
|
||||||
|
length = max(int(np.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)), 1)
|
||||||
|
for i in range(length):
|
||||||
|
frac = i / length
|
||||||
|
px = int(x1 + frac * (x2 - x1))
|
||||||
|
py = int(y1 + frac * (y2 - y1))
|
||||||
|
for dx in range(-thickness, thickness + 1):
|
||||||
|
for dy in range(-thickness, thickness + 1):
|
||||||
|
nx, ny = px + dx, py + dy
|
||||||
|
if 0 <= nx < W and 0 <= ny < H:
|
||||||
|
frame[ny, nx] = color
|
||||||
|
|
||||||
|
|
||||||
|
def _draw_arrow(
|
||||||
|
frame: np.ndarray,
|
||||||
|
x1: int,
|
||||||
|
y1: int,
|
||||||
|
x2: int,
|
||||||
|
y2: int,
|
||||||
|
color: tuple[int, ...],
|
||||||
|
thickness: int = 2,
|
||||||
|
) -> None:
|
||||||
|
r = 32
|
||||||
|
length = max(np.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2), 1)
|
||||||
|
ddx = (x2 - x1) / length
|
||||||
|
ddy = (y2 - y1) / length
|
||||||
|
sx = int(x1 + ddx * r)
|
||||||
|
sy = int(y1 + ddy * r)
|
||||||
|
ex = int(x2 - ddx * r)
|
||||||
|
ey = int(y2 - ddy * r)
|
||||||
|
_draw_line(frame, sx, sy, ex, ey, color, thickness)
|
||||||
|
angle = np.arctan2(ey - sy, ex - sx)
|
||||||
|
arrow_len = 12
|
||||||
|
for side in [-1, 1]:
|
||||||
|
a = angle + np.pi + side * 0.4
|
||||||
|
ax = int(ex + arrow_len * np.cos(a))
|
||||||
|
ay = int(ey + arrow_len * np.sin(a))
|
||||||
|
_draw_line(frame, ex, ey, ax, ay, color, thickness)
|
||||||
|
|
||||||
|
|
||||||
|
def _render_graph(
|
||||||
|
nodes: dict[str, tuple[int, int]],
|
||||||
|
edges: list[tuple[str, str, int]],
|
||||||
|
_distances: dict[str, str],
|
||||||
|
current: str | None = None,
|
||||||
|
visited: set[str] | None = None,
|
||||||
|
active_edge: tuple[str, str] | None = None,
|
||||||
|
) -> np.ndarray:
|
||||||
|
if visited is None:
|
||||||
|
visited = set()
|
||||||
|
frame = np.full((H, W, 3), BG, dtype=np.uint8)
|
||||||
|
|
||||||
|
for src, dst, _w in edges:
|
||||||
|
sx, sy = nodes[src]
|
||||||
|
dx, dy = nodes[dst]
|
||||||
|
ec = COL_EDGE_ACT if active_edge == (src, dst) else COL_EDGE
|
||||||
|
_draw_arrow(frame, sx, sy, dx, dy, ec, thickness=2)
|
||||||
|
|
||||||
|
for name, (x, y) in nodes.items():
|
||||||
|
if name == current:
|
||||||
|
nc = COL_CURRENT
|
||||||
|
elif name in visited:
|
||||||
|
nc = COL_VISITED
|
||||||
|
else:
|
||||||
|
nc = COL_DEFAULT
|
||||||
|
_draw_circle(frame, x, y, 30, nc)
|
||||||
|
# Border ring
|
||||||
|
border = tuple(max(c - 40, 0) for c in nc)
|
||||||
|
yy, xx = np.ogrid[:H, :W]
|
||||||
|
ring = (((xx - x) ** 2 + (yy - y) ** 2) <= 30**2) & (
|
||||||
|
((xx - x) ** 2 + (yy - y) ** 2) > 27**2
|
||||||
|
)
|
||||||
|
frame[ring] = border
|
||||||
|
|
||||||
|
return frame
|
||||||
|
|
||||||
|
|
||||||
|
def _make_step(
|
||||||
|
nodes: dict[str, tuple[int, int]],
|
||||||
|
edges: list[tuple[str, str, int]],
|
||||||
|
distances: dict[str, str],
|
||||||
|
current: str | None = None,
|
||||||
|
visited: set[str] | None = None,
|
||||||
|
active_edge: tuple[str, str] | None = None,
|
||||||
|
step_text: str = "",
|
||||||
|
algo_name: str = "",
|
||||||
|
duration: float = STEP_DUR,
|
||||||
|
) -> CompositeVideoClip:
|
||||||
|
if visited is None:
|
||||||
|
visited = set()
|
||||||
|
|
||||||
|
graph_frame = _render_graph(nodes, edges, distances, current, visited, active_edge)
|
||||||
|
|
||||||
|
def make_frame(_t: float) -> np.ndarray:
|
||||||
|
return graph_frame.copy()
|
||||||
|
|
||||||
|
bg_clip = VideoClip(make_frame, duration=duration).with_fps(FPS)
|
||||||
|
overlays: list[VideoClip] = [bg_clip]
|
||||||
|
|
||||||
|
if algo_name:
|
||||||
|
overlays.append(
|
||||||
|
_tc(
|
||||||
|
text=algo_name,
|
||||||
|
font_size=28,
|
||||||
|
color="#64B5F6",
|
||||||
|
font=FONT_B,
|
||||||
|
)
|
||||||
|
.with_duration(duration)
|
||||||
|
.with_position((40, 20))
|
||||||
|
)
|
||||||
|
|
||||||
|
dist_items = [f"{k}: {v}" for k, v in distances.items()]
|
||||||
|
table_text = "dist = { " + ", ".join(dist_items) + " }"
|
||||||
|
overlays.append(
|
||||||
|
_tc(
|
||||||
|
text=table_text,
|
||||||
|
font_size=18,
|
||||||
|
color="#B0BEC5",
|
||||||
|
font=FONT_R,
|
||||||
|
)
|
||||||
|
.with_duration(duration)
|
||||||
|
.with_position((40, 60))
|
||||||
|
)
|
||||||
|
|
||||||
|
visited_text = f"visited = {{ {', '.join(sorted(visited))} }}"
|
||||||
|
overlays.append(
|
||||||
|
_tc(
|
||||||
|
text=visited_text,
|
||||||
|
font_size=18,
|
||||||
|
color="#A5D6A7",
|
||||||
|
font=FONT_R,
|
||||||
|
)
|
||||||
|
.with_duration(duration)
|
||||||
|
.with_position((40, 90))
|
||||||
|
)
|
||||||
|
|
||||||
|
for src, dst, w in edges:
|
||||||
|
sx, sy = nodes[src]
|
||||||
|
dx, dy = nodes[dst]
|
||||||
|
mx = (sx + dx) // 2 - 6
|
||||||
|
my = (sy + dy) // 2 - 12
|
||||||
|
wcol = "#FF8A65" if active_edge == (src, dst) else "#90A4AE"
|
||||||
|
overlays.append(
|
||||||
|
_tc(
|
||||||
|
text=str(w),
|
||||||
|
font_size=16,
|
||||||
|
color=wcol,
|
||||||
|
font=FONT_B,
|
||||||
|
)
|
||||||
|
.with_duration(duration)
|
||||||
|
.with_position((mx, my))
|
||||||
|
)
|
||||||
|
|
||||||
|
for name, (x, y) in nodes.items():
|
||||||
|
overlays.append(
|
||||||
|
_tc(
|
||||||
|
text=name,
|
||||||
|
font_size=20,
|
||||||
|
color="white",
|
||||||
|
font=FONT_B,
|
||||||
|
)
|
||||||
|
.with_duration(duration)
|
||||||
|
.with_position((x - 7, y - 12))
|
||||||
|
)
|
||||||
|
d = distances.get(name, INF)
|
||||||
|
overlays.append(
|
||||||
|
_tc(
|
||||||
|
text=f"d={d}",
|
||||||
|
font_size=14,
|
||||||
|
color="#FFE082",
|
||||||
|
font=FONT_R,
|
||||||
|
)
|
||||||
|
.with_duration(duration)
|
||||||
|
.with_position((x - 16, y + 35))
|
||||||
|
)
|
||||||
|
|
||||||
|
if step_text:
|
||||||
|
overlays.append(
|
||||||
|
_tc(
|
||||||
|
text=step_text,
|
||||||
|
font_size=18,
|
||||||
|
color="#E0E0E0",
|
||||||
|
font=FONT_R,
|
||||||
|
)
|
||||||
|
.with_duration(duration)
|
||||||
|
.with_position((40, 600))
|
||||||
|
)
|
||||||
|
|
||||||
|
return CompositeVideoClip(overlays, size=(W, H)).with_effects(
|
||||||
|
[FadeIn(0.3), FadeOut(0.3)]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _dijkstra_steps() -> list[CompositeVideoClip]:
|
||||||
|
n = NODE_POS
|
||||||
|
e = EDGES_DIJKSTRA
|
||||||
|
return [
|
||||||
|
_make_step(
|
||||||
|
n,
|
||||||
|
e,
|
||||||
|
{"S": "0", "A": INF, "B": INF, "C": INF},
|
||||||
|
current="S",
|
||||||
|
step_text="Inicjalizacja: d[S]=0, reszta=∞. Wybierz S (min d).",
|
||||||
|
algo_name="Algorytm Dijkstry",
|
||||||
|
),
|
||||||
|
_make_step(
|
||||||
|
n,
|
||||||
|
e,
|
||||||
|
{"S": "0", "A": "2", "B": "5", "C": INF},
|
||||||
|
current="S",
|
||||||
|
active_edge=("S", "A"),
|
||||||
|
step_text="Relaksacja S→A: d[A]=0+2=2. S→B: d[B]=0+5=5.",
|
||||||
|
algo_name="Algorytm Dijkstry",
|
||||||
|
),
|
||||||
|
_make_step(
|
||||||
|
n,
|
||||||
|
e,
|
||||||
|
{"S": "0", "A": "2", "B": "5", "C": "5"},
|
||||||
|
current="A",
|
||||||
|
visited={"S"},
|
||||||
|
active_edge=("A", "C"),
|
||||||
|
step_text="Zamknij S. Min=A(2). Relaksacja A→C: d[C]=2+3=5.",
|
||||||
|
algo_name="Algorytm Dijkstry",
|
||||||
|
),
|
||||||
|
_make_step(
|
||||||
|
n,
|
||||||
|
e,
|
||||||
|
{"S": "0", "A": "2", "B": "5", "C": "5"},
|
||||||
|
current="B",
|
||||||
|
visited={"S", "A"},
|
||||||
|
active_edge=("B", "A"),
|
||||||
|
step_text="Zamknij A. Min=B(5). B→A: 5+1=6>2, nie zmieniaj. B→C: 5+6=11>5.",
|
||||||
|
algo_name="Algorytm Dijkstry",
|
||||||
|
),
|
||||||
|
_make_step(
|
||||||
|
n,
|
||||||
|
e,
|
||||||
|
{"S": "0", "A": "2", "B": "5", "C": "5"},
|
||||||
|
current="C",
|
||||||
|
visited={"S", "A", "B"},
|
||||||
|
step_text="Zamknij B. Min=C(5). Koniec! Wynik: d={S:0, A:2, B:5, C:5}.",
|
||||||
|
algo_name="Dijkstra -- WYNIK",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _bellman_ford_steps() -> list[CompositeVideoClip]:
|
||||||
|
n = NODE_POS
|
||||||
|
e = EDGES_BF
|
||||||
|
return [
|
||||||
|
_make_step(
|
||||||
|
n,
|
||||||
|
e,
|
||||||
|
{"S": "0", "A": INF, "B": INF, "C": INF},
|
||||||
|
step_text="Bellman-Ford: relaksuj WSZYSTKIE krawędzie V-1=3 razy. Ujemne wagi OK!",
|
||||||
|
algo_name="Algorytm Bellmana-Forda",
|
||||||
|
),
|
||||||
|
_make_step(
|
||||||
|
n,
|
||||||
|
e,
|
||||||
|
{"S": "0", "A": "2", "B": "5", "C": "5"},
|
||||||
|
active_edge=("S", "A"),
|
||||||
|
step_text="Iteracja 1: S→A:2, A→C:5, S→B:5. Potem B→A: 5+(-4)=1 < 2 → A=1!",
|
||||||
|
algo_name="Bellman-Ford -- iteracja 1",
|
||||||
|
),
|
||||||
|
_make_step(
|
||||||
|
n,
|
||||||
|
e,
|
||||||
|
{"S": "0", "A": "1", "B": "5", "C": "5"},
|
||||||
|
active_edge=("B", "A"),
|
||||||
|
step_text="B→A z ujemną wagą -4: d[A] poprawione z 2 na 1! (Dijkstra by to pominął!)",
|
||||||
|
algo_name="Bellman-Ford -- ujemna waga",
|
||||||
|
),
|
||||||
|
_make_step(
|
||||||
|
n,
|
||||||
|
e,
|
||||||
|
{"S": "0", "A": "1", "B": "5", "C": "4"},
|
||||||
|
active_edge=("A", "C"),
|
||||||
|
step_text="Iteracja 2: A→C: 1+3=4 < 5 → C=4. Propagacja poprawionego A.",
|
||||||
|
algo_name="Bellman-Ford -- iteracja 2",
|
||||||
|
),
|
||||||
|
_make_step(
|
||||||
|
n,
|
||||||
|
e,
|
||||||
|
{"S": "0", "A": "1", "B": "5", "C": "4"},
|
||||||
|
step_text="Iteracja 3: brak zmian. V-ta iteracja: brak popraw → brak cyklu ujemnego.",
|
||||||
|
algo_name="Bellman-Ford -- WYNIK, O(V*E)",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _astar_steps() -> list[CompositeVideoClip]:
|
||||||
|
n = NODE_POS
|
||||||
|
e = EDGES_DIJKSTRA
|
||||||
|
return [
|
||||||
|
_make_step(
|
||||||
|
n,
|
||||||
|
e,
|
||||||
|
{"S": "0", "A": INF, "B": INF, "C": INF},
|
||||||
|
current="S",
|
||||||
|
step_text="A*: f(n)=g(n)+h(n). Cel=C. h(S)=5, h(A)=3, h(B)=4, h(C)=0. f(S)=0+5=5.",
|
||||||
|
algo_name="Algorytm A*",
|
||||||
|
),
|
||||||
|
_make_step(
|
||||||
|
n,
|
||||||
|
e,
|
||||||
|
{"S": "0", "A": "2", "B": "5", "C": INF},
|
||||||
|
current="S",
|
||||||
|
active_edge=("S", "A"),
|
||||||
|
step_text="Relaksuj S: A(g=2,f=2+3=5), B(g=5,f=5+4=9). Min f → A(5).",
|
||||||
|
algo_name="A* -- rozwijanie S",
|
||||||
|
),
|
||||||
|
_make_step(
|
||||||
|
n,
|
||||||
|
e,
|
||||||
|
{"S": "0", "A": "2", "B": "5", "C": "5"},
|
||||||
|
current="A",
|
||||||
|
visited={"S"},
|
||||||
|
active_edge=("A", "C"),
|
||||||
|
step_text="Rozwiń A(f=5): A→C: g=2+3=5, f=5+0=5. Min f → C(5) = CEL!",
|
||||||
|
algo_name="A* -- rozwijanie A",
|
||||||
|
),
|
||||||
|
_make_step(
|
||||||
|
n,
|
||||||
|
e,
|
||||||
|
{"S": "0", "A": "2", "B": "5", "C": "5"},
|
||||||
|
current="C",
|
||||||
|
visited={"S", "A"},
|
||||||
|
step_text="Dotarliśmy do C! Koszt=5. A* NIE przetwarza B (3 vs 4 w Dijkstrze).",
|
||||||
|
algo_name="A* -- cel osiągnięty!",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _comparison_slide() -> CompositeVideoClip:
|
||||||
|
bg = ColorClip(size=(W, H), color=BG).with_duration(12.0)
|
||||||
|
title = (
|
||||||
|
_tc(
|
||||||
|
text="Porównanie algorytmów",
|
||||||
|
font_size=40,
|
||||||
|
color="white",
|
||||||
|
font=FONT_B,
|
||||||
|
)
|
||||||
|
.with_duration(12.0)
|
||||||
|
.with_position(("center", 40))
|
||||||
|
)
|
||||||
|
rows = [
|
||||||
|
("Cecha", "Dijkstra", "Bellman-Ford", "A*"),
|
||||||
|
("Typ", "Zachłanny", "Prog. dynamiczne", "Heurystyczny"),
|
||||||
|
("Problem", "SSSP", "SSSP", "Single-pair"),
|
||||||
|
("Ujemne wagi", "NIE", "TAK", "NIE"),
|
||||||
|
("Cykl ujemny", "NIE wykrywa", "TAK wykrywa", "NIE"),
|
||||||
|
("Złożoność", "O((V+E)log V)", "O(V*E)", "Zależy od h(n)"),
|
||||||
|
]
|
||||||
|
clips: list[VideoClip] = [bg, title]
|
||||||
|
for i, row in enumerate(rows):
|
||||||
|
y_pos = 120 + i * 85
|
||||||
|
for j, cell in enumerate(row):
|
||||||
|
x_pos = 60 + j * 300
|
||||||
|
fs = 18 if i > 0 else 22
|
||||||
|
color = "#64B5F6" if i == 0 else "#CFD8DC"
|
||||||
|
tc = (
|
||||||
|
_tc(
|
||||||
|
text=cell,
|
||||||
|
font_size=fs,
|
||||||
|
color=color,
|
||||||
|
font=FONT_B if i == 0 else FONT_R,
|
||||||
|
)
|
||||||
|
.with_duration(12.0)
|
||||||
|
.with_position((x_pos, y_pos))
|
||||||
|
)
|
||||||
|
clips.append(tc)
|
||||||
|
return CompositeVideoClip(clips, size=(W, H)).with_effects(
|
||||||
|
[FadeIn(0.5), FadeOut(0.5)]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
"""Generate the Q02 shortest path visualization video."""
|
||||||
|
sections: list[VideoClip] = []
|
||||||
|
|
||||||
|
sections.append(
|
||||||
|
_make_header(
|
||||||
|
"Pytanie 2: Algorytmy najkrótszej ścieżki",
|
||||||
|
"Dijkstra * Bellman-Ford * A*",
|
||||||
|
duration=8.0,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
sections.append(_make_header("Algorytm Dijkstry", "Zachłanny, SSSP, wagi ≥ 0"))
|
||||||
|
sections.extend(_dijkstra_steps())
|
||||||
|
|
||||||
|
sections.append(
|
||||||
|
_make_header("Algorytm Bellmana-Forda", "Prog. dynamiczne, ujemne wagi, O(V·E)")
|
||||||
|
)
|
||||||
|
sections.extend(_bellman_ford_steps())
|
||||||
|
|
||||||
|
sections.append(
|
||||||
|
_make_header("Algorytm A*", "Heurystyczny, f(n)=g(n)+h(n), Single-pair")
|
||||||
|
)
|
||||||
|
sections.extend(_astar_steps())
|
||||||
|
|
||||||
|
sections.append(_comparison_slide())
|
||||||
|
|
||||||
|
sections.append(
|
||||||
|
_make_header(
|
||||||
|
"Podsumowanie",
|
||||||
|
"Dijkstra=chciwy | Bellman-Ford=brute force x(V-1) | A*=Dijkstra+GPS",
|
||||||
|
duration=8.0,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
final = concatenate_videoclips(sections, method="compose")
|
||||||
|
final.write_videofile(
|
||||||
|
OUTPUT, fps=FPS, codec="libx264", audio=False, preset="medium", threads=4
|
||||||
|
)
|
||||||
|
print(f"Video saved to: {OUTPUT}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
1627
python_pkg/praca_magisterska_video/visualize_q23.py
Normal file
1627
python_pkg/praca_magisterska_video/visualize_q23.py
Normal file
File diff suppressed because it is too large
Load Diff
1891
python_pkg/praca_magisterska_video/visualize_q24.py
Normal file
1891
python_pkg/praca_magisterska_video/visualize_q24.py
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user