Preparación para entrevistas de Visión por Computadora

Preguntas de Entrevista para Ingenieros de Visión por Computadora

15 preguntas seleccionadas de entrevista sobre visión por computadora, agrupadas por nivel de experiencia. Úsalas para repasar los fundamentos, las compensaciones prácticas y el razonamiento de producción a nivel senior.

Iniciar una Entrevista de IA de Visión por ComputadoraNo se requiere tarjeta de crédito. 1 sesión gratuita disponible.
Práctica de entrevistas técnicas en inglésDiseñado para hablantes no nativos que desean practicar entrevistas técnicas en inglés.

Preguntas para Junior

1¿Qué es una convolución 2D discreta en una imagen y cómo se relaciona con los kernels, filtros y la correlación cruzada?

La convolución 2D discreta en una imagen es una operación de filtrado lineal y espacial donde una matriz de pesos (un kernel) se desliza a través de la imagen de entrada. En cada ubicación espacial, calcula un producto elemento a elemento entre los pesos del kernel y el parche de imagen local superpuesto (campo receptivo) y suma los resultados (a menudo añadiendo un término de sesgo) para producir un único valor de píxel en el mapa de características de salida. En matemáticas clásicas y procesamiento de señales, la verdadera convolución implica voltear (rotar 180 grados) el kernel horizontal y verticalmente antes de calcular el producto escalar deslizante: $(I * K)(i, j) = \sum_m \sum_n I(i - m, j - n) K(m, n)$. Por el contrario, la correlación cruzada calcula el producto punto deslizante directamente sin voltear el kernel: $(I \star K)(i, j) = \sum_m \sum_n I(i + m, j + n) K(m, n)$. En frameworks de aprendizaje profundo como PyTorch y TensorFlow, la operación implementada bajo el nombre de "convolución" es en realidad una correlación cruzada. Dado que los pesos del kernel son parámetros que se pueden aprender y se optimizan directamente mediante retropropagación (backpropagation), la red simplemente aprende los pesos con la orientación adecuada, lo que hace que el volteo matemático del kernel sea computacionalmente redundante tanto durante el entrenamiento como en la inferencia.

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)
Probar responder esta pregunta con un coach de IA

2¿Cuándo usaría las representaciones RGB, HSV, Lab, YCbCr o de escala de grises en un pipeline de visión?

Los diferentes espacios de color desacoplan propiedades físicas, perceptuales y estadísticas específicas de los datos visuales, lo que los hace adecuados para tareas de visión específicas: 1. **RGB / BGR**: Formato estándar para sensores de cámara y hardware de visualización que representa colores primarios aditivos (Rojo, Verde, Azul). Los canales de color están altamente correlacionados con la luminancia (los cambios de iluminación afectan a los tres canales simultáneamente). Es la entrada estándar para redes neuronales profundas (CNNs (Convolutional Neural Networks), Vision Transformers). Un problema de producción importante es el intercambio de canales RGB vs BGR (por ejemplo, `OpenCV` carga BGR, mientras que `PIL`/`PyTorch` esperan RGB), lo que afecta silenciosamente la precisión del modelo. 2. **HSV / HSL**: Desacopla explícitamente la cromaticidad (Tonalidad = tinte de color, Saturación = pureza del color) de la intensidad (Valor/Luminosidad). Es ideal para la umbralización clásica de color, el seguimiento de objetos por color y la segmentación de color bajo iluminación variable, porque la Tonalidad es relativamente invariante a las sombras y los cambios de brillo. 3. **CIE Lab**: Un espacio de color perceptualmente uniforme donde la distancia euclidiana entre dos puntos ($\Delta E$) refleja directamente la diferencia perceptual humana. Separa la Luminosidad ($L^*$) de los ejes de color opuestos ($a^*$ verde-rojo, $b^*$ azul-amarillo). Se utiliza en la inspección de diferencias de color, la corrección de color, la evaluación de la calidad de imagen y las tareas de colorización. 4. **YCbCr**: Separa la luminancia ($Y$) de los componentes de croma de diferencia de azul ($Cb$) y diferencia de rojo ($Cr$). Es la base de los estándares de compresión de video e imagen (`JPEG`, `MPEG`, `H.264`/`H.265`) porque el sistema visual humano es menos sensible al detalle de croma, lo que permite el submuestreo de croma (por ejemplo, 4:2:0) para reducir el ancho de banda y el cómputo. 5. **Escala de grises**: Representación de intensidad de un solo canal ($Y \approx 0.299R + 0.587G + 0.114B$). Es ideal para tareas basadas en geometría y textura donde el color es irrelevante (por ejemplo, flujo óptico, SLAM (Simultaneous Localization and Mapping), extracción de características clásicas como `SIFT`/`ORB`, detección de bordes) para ahorrar un 66% de memoria y cómputo. Sin embargo, no debe usarse cuando el color es una pista discriminatoria clave (por ejemplo, clasificación del estado de un semáforo).

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)
Probar responder esta pregunta con un coach de IA

3¿Por qué son importantes la normalización de imágenes por canal, la normalización por imagen y el escalado consistente de píxeles en los sistemas de visión?

La normalización de imágenes es vital en las *pipelines* (cadenas de procesamiento) de visión para mantener la estabilidad numérica, acelerar la convergencia del descenso de gradiente y prevenir el desplazamiento de la distribución entre entrenamiento y servicio (*train-serving distribution shift*): 1. **Escalado de Píxeles ([0, 1] o [-1, 1])**: Los píxeles de imágenes en bruto se almacenan como enteros sin signo de 8 bits (`uint8` $\in [0, 255]$). Alimentar valores brutos de $[0, 255]$ a las redes neuronales provoca activaciones iniciales extremadamente grandes y explosión o saturación de gradientes. Escalar a $[0.0, 1.0]$ (dividiendo por 255.0) o $[-1.0, 1.0]$ centra las entradas alrededor de cero, alineándose con los métodos de inicialización de pesos estándar (He, Xavier). 2. **Normalización de Canal a Nivel de Conjunto de Datos (Estandarización de Media-Desviación Estándar)**: Estandariza las entradas mediante $x_{norm} = \frac{x - \mu_c}{\sigma_c}$ utilizando estadísticas globales precalculadas por canal (por ejemplo, media de ImageNet `[0.485, 0.456, 0.406]` y desviación estándar `[0.229, 0.224, 0.225]`). Esto centra cada canal alrededor de una media cero con varianza unitaria en todo el conjunto de datos, asegurando un flujo de gradientes equilibrado entre canales. Un fallo operativo crítico es el **sesgo entre entrenamiento y servicio** (*train-serving skew*): si la inferencia omite la división por 255, utiliza constantes de media/desviación estándar que no coinciden, o las aplica en el orden de canal incorrecto (RGB frente a BGR), el rendimiento del modelo disminuye severamente. 3. **Normalización por Imagen (Normalización de Instancia / Min-Max / Puntuación Z)**: Calcula la media y la desviación estándar por imagen individual: $x_{norm} = \frac{x - \mu_{img}}{\sigma_{img}}$. Esto elimina las variaciones globales de contraste e iluminación entre diferentes condiciones de captura. Aunque es efectiva en la transferencia de estilo, imágenes médicas de MRI/CT o imágenes satelitales, puede ser perjudicial cuando la intensidad de píxel absoluta tiene un significado físico (por ejemplo, clasificación día vs. noche, reflectancia del material o detección de defectos bajo iluminación calibrada).

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))
Probar responder esta pregunta con un coach de IA

4¿Cómo fomentan los aumentos geométricos y fotométricos la invarianza, y cómo se decide si un aumento preserva las etiquetas?

Los aumentos geométricos (como rotación, traslación, escalado y volteo) alteran el mapeo de coordenadas espaciales de los píxeles, mientras que los aumentos fotométricos (como brillo, contraste, fluctuación de tono (`hue jitter`) y desenfoque) alteran la intensidad de píxeles y los canales de color sin modificar las coordenadas espaciales. En la clasificación, ambas categorías exponen el modelo a variaciones realistas, fomentando que aprenda representaciones invariantes donde las predicciones permanecen estables a pesar de los cambios en la pose, el punto de vista o las condiciones de iluminación. En tareas de predicción espacial (detección y segmentación), las transformaciones geométricas requieren transformaciones coincidentes a las cajas o máscaras de verdad fundamental (`ground-truth boxes`) para preservar la equivariancia. Decidir si un aumento preserva las etiquetas requiere evaluar la semántica del dominio y los priors físicos. Una transformación que preserva la etiqueta es aquella en la que la imagen resultante sigue siendo una instancia plausible de la clase objetivo original sin alterar su identidad semántica. Por ejemplo, el volteo horizontal preserva la identidad para objetos generales (como coches o animales), pero altera la identidad o invalida caracteres en el reconocimiento óptico de caracteres (OCR) y la clasificación de dígitos. De manera similar, la rotación completa de 360 grados es válida en histología o imágenes satelitales donde no existe una orientación canónica, pero antinatural en la conducción autónoma donde los objetos invertidos violan las restricciones físicas del mundo real.

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),
])
Probar responder esta pregunta con un coach de IA

5¿Qué son las interpolaciones de vecino más cercano, bilineal, bicúbica y de área, y cómo puede el redimensionamiento introducir aliasing o desenfoque?

Los métodos de interpolación estiman las intensidades de píxeles en coordenadas continuas al remuestrear en una nueva cuadrícula discreta: - **Vecino más cercano:** Asigna la intensidad del píxel discreto más cercano. Es computacionalmente $O(1)$ y conserva los valores originales exactos, pero crea artefactos de bloques al sobremuestrear y bordes dentados severos al submuestrear. - **Interpolación bilineal:** Calcula un promedio ponderado por distancia de los $2 \times 2$ (4 más cercanos) píxeles vecinos utilizando interpolación lineal a lo largo de ambos ejes, produciendo transiciones suaves con un desenfoque moderado. - **Interpolación bicúbica:** Muestra un vecindario de $4 \times 4$ (16 más cercanos) utilizando kernels polinomiales cúbicos. Captura gradientes de intensidad locales, produciendo bordes más nítidos y curvas más suaves que la bilineal, pero puede producir artefactos de sobreimpulso/ringing alrededor de transiciones bruscas. - **Interpolación de área (caja):** Calcula la superposición fraccional de área de los píxeles objetivo proyectados sobre los píxeles fuente y promedia las intensidades de fuente subyacentes. Es especialmente efectiva para el submuestreo sin moiré. **Mecánica del Aliasing vs. Desenfoque:** - El **aliasing** ocurre durante el submuestreo cuando el contenido de alta frecuencia excede la frecuencia de Nyquist ($f_s / 2$) de la nueva tasa de muestreo sin suficiente filtrado de paso bajo. Las altas frecuencias se pliegan en bandas de frecuencia más bajas, produciendo patrones falsos, moiré y bordes dentados. - El **desenfoque** ocurre cuando los kernels de interpolación actúan como filtros de suavizado de paso bajo que atenúan las frecuencias altas legítimas (por ejemplo, el suavizado bilineal) o cuando los detalles de alta frecuencia se promedian durante el submuestreo.

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)
Probar responder esta pregunta con un coach de IA

6¿Contra qué modos de fallo visuales del mundo real debe evaluarse un sistema de visión, y cómo desplazan la distribución de entrada?

Los modos de fallo visuales del mundo real se agrupan en varias categorías amplias: condiciones ambientales (poca luz, deslumbramiento directo, lluvia, nieve, niebla), efectos del sensor y ópticos (desenfoque por movimiento, desenfoque por falta de foco, ruido del sensor ISO alto, distorsión por persiana enrollable), degradación por compresión y transmisión (pixelación JPEG, limitación del bitrate de vídeo, submuestreo), y cambios semánticos/espaciales (oclusión parcial, truncamiento, poses de objetos fuera de distribución). Estos modos de fallo desplazan la distribución de entrada lejos de los dominios de entrenamiento nominales en múltiples niveles. A nivel de píxel, alteran los histogramas de luminancia y crominancia, destruyen el contraste local, reducen la relación señal-ruido (SNR) e introducen artefactos artificiales de alta frecuencia o núcleos de desenfoque. A nivel estructural y semántico, atenúan los gradientes de los bordes, oscurecen las características visuales clave, distorsionan las siluetas y ocultan regiones de diagnóstico críticas. Cuando los modelos entrenados con datos limpios encuentran estas entradas desplazadas, los extractores de características convolucionales o de atención de bajo nivel no se activan correctamente, produciendo detecciones perdidas, falsos positivos alucinatorios en patrones de ruido y errores sobreconfiados.

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
Probar responder esta pregunta con un coach de IA

7¿Cómo representan los descriptores clásicos como HOG la estructura de la imagen, y qué limitaciones tienen en comparación con las características aprendidas de las CNN?

El HOG (Histograma de Gradientes Orientados) es un descriptor de características clásico, diseñado manualmente, que captura la forma y apariencia local de los objetos a través de la distribución de las orientaciones de los gradientes de intensidad. El proceso de cálculo de HOG implica: (1) calcular los gradientes de imagen horizontales y verticales (por ejemplo, con filtros de derivada 1D [-1, 0, 1]) para obtener la magnitud y orientación del gradiente; (2) dividir la imagen en pequeñas regiones espaciales llamadas 'celdas' (como 8x8 píxeles) y acumular histogramas 1D de orientaciones de gradientes ponderados por la magnitud del gradiente; (3) agrupar celdas adyacentes en 'bloques' superpuestos más grandes (como 2x2 celdas) y normalizar los vectores de características del bloque (usando la norma L2 o L1-sqrt) para lograr robustez contra variaciones locales de iluminación, contraste y sombreado; y (4) concatenar los vectores de bloque normalizados en una representación final de características 1D, históricamente emparejada con máquinas de vectores de soporte (SVM) lineales para la detección de peatones y objetos. En comparación con las características aprendidas de las redes neuronales convolucionales (CNN), HOG tiene limitaciones importantes: Primero, las características HOG son fijas y elaboradas manualmente, capturando solo estadísticas de orientación de bordes locales de bajo nivel sin la capacidad de aprender representaciones jerárquicas específicas de la tarea (como texturas, partes de objetos y conceptos semánticos). Segundo, HOG proporciona una invarianza geométrica muy limitada: si bien la normalización de bloques maneja cambios de iluminación monótonos y pequeños desplazamientos espaciales dentro de las celdas, HOG es frágil ante rotaciones 3D fuera del plano, variaciones de escala significativas, articulaciones de pose no rígidas y fuerte desorden de fondo. En contraste, las CNN aprenden abstracciones no lineales multicapa optimizadas de extremo a extremo para el objetivo deseado.

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
Probar responder esta pregunta con un coach de IA

Preguntas para Middle

8¿Cómo determinan el `stride`, el `padding`, la `dilation` y el tamaño del kernel (`kernel size`) el tamaño de salida de la convolución y el campo receptivo?

Las dimensiones espaciales de salida de una capa convolucional y su campo receptivo acumulativo están determinadas por el tamaño del kernel $k$, el `stride` $s$, el `padding` $p$ y la `dilation` $d$: 1. **Tamaño espacial de salida**: La `dilation` introduce $(d - 1)$ espacios entre los elementos del kernel, dando un tamaño de kernel efectivo de $k' = d(k - 1) + 1$. La dimensión de salida a lo largo de un eje se calcula como: $$O = \left\lfloor \frac{I + 2p - k'}{s} \right\rfloor + 1 = \left\lfloor \frac{I + 2p - d(k - 1) - 1}{s} \right\rfloor + 1$$ Aquí, el `stride` submuestra la resolución avanzando el kernel $s$ píxeles por paso, el `padding` añade píxeles de borde virtuales para preservar o ajustar las dimensiones espaciales, y la `dilation` expande la huella del filtro sin añadir parámetros extra. 2. **Campo Receptivo (RF)**: El campo receptivo teórico describe la extensión espacial en la imagen de entrada que influye en una activación particular. A través de capas apiladas, el campo receptivo $RF_l$ y el `stride` acumulativo (salto $j_l$) se actualizan recursivamente: * Salto: $j_l = j_{l-1} \cdot s_l$, con $j_0 = 1$ * Campo Receptivo: $RF_l = RF_{l-1} + (k'_l - 1) \cdot j_{l-1}$, con $RF_0 = 1$ Los `strides` y el `pooling` expanden el campo receptivo multiplicativamente a través de la profundidad porque los pasos del kernel de capas subsiguientes corresponden a saltos de píxeles más grandes en el espacio de coordenadas de entrada original. La `dilation` expande el campo receptivo aditivamente dentro de una sola capa al aumentar $k'_l$ sin submuestreo espacial.

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)
Probar responder esta pregunta con un coach de IA

9¿Cómo se corresponde un núcleo de convolución con un filtro de imagen lineal clásico, y cuándo se puede hacer que un filtro 2D sea separable?

En el procesamiento clásico de señales e imágenes lineales, un núcleo de convolución representa la respuesta impulsional espacial (función de dispersión de punto) de un sistema Lineal Invariante en el Espacio (LSI). La aplicación del núcleo es una operación de filtrado lineal en el dominio espacial que da forma a la respuesta en frecuencia de la imagen (su transformada de Fourier 2D). Los núcleos pasabajos (por ejemplo, Gaussiano, desenfoque de caja) atenúan las altas frecuencias espaciales para suprimir el ruido y suavizar las texturas, mientras que los núcleos pasabajos o pasabanda (por ejemplo, Sobel, Laplaciano, Prewitt) amplifican las altas frecuencias para detectar bordes y gradientes. Un núcleo de filtro 2D $K \in \mathbb{R}^{M \times N}$ es espacialmente separable si se puede descomponer en el producto exterior de dos filtros 1D: $K = u \cdot v^T$, donde $u \in \mathbb{R}^{M \times 1}$ y $v \in \mathbb{R}^{N \times 1}$. En términos de álgebra lineal, una matriz 2D es separable si y solo si su rango de matriz es 1. Esto se puede verificar mediante la Descomposición en Valores Singulares (SVD), donde exactamente un valor singular es distinto de cero ($\sigma_1 > 0, \sigma_2 = \dots = 0$). La separabilidad reduce significativamente la complejidad computacional. Aplicar un núcleo $K \times K$ no separable a una imagen $H \times W$ requiere $O(H \cdot W \cdot K^2)$ multiplicaciones y adiciones. Cuando se descompone en dos pasadas 1D consecutivas (horizontal y luego vertical), la complejidad se reduce a $O(H \cdot W \cdot 2K)$. Para un núcleo de $15 \times 15$, esto proporciona aproximadamente una aceleración de $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))
Probar responder esta pregunta con un coach de IA

10¿Cómo cambian el redimensionamiento, el recorte central, el recorte aleatorio redimensionado, el estiramiento y el letterboxing la distribución de entrada?

Las diferentes estrategias de redimensionamiento y recorte cambian la distribución de los datos de entrada en cuanto a geometría, escala, cobertura de contenido y límites espaciales: 1. **Redimensionamiento Directo (Estiramiento/Comprimir):** Reajusta la imagen de forma no isotrópica a una dimensión objetivo $(H, W)$, distorsionando las relaciones de aspecto originales. Esto obliga al modelo a procesar formas de objetos deformadas (por ejemplo, los objetos circulares se convierten en elipses alargadas). 2. **Recorte Central (Center Cropping):** Extrae un recorte central de tamaño o proporción fijos. Preserva la relación de aspecto nativa y la escala local, pero introduce un fuerte sesgo central (asumiendo que los objetos objetivo están encuadrados centralmente) y descarta el contexto periférico o corta objetos posicionados en los bordes. 3. **Recorte Aleatorio Redimensionado (Random-Resized Cropping, RRC):** Extrae subregiones aleatorias en diversas escalas de área y relaciones de aspecto, y luego las redimensiona a una dimensión fija. Esto amplía la distribución de la escala de entrenamiento y fomenta el aprendizaje de características basado en partes, pero los recortes extremos pueden excluir el objeto objetivo por completo. 4. **Letterboxing (Redimensionamiento Isotropico con Relleno):** Escala la imagen uniformemente hasta que su dimensión más larga encaja en el tamaño objetivo, luego rellena los bordes restantes con un valor constante. Mantiene las proporciones y relaciones de aspecto reales del objeto, pero introduce bordes artificiales de alto contraste y píxeles de relleno no informativos.

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)
Probar responder esta pregunta con un coach de IA

11¿Qué modos de ruido de anotación ocurren en los conjuntos de datos de visión y cómo los detectarías o mitigarías?

Los conjuntos de datos de visión por computadora exhiben comúnmente cuatro modos principales de ruido en la anotación: 1. **Ruido en la Etiqueta Categórica**: Una imagen u objeto se le asigna la etiqueta de clase incorrecta (por ejemplo, clasificar erróneamente un gato como un perro, o confundir clases sutiles de grano fino). 2. **Anotaciones Faltantes (Ruido por Omisión)**: Objetos válidos en primer plano se dejan sin anotar. En la detección de objetos, los objetivos omitidos se tratan como negativos de fondo, penalizando directamente las detecciones correctas del modelo durante el entrenamiento. 3. **Jitter de Caja Delimitadora / Punto Clave (Ruido de Localización)**: Coordenadas de la caja delimitadora y ubicaciones de puntos clave imprecisas, sueltas o desplazadas, causadas por inconsistencia del anotador humano. 4. **Ambigüedad del Contorno de la Máscara**: Contornos de segmentación inconsistentes o gruesos a lo largo de límites complejos o borrosos (por ejemplo, cabello, superficies semitransparentes, desenfoque de movimiento). **Estrategias de Detección y Mitigación:** - **Seguimiento de Valores Atípicos en la Pérdida y Aprendizaje Confiable**: El seguimiento de la pérdida de la muestra a través de las épocas de entrenamiento identifica valores atípicos persistentes de alta pérdida, que a menudo indican instancias mal etiquetadas u omitidas. Algoritmos como el Aprendizaje Confiable (_Confident Learning_) estiman las distribuciones de ruido para podar o corregir errores. - **Discrepancias de Predicción Fuera de la Carpeta (OOF - _Out-of-Fold_)**: Entrenar modelos de validación cruzada y comparar las predicciones de la muestra de retención (_holdout_) con la verdad fundamental (_ground truth_) resalta imágenes mal etiquetadas y cajas delimitadoras faltantes. - **Acuerdo Inter-Anotador (IAA) y Adjudicación**: Medir métricas como la Kappa de Cohen (clasificación) o la Intersection-over-Union (IoU) media (localización) entre múltiples anotadores señala muestras de bajo acuerdo para su revisión por consenso. - **Formulaciones de Pérdida Robustas**: Usar suavizado de etiquetas (_label smoothing_), pérdidas de clasificación tolerantes al ruido o pérdidas de regresión robustas basadas en IoU reduce la sensibilidad al _jitter_ y el etiquetado erróneo.

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]
Probar responder esta pregunta con un coach de IA

12¿Cómo afecta el desequilibrio de clases al entrenamiento de clasificación, detección y predicción densa de imágenes?

El desequilibrio de clases afecta las dinámicas de optimización y los paisajes de pérdida en las tareas de visión: 1. **Clasificación de imágenes:** Cuando las clases mayoritarias superan en número a las minoritarias, la minimización del riesgo empírico hace que las actualizaciones de los parámetros estén dominadas por los gradientes de las clases mayoritarias. El modelo aprende la probabilidad a priori empírica de clase $P(Y)$ y sesga su límite de decisión en contra de las clases raras. Esto produce una alta precisión top-1 general, pero sufre un colapso severo de la exhaustividad (recall) para las clases minoritarias. 2. **Detección de objetos:** - *Desequilibrio entre primer plano y fondo:* En los detectores densos de una sola etapa y basados en anclas, se evalúan cientos de miles de ubicaciones candidatas por imagen, donde >99% son de fondo. Incluso si las anclas de fondo individuales fáciles producen pequeñas pérdidas, su vasto gradiente agregado ahoga las señales de gradiente informativas de los objetos dispersos en primer plano. - *Desequilibrio de clases en primer plano:* Las categorías de objetos comunes aparecen con órdenes de magnitud más frecuencia que las categorías raras, lo que reduce el tamaño efectivo de la muestra y la señal de aprendizaje para las clases raras. 3. **Predicción densa (Segmentación):** El desequilibrio a nivel de píxel es severo porque las grandes clases de fondo y de 'relleno' (por ejemplo, carretera, cielo) ocupan millones de píxeles, mientras que las clases de 'objetos' pequeños (por ejemplo, señales de tráfico, peatones) pueden ocupar menos del 0,1% de los píxeles de la imagen. La pérdida de entropía cruzada estándar por píxel está dominada por las clases de área grande, lo que lleva a la subsegmentación o a la desaparición completa de estructuras pequeñas y delgadas.

# 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}')
Probar responder esta pregunta con un coach de IA

Preguntas para Senior

13¿Cómo depuraría los valores NaN (Not a Number) o la pérdida inestable durante el entrenamiento de visión distribuido a gran escala?

La depuración de valores NaN o la inestabilidad de la pérdida en el entrenamiento de visión con DDP (Distributed Data Parallel) a gran escala requiere aislar si la causa raíz se origina en entradas de datos incorrectas, desbordamiento/subdesbordamiento numérico en precisión mixta (AMP (Automatic Mixed Precision)) o inestabilidades de optimización entre los trabajadores. Primero, establezca determinismo y ganchos de seguridad: habilite la detección de anomalías (`torch.autograd.set_detect_anomaly(True)`), registre ganchos de gradiente/activación para detectar la capa exacta donde aparecen los NaN, y agregue una validación de datos estricta en el DataLoader (afirmando valores finitos, verificando la ausencia de imágenes corruptas de 0 bytes, cuadros delimitadores vacíos o divisores de normalización con varianza cero). El registro a nivel de minibatch debe rastrear la pérdida por rango, los URIs de entrada, la norma del gradiente antes del recorte y los factores de escala de GradScaler. Segundo, inspeccione la precisión mixta automática (AMP) y el escalado dinámico de la pérdida: en FP16, los gradientes grandes se desbordan fácilmente (`> 65504`), lo que hace que el escalador de pérdida omita pasos y reduzca repetidamente a la mitad su factor de escala hasta que este llegue a cero; cambiar operaciones inestables (p. ej., `softmax`, `LayerNorm`, exponentes de pérdida focal o denominadores de IoU de cuadros delimitadores) a FP32 o adoptar BF16 (que coincide con el rango dinámico de FP32) normalmente estabiliza el entrenamiento. Finalmente, verifique los escollos específicos de DDP como las operaciones de reducción total (all-reduce) que propagan los NaN de un solo trabajador a todos los rangos, el escalado de calentamiento de la tasa de aprendizaje (p. ej., la regla de escalado lineal con tamaños de batch globales grandes) y el recorte de gradiente.

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()
Probar responder esta pregunta con un coach de IA

14¿Cómo diseñaría un componente de estabilización de video digital en línea para una cámara con temblor de fotogramas?

El diseño de un pipeline de estabilización de video digital en línea para el temblor de cámara en tiempo real implica cuatro etapas principales: estimación de movimiento, suavizado de movimiento, compensación de movimiento y manejo de bordes. 1. **Estimación de Movimiento Inter-Fotograma:** Extraiga puntos clave dispersos 2D en fotogramas consecutivos utilizando detectores de características rápidos (p. ej., ORB, FAST o esquinas de Shi-Tomasi) y calcule las correspondencias utilizando el flujo óptico de Lucas-Kanade o la coincidencia de descriptores de características. Estime una transformación geométrica inter-fotograma (como un modelo afín u homografía) utilizando RANSAC para rechazar coincidencias atípicas causadas por objetos en primer plano que se mueven independientemente. 2. **Acumulación de Trayectoria de Movimiento y Suavizado en Línea:** Integre las transformaciones fotograma a fotograma a lo largo del tiempo para mantener la ruta acumulativa de la cámara `P_t = P_{t-1} \cdot H_t`. Aplique un filtro de suavizado en línea —como un filtro de Kalman 1D/2D o un promedio móvil causal de ventana corta— para separar el temblor involuntario de alta frecuencia del *paneo* intencional de la cámara de baja frecuencia. 3. **Compensación y Deformación de Imagen:** Calcule la transformación de corrección `C_t = S_t \cdot P_t^{-1}` (donde `S_t` es la ruta suavizada) y deforme el fotograma actual utilizando interpolación bilineal/bicúbica. 4. **Manejo de Bordes y Restricciones de Latencia:** La deformación introduce píxeles de borde faltantes / bordes negros; resuelva esto aplicando un factor de recorte dinámico fijo (p. ej., un acercamiento del 5-10%) con extrapolación de bordes o escala adaptativa. En un entorno en línea, mantenga un almacenamiento en búfer mínimo (1-3 fotogramas de anticipación) para limitar la latencia de procesamiento.

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
Probar responder esta pregunta con un coach de IA

15¿Cómo optimizaría un pipeline de visión en tiempo real cuando el detector es más lento que la tasa de fotogramas objetivo?

Cuando un detector de objetos no puede mantener la tasa de fotogramas objetivo (por ejemplo, ejecutándose a 15-20 FPS en una alimentación de video de 60 FPS), una arquitectura de producción robusta desacopla la detección del bucle de presentación en tiempo real utilizando un pipeline asincrónico multihilo que combina detección pesada con seguimiento ligero. En esta arquitectura híbrida, un hilo de ingestión captura fotogramas de video continuamente. El detector pesado se ejecuta asincrónicamente como un 'detector de fotogramas clave' en un hilo de trabajo en segundo plano. Mientras tanto, un rastreador ligero (como el flujo óptico Lucas-Kanade, ByteTrack/BoT-SORT, o un filtro de correlación rápido/filtro de Kalman) se ejecuta sincrónicamente en cada fotograma a 60 FPS completos, manteniendo el estado del objeto, la identidad y trayectorias suaves de las bounding boxes. Para manejar el retraso y la contrapresión sin introducir latencia ilimitada o fotogramas obsoletos, se utilizan buffers de anillo acotados y políticas de descarte del último fotograma. Cuando el detector termina un fotograma `T_0` en el instante `T_curr`, su salida está obsoleta. El pipeline realiza una realineación de coordenadas/retroproyección: asocia los resultados de detección retrasados con el estado histórico del *tracklet* (pequeño rastro) en `T_0`, actualiza identidades y *tracks* (rastros) perdidos, y propaga las correcciones hacia adelante a `T_curr` mediante los vectores de movimiento de seguimiento o los pasos de predicción del filtro de Kalman.

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)
Probar responder esta pregunta con un coach de IA