Métricas de Calidad de Imagen IA LPIPS・SSIM Guía Práctica 2025

Publicado: 26 sept 2025 · Tiempo de lectura: 17 min · Por el equipo editorial de Unified Image Tools

La evaluación de la calidad del procesamiento de imágenes está evolucionando desde métricas numéricas tradicionales hacia evaluación basada en IA que se basa en la percepción humana. Este artículo proporciona explicaciones detalladas a nivel de implementación de los métodos de evaluación más recientes, incluyendo LPIPS (Learned Perceptual Image Patch Similarity) y SSIM (Structural Similarity Index Measure).

Evolución de la Evaluación de Calidad de Imagen IA

Limitaciones de los Métodos Tradicionales

Problemas con PSNR (Peak Signal-to-Noise Ratio)

  • Solo evalúa diferencias a nivel de píxel
  • Gran divergencia de la percepción humana
  • Ignora la similitud estructural
  • No puede evaluar apropiadamente los artefactos de compresión

Necesidad de Nuevos Enfoques

  • Imitar el sistema visual humano
  • Extracción de características mediante aprendizaje profundo
  • Cuantificación de similitud perceptual
  • Evaluación adaptiva al contenido

Enlaces Internos: Presupuestos de Calidad de Imagen y Puertas CI 2025 — Operaciones para Prevenir Fallos Proactivamente, Estrategia Definitiva de Compresión de Imágenes 2025 — Guía Práctica para Optimizar Rendimiento Preservando Calidad

LPIPS: Métricas Perceptuales Basadas en Aprendizaje

Fundamento Teórico de LPIPS

LPIPS (Learned Perceptual Image Patch Similarity) es una métrica de similitud perceptual que aprovecha las representaciones de características de redes neuronales profundas.

import torch
import torch.nn as nn
import lpips
from torchvision import models, transforms

class LPIPSEvaluator:
    def __init__(self, net='alex', use_gpu=True):
        """
        Inicialización del modelo LPIPS
        net: Elegir de 'alex', 'vgg', 'squeeze'
        """
        self.loss_fn = lpips.LPIPS(net=net)
        self.device = torch.device('cuda' if use_gpu and torch.cuda.is_available() else 'cpu')
        self.loss_fn.to(self.device)
        
        # Pipeline de preprocesamiento
        self.transform = transforms.Compose([
            transforms.ToTensor(),
            transforms.Normalize(mean=[0.485, 0.456, 0.406],
                               std=[0.229, 0.224, 0.225])
        ])
    
    def calculate_lpips(self, img1, img2):
        """
        Calcular distancia LPIPS entre dos imágenes
        """
        # Preprocesamiento
        tensor1 = self.transform(img1).unsqueeze(0).to(self.device)
        tensor2 = self.transform(img2).unsqueeze(0).to(self.device)
        
        # Cálculo LPIPS
        with torch.no_grad():
            distance = self.loss_fn(tensor1, tensor2)
        
        return distance.item()
    
    def batch_evaluate(self, image_pairs):
        """
        Evaluación LPIPS con procesamiento por lotes
        """
        results = []
        
        for img1, img2 in image_pairs:
            lpips_score = self.calculate_lpips(img1, img2)
            results.append({
                'lpips_distance': lpips_score,
                'perceptual_similarity': 1 - lpips_score,  # Expresar como similitud
                'quality_category': self.categorize_quality(lpips_score)
            })
        
        return results
    
    def categorize_quality(self, lpips_score):
        """
        Clasificación de categoría de calidad basada en puntuación LPIPS
        """
        if lpips_score < 0.1:
            return 'excellent'
        elif lpips_score < 0.2:
            return 'good'
        elif lpips_score < 0.4:
            return 'acceptable'
        else:
            return 'poor'

Construcción de Red LPIPS Personalizada

class CustomLPIPSNetwork(nn.Module):
    def __init__(self, backbone='resnet50'):
        super().__init__()
        
        # Selección de red backbone
        if backbone == 'resnet50':
            self.features = models.resnet50(pretrained=True)
            self.features = nn.Sequential(*list(self.features.children())[:-2])
        elif backbone == 'efficientnet':
            self.features = models.efficientnet_b0(pretrained=True).features
        
        # Capas de extracción de características
        self.feature_layers = [1, 4, 8, 12, 16]  # Índices de capas a extraer
        
        # Capas de transformación lineal
        self.linear_layers = nn.ModuleList([
            nn.Sequential(
                nn.Conv2d(64, 1, 1, bias=False),
                nn.GroupNorm(1, 1, affine=False)
            ),
            nn.Sequential(
                nn.Conv2d(256, 1, 1, bias=False),
                nn.GroupNorm(1, 1, affine=False)
            ),
            nn.Sequential(
                nn.Conv2d(512, 1, 1, bias=False),
                nn.GroupNorm(1, 1, affine=False)
            )
        ])
    
    def forward(self, x1, x2):
        # Extracción de características
        features1 = self.extract_features(x1)
        features2 = self.extract_features(x2)
        
        # Cálculo de distancia en cada capa
        distances = []
        for i, (f1, f2) in enumerate(zip(features1, features2)):
            # Normalización L2
            f1_norm = f1 / (torch.norm(f1, dim=1, keepdim=True) + 1e-8)
            f2_norm = f2 / (torch.norm(f2, dim=1, keepdim=True) + 1e-8)
            
            # Cálculo de distancia
            diff = (f1_norm - f2_norm) ** 2
            
            # Transformación lineal
            if i < len(self.linear_layers):
                diff = self.linear_layers[i](diff)
            
            # Promedio espacial
            distance = torch.mean(diff, dim=[2, 3])
            distances.append(distance)
        
        # Promedio ponderado
        total_distance = sum(distances) / len(distances)
        return total_distance

SSIM: Índice de Similitud Estructural

Definición Matemática de SSIM

import numpy as np
from skimage.metrics import structural_similarity
from scipy.ndimage import gaussian_filter

class SSIMEvaluator:
    def __init__(self, window_size=11, k1=0.01, k2=0.03, sigma=1.5):
        self.window_size = window_size
        self.k1 = k1
        self.k2 = k2
        self.sigma = sigma
    
    def calculate_ssim(self, img1, img2, data_range=1.0):
        """
        Cálculo SSIM básico
        """
        return structural_similarity(
            img1, img2,
            data_range=data_range,
            multichannel=True,
            gaussian_weights=True,
            sigma=self.sigma,
            use_sample_covariance=False
        )
    
    def calculate_ms_ssim(self, img1, img2, weights=None):
        """
        Implementación Multi-Scale SSIM (MS-SSIM)
        """
        if weights is None:
            weights = [0.0448, 0.2856, 0.3001, 0.2363, 0.1333]
        
        levels = len(weights)
        mssim = 1.0
        
        for i in range(levels):
            ssim_val = self.calculate_ssim(img1, img2)
            
            if i < levels - 1:
                # Submuestreo
                img1 = self.downsample(img1)
                img2 = self.downsample(img2)
                mssim *= ssim_val ** weights[i]
            else:
                mssim *= ssim_val ** weights[i]
        
        return mssim
    
    def downsample(self, img):
        """
        Filtrado Gaussiano + submuestreo
        """
        filtered = gaussian_filter(img, sigma=1.0, axes=[0, 1])
        return filtered[::2, ::2]
    
    def ssim_map(self, img1, img2):
        """
        Generar mapa SSIM
        """
        # Conversión a escala de grises
        if len(img1.shape) == 3:
            img1_gray = np.mean(img1, axis=2)
            img2_gray = np.mean(img2, axis=2)
        else:
            img1_gray = img1
            img2_gray = img2
        
        # Media
        mu1 = gaussian_filter(img1_gray, self.sigma)
        mu2 = gaussian_filter(img2_gray, self.sigma)
        
        mu1_sq = mu1 ** 2
        mu2_sq = mu2 ** 2
        mu1_mu2 = mu1 * mu2
        
        # Varianza y covarianza
        sigma1_sq = gaussian_filter(img1_gray ** 2, self.sigma) - mu1_sq
        sigma2_sq = gaussian_filter(img2_gray ** 2, self.sigma) - mu2_sq
        sigma12 = gaussian_filter(img1_gray * img2_gray, self.sigma) - mu1_mu2
        
        # Cálculo SSIM
        c1 = (self.k1 * 1.0) ** 2
        c2 = (self.k2 * 1.0) ** 2
        
        ssim_map = ((2 * mu1_mu2 + c1) * (2 * sigma12 + c2)) / \
                   ((mu1_sq + mu2_sq + c1) * (sigma1_sq + sigma2_sq + c2))
        
        return ssim_map

Métricas de Evaluación Avanzadas

DISTS: Similitud Profunda de Estructura y Textura de Imagen

import torch
import torchvision.models as models

class DISTSEvaluator:
    def __init__(self, use_gpu=True):
        self.device = torch.device('cuda' if use_gpu and torch.cuda.is_available() else 'cpu')
        
        # Usar parte de extracción de características de red VGG
        vgg = models.vgg16(pretrained=True).features
        self.stages = nn.ModuleList([
            vgg[:4],   # conv1_2
            vgg[:9],   # conv2_2
            vgg[:16],  # conv3_3
            vgg[:23],  # conv4_3
            vgg[:30]   # conv5_3
        ]).to(self.device)
        
        for param in self.stages.parameters():
            param.requires_grad = False
    
    def extract_features(self, x):
        features = []
        for stage in self.stages:
            x = stage(x)
            features.append(x)
        return features
    
    def calculate_dists(self, img1, img2):
        """
        Calcular DISTS (Deep Image Structure and Texture Similarity)
        """
        # Preprocesamiento
        tensor1 = self.preprocess(img1).to(self.device)
        tensor2 = self.preprocess(img2).to(self.device)
        
        # Extracción de características
        feats1 = self.extract_features(tensor1)
        feats2 = self.extract_features(tensor2)
        
        structure_score = 0
        texture_score = 0
        
        for f1, f2 in zip(feats1, feats2):
            # Similitud de estructura (similitud de media)
            struct_sim = self.structure_similarity(f1, f2)
            structure_score += struct_sim
            
            # Similitud de textura (similitud de covarianza)
            texture_sim = self.texture_similarity(f1, f2)
            texture_score += texture_sim
        
        # Composición ponderada
        alpha = 0.8  # peso estructura
        beta = 0.2   # peso textura
        
        dists_score = alpha * structure_score + beta * texture_score
        return dists_score.item()
    
    def structure_similarity(self, feat1, feat2):
        """
        Calcular similitud de estructura
        """
        # Media por dirección de canal
        mean1 = torch.mean(feat1, dim=1, keepdim=True)
        mean2 = torch.mean(feat2, dim=1, keepdim=True)
        
        # Similitud estructural
        numerator = 2 * mean1 * mean2
        denominator = mean1 ** 2 + mean2 ** 2
        
        structure_map = numerator / (denominator + 1e-8)
        return torch.mean(structure_map)
    
    def texture_similarity(self, feat1, feat2):
        """
        Calcular similitud de textura
        """
        # Calcular matriz de covarianza de mapas de características
        b, c, h, w = feat1.shape
        feat1_flat = feat1.view(b, c, -1)
        feat2_flat = feat2.view(b, c, -1)
        
        # Cálculo de covarianza
        cov1 = torch.bmm(feat1_flat, feat1_flat.transpose(1, 2)) / (h * w - 1)
        cov2 = torch.bmm(feat2_flat, feat2_flat.transpose(1, 2)) / (h * w - 1)
        
        # Similitud por norma de Frobenius
        diff_norm = torch.norm(cov1 - cov2, 'fro', dim=[1, 2])
        max_norm = torch.maximum(torch.norm(cov1, 'fro', dim=[1, 2]),
                                torch.norm(cov2, 'fro', dim=[1, 2]))
        
        texture_sim = 1 - diff_norm / (max_norm + 1e-8)
        return torch.mean(texture_sim)

FID: Distancia de Fréchet Inception

from scipy.linalg import sqrtm
import numpy as np

class FIDEvaluator:
    def __init__(self):
        # Modelo Inception v3 (para extracción de características)
        self.inception = models.inception_v3(pretrained=True, transform_input=False)
        self.inception.fc = nn.Identity()  # Remover capa de clasificación
        self.inception.eval()
        
        for param in self.inception.parameters():
            param.requires_grad = False
    
    def extract_features(self, images):
        """
        Extracción de características usando Inception v3
        """
        features = []
        
        with torch.no_grad():
            for img in images:
                # Redimensionar a tamaño apropiado (299x299)
                img_resized = F.interpolate(img.unsqueeze(0), 
                                          size=(299, 299), 
                                          mode='bilinear')
                
                feat = self.inception(img_resized)
                features.append(feat.cpu().numpy())
        
        return np.concatenate(features, axis=0)
    
    def calculate_fid(self, real_images, generated_images):
        """
        Calcular FID (Fréchet Inception Distance)
        """
        # Extracción de características
        real_features = self.extract_features(real_images)
        gen_features = self.extract_features(generated_images)
        
        # Cálculo de estadísticas
        mu_real = np.mean(real_features, axis=0)
        sigma_real = np.cov(real_features, rowvar=False)
        
        mu_gen = np.mean(gen_features, axis=0)
        sigma_gen = np.cov(gen_features, rowvar=False)
        
        # Cálculo de distancia de Fréchet
        diff = mu_real - mu_gen
        covmean = sqrtm(sigma_real.dot(sigma_gen))
        
        # Remover componentes imaginarios debido a errores numéricos
        if np.iscomplexobj(covmean):
            covmean = covmean.real
        
        fid = diff.dot(diff) + np.trace(sigma_real + sigma_gen - 2 * covmean)
        
        return fid

Construcción de Sistema de Evaluación Integrado

Evaluador Multi-métrica

class ComprehensiveQualityEvaluator:
    def __init__(self):
        self.lpips_evaluator = LPIPSEvaluator()
        self.ssim_evaluator = SSIMEvaluator()
        self.dists_evaluator = DISTSEvaluator()
        self.fid_evaluator = FIDEvaluator()
        
        # Configuración de pesos
        self.weights = {
            'lpips': 0.3,
            'ssim': 0.3,
            'dists': 0.2,
            'psnr': 0.1,
            'fid': 0.1
        }
    
    def evaluate_single_pair(self, img1, img2):
        """
        Evaluación de calidad integral de par de imágenes
        """
        results = {}
        
        # LPIPS
        results['lpips'] = self.lpips_evaluator.calculate_lpips(img1, img2)
        
        # SSIM
        results['ssim'] = self.ssim_evaluator.calculate_ssim(img1, img2)
        
        # DISTS
        results['dists'] = self.dists_evaluator.calculate_dists(img1, img2)
        
        # PSNR (valor de referencia)
        results['psnr'] = self.calculate_psnr(img1, img2)
        
        # Calcular puntuación compuesta
        composite_score = self.calculate_composite_score(results)
        results['composite_score'] = composite_score
        
        # Determinar nivel de calidad
        results['quality_level'] = self.determine_quality_level(composite_score)
        
        return results
    
    def calculate_psnr(self, img1, img2):
        """
        Cálculo PSNR
        """
        mse = np.mean((img1 - img2) ** 2)
        if mse == 0:
            return float('inf')
        return 20 * np.log10(1.0 / np.sqrt(mse))
    
    def calculate_composite_score(self, metrics):
        """
        Puntuación compuesta de múltiples métricas
        """
        # Normalizar cada métrica al rango 0-1
        normalized_scores = {
            'lpips': 1 - min(metrics['lpips'], 1.0),  # Menor es mejor
            'ssim': metrics['ssim'],                   # Mayor es mejor
            'dists': metrics['dists'],                 # Mayor es mejor
            'psnr': min(metrics['psnr'] / 50, 1.0),   # Normalización
        }
        
        # Composición ponderada
        composite = sum(
            self.weights[metric] * score 
            for metric, score in normalized_scores.items()
            if metric in self.weights
        )
        
        return composite
    
    def determine_quality_level(self, score):
        """
        Determinación de nivel de calidad basado en puntuación
        """
        if score >= 0.9:
            return 'excellent'
        elif score >= 0.8:
            return 'very_good'
        elif score >= 0.7:
            return 'good'
        elif score >= 0.6:
            return 'acceptable'
        elif score >= 0.5:
            return 'poor'
        else:
            return 'very_poor'

Sistema de Procesamiento por Lotes

import asyncio
import aiofiles
from pathlib import Path

class BatchQualityEvaluator:
    def __init__(self, evaluator, max_workers=4):
        self.evaluator = evaluator
        self.max_workers = max_workers
        self.semaphore = asyncio.Semaphore(max_workers)
    
    async def evaluate_directory(self, original_dir, processed_dir, output_file):
        """
        Evaluación por lotes por directorio
        """
        original_path = Path(original_dir)
        processed_path = Path(processed_dir)
        
        # Obtener pares de archivos de imagen
        image_pairs = self.get_image_pairs(original_path, processed_path)
        
        # Evaluación por lotes con procesamiento paralelo
        tasks = [
            self.evaluate_pair_async(orig, proc) 
            for orig, proc in image_pairs
        ]
        
        results = await asyncio.gather(*tasks, return_exceptions=True)
        
        # Generar reporte
        report = self.generate_report(image_pairs, results)
        
        # Guardar resultados
        await self.save_report(report, output_file)
        
        return report
    
    async def evaluate_pair_async(self, original_path, processed_path):
        """
        Evaluación asíncrona de par de imágenes
        """
        async with self.semaphore:
            # Cargar imágenes
            img1 = await self.load_image_async(original_path)
            img2 = await self.load_image_async(processed_path)
            
            # Ejecutar evaluación
            result = self.evaluator.evaluate_single_pair(img1, img2)
            result['original_path'] = str(original_path)
            result['processed_path'] = str(processed_path)
            
            return result
    
    async def load_image_async(self, path):
        """
        Carga asíncrona de imagen
        """
        async with aiofiles.open(path, 'rb') as f:
            data = await f.read()
        
        # Decodificar imagen con PIL
        from PIL import Image
        import io
        img = Image.open(io.BytesIO(data))
        return np.array(img) / 255.0
    
    def generate_report(self, image_pairs, results):
        """
        Generar reporte de evaluación
        """
        successful_results = [r for r in results if not isinstance(r, Exception)]
        
        # Cálculo de estadísticas
        stats = {
            'total_images': len(image_pairs),
            'successful_evaluations': len(successful_results),
            'average_composite_score': np.mean([r['composite_score'] for r in successful_results]),
            'average_lpips': np.mean([r['lpips'] for r in successful_results]),
            'average_ssim': np.mean([r['ssim'] for r in successful_results]),
            'quality_distribution': self.calculate_quality_distribution(successful_results)
        }
        
        report = {
            'summary': stats,
            'detailed_results': successful_results,
            'failed_evaluations': [r for r in results if isinstance(r, Exception)]
        }
        
        return report
    
    async def save_report(self, report, output_file):
        """
        Guardar reporte como JSON
        """
        import json
        async with aiofiles.open(output_file, 'w') as f:
            await f.write(json.dumps(report, indent=2, default=str))

Monitoreo de Calidad en Tiempo Real

Monitor de Calidad en Tiempo Real

import threading
import queue
from collections import deque

class RealTimeQualityMonitor:
    def __init__(self, evaluator, window_size=100):
        self.evaluator = evaluator
        self.window_size = window_size
        self.quality_history = deque(maxlen=window_size)
        self.alert_queue = queue.Queue()
        self.is_running = False
        
        # Umbrales de alerta
        self.thresholds = {
            'composite_score': {
                'warning': 0.6,
                'critical': 0.4
            },
            'lpips': {
                'warning': 0.3,
                'critical': 0.5
            }
        }
    
    def start_monitoring(self, input_queue):
        """
        Iniciar monitoreo en tiempo real
        """
        self.is_running = True
        monitor_thread = threading.Thread(
            target=self.monitor_loop, 
            args=(input_queue,)
        )
        monitor_thread.start()
        return monitor_thread
    
    def monitor_loop(self, input_queue):
        """
        Bucle principal de monitoreo
        """
        while self.is_running:
            try:
                # Obtener par de imágenes de la cola
                img_pair = input_queue.get(timeout=1.0)
                
                if img_pair is None:  # Señal de terminación
                    break
                
                # Evaluación de calidad
                result = self.evaluator.evaluate_single_pair(*img_pair)
                
                # Agregar al historial
                self.quality_history.append(result)
                
                # Verificar alertas
                self.check_alerts(result)
                
                # Actualizar estadísticas
                self.update_statistics()
                
            except queue.Empty:
                continue
            except Exception as e:
                print(f"Error de monitoreo: {e}")
    
    def check_alerts(self, result):
        """
        Verificar condiciones de alerta
        """
        for metric, thresholds in self.thresholds.items():
            if metric in result:
                value = result[metric]
                
                if value < thresholds['critical']:
                    self.alert_queue.put({
                        'level': 'critical',
                        'metric': metric,
                        'value': value,
                        'threshold': thresholds['critical'],
                        'timestamp': time.time()
                    })
                elif value < thresholds['warning']:
                    self.alert_queue.put({
                        'level': 'warning',
                        'metric': metric,
                        'value': value,
                        'threshold': thresholds['warning'],
                        'timestamp': time.time()
                    })
    
    def get_current_statistics(self):
        """
        Obtener estadísticas actuales
        """
        if not self.quality_history:
            return {}
        
        recent_scores = [r['composite_score'] for r in self.quality_history]
        recent_lpips = [r['lpips'] for r in self.quality_history]
        
        return {
            'window_size': len(self.quality_history),
            'average_quality': np.mean(recent_scores),
            'quality_trend': self.calculate_trend(recent_scores),
            'average_lpips': np.mean(recent_lpips),
            'quality_stability': np.std(recent_scores)
        }

Automatización de Optimización de Calidad

Ajuste Dinámico de Parámetros

class AdaptiveQualityOptimizer:
    def __init__(self, evaluator, target_quality=0.8):
        self.evaluator = evaluator
        self.target_quality = target_quality
        self.parameter_history = []
        
        # Parámetros objetivo para optimización
        self.parameters = {
            'compression_quality': {'min': 50, 'max': 100, 'current': 85},
            'resize_algorithm': {'options': ['lanczos', 'bicubic', 'bilinear'], 'current': 'lanczos'},
            'sharpening_strength': {'min': 0.0, 'max': 2.0, 'current': 1.0}
        }
    
    def optimize_parameters(self, test_images, max_iterations=50):
        """
        Optimización de parámetros hacia objetivo de calidad
        """
        best_params = self.parameters.copy()
        best_quality = 0
        
        for iteration in range(max_iterations):
            # Procesar con parámetros actuales
            processed_images = self.process_with_parameters(
                test_images, self.parameters
            )
            
            # Evaluación de calidad
            avg_quality = self.evaluate_batch_quality(
                test_images, processed_images
            )
            
            print(f"Iteración {iteration + 1}: Calidad = {avg_quality:.3f}")
            
            # Actualizar mejor resultado
            if avg_quality > best_quality:
                best_quality = avg_quality
                best_params = self.parameters.copy()
            
            # Verificar logro de objetivo
            if avg_quality >= self.target_quality:
                print(f"¡Calidad objetivo {self.target_quality} lograda!")
                break
            
            # Actualizar parámetros
            self.update_parameters(avg_quality)
            
            # Registrar historial
            self.parameter_history.append({
                'iteration': iteration,
                'parameters': self.parameters.copy(),
                'quality': avg_quality
            })
        
        return best_params, best_quality
    
    def update_parameters(self, current_quality):
        """
        Actualización de parámetros basada en calidad actual
        """
        quality_gap = self.target_quality - current_quality
        
        # Usar configuraciones más conservadoras cuando la calidad es baja
        if quality_gap > 0.1:
            # Incrementar calidad de compresión
            self.parameters['compression_quality']['current'] = min(
                100, 
                self.parameters['compression_quality']['current'] + 5
            )
            
            # Reducir nitidez
            self.parameters['sharpening_strength']['current'] = max(
                0.0,
                self.parameters['sharpening_strength']['current'] - 0.1
            )
        
        # Enfocarse en eficiencia cuando la calidad es suficientemente alta
        elif quality_gap < -0.05:
            self.parameters['compression_quality']['current'] = max(
                50,
                self.parameters['compression_quality']['current'] - 2
            )

Implementación y Despliegue

Servicio de Evaluación Dockerizado

FROM pytorch/pytorch:1.9.0-cuda10.2-cudnn7-runtime

WORKDIR /app

# Instalar dependencias
COPY requirements.txt .
RUN pip install -r requirements.txt

# Código de aplicación
COPY src/ ./src/
COPY models/ ./models/

# Punto de entrada
COPY entrypoint.sh .
RUN chmod +x entrypoint.sh

EXPOSE 8080

ENTRYPOINT ["./entrypoint.sh"]

Implementación de API Web

from fastapi import FastAPI, File, UploadFile, HTTPException
from fastapi.responses import JSONResponse
import uvicorn

app = FastAPI(title="API de Evaluación de Calidad de Imagen")

# Evaluador global
quality_evaluator = ComprehensiveQualityEvaluator()

@app.post("/evaluate/single")
async def evaluate_single_image(
    original: UploadFile = File(...),
    processed: UploadFile = File(...)
):
    """
    Evaluación de par de imágenes único
    """
    try:
        # Cargar imágenes
        original_img = await load_upload_image(original)
        processed_img = await load_upload_image(processed)
        
        # Ejecutar evaluación
        result = quality_evaluator.evaluate_single_pair(
            original_img, processed_img
        )
        
        return JSONResponse(content=result)
    
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/evaluate/batch")
async def evaluate_batch_images(
    files: List[UploadFile] = File(...)
):
    """
    Evaluación por lotes
    """
    if len(files) % 2 != 0:
        raise HTTPException(
            status_code=400, 
            detail="Se requiere número par de archivos (pares original + procesado)"
        )
    
    results = []
    for i in range(0, len(files), 2):
        original_img = await load_upload_image(files[i])
        processed_img = await load_upload_image(files[i + 1])
        
        result = quality_evaluator.evaluate_single_pair(
            original_img, processed_img
        )
        results.append(result)
    
    # Cálculo de estadísticas
    summary = {
        'total_pairs': len(results),
        'average_quality': np.mean([r['composite_score'] for r in results]),
        'quality_distribution': calculate_quality_distribution(results)
    }
    
    return JSONResponse(content={
        'summary': summary,
        'results': results
    })

@app.get("/health")
async def health_check():
    return {"status": "healthy"}

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8080)

Resumen

Las métricas de evaluación de calidad de imagen IA permiten evaluación que excede por mucho los indicadores numéricos tradicionales en reflejar con precisión la percepción humana. Las técnicas introducidas en este artículo pueden mejorar significativamente la gestión de calidad para sistemas de procesamiento de imágenes.

Puntos Clave:

  1. Evaluación Multifacética: Evaluación integral de calidad a través de combinaciones de LPIPS, SSIM, y DISTS
  2. Monitoreo en Tiempo Real: Detección temprana de problemas a través de monitoreo de calidad en tiempo real
  3. Optimización Automática: Ajuste dinámico de parámetros hacia objetivos de calidad
  4. Escalabilidad: Soporte de operación a gran escala a través de procesamiento por lotes y desarrollo de API

Enlaces Internos: Presupuestos de Calidad de Imagen y Puertas CI 2025 — Operaciones para Prevenir Fallos Proactivamente, Estrategia Definitiva de Compresión de Imágenes 2025 — Guía Práctica para Optimizar Rendimiento Preservando Calidad, Estrategias de Conversión de Formato 2025 — Guía para Diferenciación de WebP/AVIF/JPEG/PNG

Herramientas relacionadas

Artículos relacionados