Подготовка к собеседованию по компьютерному зрению

Вопросы для собеседования инженера по компьютерному зрению

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

Начать ИИ-собеседование по компьютерному зрениюКредитная карта не требуется. Доступна 1 бесплатная сессия.
Практика технических собеседований на английскомРежим, в котором не носители языка могут потренироваться проходить интервью.

Вопросы для Junior-специалистов

1Что такое дискретная двумерная свёртка на изображении, и как она связана с ядрами, фильтрами и кросс-корреляцией?

Дискретная двумерная свёртка на изображении — это линейная операция пространственной фильтрации, при которой матрица весов (ядро) скользит по входному изображению. В каждой пространственной позиции она вычисляет поэлементное произведение между весами ядра и перекрывающимся локальным фрагментом изображения (рецептивным полем), суммирует результаты (часто добавляя член смещения) для получения одного значения пикселя в выходной карте признаков. В классической математике и обработке сигналов истинная свёртка включает переворот (поворот на 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, операция, реализованная под названием «свёртка», на самом деле является кросс-корреляцией. Поскольку веса ядра являются обучаемыми параметрами, оптимизируемыми напрямую с помощью обратного распространения ошибки, сеть просто изучает соответствующим образом ориентированные веса, что делает математический переворот ядра избыточным с вычислительной точки зрения как во время обучения, так и во время инференса.

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)
Попробовать ответить на вопрос AI-тренеру

2В каких случаях в конвейере компьютерного зрения следует использовать представления RGB, HSV, Lab, YCbCr или градации серого (grayscale)?

Используйте представление, которое сохраняет необходимые для задачи признаки и соответствует предварительной обработке данных при обучении модели. RGB (красный, зелёный и синий каналы) общепринят для обучаемых моделей; многие API в OpenCV используют BGR (синий, зелёный и красный каналы), поэтому проверяйте порядок каналов, а не предполагайте, что фреймворк обеспечивает его автоматически. HSV (тон, насыщенность, значение) и HSL (тон, насыщенность, светлота) могут быть удобны для цветовой пороговой фильтрации, но цветовой тон (hue) нестабилен вблизи нулевой насыщенности и, как правило, не инвариантен к изменениям освещения или баланса белого. CIE Lab разделяет светлоту и координаты оппонентных цветов и приблизительно перцептивно равномерен при заданных предположениях об условиях просмотра и опорном белом; евклидово расстояние Delta E 1976 является лишь приближённой мерой визуального различия. YCbCr отделяет яркость Y' от каналов цветоразности и поддерживает субдискретизацию цветности (chroma subsampling) во многих форматах изображений и видео; коэффициенты, диапазон и функции передачи зависят от стандарта. Градации серого полезны, когда цвет не имеет значения: они сокращают объем памяти для хранения несжатых каналов с трех до одного при той же точности, но не гарантируют сокращение общих вычислений модели на две трети. Сохраняйте цвет, если он несёт информацию о метках, и проверяйте соглашения о масштабировании и преобразовании.

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)
Попробовать ответить на вопрос AI-тренеру

3Почему в системах компьютерного зрения важны поканальная нормализация изображений, нормализация каждого отдельного изображения и согласованное масштабирование пикселей?

Используйте диапазон пикселей, порядок каналов и нормализацию, ожидаемые обученной моделью, согласованно как при обучении, так и на этапе вывода (inference). Для 8-битных изображений деление на 255 отображает значения в диапазон [0,1], что масштабирует их, но не центрирует относительно нуля; отображение в [-1,1] центрирует диапазон, но не обязательно среднее значение данных. Изображения также могут использовать другую целочисленную разрядность или физические единицы с плавающей точкой. Большие немасштабированные значения могут ухудшить обусловленность оптимизации, но не приводят неизбежно к взрыву градиентов. Поканальная стандартизация датасета использует формулу (x-mu_c)/sigma_c со статистиками обучающей выборки или заданными константами предобученной модели; она обеспечивает нулевое среднее и единичную дисперсию на том распределении, по которому они оценивались, а не на каждом новом изображении или датасете. Стандартизация по z-оценке отдельного изображения использует среднее и стандартное отклонение этого конкретного изображения с защитой от околонулевой дисперсии; масштабирование min-max — это другая операция. InstanceNorm — это слой нейронной сети, который обычно нормализует каждый пример и канал по пространственным осям, а не синоним любой предобработки отдельных изображений. Нормализация каждого изображения по его собственным статистикам может уменьшить вариации яркости и контраста, но способна удалить полезную информацию об абсолютной интенсивности, поэтому проверяйте её целесообразность для конкретной задачи.

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))
Попробовать ответить на вопрос AI-тренеру

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),
])
Попробовать ответить на вопрос AI-тренеру

5Что такое интерполяция ближайшего соседа, билинейная, бикубическая интерполяция и интерполяция по площади, и как изменение размера может привести к эффекту наложения частот (aliasing) или размытию?

Методы интерполяции оценивают интенсивность пикселей в непрерывных координатах при повторной дискретизации на новую дискретную сетку: - **Интерполяция ближайшего соседа:** Присваивает интенсивность ближайшего дискретного пикселя. Вычислительная сложность составляет $O(1)$, сохраняет точные исходные значения, но создает блочные артефакты при увеличении разрешения (upsampling) и сильные зазубренные края при уменьшении разрешения (downsampling). - **Билинейная интерполяция:** Вычисляет взвешенное по расстоянию среднее значение из $2 \times 2$ (4 ближайших) соседних пикселей, используя линейную интерполяцию вдоль обеих осей, что обеспечивает плавные переходы с умеренным размытием. - **Бикубическая интерполяция:** Выбирает окрестность $4 \times 4$ (16 ближайших) пикселей, используя кубические полиномиальные ядра. Она захватывает локальные градиенты интенсивности, создавая более резкие края и более плавные кривые, чем билинейная, но может вызывать артефакты перерегулирования/звона (overshoot/ringing) вокруг резких переходов. - **Интерполяция по площади (Box):** Вычисляет дробное перекрытие площади целевых пикселей, проецируемых на исходные пиксели, и усредняет базовые исходные интенсивности. Особенно эффективна для уменьшения разрешения без эффекта муара. **Механика эффекта наложения частот (алиасинга) против размытия:** - **Алиасинг** возникает при уменьшении разрешения, когда высокочастотное содержимое превышает частоту Найквиста ($f_s / 2$) новой частоты дискретизации без достаточной низкочастотной фильтрации. Высокие частоты «сворачиваются» (fold back) в более низкие частотные диапазоны, создавая ложные узоры, муар и зазубренные края. - **Размытие** происходит, когда интерполяционные ядра действуют как низкочастотные сглаживающие фильтры, ослабляющие легитимные высокие частоты (например, билинейное сглаживание), или когда высокочастотные детали усредняются при уменьшении разрешения.

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)
Попробовать ответить на вопрос AI-тренеру

6По отношению к каким реальным режимам отказа визуальных систем следует оценивать систему машинного зрения, и как они смещают входное распределение?

Режимы отказа визуальных систем в реальном мире делятся на несколько широких категорий: условия окружающей среды (низкая освещенность, прямые блики, дождь, снег, туман), сенсорные и оптические эффекты (размытие движения, расфокусировка, шум сенсора при высоком ISO, искажение от эффекта `rolling shutter`), деградация при сжатии и передаче (блочность 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
Попробовать ответить на вопрос AI-тренеру

7Как классические дескрипторы, такие как HOG (Histogram of Oriented Gradients), представляют структуру изображения, и какие ограничения они имеют по сравнению с признаками, извлекаемыми свёрточными нейронными сетями (CNN)?

HOG (Histogram of Oriented Gradients) — это классический дескриптор признаков, разработанный вручную для захвата локальной формы и внешнего вида объектов через распределение ориентаций градиентов интенсивности. Конвейер вычисления HOG включает: (1) вычисление горизонтальных и вертикальных градиентов изображения (например, с одномерными производными фильтрами `[-1, 0, 1]`) для получения величины и ориентации градиента; (2) деление изображения на небольшие пространственные области, называемые «ячейками» (например, 8x8 пикселей), и накопление одномерных гистограмм ориентаций градиентов, взвешенных по величине градиента; (3) группировка соседних ячеек в более крупные перекрывающиеся «блоки» (например, 2x2 ячейки) и нормализация векторов признаков блоков (с использованием L2-нормы или L1-sqrt) для достижения устойчивости к локальным изменениям освещенности, контрастности и теней; (4) конкатенация нормализованных векторов блоков в конечное одномерное представление признаков, исторически использовавшееся в паре с линейными SVM (Support Vector Machine) для обнаружения пешеходов и объектов. По сравнению с признаками, извлекаемыми свёрточными нейронными сетями (CNN), HOG имеет серьезные ограничения: Во-первых, признаки HOG фиксированы и созданы вручную, они захватывают только низкоуровневую статистику ориентации локальных границ без возможности изучения иерархических представлений, специфичных для задачи (таких как текстуры, части объектов и семантические концепции). Во-вторых, HOG обеспечивает очень ограниченную геометрическую инвариантность: хотя нормализация блоков обрабатывает монотонные изменения освещенности и небольшие пространственные сдвиги внутри ячеек, HOG неустойчив при трёхмерных поворотах вне плоскости, значительных изменениях масштаба, нежёстких изменениях позы и сильном фоновом шуме. В отличие от этого, 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
Попробовать ответить на вопрос AI-тренеру

Вопросы для Middle-специалистов

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

Пространственные размеры выхода сверточного слоя и его кумулятивное рецептивное поле определяются размером ядра $k$, шагом $s$, дополнением $p$ и диляцией $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$$ Здесь шаг уменьшает разрешение, перемещая ядро на $s$ пикселей за один шаг, дополнение добавляет виртуальные граничные пиксели для сохранения или корректировки пространственных размеров, а диляция расширяет область действия фильтра без добавления дополнительных параметров. 2. **Рецептивное поле (RF)**: Теоретическое рецептивное поле описывает пространственную протяженность во входном изображении, которая влияет на конкретную активацию. В последовательных слоях рецептивное поле $RF_l$ и кумулятивный шаг (прыжок $j_l$) обновляются рекурсивно: * Прыжок: $j_l = j_{l-1} \cdot s_l$, с $j_0 = 1$ * Рецептивное поле: $RF_l = RF_{l-1} + (k'_l - 1) \cdot j_{l-1}$, с $RF_0 = 1$ Шаги и пулинг мультипликативно расширяют рецептивное поле по глубине, потому что шаги ядра последующих слоев соответствуют большим скачкам пикселей в исходном координатном пространстве входа. Диляция аддитивно расширяет рецептивное поле в пределах одного слоя за счет увеличения $k'_l$ без уменьшения пространственного разрешения.

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)
Попробовать ответить на вопрос AI-тренеру

9Как ядро свертки соотносится с классическим линейным фильтром изображений, и когда двумерный (2D) фильтр может быть сделан разделимым?

В классической обработке линейных сигналов и изображений ядро свертки представляет собой пространственную импульсную характеристику (функцию рассеяния точки) линейной сдвигово-инвариантной (ЛСИ) системы. Применение ядра является операцией линейной фильтрации в пространственной области, которая формирует частотную характеристику изображения (его двумерное преобразование Фурье). Низкочастотные ядра (например, Гаусса, усредняющий фильтр) ослабляют высокие пространственные частоты для подавления шума и сглаживания текстур, тогда как высокочастотные или полосовые ядра (например, Собеля, Лапласиана, Прюитта) усиливают высокие частоты для обнаружения границ и градиентов. Двумерное ядро фильтра $K \in \mathbb{R}^{M \times N}$ является пространственно разделимым, если его можно разложить на внешнее произведение двух одномерных фильтров: $K = u \cdot v^T$, где $u \in \mathbb{R}^{M \times 1}$ и $v \in \mathbb{R}^{N \times 1}$. В терминах линейной алгебры, двумерная матрица разделима тогда и только тогда, когда ее ранг равен 1. Это можно проверить с помощью сингулярного разложения (SVD), когда только одно сингулярное число является ненулевым ($ \sigma_1 > 0, \sigma_2 = \dots = 0$). Разделимость значительно снижает вычислительную сложность. Применение неразделимого ядра $K \times K$ к изображению $H \times W$ требует $O(H \cdot W \cdot K^2)$ умножений и сложений. При разложении на два последовательных одномерных прохода (сначала горизонтальный, затем вертикальный) сложность снижается до $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))
Попробовать ответить на вопрос AI-тренеру

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

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

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)
Попробовать ответить на вопрос AI-тренеру

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

Наборы данных компьютерного зрения обычно демонстрируют четыре основных типа шума в разметке: 1. **Шум в категориальных метках (Categorical Label Noise):** Изображению или объекту присваивается неверная метка класса (например, ошибочная классификация кошки как собаки, или путаница тонких мелкозернистых классов). 2. **Пропущенные аннотации (Missing Annotations / Omission Noise):** Действительные объекты переднего плана остаются без аннотации. При обнаружении объектов пропущенные цели трактуются как отрицательные фоновые элементы, что напрямую штрафует правильные обнаружения модели во время обучения. 3. **Дрожание ограничивающих рамок / ключевых точек (Bounding-Box / Keypoint Jitter / 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) и разрешение разногласий (Adjudication):** Измерение метрик, таких как коэффициент Каппа Коэна (Cohen's Kappa) для классификации или среднее пересечение-по-объединению (mean Intersection-over-Union, IoU) для локализации, между несколькими аннотаторами выделяет образцы с низким уровнем согласованности для совместного рассмотрения. - **Робастные формулировки функций потерь (Robust Loss Formulations):** Использование сглаживания меток (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]
Попробовать ответить на вопрос AI-тренеру

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

Дисбаланс классов влияет на динамику оптимизации и ландшафты функции потерь в задачах компьютерного зрения: 1. **Классификация изображений:** Когда мажоритарные классы численно превосходят миноритарные классы, минимизация эмпирического риска приводит к тому, что обновления параметров доминируются градиентами мажоритарного класса. Модель изучает эмпирическое априорное распределение классов $P(Y)$ и смещает свою границу принятия решений против редких классов. Это приводит к высокой общей точности top-1 при катастрофическом снижении полноты для миноритарных классов. 2. **Обнаружение объектов:** - *Дисбаланс между передним планом и фоном:* В плотных одноэтапных и анкерных детекторах сотни тысяч кандидатных областей оцениваются для каждого изображения, где >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}')
Попробовать ответить на вопрос AI-тренеру

Вопросы для Senior-специалистов

13Как бы вы отлаживали появление значений NaN или нестабильность функции потерь при масштабном распределённом обучении моделей компьютерного зрения?

Отладка значений NaN или нестабильности функции потерь при масштабном обучении моделей компьютерного зрения с использованием распределённого параллелизма по данным (DDP) требует изоляции первопричины: вызвана ли она некорректными входными данными, численным переполнением или округлением слишком малых значений до нуля при автоматическом использовании смешанной точности (AMP) или нестабильностью оптимизации между рабочими процессами. Во-первых, зафиксируйте воспроизводимые входные данные, начальные состояния генераторов случайных чисел и конфигурацию и добавьте диагностические хуки (само по себе обнаружение аномалий не делает выполнение детерминированным): включите детектор аномалий (`torch.autograd.set_detect_anomaly(True)`), зарегистрируйте хуки на градиенты и активации, чтобы отследить точный слой, где появляются NaN, и внедрите строгую валидацию данных в DataLoader (проверка на конечность значений, выявление поврежденных файлов нулевого размера, пустых ограничивающих рамок или нулевой дисперсии в делителях при нормализации). Логирование на уровне мини-батчей должно отслеживать потери по каждому рангу, URI входных данных, норму градиентов до клиппинга и коэффициенты масштабирования GradScaler. Во-вторых, проверьте работу автоматического использования смешанной точности (AMP) и динамического масштабирования потерь: в FP16 большие градиенты легко приводят к переполнению (`> 65504`), в результате чего GradScaler пропускает некорректные шаги обновления и умножает масштаб на заданный коэффициент уменьшения. Устойчивое падение коэффициента масштабирования является симптомом проблемы, требующим исследования, а не нормальной сходимостью к нулю; перевод нестабильных операций (например, softmax, LayerNorm, экспонент в focal loss или знаменателей IoU для рамок) в FP32 или переход на BF16 (чей динамический диапазон совпадает с FP32) обычно стабилизирует обучение. Наконец, проверьте специфичные для DDP проблемы: операции all-reduce, распространяющие NaN с одного рабочего процесса на все ранги, масштабирование темпа обучения при прогреве (warmup, например, линейное правило масштабирования при больших глобальных размерах батча) и выполнение клиппинга градиентов строго после отмены масштабирования (unscaling) градиентов AMP. Пустые аннотации могут являться валидными отрицательными примерами, поэтому сверяйте их с контрактом задачи, а не отбрасывайте любое изображение без объектов.

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()
Попробовать ответить на вопрос AI-тренеру

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

Оцените робастное межкадровое движение по отслеживаемым углам или сопоставленным дескрипторам, исключая движение объектов переднего плана и выбирая подходящую модель движения: сдвиг (translation), подобие (similarity), аффинную модель или гомографию. Явно определите направления преобразований: если используются векторы-столбцы и матрица H_t отображает координаты кадра t-1 в кадр t, траектория от опорного кадра к текущему выражается как P_t = H_t * P_{t-1}. Сглаживайте эту траекторию до S_t с помощью каузального фильтра, использующего только текущие и прошлые кадры, или ограниченного окна с будущими кадрами (lookahead), корректно обрабатывая вращения и преобразования, а не просто усредняя матричные элементы. Прямое корректирующее преобразование, переводящее координаты текущего кадра в стабилизированный, задается как C_t = S_t * inverse(P_t); передавайте само преобразование или обратное к нему в зависимости от соглашения о выборке координат в API трансформации (warp API). Ограничьте величину обрезки/зума (crop/zoom) и силу стабилизации для обработки недопустимых границ; сбрасывайте или восстанавливайте состояние при срыве трекинга и смене планов сцены. Сильный параллакс и эффект построчного считывания при скользящем затворе (rolling shutter) могут потребовать более сложных моделей движения или поддержки инерциальных датчиков. Строго каузальная реализация не использует будущие кадры; каждый кадр в окне lookahead увеличивает задержку захвата, поэтому выбирайте глубину буферизации и политику кадрирования на основе измеренного качества выходного видео и требований к задержке.

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
Попробовать ответить на вопрос AI-тренеру

15Как бы вы оптимизировали конвейер компьютерного зрения реального времени, если детектор работает медленнее целевой частоты кадров?

Когда детектор объектов не может поддерживать целевую частоту кадров (например, выдаёт 15–20 кадров в секунду (FPS) на видеопотоке в 60 FPS), надёжная архитектура для рабочей среды отделяет детекцию от цикла отображения реального времени с помощью асинхронного многопоточного конвейера, сочетающего тяжёлую детекцию с лёгким трекингом. В такой гибридной архитектуре поток захвата непрерывно считывает видеокадры. Тяжёлый детектор выполняется асинхронно в качестве «детектора опорных кадров» в фоновом рабочем потоке. В то же время лёгкий трекер (например, на основе оптического потока Лукаса — Канаде или корреляционного фильтра, опционально совмещённый с предсказанием фильтра Калмана) выполняется синхронно на каждом кадре на полных 60 FPS, поддерживая предсказанное состояние объектов между обновлениями детектора. Системы ByteTrack/BoT-SORT работают по принципу отслеживания на основе детекции (tracking-by-detection): при отсутствии новых детекций им требуется явная стратегия предсказания или визуального трекинга, при этом необходимо отслеживать возраст треков, неопределённость и полноту обнаружения (recall) новых объектов. Для обработки отставания и ограничения поступления данных при перегрузке (backpressure) без создания неограниченной задержки или накопления устаревших кадров используются ограниченные кольцевые буферы и стратегии сброса кадров в пользу самого свежего. Когда детектор завершает обработку кадра 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)
Попробовать ответить на вопрос AI-тренеру