Przygotowanie do rozmów rekrutacyjnych z wizji komputerowej

Pytania Rekrutacyjne dla Inżynierów Wizji Komputerowej

15 wybranych pytań rekrutacyjnych z wizji komputerowej pogrupowanych według poziomu doświadczenia. Użyj ich, aby powtórzyć podstawy, praktyczne kompromisy i rozumowanie na poziomie seniorskim dotyczące produkcji.

Rozpocznij wywiad AI z wizji komputerowejKarta kredytowa nie jest wymagana. Dostępna 1 darmowa sesja.
Trening technicznych rozmów rekrutacyjnych po angielskuTryb dla osób, dla których angielski nie jest językiem ojczystym, do ćwiczenia rozmów technicznych.

Pytania dla Juniorów

1Czym jest dyskretna konwolucja 2D na obrazie i jak odnosi się do jąder (kernels), filtrów i korelacji krzyżowej (cross-correlation)?

Dyskretna konwolucja 2D na obrazie to liniowa operacja filtrowania przestrzennego, w której macierz wag (jądro, ang. kernel) przesuwa się po obrazie wejściowym. W każdej lokalizacji przestrzennej obliczany jest iloczyn element po elemencie między wagami jądra a nakładającą się lokalną łatką obrazu (pole recepcyjne, ang. receptive field), a następnie sumowane są wyniki (często dodawany jest wyraz wolny, ang. bias), aby uzyskać pojedynczą wartość piksela w wyjściowej mapie cech (feature map). W klasycznej matematyce i przetwarzaniu sygnałów prawdziwa konwolucja wymaga odwrócenia (obrotu o 180 stopni) jądra w poziomie i pionie przed obliczeniem przesuwanego iloczynu skalarnego: $(I * K)(i, j) = \sum_m \sum_n I(i - m, j - n) K(m, n)$. Natomiast korelacja krzyżowa (cross-correlation) oblicza iloczyn skalarny bezpośrednio, bez odwracania jądra: $(I \star K)(i, j) = \sum_m \sum_n I(i + m, j + n) K(m, n)$. We frameworkach głębokiego uczenia, takich jak PyTorch i TensorFlow, operacja zaimplementowana pod nazwą „konwolucja” jest w rzeczywistości korelacją krzyżową. Ponieważ wagi jądra są uczonymi parametrami optymalizowanymi bezpośrednio poprzez wsteczną propagację (backpropagation), sieć po prostu uczy się odpowiednio zorientowanych wag, czyniąc matematyczne odwracanie jądra zbędnym obliczeniowo zarówno podczas treningu, jak i inferencji.

import numpy as np
from scipy.signal import convolve2d, correlate2d

image = np.array([[1, 2, 3],
                  [4, 5, 6],
                  [7, 8, 9]], dtype=float)

# Asymmetric directional kernel
kernel = np.array([[1, 0],
                   [0, -1]], dtype=float)

# Convolve flips kernel by 180 degrees: kernel[::-1, ::-1]
conv_res = convolve2d(image, kernel, mode='valid')
corr_res = correlate2d(image, kernel, mode='valid')

print("Convolve output:\n", conv_res)
print("Correlate output:\n", corr_res)
Odpowiedz na to pytanie z trenerem AI

2Kiedy użyłbyś reprezentacji RGB, HSV, Lab, YCbCr lub skali szarości w potoku przetwarzania obrazu (vision pipeline)?

Różne przestrzenie barw rozdzielają specyficzne właściwości fizyczne, percepcyjne i statystyczne danych wizualnych, dzięki czemu nadają się do konkretnych zadań wizyjnych: 1. **RGB / BGR**: Standardowy format dla czujników kamer i sprzętu wyświetlającego, reprezentujący addytywne kolory podstawowe (czerwony, zielony, niebieski). Kanały kolorów są silnie skorelowane z luminancją (zmiany oświetlenia wpływają na wszystkie trzy kanały jednocześnie). Jest to standardowe wejście dla głębokich sieci neuronowych (CNNs, Vision Transformers). Poważnym problemem produkcyjnym jest zamiana kanałów RGB vs BGR (np. OpenCV ładuje BGR, podczas gdy PIL/PyTorch oczekują RGB), co po cichu obniża dokładność modelu. 2. **HSV / HSL**: Jawnie rozdziela chromatyczność (Hue = odcień, Saturation = nasycenie) od intensywności (Value/Lightness). Jest idealny do klasycznego progowania koloru, śledzenia obiektów po kolorze i segmentacji kolorów przy zmiennym oświetleniu, ponieważ odcień jest stosunkowo niewrażliwy na cienie i zmiany jasności. 3. **CIE Lab**: Percepcyjnie jednolita przestrzeń barw, w której odległość euklidesowa między dwoma punktami ($\Delta E$) bezpośrednio odzwierciedla ludzką różnicę percepcyjną. Oddziela jasność ($L^*$) od osi kolorów przeciwstawnych ($a^*$ zielony-czerwony, $b^*$ niebieski-żółty). Używany jest w inspekcji różnic kolorów, korekcji kolorów, ocenie jakości obrazu i zadaniach koloryzacji. 4. **YCbCr**: Oddziela luminancję ($Y$) od składowych chrominancji różnicowych niebieskiego ($Cb$) i czerwonego ($Cr$). Jest podstawą standardów kompresji wideo i obrazu (JPEG, MPEG, H.264/H.265), ponieważ ludzki układ wzrokowy jest mniej wrażliwy na szczegóły chrominancji, co pozwala na subsampling chrominancji (np. 4:2:0) w celu zmniejszenia przepustowości i obciążenia obliczeniowego. 5. **Skala szarości (Grayscale)**: Reprezentacja intensywności jednokanałowej ($Y \approx 0.299R + 0.587G + 0.114B$). Jest idealna do zadań opartych na geometrii i teksturze, gdzie kolor jest nieistotny (np. przepływ optyczny (optical flow), SLAM, klasyczne ekstrakcje cech takie jak SIFT/ORB, detekcja krawędzi) w celu zaoszczędzenia 66% pamięci i mocy obliczeniowej. Jednak nie należy jej używać, gdy kolor jest kluczową wskazówką rozróżniającą (np. klasyfikacja stanu sygnalizacji świetlnej).

import cv2
import numpy as np

# Sample image: saturated red under normal and shaded lighting
img_bgr = np.zeros((100, 100, 3), dtype=np.uint8)
img_bgr[:, :50] = [0, 0, 255]    # Bright Red in BGR
img_bgr[:, 50:] = [0, 0, 120]    # Shaded/Dark Red in BGR

# Convert to HSV (OpenCV H: 0-179, S: 0-255, V: 0-255)
img_hsv = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV)

# Red Hue wraps around 0/180; track red with single Hue range [0, 10] & high saturation
lower_red = np.array([0, 100, 50])
upper_red = np.array([10, 255, 255])
mask_hsv = cv2.inRange(img_hsv, lower_red, upper_red)

print("HSV segmented pixels:", np.count_nonzero(mask_hsv), "out of", mask_hsv.size)
Odpowiedz na to pytanie z trenerem AI

3Dlaczego normalizacja obrazu kanałami, normalizacja dla każdego obrazu i spójne skalowanie pikseli są ważne w systemach wizyjnych?

Normalizacja obrazu jest kluczowa w potokach wizyjnych do utrzymania stabilności numerycznej, przyspieszenia zbieżności algorytmu gradientu prostego i zapobiegania przesunięciu rozkładu danych między treningiem a obsługą (train-serving distribution shift): 1. **Skalowanie Pikseli ([0, 1] lub [-1, 1])**: Surowe piksele obrazu są przechowywane jako 8-bitowe liczby całkowite bez znaku (`uint8` $\in [0, 255]$). Podawanie surowych wartości $[0, 255]$ do sieci neuronowych powoduje ekstremalnie duże wczesne aktywacje oraz eksplozję lub nasycenie gradientu. Skalowanie do $[0.0, 1.0]$ (dzieląc przez 255.0) lub $[-1.0, 1.0]$ centruje dane wejściowe wokół zera, co jest zgodne ze standardowymi metodami inicjalizacji wag (He, Xavier). 2. **Normalizacja Kanałowa na Poziomie Zestawu Danych (Standaryzacja Średnia-Odchylenie Standardowe)**: Standaryzuje dane wejściowe za pomocą $x_{norm} = \frac{x - \mu_c}{\sigma_c}$ wykorzystując wstępnie obliczone globalne statystyki dla każdego kanału (np. średnia ImageNet `[0.485, 0.456, 0.406]` i odchylenie standardowe `[0.229, 0.224, 0.225]`). Centruje to każdy kanał wokół zerowej średniej z jednostkową wariancją w całym zestawie danych, zapewniając zrównoważony przepływ gradientu przez kanały. Krytycznym błędem operacyjnym jest **rozbieżność między treningiem a obsługą (train-serving skew)**: jeśli wnioskowanie pomija dzielenie przez 255, używa niedopasowanych stałych średniej/odchylenia standardowego lub stosuje je w niewłaściwej kolejności kanałów (RGB vs BGR), wydajność modelu drastycznie spada. 3. **Normalizacja dla Każdego Obrazu (Normalizacja Instancyjna / Min-Max / Z-score)**: Oblicza średnią i odchylenie standardowe dla każdego indywidualnego obrazu: $x_{norm} = \frac{x - \mu_{img}}{\sigma_{img}}$. Eliminuje to globalne wahania kontrastu i oświetlenia w różnych warunkach przechwytywania. Chociaż skuteczna w przenoszeniu stylu, obrazowaniu medycznym (MRI/CT) lub zdjęciach satelitarnych, może być szkodliwa, gdy bezwzględna intensywność pikseli ma fizyczne znaczenie (np. klasyfikacja dzień/noc, odbiciowość materiału lub wykrywanie defektów w kalibrowanym oświetleniu).

import torch
import torchvision.transforms as T
from PIL import Image
import numpy as np

# Create a mock uint8 RGB image
raw_img = Image.fromarray(np.random.randint(0, 256, (224, 224, 3), dtype=np.uint8))

# Standard transform pipeline:
# 1. ToTensor scales uint8 [0, 255] -> float32 [0.0, 1.0] and permutes HWC -> CHW
# 2. Normalize standardizes per-channel using dataset mean and std
preprocess = T.Compose([
    T.ToTensor(),
    T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

tensor_img = preprocess(raw_img)
print("Tensor shape:", tensor_img.shape)
print("Tensor dtype:", tensor_img.dtype)
print("Channel 0 min/max:", round(tensor_img[0].min().item(), 2), round(tensor_img[0].max().item(), 2))
Odpowiedz na to pytanie z trenerem AI

4W jaki sposób augmentacje geometryczne i fotometryczne sprzyjają niezmiennictwu, i jak decydujesz, czy augmentacja zachowuje etykiety?

Augmentacje geometryczne (takie jak rotacja, translacja, skalowanie i odbicie (flipping)) zmieniają mapowanie współrzędnych przestrzennych pikseli, podczas gdy augmentacje fotometryczne (takie jak jasność, kontrast, jitter barwy (hue jitter) i rozmycie) zmieniają intensywność pikseli i kanały kolorów bez modyfikowania współrzędnych przestrzennych. W klasyfikacji, obie kategorie wystawiają model na realistyczne wariacje, zachęcając go do nauki niezmienniczych reprezentacji, gdzie predykcje pozostają stabilne pomimo zmian w pozycji, punkcie widzenia lub warunkach oświetleniowych. W zadaniach predykcji przestrzennej (detekcji i segmentacji) transformacje geometryczne wymagają dopasowania transformacji do ramek referencyjnych (ground-truth boxes) lub masek, aby zachować ekwiwariantność. Decyzja, czy augmentacja zachowuje etykiety, wymaga oceny semantyki dziedziny i fizycznych założeń (priors). Transformacja zachowuje etykietę, jeśli wynikowy obraz pozostaje wiarygodną instancją oryginalnej klasy docelowej bez zmiany jej tożsamości semantycznej. Na przykład, poziome odbicie (horizontal flipping) zachowuje tożsamość dla ogólnych obiektów (takich jak samochody czy zwierzęta), ale zmienia tożsamość lub unieważnia znaki w optycznym rozpoznawaniu znaków (OCR) i klasyfikacji cyfr. Podobnie, pełny obrót o 360 stopni jest prawidłowy w histologii lub zdjęciach satelitarnych, gdzie nie istnieje kanoniczna orientacja, ale nienaturalny w autonomicznym prowadzeniu pojazdów, gdzie obiekty do góry nogami naruszają rzeczywiste ograniczenia fizyczne.

import albumentations as A

# Natural scene classification / object detection: horizontal flip is valid
natural_scene_transform = A.Compose([
    A.HorizontalFlip(p=0.5),
    A.RandomBrightnessContrast(p=0.2),
], bbox_params=A.BboxParams(format='pascal_voc', label_fields=['category_ids']))

# OCR / Digit classification: horizontal flip corrupts semantics
ocr_transform = A.Compose([
    A.Affine(scale=(0.9, 1.1), rotate=(-10, 10), p=0.5),
    A.ColorJitter(brightness=0.2, contrast=0.2, p=0.5),
])
Odpowiedz na to pytanie z trenerem AI

5Czym są interpolacja najbliższego sąsiada, biliniowa, bikubiczna i obszarowa, oraz jak zmiana rozmiaru może wprowadzić aliasing (błędy schodkowe) lub rozmycie?

Metody interpolacji szacują intensywność pikseli we współrzędnych ciągłych podczas reprobkowania na nową dyskretną siatkę: - **Interpolacja najbliższego sąsiada (Nearest-Neighbor):** Przypisuje intensywność najbliższego dyskretnego piksela. Jest obliczeniowo $O(1)$ i zachowuje dokładne oryginalne wartości, ale tworzy blokowe artefakty podczas zwiększania rozdzielczości (upsampling) oraz silne postrzępione krawędzie podczas zmniejszania rozdzielczości (downsampling). - **Interpolacja biliniowa (Bilinear Interpolation):** Oblicza średnią ważoną odległością dla $2 \times 2$ (4 najbliższych) pikseli sąsiadujących, wykorzystując interpolację liniową wzdłuż obu osi, co daje płynne przejścia z umiarkowanym rozmyciem. - **Interpolacja bikubiczna (Bicubic Interpolation):** Próbkuje $4 \times 4$ (16 najbliższych) sąsiadujących pikseli, używając jąder wielomianowych trzeciego rzędu (cubic polynomial kernels). Przechwytuje lokalne gradienty intensywności, tworząc ostrzejsze krawędzie i płynniejsze krzywe niż interpolacja biliniowa, ale może generować artefakty przeregulowania/dzwonienia (overshoot/ringing) wokół ostrych przejść. - **Interpolacja obszarowa (Area (Box) Interpolation):** Oblicza częściowe pokrycie obszaru pikseli docelowych rzutowanych na piksele źródłowe i uśrednia bazowe intensywności źródłowe. Jest szczególnie skuteczna dla zmniejszania rozdzielczości (downsampling) bez efektu mory. **Mechanika Aliasingu (Błędów Schodkowych) a Rozmycia:** - **Aliasing (błędy schodkowe)** występuje podczas zmniejszania rozdzielczości (downsampling), gdy zawartość wysokich częstotliwości przekracza częstotliwość Nyquista ($f_s / 2$) nowej częstotliwości próbkowania bez wystarczającego filtrowania dolnoprzepustowego. Wysokie częstotliwości składają się z powrotem w niższe pasma częstotliwości, tworząc fałszywe wzory, mory i postrzępione krawędzie. - **Rozmycie** występuje, gdy jądra interpolacji działają jako dolnoprzepustowe filtry wygładzające, które tłumią rzeczywiste wysokie częstotliwości (np. wygładzanie biliniowe) lub gdy szczegóły o wysokiej częstotliwości są uśredniane podczas zmniejszania rozdzielczości.

import cv2

# Severe downsampling: area downsampling prevents aliasing
img = cv2.imread('high_res_pattern.jpg')
downscaled_area = cv2.resize(img, (400, 300), interpolation=cv2.INTER_AREA)
downscaled_nn = cv2.resize(img, (400, 300), interpolation=cv2.INTER_NEAREST)  # heavy aliasing/moiré

# Upscaling: bicubic preserves edge sharpness
upscaled_cubic = cv2.resize(downscaled_area, (400, 400), interpolation=cv2.INTER_CUBIC)
upscaled_linear = cv2.resize(downscaled_area, (400, 400), interpolation=cv2.INTER_LINEAR)
Odpowiedz na to pytanie z trenerem AI

6Jakie rzeczywiste tryby awarii wizualnych (failure modes) powinien uwzględniać system wizyjny podczas oceny i w jaki sposób zmieniają one rozkład danych wejściowych?

Rzeczywiste tryby awarii wizualnych (failure modes) dzielą się na kilka szerokich kategorii: warunki środowiskowe (słabe oświetlenie, bezpośrednie odblaski, deszcz, śnieg, mgła), efekty sensoryczne i optyczne (rozmycie spowodowane ruchem, rozmycie spowodowane utratą ostrości, szum matrycy przy wysokim ISO, zniekształcenie z efektem rolling shutter), degradacja spowodowana kompresją i transmisją (artefakty blokowe JPEG, ograniczenie przepustowości strumienia wideo, próbkowanie w dół (downsampling)) oraz przesunięcia semantyczne/przestrzenne (częściowe zasłonięcie, ucięcie, pozycje obiektów spoza rozkładu treningowego (out-of-distribution)). Te tryby awarii przesuwają rozkład danych wejściowych poza nominalne domeny treningowe na wielu poziomach. Na poziomie pikseli zmieniają histogramy luminancji i chrominancji, niszczą lokalny kontrast, redukują stosunek sygnału do szumu (SNR) oraz wprowadzają sztuczne artefakty wysokich częstotliwości lub jądra rozmycia (blur kernels). Na poziomie strukturalnym i semantycznym osłabiają gradienty krawędzi, zaciemniają kluczowe cechy wizualne, zniekształcają sylwetki i ukrywają krytyczne obszary diagnostyczne. Gdy modele trenowane na czystych danych napotykają te przesunięte dane wejściowe, niskopoziomowe ekstraktery cech konwolucyjnych lub uwagi nie aktywują się poprawnie, co powoduje pominięte detekcje, halucynowane fałszywe pozytywy na wzorcach szumu i nadmiernie pewne błędy.

Category              | Physical Mechanism             | Input Shift & Feature Impact
-----------------------------------------------------------------------------------------
Low Light / Noise     | Low photon flux, high sensor gain | Drops SNR, suppresses gradient edges, fires spurious texture filters
Motion / Defocus Blur | Scene/camera motion, out-of-focus| Attenuates high spatial frequencies, smears object boundaries
Adverse Weather/Glare | Fog/rain scattering, lens flare  | Compresses dynamic range, lowers contrast, masks semantic regions
Occlusion / Cutoff    | Physical foreground obstructions | Masks critical keypoints/parts, breaks holistic spatial priors
Compression Artifacts | Block DCT quantization, subsampling| Introduces 8x8 grid boundaries, removes fine textural details
Odpowiedz na to pytanie z trenerem AI

7W jaki sposób klasyczne deskryptory, takie jak HOG (Histogram of Oriented Gradients), reprezentują strukturę obrazu i jakie mają ograniczenia w porównaniu z nauczonymi cechami sieci konwolucyjnych (CNN)?

Histogram Skierowanych Gradientów (HOG) to klasyczny, ręcznie tworzony deskryptor cech, zaprojektowany do uchwycenia lokalnego kształtu i wyglądu obiektu poprzez rozkład orientacji gradientów intensywności. Potok obliczeń HOG obejmuje: (1) obliczanie poziomych i pionowych gradientów obrazu (np. za pomocą jednowymiarowych filtrów pochodnych [-1, 0, 1]) w celu uzyskania wielkości i orientacji gradientu; (2) podział obrazu na małe obszary przestrzenne zwane „komórkami” (np. 8x8 pikseli) i akumulowanie jednowymiarowych histogramów orientacji gradientów ważonych wielkością gradientu; (3) grupowanie sąsiednich komórek w większe, nakładające się „bloki” (np. 2x2 komórki) i normalizowanie wektorów cech bloku (przy użyciu normy L2 lub pierwiastka z normy L1) w celu osiągnięcia odporności na lokalne zmiany oświetlenia, kontrastu i zacienienia; oraz (4) konkatenację znormalizowanych wektorów bloków w końcową jednowymiarową reprezentację cech, historycznie parowaną z liniowymi maszynami wektorów nośnych (SVM) do wykrywania pieszych i obiektów. W porównaniu z nauczonymi cechami konwolucyjnych sieci neuronowych (CNN), HOG ma poważne ograniczenia: Po pierwsze, cechy HOG są stałe i ręcznie tworzone, wychwytując jedynie niskopoziomowe statystyki orientacji krawędzi bez możliwości uczenia się hierarchicznych reprezentacji specyficznych dla zadania (takich jak tekstury, części obiektów i koncepcje semantyczne). Po drugie, HOG zapewnia bardzo ograniczoną niezmienniczość geometryczną: podczas gdy normalizacja bloków radzi sobie z monotonicznymi zmianami oświetlenia i małymi przesunięciami przestrzennymi w komórkach, HOG jest wrażliwy na obroty w 3D poza płaszczyzną, znaczące zmiany skali, niestabilne artykulacje pozy i duże zaśmiecenie tła. W przeciwieństwie do tego, sieci CNN uczą się wielowarstwowych nieliniowych abstrakcji zoptymalizowanych kompleksowo (end-to-end) dla docelowego zadania.

Input Image Patch (e.g., 64x128)
  │
  ▼
[ 1. Compute Gradients ] ──> Gx, Gy -> Mag = sqrt(Gx^2 + Gy^2), Angle = atan2(Gy, Gx)
  │
  ▼
[ 2. Cell Histograms   ] ──> 8x8 pixel cells accumulate 9-bin orientation histograms (0°-180°)
  │
  ▼
[ 3. Block Normalization] ──> Overlapping 2x2 cell blocks normalized (e.g., v / sqrt(||v||_2^2 + eps^2))
  │
  ▼
[ 4. Feature Vector    ] ──> Concatenate block vectors -> Feed to Linear SVM / Classifier
Odpowiedz na to pytanie z trenerem AI

Pytania dla Midów

8Jak krok (stride), dopełnienie (padding), dylatacja (dilation) i rozmiar jądra określają rozmiar wyjściowy konwolucji oraz pole recepcyjne?

Przestrzenne wymiary wyjścia warstwy konwolucyjnej i jej skumulowane pole recepcyjne są określane przez rozmiar jądra $k$, krok $s$, dopełnienie $p$ i dylatację $d$: 1. **Przestrzenny rozmiar wyjściowy**: Dylatacja wprowadza $(d - 1)$ szczelin między elementami jądra, dając efektywny rozmiar jądra $k' = d(k - 1) + 1$. Wymiar wyjściowy wzdłuż osi jest dany wzorem: $$O = \left\lfloor \frac{I + 2p - k'}{s} \right\rfloor + 1 = \left\lfloor \frac{I + 2p - d(k - 1) - 1}{s} \right\rfloor + 1$$ Tutaj, krok zmniejsza rozdzielczość, przesuwając jądro o $s$ pikseli na krok, dopełnienie dodaje wirtualne piksele brzegowe, aby zachować lub dostosować wymiary przestrzenne, a dylatacja rozszerza zasięg filtra bez dodawania dodatkowych parametrów. 2. **Pole recepcyjne (RF)**: Teoretyczne pole recepcyjne opisuje zakres przestrzenny w obrazie wejściowym, który wpływa na daną aktywację. W obrębie ułożonych warstw, pole recepcyjne $RF_l$ i skumulowany krok (skok $j_l$) aktualizują się rekurencyjnie: * Skok: $j_l = j_{l-1} \cdot s_l$, gdzie $j_0 = 1$ * Pole recepcyjne: $RF_l = RF_{l-1} + (k'_l - 1) \cdot j_{l-1}$, gdzie $RF_0 = 1$ Kroki i pooling rozszerzają pole recepcyjne multiplikatywnie w głąb sieci, ponieważ kroki jądra kolejnej warstwy odpowiadają większym skokom pikseli w oryginalnej przestrzeni współrzędnych wejściowych. Dylatacja rozszerza pole recepcyjne addytywnie w ramach pojedynczej warstwy, zwiększając $k'_l$ bez przestrzennego zmniejszania próbkowania.

def compute_conv_output_and_rf(layers, input_size=224):
    current_size = input_size
    rf = 1
    jump = 1
    
    for idx, layer in enumerate(layers, 1):
        k, s, p, d = layer['k'], layer['s'], layer['p'], layer['d']
        k_eff = d * (k - 1) + 1
        current_size = ((current_size + 2 * p - k_eff) // s) + 1
        rf = rf + (k_eff - 1) * jump
        jump = jump * s
        print(f"Layer {idx}: Out={current_size}x{current_size}, RF={rf}x{rf}, Jump={jump}")

layers = [
    {'k': 3, 's': 1, 'p': 1, 'd': 1},  # Conv1: 3x3 standard
    {'k': 3, 's': 2, 'p': 1, 'd': 1},  # Conv2: 3x3 strided
    {'k': 3, 's': 1, 'p': 2, 'd': 2},  # Conv3: 3x3 dilated (d=2)
]
compute_conv_output_and_rf(layers, 224)
Odpowiedz na to pytanie z trenerem AI

9W jaki sposób jądro splotu odpowiada klasycznemu liniowemu filtrowi obrazu i kiedy filtr 2D może być rozdzielny?

W klasycznym przetwarzaniu sygnałów i obrazów liniowych, jądro splotu reprezentuje przestrzenną odpowiedź impulsową (funkcję rozmycia punktowego) liniowego systemu niezmiennego przestrzennie (LSI). Zastosowanie jądra jest liniową operacją filtrowania w dziedzinie przestrzennej, która kształtuje odpowiedź częstotliwościową obrazu (jego dwuwymiarową transformatę Fouriera). Jądra dolnoprzepustowe (np. Gaussowskie, rozmycie pudełkowe) tłumią wysokie częstotliwości przestrzenne, aby tłumić szumy i wygładzać tekstury, podczas gdy jądra górnoprzepustowe lub pasmowoprzepustowe (np. Sobel, Laplacian, Prewitt) wzmacniają wysokie częstotliwości w celu wykrywania krawędzi i gradientów. Jądro filtra 2D $K \in \mathbb{R}^{M \times N}$ jest przestrzennie rozdzielne, jeśli może zostać rozłożone na iloczyn zewnętrzny dwóch filtrów 1D: $K = u \cdot v^T$, gdzie $u \in \mathbb{R}^{M \times 1}$ i $v \in \mathbb{R}^{N \times 1}$. W terminach algebry liniowej, macierz 2D jest rozdzielna wtedy i tylko wtedy, gdy jej ranga wynosi 1. Można to zweryfikować za pomocą rozkładu według wartości osobliwych (SVD), gdzie dokładnie jedna wartość osobliwa jest niezerowa ($ \sigma_1 > 0, \sigma_2 = \dots = 0$). Rozdzielność znacząco redukuje złożoność obliczeniową. Zastosowanie nierozdzielnego jądra o rozmiarze $K \times K$ do obrazu o rozmiarze $H \times W$ wymaga $O(H \cdot W \cdot K^2)$ mnożeń i dodawań. Po rozłożeniu na dwa kolejne przejścia 1D (najpierw poziome, potem pionowe), złożoność spada do $O(H \cdot W \cdot 2K)$. Dla jądra $15 \times 15$ zapewnia to około 7,5-krotne przyspieszenie.

import numpy as np

# Gaussian kernel is rank-1 (separable)
gaussian_1d = np.array([1, 2, 1], dtype=float)[:, None]
gaussian_2d = gaussian_1d @ gaussian_1d.T
gaussian_2d /= gaussian_2d.sum()

# Sobel-X kernel is rank-1 (separable)
sobel_x = np.array([[-1, 0, 1],
                    [-2, 0, 2],
                    [-1, 0, 1]], dtype=float)

# Diagonal kernel is rank-2 (non-separable)
non_sep = np.array([[1, 0],
                    [0, 1]], dtype=float)

print("Gaussian rank:", np.linalg.matrix_rank(gaussian_2d))
print("Sobel-X rank:", np.linalg.matrix_rank(sobel_x))
print("Diagonal rank:", np.linalg.matrix_rank(non_sep))

# SVD decomposition for Sobel-X
U, S, Vt = np.linalg.svd(sobel_x)
print("Sobel-X singular values:", np.round(S, 4))
Odpowiedz na to pytanie z trenerem AI

10Jak zmiana rozmiaru (resizing), kadrowanie centralne (center cropping), losowe kadrowanie ze zmianą rozmiaru (random-resized cropping), rozciąganie (stretching) i letterboxing zmieniają rozkład danych wejściowych?

Różne strategie zmiany rozmiaru i kadrowania zmieniają rozkład danych wejściowych w zakresie geometrii, skali, pokrycia treści i granic przestrzennych: 1. **Bezpośrednia zmiana rozmiaru (rozciąganie/ściskanie):** Zmienia skalę obrazu nieizotropowo do docelowego wymiaru $(H, W)$, zniekształcając oryginalne proporcje obrazu. Zmusza to model do przetwarzania zdeformowanych kształtów obiektów (np. okrągłe obiekty stają się wydłużonymi elipsami). 2. **Kadrowanie centralne:** Wyodrębnia centralny wycinek o stałym rozmiarze lub proporcjach. Zachowuje natywne proporcje obrazu i lokalną skalę, ale wprowadza silną tendencję do środka (zakładając, że obiekty docelowe są kadrowane centralnie) i odrzuca kontekst peryferyjny lub odcina obiekty umieszczone na krawędziach. 3. **Losowe kadrowanie ze zmianą rozmiaru (RRC):** Wyodrębnia losowe podobszary o zmiennych skalach powierzchni i proporcjach obrazu, a następnie zmienia ich rozmiar do stałego wymiaru. Rozszerza to rozkład skali treningowej i zachęca do uczenia cech opartych na częściach, ale ekstremalne kadrowania mogą całkowicie wykluczyć obiekt docelowy. 4. **Letterboxing (izotropowa zmiana rozmiaru z wypełnieniem):** Skaluje obraz równomiernie, aż jego najdłuższy wymiar pasuje do docelowego rozmiaru, a następnie wypełnia pozostałe obramowania stałą wartością. Utrzymuje to prawdziwe proporcje obiektów i proporcje obrazu, ale wprowadza sztuczne, wysokokontrastowe obramowania i nieinformatywne piksele wypełniające.

import cv2
import numpy as np

def letterbox_image(image, target_size=(640, 640), pad_color=(114, 114, 114)):
    h, w = image.shape[:2]
    target_w, target_h = target_size
    scale = min(target_w / w, target_h / h)
    new_w, new_h = int(w * scale), int(h * scale)
    
    resized = cv2.resize(image, (new_w, new_h), interpolation=cv2.INTER_LINEAR)
    canvas = np.full((target_h, target_w, 3), pad_color, dtype=np.uint8)
    
    top = (target_h - new_h) // 2
    left = (target_w - new_w) // 2
    canvas[top:top+new_h, left:left+new_w] = resized
    return canvas, scale, (left, top)
Odpowiedz na to pytanie z trenerem AI

11Jakie tryby szumu w adnotacjach występują w zbiorach danych wizualnych (vision datasets) i jak można je wykryć lub złagodzić?

Zbiory danych wizualnych (computer vision datasets) powszechnie wykazują cztery główne tryby szumu w adnotacjach: 1. **Szum Kategoryzacji Etykiet (Categorical Label Noise):** Obrazowi lub obiektowi przypisano niewłaściwą etykietę klasy (np. błędne zaklasyfikowanie kota jako psa, lub pomylenie subtelnych, drobnoziarnistych klas). 2. **Brakujące Adnotacje (Missing Annotations / Omission Noise):** Ważne obiekty pierwszoplanowe są pozostawione bez adnotacji. W detekcji obiektów, pominięte obiekty są traktowane jako negatywy tła, bezpośrednio penalizując poprawne detekcje modelu podczas trenowania. 3. **Drgania Ramki Ograniczającej / Punktu Kluczowego (Bounding-Box / Keypoint Jitter / Localization Noise):** Nieprecyzyjne, luźne lub przesunięte współrzędne ramek ograniczających (bounding box) i lokalizacje punktów kluczowych (keypointów) spowodowane niespójnością ludzkich adnotatorów. 4. **Niejasność Granic Maski (Mask Boundary Ambiguity):** Niespójne lub zgrubne obrysy segmentacji wzdłuż złożonych lub rozmytych granic (np. włosy, powierzchnie półprzezroczyste, rozmycie ruchu). **Strategie Wykrywania i Łagodzenia:** * **Śledzenie Wartości Odstających Stracie (Loss Outlier Tracking) i Uczenie Pewne (Confident Learning):** Śledzenie wartości funkcji straty (loss) dla próbek w kolejnych epokach treningu identyfikuje trwałe wartości odstające o wysokiej stracie, które często wskazują na błędnie oznaczone lub pominięte instancje. Algorytmy takie jak Confident Learning szacują rozkłady szumu w celu usunięcia lub skorygowania błędów. * **Niespójności Przewidywań Walidacji Krzyżowej (Out-of-Fold (OOF) Prediction Discrepancies):** Trenowanie modeli walidacji krzyżowej (cross-validation) i porównywanie przewidywań dla zbiorów testowych (holdout) z rzeczywistymi danymi (ground truth) uwidacznia błędnie oznaczone obrazy i brakujące ramki ograniczające. * **Zgodność Między Adnotatorami (Inter-Annotator Agreement - IAA) i Rozstrzyganie Sporów (Adjudication):** Mierzenie metryk takich jak Kappa Cohena (dla klasyfikacji) lub średnie Intersection-over-Union (dla lokalizacji) pomiędzy wieloma adnotatorami oznacza próbki o niskiej zgodności do przeglądu konsensusu. * **Solidne Formułowanie Funkcji Straty (Robust Loss Formulations):** Stosowanie wygładzania etykiet (label smoothing), funkcji straty klasyfikacji odpornych na szum (noise-tolerant classification losses) lub solidnych funkcji straty regresji opartych na IoU (robust IoU-based regression losses) zmniejsza wrażliwość na drgania i błędne oznaczanie.

import torch
import torch.nn.functional as F

def find_label_noise_candidates(model, dataloader, device, top_k=50):
    model.eval()
    sample_losses = []
    
    with torch.no_grad():
        for batch_idx, (images, targets, sample_ids) in enumerate(dataloader):
            images, targets = images.to(device), targets.to(device)
            logits = model(images)
            loss_per_sample = F.cross_entropy(logits, targets, reduction='none')
            
            for sid, l, target, pred in zip(sample_ids, loss_per_sample.cpu(), targets.cpu(), logits.argmax(dim=-1).cpu()):
                sample_losses.append({'id': sid, 'loss': l.item(), 'target': target.item(), 'pred': pred.item()})
                
    sample_losses.sort(key=lambda x: x['loss'], reverse=True)
    return sample_losses[:top_k]
Odpowiedz na to pytanie z trenerem AI

12Jak nierównowaga klas wpływa na trening klasyfikacji obrazów, detekcji i gęstej predykcji?

Nierównowaga klas wpływa na dynamikę optymalizacji i krajobrazy funkcji straty (loss landscapes) w różnych zadaniach wizyjnych: 1. **Klasyfikacja obrazów:** Gdy klasy większościowe przewyższają liczebnie klasy mniejszościowe, empiryczna minimalizacja ryzyka sprawia, że aktualizacje parametrów są zdominowane przez gradienty klas większościowych. Model uczy się empirycznego rozkładu a priori klasy $P(Y)$ i przechyla swoją granicę decyzyjną na niekorzyść rzadkich klas. Prowadzi to do wysokiej ogólnej dokładności top-1, ale jednocześnie do poważnego załamania (spadku) recall dla klas mniejszościowych. 2. **Detekcja obiektów:** * *Nierównowaga tło-pierwszy plan:* W gęstych detektorach jednoetapowych i opartych na kotwicach (anchor-based detectors) setki tysięcy kandydatów lokalizacji są oceniane na obraz, gdzie >99% to tło. Nawet jeśli pojedyncze, łatwe kotwice tła generują małe straty, ich ogromny skumulowany gradient zagłusza informacyjne sygnały gradientowe pochodzące od rzadkich obiektów pierwszego planu. * *Nierównowaga klas obiektów pierwszego planu:* Powszechne kategorie obiektów pojawiają się o rzędy wielkości częściej niż rzadkie kategorie, zmniejszając efektywną wielkość próbki i sygnał uczenia dla rzadkich klas. 3. **Gęsta predykcja (segmentacja):** Nierównowaga na poziomie pikseli jest poważna, ponieważ duże klasy tła i obiektów masowych ('stuff' classes) (np. droga, niebo) zajmują miliony pikseli, podczas gdy małe klasy obiektów indywidualnych ('thing' classes) (np. znaki drogowe, piesi) mogą zajmować mniej niż 0,1% pikseli obrazu. Standardowa strata entropii krzyżowej dla każdego piksela jest zdominowana przez klasy o dużej powierzchni, co prowadzi do niedosegmentacji lub całkowitego zanikania małych, cienkich struktur.

# Illustrating cumulative gradient dominance by background anchors
import torch

num_bg_anchors = 100_000
num_fg_anchors = 10

bg_loss_per_sample = torch.tensor(0.001, requires_grad=True)
fg_loss_per_sample = torch.tensor(1.5, requires_grad=True)

total_loss = (num_bg_anchors * bg_loss_per_sample) + (num_fg_anchors * fg_loss_per_sample)
total_loss.backward()

print(f'Total BG gradient contribution: {num_bg_anchors * bg_loss_per_sample.grad.item():.1f}')
print(f'Total FG gradient contribution: {num_fg_anchors * fg_loss_per_sample.grad.item():.1f}')
Odpowiedz na to pytanie z trenerem AI

Pytania dla Seniorów

13Jak debugowałbyś wartości NaN (Not a Number) lub niestabilną funkcję straty podczas rozproszonego trenowania wizji na dużą skalę?

Debugowanie wartości NaN (Not a Number) lub niestabilności funkcji straty w rozproszonym trenowaniu równoległym danych (Distributed Data Parallel – DDP) wizji na dużą skalę wymaga wyizolowania, czy pierwotna przyczyna pochodzi ze złych danych wejściowych, numerycznego przepełnienia/niedomiaru (overflow/underflow) w mieszanej precyzji (Automatic Mixed Precision – AMP), czy niestabilności optymalizacji wśród procesów roboczych. Po pierwsze, ustal determinizm i punkty zaczepienia bezpieczeństwa: włącz wykrywanie anomalii (`torch.autograd.set_detect_anomaly(True)`), zarejestruj punkty zaczepienia gradientów/aktywacji, aby wykryć dokładną warstwę, w której pojawiają się NaN, i dodaj ścisłą walidację danych w `DataLoader` (sprawdzanie wartości skończonych, szukanie uszkodzonych obrazów 0-bajtowych, pustych ramek ograniczających lub dzielników normalizacji o zerowej wariancji). Logowanie na poziomie mini-paczki powinno śledzić stratę dla każdej rangi, identyfikatory URI wejść, normę gradientu przed obcięciem i współczynniki skali `GradScaler`. Po drugie, sprawdź automatyczną mieszaną precyzję (AMP) i dynamiczne skalowanie strat: w FP16 duże gradienty łatwo się przepełniają (`> 65504`), powodując, że skalujący stratę pomija kroki i wielokrotnie zmniejsza swój współczynnik skali, aż skala osiągnie zero; przełączenie niestabilnych operacji (np. `softmax`, `LayerNorm`, wykładniki funkcji straty `focal loss` lub mianowniki IoU (Intersection over Union) ramek ograniczających) na FP32 lub zastosowanie BF16 (które odpowiada zakresowi dynamicznemu FP32) zazwyczaj stabilizuje trenowanie. Na koniec, sprawdź pułapki specyficzne dla DDP, takie jak operacje `all-reduce` propagujące NaN z pojedynczego procesu roboczego do wszystkich rang, skalowanie rozgrzewki współczynnika uczenia (np. reguła skalowania liniowego z dużymi globalnymi rozmiarami wsadowymi) oraz obcinanie gradientów (gradient clipping).

import torch
from torch.cuda.amp import autocast, GradScaler

scaler = GradScaler()
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4)

for step, (images, targets, uris) in enumerate(dataloader):
    # 1. Input sanitization
    if not torch.isfinite(images).all():
        print(f"Corrupt input detected from URIs: {uris}")
        continue
        
    optimizer.zero_grad(set_to_none=True)
    
    with autocast(dtype=torch.float16):
        outputs = model(images)
        loss = criterion(outputs, targets)
    
    if not torch.isfinite(loss):
        print(f"NaN/Inf loss at step {step} on rank {torch.distributed.get_rank()}; skipping step.")
        continue
        
    scaler.scale(loss).backward()
    
    # Unscale before clipping to inspect true gradient norms
    scaler.unscale_(optimizer)
    grad_norm = torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
    
    if torch.isnan(grad_norm) or torch.isinf(grad_norm):
        print(f"Warning: Grad norm is {grad_norm}. Scaler will adjust.")
        
    scaler.step(optimizer)
    scaler.update()
Odpowiedz na to pytanie z trenerem AI

14Jak zaprojektować komponent cyfrowej stabilizacji wideo działający online dla kamery z drżeniem klatki?

Projektowanie potoku cyfrowej stabilizacji wideo działającego online dla drżenia kamery w czasie rzeczywistym obejmuje cztery główne etapy: estymację ruchu, wygładzanie ruchu, kompensację ruchu (deformowanie / warping) i obsługę obramowań. 1. **Estymacja ruchu międzyklatkowego**: Ekstrahuj rzadkie punkty kluczowe 2D w kolejnych klatkach, używając szybkich detektorów cech (np. ORB, FAST lub Shi-Tomasi corners) i oblicz korespondencje, używając przepływu optycznego Lucasa-Kanade lub dopasowywania deskryptorów cech. Oszacuj geometryczną transformację międzyklatkową (taką jak model afiniczny lub homograficzny), używając RANSAC, aby odrzucić dopasowania odstające spowodowane przez niezależnie poruszające się obiekty pierwszego planu. 2. **Akumulacja trajektorii ruchu i wygładzanie online**: Zintegruj transformacje klatka po klatce w czasie, aby utrzymać skumulowaną ścieżkę kamery $P_t = P_{t-1} \cdot H_t$. Zastosuj filtr wygładzający działający online — taki jak filtr Kalmana 1D/2D lub przyczynowa średnia ruchoma z krótkiego okna — aby oddzielić niezamierzone drżenie o wysokiej częstotliwości od zamierzonego panoramowania kamery o niskiej częstotliwości. 3. **Kompensacja i deformowanie obrazu**: Oblicz transformację korekcyjną $C_t = S_t \cdot P_t^{-1}$ (gdzie $S_t$ jest wygładzoną ścieżką) i zdeformuj bieżącą klatkę, używając interpolacji bilinearnej/bikubicznej. 4. **Obsługa obramowań i ograniczenia opóźnienia**: Deformowanie wprowadza brakujące piksele krawędzi / czarne obramowania; rozwiąż to, stosując stały dynamiczny współczynnik przycięcia (np. 5-10% powiększenie) z ekstrapolacją obramowania lub adaptacyjną skalą. W środowisku online utrzymuj minimalne buforowanie (1–3 klatki wyprzedzające), aby ograniczyć opóźnienie przetwarzania.

import cv2
import numpy as np

class OnlineStabilizer:
    def __init__(self, crop_ratio=0.9):
        self.prev_gray = None
        self.smoothed_x = 0.0
        self.smoothed_y = 0.0
        self.smoothed_a = 0.0
        self.alpha = 0.85  # Low-pass filter smoothing coefficient
        self.crop_ratio = crop_ratio

    def process_frame(self, frame):
        curr_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        h, w = curr_gray.shape
        
        if self.prev_gray is None:
            self.prev_gray = curr_gray
            return frame
            
        # 1. Feature detection & tracking
        p0 = cv2.goodFeaturesToTrack(self.prev_gray, maxCorners=200, qualityLevel=0.01, minDistance=30)
        p1, status, _ = cv2.calcOpticalFlowPyrLK(self.prev_gray, curr_gray, p0, None)
        
        good_p0 = p0[status == 1]
        good_p1 = p1[status == 1]
        
        # 2. Estimate rigid motion (dx, dy, da) with RANSAC
        T, inliers = cv2.estimateAffinePartial2D(good_p0, good_p1, method=cv2.RANSAC)
        if T is None: 
            return frame
            
        dx = T[0, 2]
        dy = T[1, 2]
        da = np.arctan2(T[1, 0], T[0, 0])
        
        # 3. Online low-pass filtering of jitter
        self.smoothed_x = self.alpha * self.smoothed_x + (1 - self.alpha) * dx
        self.smoothed_y = self.alpha * self.smoothed_y + (1 - self.alpha) * dy
        self.smoothed_a = self.alpha * self.smoothed_a + (1 - self.alpha) * da
        
        diff_x = self.smoothed_x - dx
        diff_y = self.smoothed_y - dy
        diff_a = self.smoothed_a - da
        
        # 4. Warp and crop frame
        M = np.array([
            [np.cos(diff_a), -np.sin(diff_a), diff_x],
            [np.sin(diff_a),  np.cos(diff_a), diff_y]
        ])
        stabilized = cv2.warpAffine(frame, M, (w, h))
        
        # Center-crop to remove boundary artifacts
        cw, ch = int(w * self.crop_ratio), int(h * self.crop_ratio)
        x1, y1 = (w - cw) // 2, (h - ch) // 2
        stabilized = cv2.resize(stabilized[y1:y1+ch, x1:x1+cw], (w, h))
        
        self.prev_gray = curr_gray
        return stabilized
Odpowiedz na to pytanie z trenerem AI

15Jak zoptymalizować potok wizyjny czasu rzeczywistego, gdy detektor jest wolniejszy niż docelowa liczba klatek na sekundę?

Gdy detektor obiektów nie jest w stanie utrzymać docelowej liczby klatek na sekundę (FPS) (np. działa z prędkością 15-20 FPS przy strumieniu wideo 60 FPS), solidna architektura produkcyjna oddziela detekcję od pętli prezentacji w czasie rzeczywistym, wykorzystując asynchroniczny, wielowątkowy potok, który łączy wymagającą detekcję z lekkim śledzeniem. W tej hybrydowej architekturze wątek pobierający dane (ingestion thread) ciągle przechwytuje klatki wideo. Wymagający detektor działa asynchronicznie jako „detektor klatek kluczowych” (keyframe detector) w tle na wątku roboczym. W międzyczasie lekki tracker (taki jak optyczny przepływ Lucasa-Kanade'a, ByteTrack/BoT-SORT lub szybki filtr korelacyjny/filtr Kalmana) działa synchronicznie na każdej klatce z pełnymi 60 FPS, utrzymując stan obiektu, tożsamość i płynne trajektorie ramek ograniczających. Aby poradzić sobie z opóźnieniami (lag) i oporem wstecznym (backpressure) bez wprowadzania nieograniczonego opóźnienia lub nieaktualnych klatek, stosuje się ograniczone bufory cykliczne (bounded ring buffers) i polityki odrzucania najnowszych klatek (latest-frame drop policies). Kiedy detektor kończy przetwarzanie klatki T_0 w czasie T_curr, jego wynik jest nieaktualny. Potok wykonuje ponowne dopasowanie współrzędnych/reprojekcję (coordinate realignment/back-projection): kojarzy opóźnione wyniki detekcji z historycznym stanem ścieżki śledzenia (tracklet state) w T_0, aktualizuje tożsamości i pominięte ścieżki, a następnie propaguje korekty do T_curr za pomocą wektorów ruchu śledzenia lub kroków predykcji filtra Kalmana.

import queue
import threading

frame_queue = queue.Queue(maxsize=1)  # Drop stale frames, keep latest
det_result_queue = queue.Queue()

def detector_worker():
    while True:
        frame, frame_id, timestamp = frame_queue.get()
        boxes, scores, classes = heavy_detector.infer(frame)
        det_result_queue.put({'frame_id': frame_id, 'boxes': boxes, 'timestamp': timestamp})

def realtime_pipeline(video_stream):
    tracker = FastTracker()  # e.g. Optical flow or Kalman tracker
    
    for frame, frame_id, timestamp in video_stream:
        if frame_queue.empty():
            frame_queue.put((frame, frame_id, timestamp))
            
        if not det_result_queue.empty():
            det_result = det_result_queue.get()
            tracker.reconcile_and_correct(det_result, current_frame_id=frame_id)
            
        active_tracks = tracker.update(frame)
        display_or_downstream(frame, active_tracks)
Odpowiedz na to pytanie z trenerem AI