Testeo de Sistemas de Visión por Computadora

La visión por computadora impulsa vehículos autónomos, diagnósticos médicos, sistemas de seguridad y QA en manufactura. A diferencia del software tradicional, los modelos CV lidian con ambigüedad, variabilidad visual y complejidad del mundo real.

Testear sistemas CV requiere evaluar precisión bajo condiciones diversas, robustez adversarial, equidad entre demografías y restricciones de rendimiento en tiempo real.

Estrategias de Testing Core

1. Métricas de Precisión

from sklearn.metrics import accuracy_score, precision_recall_fscore_support
import numpy as np

class EvaluadorModeloCV:
    def __init__(self, modelo):
        self.modelo = modelo

    def evaluar_clasificacion(self, imagenes_test, etiquetas_verdaderas):
        """Evaluar modelo de clasificación"""
        predicciones = self.modelo.predict(imagenes_test)
        etiquetas_predichas = np.argmax(predicciones, axis=1)

        # Precisión general
        precision = accuracy_score(etiquetas_verdaderas, etiquetas_predichas)

        # Métricas por clase
        precision_clase, recall, f1, support = precision_recall_fscore_support(
            etiquetas_verdaderas,
            etiquetas_predichas,
            average=None
        )

        return {
            'precision': precision,
            'metricas_por_clase': {
                self.modelo.nombres_clases[i]: {
                    'precision': precision_clase[i],
                    'recall': recall[i],
                    'f1_score': f1[i],
                    'support': support[i]
                }
                for i in range(len(self.modelo.nombres_clases))
            }
        }

2. Validación de Dataset

import cv2
from collections import Counter

class ValidadorDataset:
    def verificar_balance_clases(self):
        """Detectar desbalance de clases"""
        conteos_etiquetas = Counter(self.dataset.etiquetas)
        total = len(self.dataset.etiquetas)

        reporte_desbalance = {}
        for nombre_clase, conteo in conteos_etiquetas.items():
            porcentaje = (conteo / total) * 100
            reporte_desbalance[nombre_clase] = {
                'conteo': conteo,
                'porcentaje': porcentaje,
                'desbalanceado': porcentaje < 5 or porcentaje > 50
            }

        return reporte_desbalance

    def analizar_calidad_imagen(self):
        """Verificar imágenes de baja calidad"""
        problemas_calidad = []

        for ruta_img in self.dataset.rutas_imagenes:
            img = cv2.imread(ruta_img)

            # Verificar resolución
            alto, ancho = img.shape[:2]
            if alto < 224 or ancho < 224:
                problemas_calidad.append({
                    'imagen': ruta_img,
                    'problema': 'baja_resolucion',
                    'resolucion': f"{ancho}x{alto}"
                })

            # Verificar brillo
            gris = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            brillo = np.mean(gris)
            if brillo < 30 or brillo > 225:
                problemas_calidad.append({
                    'imagen': ruta_img,
                    'problema': 'brillo_pobre',
                    'brillo': brillo
                })

        return problemas_calidad

3. Testing Adversarial

import tensorflow as tf

class TestadorAdversarial:
    def __init__(self, modelo):
        self.modelo = modelo

    def ataque_fgsm(self, imagen, etiqueta_verdadera, epsilon=0.01):
        """Ataque Fast Gradient Sign Method"""
        tensor_imagen = tf.convert_to_tensor(imagen[np.newaxis, ...])

        with tf.GradientTape() as tape:
            tape.watch(tensor_imagen)
            prediccion = self.modelo(tensor_imagen)
            perdida = tf.keras.losses.sparse_categorical_crossentropy(
                [etiqueta_verdadera], prediccion
            )

        gradiente = tape.gradient(perdida, tensor_imagen)
        grad_firmado = tf.sign(gradiente)

        # Crear imagen adversarial
        imagen_adversarial = imagen + epsilon * grad_firmado.numpy()[0]
        imagen_adversarial = np.clip(imagen_adversarial, 0, 1)

        # Testear si ataque tuvo éxito
        pred_adv = self.modelo.predict(imagen_adversarial[np.newaxis, ...])
        etiqueta_adv = np.argmax(pred_adv)

        return {
            'etiqueta_original': etiqueta_verdadera,
            'etiqueta_adversarial': etiqueta_adv,
            'ataque_exitoso': etiqueta_adv != etiqueta_verdadera,
            'imagen_adversarial': imagen_adversarial
        }

    def testear_robustez(self, conjunto_test, valores_epsilon=[0.01, 0.05, 0.1]):
        """Testear robustez entre fuerzas de ataque"""
        resultados = {eps: {'exitos': 0, 'total': 0} for eps in valores_epsilon}

        for imagen, etiqueta in conjunto_test:
            for epsilon in valores_epsilon:
                resultado = self.ataque_fgsm(imagen, etiqueta, epsilon)
                resultados[epsilon]['total'] += 1
                if resultado['ataque_exitoso']:
                    resultados[epsilon]['exitos'] += 1

        # Calcular puntajes de robustez
        puntajes_robustez = {
            eps: 1 - (datos['exitos'] / datos['total'])
            for eps, datos in resultados.items()
        }

        return puntajes_robustez

4. Testing de Augmentación

import albumentations as A

class TestadorAugmentacion:
    def testear_con_augmentaciones(self, imagen, etiqueta_verdadera):
        """Testear consistencia del modelo bajo augmentaciones"""
        augmentaciones = [
            ('rotacion', A.Rotate(limit=15, p=1)),
            ('brillo', A.RandomBrightness(limit=0.2, p=1)),
            ('blur', A.Blur(blur_limit=3, p=1)),
            ('ruido', A.GaussNoise(var_limit=(10, 50), p=1)),
            ('flip', A.HorizontalFlip(p=1))
        ]

        prediccion_original = self.modelo.predict(imagen[np.newaxis, ...])[0]
        clase_original = np.argmax(prediccion_original)

        resultados = {}

        for nombre_aug, augmentacion in augmentaciones:
            aumentada = augmentacion(image=imagen)['image']
            pred_aug = self.modelo.predict(aumentada[np.newaxis, ...])[0]
            clase_aug = np.argmax(pred_aug)

            resultados[nombre_aug] = {
                'prediccion_cambio': clase_aug != clase_original,
                'aun_correcto': clase_aug == etiqueta_verdadera
            }

        # Calcular puntaje de invariancia
        puntaje_invariancia = sum(
            1 for r in resultados.values() if not r['prediccion_cambio']
        ) / len(resultados)

        return {
            'resultados_augmentacion': resultados,
            'puntaje_invariancia': puntaje_invariancia
        }

Testing de Rendimiento

import time

class TestadorRendimiento:
    def benchmark_inferencia(self, imagenes_test, tamanos_batch=[1, 8, 32]):
        """Benchmark de velocidad de inferencia"""
        resultados = {}

        for tamano_batch in tamanos_batch:
            latencias = []

            for i in range(0, len(imagenes_test), tamano_batch):
                lote = imagenes_test[i:i+tamano_batch]

                inicio = time.time()
                _ = self.modelo.predict(lote)
                fin = time.time()

                latencia_ms = (fin - inicio) * 1000 / len(lote)
                latencias.append(latencia_ms)

            resultados[f'batch_{tamano_batch}'] = {
                'latencia_promedio_ms': np.mean(latencias),
                'latencia_p95_ms': np.percentile(latencias, 95),
                'throughput_fps': 1000 / np.mean(latencias)
            }

        return resultados

Mejores Prácticas

PrácticaDescripción
Conjunto de Test DiversoIncluir variada iluminación, ángulos, fondos
Colección Casos ExtremosOclusiones, ángulos extremos, poca luz
Validación Cross-DatasetTestear en datos de diferentes fuentes
Endurecimiento AdversarialIncluir ejemplos adversariales en entrenamiento
Evaluación ContinuaMonitorear drift de rendimiento en producción
Testing de EquidadTestear entre demografías (tonos piel, edades)

Conclusión

El testing de visión por computadora va más allá de métricas de precisión—requiriendo testing de robustez, validación de dataset, defensas adversariales y evaluación de equidad. A medida que los sistemas CV se despliegan en aplicaciones críticas para seguridad, el testing riguroso se vuelve esencial.