Подготовка за интервю по компютърно зрение

Въпроси за интервю за инженер по компютърно зрение

15 подбрани въпроса за интервю по компютърно зрение, групирани по ниво на опит. Използвайте ги, за да преговорите основите, практическите компромиси и аргументацията на Senior ниво за производствена среда.

Започнете AI интервю по компютърно зрениеНе се изисква кредитна карта. Налична е 1 безплатна сесия.
Практика за техническо интервю на английски езикРежим, в който хора, за които английският не е роден език, могат да се упражняват за преминаване на технически интервюта.

Въпроси за Junior ниво

1Какво е дискретна 2D конволюция върху изображение и как е свързана с ядра, филтри и кръстосана корелация?

Дискретната 2D конволюция върху изображение е линейна, пространствена филтрираща операция, при която матрица от тегла (ядро) се плъзга по входното изображение. На всяка пространствена позиция, тя изчислява поелементен продукт между теглата на ядрото и припокриващия се локален участък от изображението (рецептивно поле) и сумира резултатите (често добавяйки член на отместване), за да произведе единична стойност на пиксел в изходната карта на признаците (feature map). В класическата математика и обработката на сигнали, истинската конволюция включва обръщане (завъртане на 180 градуса) на ядрото хоризонтално и вертикално, преди да се изчисли плъзгащият се вътрешен продукт: $(I * K)(i, j) = \sum_m \sum_n I(i - m, j - n) K(m, n)$. За разлика от това, кръстосаната корелация изчислява плъзгащия се скаларен продукт директно, без да обръща ядрото: $(I \star K)(i, j) = \sum_m \sum_n I(i + m, j + n) K(m, n)$. Във фреймуъркове за дълбоко обучение като PyTorch и TensorFlow, операцията, имплементирана под името „конволюция“, всъщност е кръстосана корелация. Тъй като теглата на ядрото са обучаеми параметри, оптимизирани директно чрез обратно разпространение (backpropagation), мрежата просто научава подходящо ориентираните тегла, което прави математическото обръщане на ядрото изчислително излишно по време както на обучението, така и на инференцията.

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)
Опитайте да отговорите на този въпрос с треньор с ИИ

2Кога бихте използвали RGB, HSV, Lab, YCbCr или черно-бели (grayscale) представяния в един конвейер за визуална обработка (vision pipeline)?

Различните цветови пространства разделят специфични физически, перцептивни и статистически свойства на визуалните данни, което ги прави подходящи за конкретни визуални задачи: 1. **RGB / BGR**: Стандартен формат за сензори на камери и хардуер за дисплеи, който представя адитивни основни цветове (червено, зелено, синьо). Цветовите канали са силно корелирани с осветеността (промените в осветлението засягат и трите канала едновременно). Това е стандартен вход за дълбоки невронни мрежи (като CNNs, Vision Transformers). Основен производствен проблем е размяната на канали RGB спрямо BGR (напр. OpenCV зарежда BGR, докато PIL/PyTorch очакват RGB), което незабелязано намалява точността на модела. 2. **HSV / HSL**: Изрично разделя хроматичността (Hue = цветови нюанс, Saturation = чистота на цвета) от интензитета (Value/Lightness). Идеален е за класическо цветово праговане, проследяване на обекти по цвят и сегментиране на цветове при променливо осветление, тъй като Hue е относително инвариантен спрямо сенки и промени в яркостта. 3. **CIE Lab**: Перцептивно еднородно цветово пространство, където евклидовото разстояние между две точки ($\Delta E$) директно отразява човешката перцептивна разлика. Той разделя светлината ($L^*$) от цветовите оси на опонентите ($a^*$ зелено-червено, $b^*$ синьо-жълто). Използва се при проверка на цветови разлики, корекция на цветове, оценка на качеството на изображението и задачи по колоризация. 4. **YCbCr**: Разделя осветеността ($Y$) от компонентите на синьо-различната ($Cb$) и червено-различната ($Cr$) хрома. Той е гръбнакът на стандартите за компресия на видео и изображения (JPEG, MPEG, H.264/H.265), тъй като човешката зрителна система е по-малко чувствителна към детайлите на хромата, което позволява подсемплиране на хромата (напр. 4:2:0) за намаляване на пропускателната способност и изчислителната мощност. 5. **Черно-бяло (Grayscale)**: Едноканален интензитет ($Y \approx 0.299R + 0.587G + 0.114B$). Идеален е за задачи, базирани на геометрия и текстура, където цветът е без значение (напр. оптичен поток, SLAM, класическо извличане на признаци като SIFT/ORB, откриване на ръбове), за да спести 66% от паметта и изчислителната мощност. Въпреки това, не трябва да се използва, когато цветът е ключов дискриминативен признак (напр. класификация на състоянието на светофар).

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)
Опитайте да отговорите на този въпрос с треньор с ИИ

3Защо каналната нормализация на изображенията, нормализацията на изображение и последователното мащабиране на пикселите са важни във визуалните системи?

Нормализацията на изображенията е жизненоважна във визуалните пайплайни за поддържане на числова стабилност, ускоряване на сходимостта на градиентния спускане и предотвратяване на изместване на разпределението между тренировка и обслужване (train-serving distribution shift): 1. **Мащабиране на пикселите ([0, 1] или [-1, 1])**: Суровите пиксели на изображението се съхраняват като беззнакови 8-битови цели числа (`uint8` $\in [0, 255]$). Подаването на сурови стойности $[0, 255]$ към невронни мрежи причинява изключително големи ранни активации и експлозия или насищане на градиентите. Мащабирането до $[0.0, 1.0]$ (чрез деление на 255.0) или $[-1.0, 1.0]$ центрира входовете около нула, съобразявайки се със стандартните методи за инициализация на теглата (He, Xavier). 2. **Канална нормализация на ниво набор от данни (стандартизация по средна стойност и стандартно отклонение)**: Стандартизира входовете чрез $x_{norm} = \frac{x - \mu_c}{\sigma_c}$, използвайки предварително изчислени глобални статистики за всеки канал (напр., средна стойност на ImageNet `[0.485, 0.456, 0.406]` и стандартно отклонение `[0.229, 0.224, 0.225]`). Това центрира всеки канал около нулева средна стойност с единична дисперсия в целия набор от данни, осигурявайки балансиран поток на градиенти между каналите. Критична оперативна грешка е **разминаване тренировка-обслужване (train-serving skew)**: ако инференцията пропусне делението на 255, използва несъответстващи константи за средна стойност/стандартно отклонение или ги приложи в грешен ред на каналите (RGB срещу BGR), производителността на модела рязко спада. 3. **Нормализация на изображение (за всяко изображение) (нормализация на инстанциите / Min-Max / Z-оценка)**: Изчислява средната стойност и стандартното отклонение за всяко отделно изображение: $x_{norm} = \frac{x - \mu_{img}}{\sigma_{img}}$. Това премахва глобалните вариации на контраста и осветеността при различни условия на заснемане. Въпреки че е ефективна при пренос на стил, ЯМР/КТ медицински изображения или сателитни изображения, тя може да бъде вредна, когато абсолютната интензивност на пиксела носи физически смисъл (напр. класификация ден срещу нощ, отражателна способност на материала или откриване на дефекти при калибрирано осветление).

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))
Опитайте да отговорите на този въпрос с треньор с ИИ

4Как геометричните и фотометричните аугментации насърчават инвариантността и как решавате дали една аугментация запазва етикетите?

Геометричните аугментации (като ротация, транслация, мащабиране и обръщане) променят пространственото картиране на координатите на пикселите, докато фотометричните аугментации (като яркост, контраст, трептене на нюанса и замъгляване) променят интензитета на пикселите и цветовите канали, без да променят пространствените координати. При класификацията и двете категории излагат модела на реалистични вариации, насърчавайки го да научи инвариантни представяния, където предвижданията остават стабилни въпреки промените в позата, гледната точка или условията на осветление. При задачите за пространствено предсказване (детекция и сегментация) геометричните трансформации изискват съответстващи трансформации към референтните кутии (ground-truth boxes) или маски, за да се запази еквивариантността. Решаването дали дадена аугментация запазва етикетите изисква оценка на домейн семантиката и физическите априорни знания. Една трансформация запазва етикета, ако полученото изображение остава правдоподобен екземпляр на оригиналния целеви клас, без да променя неговата семантична идентичност. Например, хоризонталното обръщане запазва идентичността за общи обекти (като коли или животни), но променя идентичността или прави невалидни символи при оптично разпознаване на символи (OCR) и класификация на цифри. По подобен начин, пълната ротация на 360 градуса е валидна в хистологията или сателитните изображения, където няма канонична ориентация, но е неестествена при автономно шофиране, където обекти с главата надолу нарушават физическите ограничения от реалния свят.

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),
])
Опитайте да отговорите на този въпрос с треньор с ИИ

5Какво представляват интерполациите по най-близък съсед (nearest-neighbor), билинейна (bilinear), бикубична (bicubic) и по площ (area interpolation), и как преоразмеряването може да въведе алиасинг (aliasing) или размазване (blur)?

Методите за интерполация оценяват интензитета на пикселите при непрекъснати координати при пресемплиране върху нова дискретна мрежа: - **Най-близък съсед (Nearest-Neighbor):** Присвоява интензитета на най-близкия дискретен пиксел. Изчислително е $O(1)$ и запазва точните оригинални стойности, но създава блокови артефакти при увеличаване на размера (upsampling) и силно назъбени ръбове при намаляване на размера (downsampling). - **Билинейна интерполация (Bilinear Interpolation):** Изчислява средно аритметично, претеглено по разстояние, на $2 \times 2$ (4 най-близки) съседни пиксела, използвайки линейна интерполация по двете оси, което води до плавни преходи с умерено размазване. - **Бикубична интерполация (Bicubic Interpolation):** Взима проби от съседство $4 \times 4$ (16 най-близки) с помощта на кубични полиномни ядра. Тя улавя локални градиенти на интензитета, произвеждайки по-остри ръбове и по-плавни криви от билинейната, но може да създаде артефакти на превишаване/звънене (overshoot/ringing) около резки преходи. - **Интерполация по площ (Area (Box) Interpolation):** Изчислява частичното припокриване на площта на целеви пиксели, проектирани върху изходни пиксели, и осреднява основните изходни интензитети. Особено ефективна е за намаляване на размера (downsampling) без моаре. **Механизми на алиасинг срещу размазване:** - **Алиасингът (Aliasing)** възниква по време на намаляване на размера (downsampling), когато високочестотното съдържание надвишава честотата на Найкуист ($f_s / 2$) на новата честота на семплиране без достатъчно нискочестотно филтриране. Високите честоти се преобразуват в по-ниски честотни ленти, произвеждайки фалшиви шарки, моаре и назъбени ръбове. - **Размазването (Blur)** възниква, когато интерполационните ядра действат като нискочестотни филтри за изглаждане, които отслабват легитимни високи честоти (напр., билинейно изглаждане) или когато високочестотни детайли се осредняват по време на намаляване на размера (downsampling).

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)
Опитайте да отговорите на този въпрос с треньор с ИИ

6Спрямо какви реални режими на визуална повреда (visual failure modes) трябва да бъде оценявана една визуална система и как те изместват разпределението на входа?

Реалните режими на визуална повреда попадат в няколко широки категории: условия на околната среда (слаба светлина, пряк отблясък, дъжд, сняг, мъгла), сензорни и оптични ефекти (размазване от движение, размазване от дефокусиране, шум от сензор с високо ISO, изкривяване от търкалящ се затвор), деградация от компресия и предаване (блоковост на JPEG, ограничаване на битрейта на видео, намаляване на семплирането) и семантични/пространствени измествания (частично запушване, отрязване, пози на обекти извън разпределението). Тези режими на повреда изместват разпределението на входа далеч от номиналните домейни за обучение на множество нива. На ниво пиксел те променят хистограмите на яркостта и цветността, унищожават локалния контраст, намаляват съотношението сигнал/шум (SNR) и въвеждат изкуствени високочестотни артефакти или ядра за размазване. На структурно и семантично ниво те отслабват градиентите на ръбовете, прикриват ключови визуални характеристики, изкривяват силуетите и скриват критични диагностични региони. Когато модели, обучени на чисти данни, срещнат тези изместени входове, нисконивовите конволюционни или атеншън екстрактори на характеристики не се активират правилно, което води до пропуснати откривания, халюцинирани фалшиви позитиви при шумови модели и прекалено уверени грешки.

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
Опитайте да отговорите на този въпрос с треньор с ИИ

7Как класическите дескриптори като HOG представят структурата на изображението и какви ограничения имат те в сравнение с научените характеристики от CNN?

HOG (Histogram of Oriented Gradients) е класически, ръчно създаден дескриптор на характеристики, предназначен да улавя локалната форма и изглед на обекти чрез разпределението на ориентациите на градиента на интензитета. Процесът на изчисляване на HOG включва: (1) изчисляване на хоризонтални и вертикални градиенти на изображението (напр. с едноизмерни производни филтри `[-1, 0, 1]`), за да се получат големина и ориентация на градиента; (2) разделяне на изображението на малки пространствени региони, наречени „клетки“ (като 8x8 пиксела), и натрупване на едноизмерни хистограми на ориентациите на градиента, претеглени по големината на градиента; (3) групиране на съседни клетки в по-големи, припокриващи се „блокове“ (като 2x2 клетки) и нормализиране на векторните характеристики на блока (използвайки L2-норма или L1-sqrt), за да се постигне устойчивост срещу локални вариации на осветлението, контраста и засенчването; и (4) конкатениране на нормализираните блокови вектори в окончателно едноизмерно представяне на характеристиките, исторически съчетано с линейни SVM (Support Vector Machine) за разпознаване на пешеходци и обекти. В сравнение с научените характеристики от CNN (Convolutional Neural Network), HOG има основни ограничения: Първо, HOG характеристиките са фиксирани и ръчно създадени, улавяйки само нисконивови статистики за ориентацията на локални ръбове без възможност да научават йерархични представяния, специфични за задачата (като текстури, части на обекти и семантични концепции). Второ, HOG предоставя много ограничена геометрична инвариантност: докато нормализацията на блока обработва монотонни промени в осветлението и малки пространствени измествания в клетките, HOG е неустойчив при 3D ротации извън равнината, значителни вариации в мащаба, неригидни артикулации на позата и силен фонов шум. За разлика от това, CNN научават многослойни нелинейни абстракции, оптимизирани от край до край за целевата задача.

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
Опитайте да отговорите на този въпрос с треньор с ИИ

Въпроси за Middle ниво

8Как *stride*, *padding*, *dilation* и размерът на ядрото (*kernel size*) определят размера на изхода на конволюцията и рецептивното поле?

Пространствените изходни измерения на конволюционен слой и неговото кумулативно рецептивно поле се определят от размера на ядрото $k$, стъпката (*stride*) $s$, запълването (*padding*) $p$ и дилатацията (*dilation*) $d$: 1. **Изходен пространствен размер**: Дилатацията въвежда $(d - 1)$ празнини между елементите на ядрото, давайки ефективен размер на ядрото $k' = d(k - 1) + 1$. Изходното измерение по ос е дадено от: $$O = \left\lfloor \frac{I + 2p - k'}{s} \right\rfloor + 1 = \left\lfloor \frac{I + 2p - d(k - 1) - 1}{s} \right\rfloor + 1$$ Тук стъпката (*stride*) намалява резолюцията, като премества ядрото с $s$ пиксела на стъпка, запълването (*padding*) добавя виртуални гранични пиксели за запазване или регулиране на пространствените измерения, а дилатацията (*dilation*) разширява отпечатъка на филтъра, без да добавя допълнителни параметри. 2. **Рецептивно поле (RF)**: Теоретичното рецептивно поле описва пространственото разпространение във входното изображение, което влияе на конкретна активация. При насложени слоеве, рецептивното поле $RF_l$ и кумулативната стъпка (*jump* $j_l$) се актуализират рекурсивно: * Скок (*Jump*): $j_l = j_{l-1} \cdot s_l$, с $j_0 = 1$ * Рецептивно поле (*Receptive Field*): $RF_l = RF_{l-1} + (k'_l - 1) \cdot j_{l-1}$, с $RF_0 = 1$ Стъпките (*strides*) и обединяването (*pooling*) разширяват рецептивното поле мултипликативно по дълбочина, тъй като стъпките на ядрото на следващия слой съответстват на по-големи пикселни скокове в оригиналното входно координатно пространство. Дилатацията разширява рецептивното поле адитивно в рамките на един слой, като увеличава $k'_l$ без пространствено намаляване на мащаба (*downsampling*).

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)
Опитайте да отговорите на този въпрос с треньор с ИИ

9Как конволюционното ядро съответства на класически линеен филтър за изображения и кога един 2D филтър може да бъде направен отделим?

В класическата линейна обработка на сигнали и изображения, конволюционното ядро представлява пространствения импулсен отклик (функция на разпространение на точката) на една LSI (Linear Shift-Invariant – линейна инвариантна спрямо изместване) система. Прилагането на ядрото е операция за линейно филтриране в пространствената област, която оформя честотния отклик на изображението (неговата 2D Фурие трансформация). Нискочестотните ядра (напр. Гаусово, кутия за размазване) отслабват високите пространствени честоти, за да потиснат шума и да изгладят текстурите, докато високочестотните или лентово-пропускащи ядра (напр. Sobel, Laplacian, Prewitt) усилват високите честоти, за да откриват ръбове и градиенти. Едно 2D филтърно ядро $K \in \mathbb{R}^{M \times N}$ е пространствено отделимо, ако може да бъде разложено на външно произведение на два 1D филтъра: $K = u \cdot v^T$, където $u \in \mathbb{R}^{M \times 1}$ и $v \in \mathbb{R}^{N \times 1}$. В терминологията на линейната алгебра, една 2D матрица е отделима тогава и само тогава, когато нейният ранг на матрицата е 1. Това може да бъде проверено чрез SVD (Singular Value Decomposition – декомпозиция по сингулярни стойности), където точно една сингулярна стойност е ненулева ($\sigma_1 > 0, \sigma_2 = \dots = 0$). Отделимостта значително намалява изчислителната сложност. Прилагането на неотделимо ядро $K \times K$ към изображение $H \times W$ изисква $O(H \cdot W \cdot K^2)$ умножения и събирания. Когато е разложено на две последователни 1D преминавания (хоризонтално, след това вертикално), сложността намалява до $O(H \cdot W \cdot 2K)$. За ядро $15 \times 15$, това осигурява приблизително $7.5\times$ ускорение.

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))
Опитайте да отговорите на този въпрос с треньор с ИИ

10Как преоразмеряването, централното изрязване, случайното преоразмерявано изрязване, разтягането и летербоксингът променят входното разпределение?

Различните стратегии за преоразмеряване и изрязване променят разпределението на входните данни по отношение на геометрията, мащаба, покритието на съдържанието и пространствените граници: 1. **Директно преоразмеряване (разтягане/свиване):** Преоразмерява изображението неизотропно до целеви размери $(H, W)$, изкривявайки оригиналните съотношения на страните. Това принуждава модела да обработва деформирани форми на обекти (напр. кръгли обекти стават удължени елипси). 2. **Централно изрязване:** Извлича централен изрез с фиксиран размер или съотношение. То запазва естественото съотношение на страните и локалния мащаб, но въвежда силна централна пристрастност (ако се предполага, че целите са центрирани) и отхвърля периферния контекст или отрязва обекти, позиционирани по краищата. 3. **Случайно преоразмерявано изрязване (RRC - Random-Resized Cropping):** Извлича случайни подрегиони в различни мащаби на площ и съотношения на страните, след което ги преоразмерява до фиксирани размери. Това разширява разпределението на мащаба за обучение и насърчава обучението на характеристики, базирани на части, но екстремни изрязвания могат напълно да изключат целевия обект. 4. **Летербоксинг (Изотропно преоразмеряване с подплънки):** Мащабира изображението равномерно, докато най-дългото му измерение не отговаря на целевия размер, след което запълва останалите граници с постоянна стойност. Той поддържа истински пропорции на обектите и съотношения на страните, но въвежда изкуствени висококонтрастни граници и неинформативни пиксели с подплънки.

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)
Опитайте да отговорите на този въпрос с треньор с ИИ

11Какви режими на шума в анотациите се срещат в набори от данни за компютърно зрение и как бихте ги открили или смекчили?

Наборите от данни за компютърно зрение обикновено проявяват четири основни режима на шума в анотациите: 1. **Шум в категорийните етикети (Categorical Label Noise):** Изображение или обект е присвоен към грешен клас етикет (напр. погрешно класифициране на котка като куче или объркване на фини, детайлни класове). 2. **Липсващи анотации (Шум от пропускане / Omission Noise):** Валидни обекти на преден план са оставени без анотация. При откриването на обекти пропуснатите цели се третират като негативи от фона, директно наказвайки правилните откривания от модела по време на обучение. 3. **Разклащане на ограничителната кутия / ключовите точки (Шум от локализация / Localization Noise):** Неточни, свободни или изместени координати на ограничителна кутия и местоположения на забележителни точки, причинени от непоследователност на човешкия анотатор. 4. **Неяснота на границата на маската (Mask Boundary Ambiguity):** Непоследователни или груби очертания на сегментацията по сложни или размити граници (напр. коса, полупрозрачни повърхности, размазване от движение). **Стратегии за откриване и смекчаване:** * **Проследяване на аномалии в загубата и уверено обучение (Loss Outlier Tracking & Confident Learning):** Проследяването на загубата на извадка през епохите на обучение идентифицира постоянни аномалии с висока загуба, които често показват грешно етикетирани или пропуснати инстанции. Алгоритми като Confident Learning оценяват разпределенията на шума, за да изчистят или коригират грешки. * **Разлики в предсказанията извън сгъвката (Out-of-Fold (OOF) Prediction Discrepancies):** Обучението на модели за кръстосана валидация и сравняването на предсказанията от задържания набор с истинските данни (ground truth) подчертава грешно етикетирани изображения и липсващи ограничителни кутии. * **Съгласие между анотатори (Inter-Annotator Agreement (IAA)) и арбитраж:** Измерването на метрики като Капа на Коен (за класификация) или средна Intersection-over-Union (IoU) (за локализация) между множество анотатори маркира извадки с ниско съгласие за консенсусен преглед. * **Устойчиви формулировки на функцията на загуба:** Използването на изглаждане на етикети (label smoothing), толерантни към шум функции на загуба за класификация или устойчиви IoU-базирани функции на загуба за регресия намалява чувствителността към разклащане и грешно етикетиране.

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]
Опитайте да отговорите на този въпрос с треньор с ИИ

12Как класовият дисбаланс влияе на обучението за класификация на изображения, засичане и плътна прогноза?

Класовият дисбаланс засяга динамиката на оптимизацията и ландшафтите на функцията на загуба при задачи в компютърното зрение: 1. **Класификация на изображения:** Когато доминиращите класове превъзхождат малцинствените класове, емпиричното минимизиране на риска кара актуализациите на параметрите да бъдат доминирани от градиентите на доминиращите класове. Моделът научава емпиричната априорна вероятност на класа $P(Y)$ и измества своята разделителна граница срещу редките класове. Това води до висока обща Top-1 точност, като същевременно страда от сериозен срив на *recall* метриката за малцинствените класове. 2. **Засичане на обекти:** * **Дисбаланс между преден план и фон:** При плътни еднофазни детектори, базирани на котви (anchor-based detectors), стотици хиляди кандидат-местоположения се оценяват за изображение, като над 99% са фон. Дори ако индивидуалните лесни фонови котви водят до малки загуби, техният огромен агрегиран градиент заглушава информативните градиентни сигнали от разредените обекти от предния план. * **Класов дисбаланс на предния план:** Често срещаните категории обекти се появяват порядъци по-често от редките категории, намалявайки ефективния размер на извадката и учебния сигнал за редките класове. 3. **Плътна прогноза (Сегментация):** Дисбалансът на ниво пиксел е сериозен, тъй като големите класове фон и „общ фон“ (напр. път, небе) заемат милиони пиксели, докато малките класове „обекти“ (напр. пътни знаци, пешеходци) могат да заемат по-малко от 0,1% от пикселите на изображението. Стандартната крос-ентропийна загуба за всеки пиксел е доминирана от класовете с голяма площ, което води до под-сегментация или пълно изчезване на малки, тънки структури.

# 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}')
Опитайте да отговорите на този въпрос с треньор с ИИ

Въпроси за Senior ниво

13Как бихте дебъгвали NaN (Not a Number) стойности или нестабилна загуба по време на мащабно разпределено обучение за компютърно зрение?

Дебъгването на NaN стойности или нестабилност на загубата при мащабно Разпределен паралелизъм на данните (DDP) обучение за компютърно зрение изисква изолиране на това дали коренната причина произлиза от лоши входни данни, числено препълване/подпълване при смесена точност (AMP - Automatic Mixed Precision) или нестабилност на оптимизацията между работниците. Първо, установете детерминизъм и защитни куки: активирайте откриване на аномалии (`torch.autograd.set_detect_anomaly(True)`), регистрирайте куки за градиенти/активации, за да уловите точния слой, където се появяват NaN, и добавете стриктна валидация на данните в DataLoader (утвърждаване на крайни стойности, проверка за повредени изображения с размер 0 байта, празни ограничителни кутии или нормализиращи делители с нулева дисперсия). Логването на ниво мини-пакет трябва да проследява загубата за всеки ранг, входни URI, норма на градиента преди отрязване и скалиращи фактори на GradScaler. Второ, инспектирайте автоматичната смесена точност (AMP) и динамичното мащабиране на загубата: в FP16, големите градиенти лесно препълват (`> 65504`), което кара скалерът на загубата да пропуска стъпки и многократно да намалява наполовина своя скалиращ фактор, докато скалата достигне нула; превключването на нестабилни операции (напр. softmax, LayerNorm, експоненти на focal loss или знаменатели на IoU за ограничителни кутии) към FP32 или приемането на BF16 (което съответства на динамичния обхват на FP32) обикновено стабилизира обучението. И накрая, проверете специфични за DDP клопки като операции all-reduce, разпространяващи NaNs от един работник към всички рангове, мащабиране на загряването на скоростта на обучение (напр. правило за линейно мащабиране с големи глобални размери на пакети) и отрязване на градиента.

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()
Опитайте да отговорите на този въпрос с треньор с ИИ

14Как бихте проектирали компонент за онлайн цифрова видео стабилизация за камера с трептене на кадъра?

Проектирането на тръбопровод за онлайн цифрова видео стабилизация за трептене на камера в реално време включва четири основни етапа: оценка на движението, изглаждане на движението, компенсация на движението (деформация) и обработка на границите. 1. **Оценка на движението между кадри:** Извличане на 2D разредени ключови точки в последователни кадри с помощта на бързи детектори на характеристики (напр. ORB, FAST или Shi-Tomasi ъгли) и изчисляване на съответствия с помощта на оптичен поток на Лукас-Канаде или съпоставяне на дескриптори на характеристики. Оценка на геометрична трансформация между кадри (като Афин или Хомографски модел) с помощта на RANSAC за отхвърляне на отклоняващи се съвпадения, причинени от независимо движещи се обекти на преден план. 2. **Натрупване на траектория на движението и онлайн изглаждане:** Интегриране на трансформациите от кадър до кадър във времето, за да се поддържа кумулативната траектория на камерата $P_t = P_{t-1} \cdot H_t$. Прилагане на онлайн филтър за изглаждане – като 1D/2D филтър на Калман или причинно движеща се средна с кратък прозорец – за отделяне на високочестотното нежелано трептене от нискочестотното преднамерено панорамиране на камерата. 3. **Компенсация и деформация на изображението:** Изчисляване на коригиращата трансформация $C_t = S_t \cdot P_t^{-1}$ (където $S_t$ е изгладената траектория) и деформиране на текущия кадър с помощта на билинейна/бикубична интерполация. 4. **Обработка на границите и ограничения на латентността:** Деформацията въвежда липсващи пиксели по ръбовете / черни граници; решаване на това чрез прилагане на фиксиран динамичен коефициент на изрязване (напр. 5-10% увеличение) с екстраполация на границите или адаптивен мащаб. В онлайн среда поддържайте минимално буфериране (1–3 предварителни кадъра), за да ограничите закъснението при обработката.

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
Опитайте да отговорите на този въпрос с треньор с ИИ

15Как бихте оптимизирали визуален поток в реално време, когато детекторът е по-бавен от целевата честота на кадрите?

Когато детекторът на обекти не може да поддържа целевата честота на кадрите (напр. работи с 15-20 FPS при видео подаване от 60 FPS), надеждната производствена архитектура разделя детектирането от цикъла на представяне в реално време, използвайки асинхронен многонишков тръбопровод, съчетаващ тежко детектиране с лек тракинг. В тази хибридна архитектура, нишка за поглъщане непрекъснато заснема видео кадри. Тежкият детектор работи асинхронно като „детектор на ключови кадри“ във фонова работна нишка. Междувременно лек тракер (като оптичен поток на Лукас-Канаде, ByteTrack/BoT-SORT или бърз корелационен филтър/филтър на Калман) работи синхронно за всеки кадър при пълни 60 FPS, поддържайки състоянието и идентичността на обекта, както и плавни траектории на ограничителната кутия. За да се справят със забавянето и обратното налягане, без да се въвежда неограничено закъснение или остарели кадри, се използват ограничени кръгови буфери и политики за отпадане на най-новия кадър. Когато детекторът приключи с кадър T_0 в момент T_curr, неговият изход е остарял. Тръбопроводът извършва пренареждане на координатите / обратна проекция: асоциира забавените резултати от детектиране с историческото състояние на трасирането в T_0, актуализира идентичностите и пропуснатите трасета и разпространява корекциите напред до T_curr чрез векторите на движение при проследяване или стъпките за предсказване на филтъра на Калман.

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)
Опитайте да отговорите на този въпрос с треньор с ИИ