Préparation à l'entretien en Vision par Ordinateur

Questions d'entretien d'ingénieur en Vision par Ordinateur

15 questions d'entretien sélectionnées sur la vision par ordinateur, regroupées par niveau d'ancienneté. Utilisez-les pour réviser les fondamentaux, les compromis pratiques et le raisonnement de production de niveau senior.

Commencer un entretien d'IA en Vision par OrdinateurAucune carte bancaire requise. 1 session gratuite disponible.
Préparation aux entretiens techniques en anglaisUn mode où les non-natifs peuvent s'entraîner à passer des entretiens techniques.

Questions Junior

1Qu'est-ce qu'une convolution discrète 2D sur une image, et comment cela se rapporte-t-il aux noyaux, aux filtres et à la corrélation croisée ?

Une convolution discrète 2D sur une image est une opération de filtrage spatial linéaire où une matrice de poids (un noyau) glisse sur l'image d'entrée. À chaque emplacement spatial, elle calcule un produit élément par élément entre les poids du noyau et le patch d'image local superposé (champ récepteur), et additionne les résultats (ajoutant souvent un terme de biais) pour produire une seule valeur de pixel dans la carte de caractéristiques de sortie. En mathématiques classiques et en traitement du signal, la véritable convolution implique de retourner (rotation de 180 degrés) le noyau horizontalement et verticalement avant de calculer le produit scalaire glissant : $(I * K)(i, j) = \sum_m \sum_n I(i - m, j - n) K(m, n)$. En revanche, la corrélation croisée calcule le produit scalaire glissant directement sans retourner le noyau : $(I \star K)(i, j) = \sum_m \sum_n I(i + m, j + n) K(m, n)$. Dans les cadres d'apprentissage profond comme PyTorch et TensorFlow, l'opération implémentée sous le nom de "convolution" est en fait une corrélation croisée. Étant donné que les poids du noyau sont des paramètres apprenables optimisés directement via la rétropropagation, le réseau apprend simplement les poids orientés de manière appropriée, rendant le retournement mathématique du noyau redondant sur le plan calculatoire pendant l'entraînement et l'inférence.

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)
Essayer de répondre à cette question avec un coach IA

2Quand utiliseriez-vous RGB (rouge, vert, bleu), HSV (teinte, saturation, valeur), Lab (clarté et axes chromatiques), YCbCr (luma et différences chromatiques) ou les niveaux de gris dans un pipeline de vision ?

Choisir une représentation conservant les indices utiles à la tâche et conforme au prétraitement d'entraînement. RGB (rouge, vert, bleu) est courant pour les modèles appris ; beaucoup d'API (interfaces de programmation) OpenCV utilisent BGR (bleu, vert, rouge), donc vérifier l'ordre sans supposer qu'un framework l'impose. HSV (teinte, saturation, valeur) et HSL (teinte, saturation, luminosité) facilitent les seuils de couleur, mais la teinte devient instable à faible saturation et n'est pas généralement invariante à l'éclairage ou à la balance des blancs. Lab, défini par la CIE (Commission internationale de l'éclairage), sépare clarté et coordonnées chromatiques opposantes et est approximativement uniforme perceptuellement sous des hypothèses définies de vision et de blanc de référence ; Delta E 1976 euclidien reste une approximation. YCbCr sépare luma Y' et différences chromatiques et permet le sous-échantillonnage chromatique dans de nombreux formats ; coefficients, plages et fonctions de transfert dépendent de la norme. Les niveaux de gris conviennent si la couleur est inutile et réduisent à précision égale trois canaux bruts à un, sans garantir une réduction de deux tiers du calcul total du modèle. Garder la couleur si elle informe l'étiquette et vérifier échelles et conversions.

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)
Essayer de répondre à cette question avec un coach IA

3Pourquoi la normalisation par canal, la normalisation par image et une mise à l'échelle cohérente des pixels sont-elles importantes en vision ?

Utiliser de façon identique à l'entraînement et en inférence la plage, l'ordre des canaux et la normalisation attendus. Pour huit bits, diviser par 255 donne [0,1], sans centrer en zéro ; passer à [−1,1] centre la plage, pas nécessairement la moyenne. D'autres profondeurs entières ou unités physiques flottantes existent. De grandes valeurs non mises à l'échelle peuvent mal conditionner l'optimisation sans causer inévitablement d'explosion des gradients. La standardisation par canal applique (x − μ_c)/σ_c avec statistiques d'entraînement ou constantes du modèle préentraîné. Moyenne nulle et variance unitaire valent sur la distribution ayant servi à les estimer, pas sur toute nouvelle image ou base. Le z-score par image utilise sa moyenne et son écart-type, avec protection contre une variance quasi nulle ; min-max est une autre opération. InstanceNorm est une couche normalisant généralement chaque échantillon et canal sur les dimensions spatiales, pas un synonyme de tout prétraitement par image. Normaliser par image peut atténuer les variations de luminosité et de contraste, mais aussi supprimer une intensité absolue utile ; le valider pour la tâche.

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))
Essayer de répondre à cette question avec un coach IA

4Comment les augmentations géométriques et photométriques favorisent-elles l'invariance, et comment décidez-vous si une augmentation préserve les étiquettes (labels) ?

Les augmentations géométriques (telles que la rotation, la translation, la mise à l'échelle et le retournement) modifient le mappage des coordonnées spatiales des pixels, tandis que les augmentations photométriques (telles que la luminosité, le contraste, le décalage de teinte et le flou) modifient l'intensité des pixels et les canaux de couleur sans modifier les coordonnées spatiales. En classification, ces deux catégories exposent le modèle à des variations réalistes, l'encourageant à apprendre des représentations invariantes où les prédictions restent stables malgré les changements de pose, de point de vue ou de conditions d'éclairage. Dans les tâches de prédiction spatiale (détection et segmentation), les transformations géométriques nécessitent des transformations correspondantes aux boîtes ou masques de vérité terrain pour préserver l'équivariance. Décider si une augmentation préserve les étiquettes exige d'évaluer la sémantique du domaine et les a priori physiques. Une transformation préserve l'étiquette si l'image résultante reste une instance plausible de la classe cible d'origine sans altérer son identité sémantique. Par exemple, le retournement horizontal préserve l'identité pour les objets généraux (comme les voitures ou les animaux), mais altère l'identité ou invalide les caractères dans la reconnaissance optique de caractères (ROC) et la classification de chiffres. De même, une rotation complète à 360 degrés est valide en histologie ou en imagerie satellite où aucune orientation canonique n'existe, mais elle est non naturelle dans la conduite autonome où les objets à l'envers violent les contraintes physiques du monde réel.

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),
])
Essayer de répondre à cette question avec un coach IA

5Que sont les interpolations par plus proche voisin, bilinéaire, bicubique et par aire, et comment le redimensionnement peut-il introduire l'aliasing ou le flou ?

Les méthodes d'interpolation estiment les intensités de pixels à des coordonnées continues lors du rééchantillonnage sur une nouvelle grille discrète : - **Plus proche voisin (Nearest-Neighbor)** : Assigné l'intensité du pixel discret le plus proche. Elle est computationnellement en $O(1)$ et préserve les valeurs originales exactes, mais crée des artefacts de bloc lors de l'agrandissement (upsampling) et des bords très dentelés lors de la réduction d'échelle (downsampling). - **Interpolation Bilinéaire (Bilinear Interpolation)** : Calcule une moyenne pondérée par la distance des $2 \times 2$ (4 plus proches) pixels voisins en utilisant l'interpolation linéaire le long des deux axes, produisant des transitions douces avec un flou modéré. - **Interpolation Bicubique (Bicubic Interpolation)** : Échantillonne un voisinage de $4 \times 4$ (16 plus proches) pixels en utilisant des noyaux polynomiaux cubiques. Elle capture les gradients d'intensité locaux, produisant des bords plus nets et des courbes plus lisses que la bilinéaire, mais peut introduire des artefacts de dépassement/d'ondulation (overshoot/ringing) autour des transitions abruptes. - **Interpolation par Aire (Area (Box) Interpolation)** : Calcule le chevauchement fractionnaire de la surface des pixels cibles projetés sur les pixels sources et moyenne les intensités sources sous-jacentes. Elle est particulièrement efficace pour le sous-échantillonnage sans moiré. **Mécanismes d'Aliasing vs. Flou :** - L'**aliasing** se produit lors de la réduction d'échelle (downsampling) lorsque le contenu haute fréquence dépasse la fréquence de Nyquist ($f_s / 2$) du nouveau taux d'échantillonnage sans un filtrage passe-bas suffisant. Les hautes fréquences se replient dans des bandes de fréquences plus basses, produisant de faux motifs, du moiré et des bords dentelés. - Le **flou (blur)** se produit lorsque les noyaux d'interpolation agissent comme des filtres de lissage passe-bas qui atténuent les hautes fréquences légitimes (par exemple, le lissage bilinéaire) ou lorsque les détails haute fréquence sont moyennés lors de la réduction d'échelle.

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)
Essayer de répondre à cette question avec un coach IA

6Quels modes de défaillance visuels réels un système de vision devrait-il évaluer, et comment déplacent-ils la distribution d'entrée ?

Les modes de défaillance visuels réels se répartissent en plusieurs grandes catégories : conditions environnementales (faible luminosité, éblouissement direct, pluie, neige, brouillard), effets de capteur et optiques (flou de mouvement, flou de défocalisation, bruit de capteur ISO élevé, distorsion par obturateur roulant), dégradation due à la compression et à la transmission (blocage JPEG, limitation du débit vidéo, sous-échantillonnage), et décalages sémantiques/spatiaux (occlusion partielle, troncature, poses d'objets hors distribution). Ces modes de défaillance déplacent la distribution d'entrée des domaines d'entraînement nominaux à plusieurs niveaux. Au niveau pixel, ils altèrent les histogrammes de luminance et de chrominance, détruisent le contraste local, réduisent le rapport signal/bruit (SNR) et introduisent des artefacts artificiels haute fréquence ou des noyaux de flou. Au niveau structurel et sémantique, ils atténuent les gradients de bord, obscurcissent les caractéristiques visuelles clés, déforment les silhouettes et masquent les régions diagnostiques critiques. Lorsque des modèles entraînés sur des données propres rencontrent ces entrées décalées, les extracteurs de caractéristiques convolutifs ou d'attention de bas niveau ne s'activent pas correctement, ce qui produit des détections manquées, des faux positifs hallucinés sur des motifs de bruit et des erreurs sur-confiantes.

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
Essayer de répondre à cette question avec un coach IA

7Comment les descripteurs classiques tels que HOG (Histogram of Oriented Gradients) représentent-ils la structure d'une image, et quelles sont leurs limites par rapport aux caractéristiques apprises par les réseaux de neurones convolutifs (CNN) ?

L'Histogramme de Gradients Orientés (HOG) est un descripteur de caractéristiques classique conçu manuellement pour capturer la forme et l'apparence locales d'objets à travers la distribution des orientations de gradients d'intensité. Le pipeline de calcul HOG implique : (1) le calcul des gradients d'image horizontaux et verticaux (par exemple, avec des filtres de dérivation 1D `[-1, 0, 1]`) pour obtenir la magnitude et l'orientation du gradient ; (2) la division de l'image en petites régions spatiales appelées 'cellules' (telles que 8x8 pixels) et l'accumulation d'histogrammes 1D d'orientations de gradients pondérés par la magnitude du gradient ; (3) le regroupement de cellules adjacentes en des 'blocs' plus grands et superposés (tels que 2x2 cellules) et la normalisation des vecteurs de caractéristiques de bloc (en utilisant la norme L2 ou L1-sqrt) pour obtenir une robustesse face aux variations locales d'illumination, de contraste et d'ombres ; et (4) la concaténation des vecteurs de blocs normalisés en une représentation de caractéristiques 1D finale, historiquement associée aux machines à vecteurs de support linéaires (SVM) pour la détection de piétons et d'objets. Comparé aux caractéristiques apprises par les réseaux de neurones convolutifs (CNN), HOG présente des limitations majeures : Premièrement, les caractéristiques HOG sont fixes et conçues manuellement, ne capturant que des statistiques d'orientation d'arêtes locales de bas niveau sans la capacité d'apprendre des représentations hiérarchiques spécifiques à la tâche (telles que les textures, les parties d'objets et les concepts sémantiques). Deuxièmement, HOG offre une invariance géométrique très limitée : bien que la normalisation des blocs gère les changements d'illumination monotones et les petits décalages spatiaux à l'intérieur des cellules, HOG est fragile face aux rotations 3D hors plan, aux variations d'échelle significatives, aux articulations de pose non rigides et à un fort désordre d'arrière-plan. En revanche, les CNN apprennent des abstractions non linéaires multicouches optimisées de bout en bout pour l'objectif cible.

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
Essayer de répondre à cette question avec un coach IA

Questions Middle

8Comment le pas, le remplissage, la dilatation et la taille du noyau déterminent-ils la taille de la sortie de convolution et le champ récepteur ?

Les dimensions spatiales de sortie d'une couche de convolution et son champ récepteur cumulatif sont déterminés par la taille du noyau $k$, le pas $s$, le remplissage $p$ et la dilatation $d$ : 1. **Taille spatiale de sortie** : La dilatation introduit $(d - 1)$ espaces entre les éléments du noyau, donnant une taille effective du noyau de $k' = d(k - 1) + 1$. La dimension de sortie le long d'un axe est donnée par : $$O = \left\lfloor \frac{I + 2p - k'}{s} \right\rfloor + 1 = \left\lfloor \frac{I + 2p - d(k - 1) - 1}{s} \right\rfloor + 1$$ Ici, le pas sous-échantillonne la résolution en faisant avancer le noyau de $s$ pixels par étape, le remplissage ajoute des pixels de bordure virtuels pour préserver ou ajuster les dimensions spatiales, et la dilatation étend l'empreinte du filtre sans ajouter de paramètres supplémentaires. 2. **Champ Récepteur (RF)** : Le champ récepteur théorique décrit l'étendue spatiale dans l'image d'entrée qui influence une activation particulière. À travers les couches empilées, le champ récepteur $RF_l$ et le pas cumulatif (saut $j_l$) se mettent à jour de manière récursive : * Saut : $j_l = j_{l-1} \cdot s_l$, avec $j_0 = 1$ * Champ Récepteur : $RF_l = RF_{l-1} + (k'_l - 1) \cdot j_{l-1}$, avec $RF_0 = 1$ Les pas et le pooling étendent le champ récepteur de manière multiplicative à travers la profondeur car les étapes du noyau de la couche suivante correspondent à des sauts de pixels plus grands dans l'espace de coordonnées d'entrée original. La dilatation étend le champ récepteur de manière additive au sein d'une seule couche en augmentant $k'_l$ sans sous-échantillonnage spatial.

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)
Essayer de répondre à cette question avec un coach IA

9Comment un noyau de convolution correspond-il à un filtre d'image linéaire classique, et quand un filtre 2D peut-il être rendu séparable ?

Dans le traitement classique des signaux et images linéaires, un noyau de convolution représente la réponse impulsionnelle spatiale (fonction d'étalement du point) d'un système linéaire invariant par translation (LSI). Appliquer le noyau est une opération de filtrage linéaire dans le domaine spatial qui façonne la réponse en fréquence de l'image (sa transformée de Fourier 2D). Les noyaux passe-bas (par exemple, gaussien, flou de boîte) atténuent les hautes fréquences spatiales pour supprimer le bruit et lisser les textures, tandis que les noyaux passe-haut ou passe-bande (par exemple, Sobel, Laplacien, Prewitt) amplifient les hautes fréquences pour détecter les contours et les gradients. Un noyau de filtre 2D $K \in \mathbb{R}^{M \times N}$ est spatialement séparable s'il peut être décomposé en le produit extérieur de deux filtres 1D : $K = u \cdot v^T$, où $u \in \mathbb{R}^{M \times 1}$ et $v \in \mathbb{R}^{N \times 1}$. En termes d'algèbre linéaire, une matrice 2D est séparable si et seulement si son rang matriciel est 1. Cela peut être vérifié via la Décomposition en valeurs singulières (SVD), où exactement une valeur singulière est non nulle ($ \sigma_1 > 0, \sigma_2 = \dots = 0$). La séparabilité réduit considérablement la complexité computationnelle. Appliquer un noyau non séparable de taille $K \times K$ à une image de taille $H \times W$ nécessite $O(H \cdot W \cdot K^2)$ multiplications et additions. Lorsqu'il est décomposé en deux passes 1D consécutives (horizontale puis verticale), la complexité tombe à $O(H \cdot W \cdot 2K)$. Pour un noyau de $15 \times 15$, cela offre une accélération d'environ $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))
Essayer de répondre à cette question avec un coach IA

10Comment le redimensionnement, le recadrage central, le recadrage redimensionné aléatoire, l'étirement et le letterboxing modifient-ils la distribution d'entrée ?

Les différentes stratégies de redimensionnement et de recadrage modifient la distribution des données d'entrée en termes de géométrie, d'échelle, de couverture de contenu et de limites spatiales : 1. **Redimensionnement direct (étirement/écrasement) :** Redimensionne l'image de manière non-isotrope à une dimension cible $(H, W)$, déformant les proportions originales. Cela force le modèle à traiter des formes d'objets déformées (par exemple, les objets circulaires deviennent des ellipses allongées). 2. **Recadrage central :** Extrait un recadrage central de taille ou de rapport fixe. Il préserve le rapport d'aspect natif et l'échelle locale, mais introduit un fort biais central (en supposant que les cibles sont centrées) et écarte le contexte périphérique ou coupe les objets positionnés en bordure. 3. **Recadrage redimensionné aléatoire (RRC - Random-Resized Cropping) :** Extrait des sous-régions aléatoires sur différentes échelles de surface et rapports d'aspect, puis les redimensionne à une dimension fixe. Cela élargit la distribution d'échelle d'entraînement et encourage l'apprentissage de caractéristiques basées sur des parties, mais des recadrages extrêmes peuvent exclure entièrement l'objet cible. 4. **Letterboxing (redimensionnement isotrope avec rembourrage) :** Redimensionne l'image uniformément jusqu'à ce que sa plus longue dimension corresponde à la taille cible, puis remplit les bordures restantes avec une valeur constante. Cela maintient les proportions et les rapports d'aspect réels des objets, mais introduit des bordures artificielles à contraste élevé et des pixels rembourrés non informatifs.

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)
Essayer de répondre à cette question avec un coach IA

11Quels modes de bruit d'annotation apparaissent dans les jeux de données visuelles, et comment les détecter ou les atténuer ?

Les jeux de données de vision par ordinateur présentent couramment quatre modes principaux de bruit d'annotation : 1. **Bruit d'étiquette catégorique** : Une image ou un objet se voit attribuer la mauvaise étiquette de classe (par exemple, classer à tort un chat comme un chien, ou confondre des classes subtiles à grain fin). 2. **Annotations manquantes (bruit d'omission)** : Des objets de premier plan valides sont laissés non annotés. En détection d'objets, les cibles omises sont traitées comme des négatifs d'arrière-plan, pénalisant directement les détections correctes du modèle pendant l'entraînement. 3. **Tremblement de boîte englobante / de point clé (bruit de localisation)** : Coordonnées de boîtes englobantes et emplacements de points de repère imprécis, lâches ou décalés, causés par l'incohérence de l'annotateur humain. 4. **Ambigüité de la frontière de masque** : Contours de segmentation inconsistants ou grossiers le long de frontières complexes ou floues (par exemple, cheveux, surfaces semi-transparentes, flou de mouvement). **Stratégies de Détection et d'Atténuation :** * **Suivi des valeurs aberrantes de perte et apprentissage confiant (Confident Learning)** : Le suivi de la perte des échantillons au fil des époques d'entraînement identifie les valeurs aberrantes à perte élevée et persistantes, qui indiquent souvent des instances mal étiquetées ou omises. Des algorithmes comme Confident Learning estiment les distributions de bruit pour élaguer ou corriger les erreurs. * **Discrepances de prédiction hors-plis (Out-of-Fold - OOF)** : Entraîner des modèles de validation croisée et comparer les prédictions des données de validation avec la vérité terrain met en évidence les images mal étiquetées et les boîtes englobantes manquantes. * **Accord inter-annotateurs (Inter-Annotator Agreement - IAA) et adjudication** : Mesurer des métriques comme le Kappa de Cohen (classification) ou l'Intersection-sur-Union moyenne (localisation) entre plusieurs annotateurs signale les échantillons à faible accord pour une révision consensuelle. * **Formulations de perte robustes** : Utiliser le lissage d'étiquettes (label smoothing), des pertes de classification tolérantes au bruit, ou des pertes de régression robustes basées sur l'IoU (Intersection-over-Union) réduit la sensibilité au tremblement et au mauvais étiquetage.

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]
Essayer de répondre à cette question avec un coach IA

12Comment le déséquilibre des classes affecte-t-il l'entraînement de la classification d'images, de la détection et de la prédiction dense ?

Le déséquilibre des classes affecte la dynamique d'optimisation et les paysages de perte dans toutes les tâches de vision : 1. **Classification d'images :** Lorsque les classes majoritaires sont plus nombreuses que les classes minoritaires, la minimisation du risque empirique fait en sorte que les mises à jour des paramètres sont dominées par les gradients des classes majoritaires. Le modèle apprend la distribution a priori empirique des classes $P(Y)$ et biaise sa frontière de décision contre les classes rares. Cela conduit à une précision top-1 globale élevée tout en souffrant d'un effondrement sévère du rappel (recall) pour les classes minoritaires. 2. **Détection d'objets :** * **Déséquilibre premier plan-arrière-plan :** Dans les détecteurs denses à une seule étape et basés sur des ancres, des centaines de milliers d'emplacements candidats sont évalués par image, dont plus de 99 % sont de l'arrière-plan. Même si les ancres d'arrière-plan individuelles et faciles génèrent de petites pertes, leur vaste gradient agrégé submerge les signaux de gradient informatifs provenant des objets de premier plan épars. * **Déséquilibre des classes de premier plan :** Les catégories d'objets courantes apparaissent des ordres de grandeur plus fréquemment que les catégories rares, ce qui réduit la taille effective de l'échantillon et le signal d'apprentissage pour les classes rares. 3. **Prédiction dense (Segmentation) :** Le déséquilibre au niveau des pixels est grave car les grandes classes d'arrière-plan et de « matériel » (par exemple, route, ciel) occupent des millions de pixels, tandis que les petites classes d'« objets » (par exemple, panneaux de signalisation, piétons) peuvent occuper moins de 0,1 % des pixels de l'image. La perte d'entropie croisée standard par pixel est dominée par les classes de grande surface, ce qui entraîne une sous-segmentation ou une disparition complète des petites structures fines.

# 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}')
Essayer de répondre à cette question avec un coach IA

Questions Senior

13Comment débogueriez-vous les valeurs non numériques (NaN) ou les pertes instables dans un entraînement distribué de vision à grande échelle ?

Isoler données incorrectes, dépassements numériques en précision mixte automatique (AMP) ou instabilités d'optimisation entre processus de travail. Capturer entrées, graines et configuration reproductibles, puis instrumenter ; la détection d'anomalies ne rend pas elle-même l'exécution déterministe. Activer torch.autograd.set_detect_anomaly(True), ajouter hooks de gradients/activations pour trouver la première couche produisant une valeur non numérique (NaN) et valider dans le DataLoader finitude, images corrompues de zéro octet, boîtes vides et diviseurs de normalisation nuls. Journaliser par rang perte, identifiants des ressources d'entrée (URI), norme du gradient avant écrêtage et échelle GradScaler. Examiner ensuite AMP : en virgule flottante sur 16 bits (FP16), des gradients supérieurs à 65 504 débordent ; GradScaler saute les mises à jour invalides et réduit l'échelle selon son facteur. Un effondrement persistant de l'échelle est un symptôme, pas une convergence normale à zéro. Exécuter softmax, LayerNorm, exposants focal ou dénominateurs d'intersection sur union (IoU) fragiles en virgule flottante sur 32 bits (FP32), ou passer au format flottant bfloat16 (BF16), de même plage dynamique que FP32, stabilise souvent. Vérifier enfin propagation des NaN par all-reduce, montée progressive du taux d'apprentissage et règles d'échelle du taux avec grand lot global, et écrêtage après déséchelonnage AMP. Des annotations vides peuvent être des négatifs valides : les vérifier selon le contrat plutôt que rejeter toute image vide.

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()
Essayer de répondre à cette question avec un coach IA

14Comment concevriez-vous une stabilisation vidéo numérique en ligne pour une caméra présentant des secousses entre images ?

Estimer robustement le mouvement interimage par coins suivis ou descripteurs appariés, écarter le mouvement du premier plan et choisir translation, similitude, affine ou homographie. Expliciter les directions : avec vecteurs colonnes et H_t allant de t−1 à t, le trajet référence-vers-courant est P_t=H_tP_{t−1}. Le lisser vers S_t par filtre causal ou anticipation bornée, en traitant correctement rotations et transformations plutôt que moyenner arbitrairement les éléments des matrices. La correction directe courant-vers-stabilisé est C_t=S_tP_t⁻¹ ; transmettre cette matrice ou son inverse selon la convention d'échantillonnage de l'API (interface de programmation). Borner recadrage, zoom et force pour les bords invalides, réinitialiser ou récupérer après perte du suivi et changement de scène. Une forte parallaxe et des effets importants d'obturateur déroulant peuvent exiger un modèle plus riche ou une assistance inertielle. Un système strictement causal ne voit pas le futur ; chaque image anticipée ajoute de la latence, donc choisir tampon et recadrage selon qualité et délais mesurés.

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
Essayer de répondre à cette question avec un coach IA

15Comment optimiseriez-vous un pipeline de vision temps réel dont le détecteur est plus lent que la fréquence cible ?

Si le détecteur tient quinze à vingt images par seconde (FPS) sur un flux à soixante, découpler détection et présentation par un pipeline asynchrone multithread mêlant détection lourde et suivi léger. Un thread capture continuellement, un thread de travail détecte sur images clés en arrière-plan et un algorithme de suivi, Lucas-Kanade ou filtre de corrélation éventuellement avec Kalman, suit chaque image à soixante FPS entre mises à jour. ByteTrack/BoT-SORT sont fondés sur les détections : sans nouvelles détections, il faut une politique explicite de prédiction ou suivi visuel, avec surveillance de l'âge, de l'incertitude et du rappel des nouveaux objets. Des tampons circulaires bornés et l'abandon d'images au profit de la plus récente évitent latence infinie et données périmées. Lorsque le résultat de T_0 arrive à T_curr, l'associer à l'état historique des trajectoires à T_0, corriger identités et pistes manquées, puis propager les corrections jusqu'au présent avec les vecteurs de mouvement ou les prédictions 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)
Essayer de répondre à cette question avec un coach IA