Métriques de Qualité d'Image IA LPIPS・SSIM Guide Pratique 2025

Publié: 26 sept. 2025 · Temps de lecture: 17 min · Par la rédaction Unified Image Tools

L'évaluation de la qualité du traitement d'images évolue des métriques numériques traditionnelles vers une évaluation basée sur l'IA qui s'appuie sur la perception humaine. Cet article fournit des explications détaillées au niveau de l'implémentation pour les méthodes d'évaluation les plus récentes, incluant LPIPS (Learned Perceptual Image Patch Similarity) et SSIM (Structural Similarity Index Measure).

Évolution de l'Évaluation de Qualité d'Image IA

Limitations des Méthodes Traditionnelles

Problèmes avec PSNR (Peak Signal-to-Noise Ratio)

  • Évalue seulement les différences au niveau des pixels
  • Grande divergence par rapport à la perception humaine
  • Ignore la similitude structurelle
  • Ne peut pas évaluer appropriément les artefacts de compression

Besoin de Nouvelles Approches

  • Imiter le système visuel humain
  • Extraction de caractéristiques par apprentissage profond
  • Quantification de la similitude perceptuelle
  • Évaluation adaptative au contenu

Liens Internes : Budgets de Qualité d'Image et Portes CI 2025 — Opérations pour Prévenir les Pannes Proactivement, Stratégie complète de compression d'images 2025 — Guide pratique pour optimiser la vitesse perçue tout en préservant la qualité

LPIPS : Métriques Perceptuelles Basées sur l'Apprentissage

Fondement Théorique de LPIPS

LPIPS (Learned Perceptual Image Patch Similarity) est une métrique de similitude perceptuelle qui tire parti des représentations de caractéristiques des réseaux de neurones profonds.

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

class LPIPSEvaluator:
    def __init__(self, net='alex', use_gpu=True):
        """
        Initialisation du modèle LPIPS
        net: Choisir parmi '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 prétraitement
        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):
        """
        Calculer la distance LPIPS entre deux images
        """
        # Prétraitement
        tensor1 = self.transform(img1).unsqueeze(0).to(self.device)
        tensor2 = self.transform(img2).unsqueeze(0).to(self.device)
        
        # Calcul LPIPS
        with torch.no_grad():
            distance = self.loss_fn(tensor1, tensor2)
        
        return distance.item()
    
    def batch_evaluate(self, image_pairs):
        """
        Évaluation LPIPS avec traitement par lots
        """
        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,  # Exprimer comme similitude
                'quality_category': self.categorize_quality(lpips_score)
            })
        
        return results
    
    def categorize_quality(self, lpips_score):
        """
        Classification de catégorie de qualité basée sur le score 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'

Construction de Réseau LPIPS Personnalisé

class CustomLPIPSNetwork(nn.Module):
    def __init__(self, backbone='resnet50'):
        super().__init__()
        
        # Sélection du réseau 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
        
        # Couches d'extraction de caractéristiques
        self.feature_layers = [1, 4, 8, 12, 16]  # Indices des couches à extraire
        
        # Couches de transformation linéaire
        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):
        # Extraction de caractéristiques
        features1 = self.extract_features(x1)
        features2 = self.extract_features(x2)
        
        # Calcul de distance à chaque couche
        distances = []
        for i, (f1, f2) in enumerate(zip(features1, features2)):
            # Normalisation 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)
            
            # Calcul de distance
            diff = (f1_norm - f2_norm) ** 2
            
            # Transformation linéaire
            if i < len(self.linear_layers):
                diff = self.linear_layers[i](diff)
            
            # Moyenne spatiale
            distance = torch.mean(diff, dim=[2, 3])
            distances.append(distance)
        
        # Moyenne pondérée
        total_distance = sum(distances) / len(distances)
        return total_distance

SSIM : Indice de Similitude Structurelle

Définition Mathématique 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):
        """
        Calcul SSIM basique
        """
        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):
        """
        Implémentation 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:
                # Sous-échantillonnage
                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):
        """
        Filtrage gaussien + sous-échantillonnage
        """
        filtered = gaussian_filter(img, sigma=1.0, axes=[0, 1])
        return filtered[::2, ::2]
    
    def ssim_map(self, img1, img2):
        """
        Générer une carte SSIM
        """
        # Convertir en niveaux de gris
        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
        
        # Moyenne
        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
        
        # Variance et covariance
        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
        
        # Calcul 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étriques d'Évaluation Avancées

DISTS : Similitude Profonde de Structure et Texture d'Image

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')
        
        # Utiliser la portion d'extraction de caractéristiques de 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):
        """
        Calculer DISTS (Deep Image Structure and Texture Similarity)
        """
        # Prétraitement
        tensor1 = self.preprocess(img1).to(self.device)
        tensor2 = self.preprocess(img2).to(self.device)
        
        # Extraction de caractéristiques
        feats1 = self.extract_features(tensor1)
        feats2 = self.extract_features(tensor2)
        
        structure_score = 0
        texture_score = 0
        
        for f1, f2 in zip(feats1, feats2):
            # Similitude de structure (similitude de moyenne)
            struct_sim = self.structure_similarity(f1, f2)
            structure_score += struct_sim
            
            # Similitude de texture (similitude de covariance)
            texture_sim = self.texture_similarity(f1, f2)
            texture_score += texture_sim
        
        # Composition pondérée
        alpha = 0.8  # poids structure
        beta = 0.2   # poids texture
        
        dists_score = alpha * structure_score + beta * texture_score
        return dists_score.item()
    
    def structure_similarity(self, feat1, feat2):
        """
        Calculer la similitude de structure
        """
        # Moyenne selon la direction du canal
        mean1 = torch.mean(feat1, dim=1, keepdim=True)
        mean2 = torch.mean(feat2, dim=1, keepdim=True)
        
        # Similitude structurelle
        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):
        """
        Calculer la similitude de texture
        """
        # Calculer la matrice de covariance des cartes de caractéristiques
        b, c, h, w = feat1.shape
        feat1_flat = feat1.view(b, c, -1)
        feat2_flat = feat2.view(b, c, -1)
        
        # Calcul de covariance
        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)
        
        # Similitude par norme 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 : Distance de Fréchet Inception

from scipy.linalg import sqrtm
import numpy as np

class FIDEvaluator:
    def __init__(self):
        # Modèle Inception v3 (pour extraction de caractéristiques)
        self.inception = models.inception_v3(pretrained=True, transform_input=False)
        self.inception.fc = nn.Identity()  # Supprimer la couche de classification
        self.inception.eval()
        
        for param in self.inception.parameters():
            param.requires_grad = False
    
    def extract_features(self, images):
        """
        Extraction de caractéristiques avec Inception v3
        """
        features = []
        
        with torch.no_grad():
            for img in images:
                # Redimensionner à la taille appropriée (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):
        """
        Calculer FID (Fréchet Inception Distance)
        """
        # Extraction de caractéristiques
        real_features = self.extract_features(real_images)
        gen_features = self.extract_features(generated_images)
        
        # Calcul des statistiques
        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)
        
        # Calcul de distance de Fréchet
        diff = mu_real - mu_gen
        covmean = sqrtm(sigma_real.dot(sigma_gen))
        
        # Supprimer les composants imaginaires dus aux erreurs numériques
        if np.iscomplexobj(covmean):
            covmean = covmean.real
        
        fid = diff.dot(diff) + np.trace(sigma_real + sigma_gen - 2 * covmean)
        
        return fid

Construction de Système d'Évaluation Complet

Évaluateur Multi-métriques

class ComprehensiveQualityEvaluator:
    def __init__(self):
        self.lpips_evaluator = LPIPSEvaluator()
        self.ssim_evaluator = SSIMEvaluator()
        self.dists_evaluator = DISTSEvaluator()
        self.fid_evaluator = FIDEvaluator()
        
        # Configuration des poids
        self.weights = {
            'lpips': 0.3,
            'ssim': 0.3,
            'dists': 0.2,
            'psnr': 0.1,
            'fid': 0.1
        }
    
    def evaluate_single_pair(self, img1, img2):
        """
        Évaluation complète de qualité d'une paire d'images
        """
        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 (valeur de référence)
        results['psnr'] = self.calculate_psnr(img1, img2)
        
        # Calculer score composite
        composite_score = self.calculate_composite_score(results)
        results['composite_score'] = composite_score
        
        # Déterminer le niveau de qualité
        results['quality_level'] = self.determine_quality_level(composite_score)
        
        return results
    
    def calculate_psnr(self, img1, img2):
        """
        Calcul 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):
        """
        Score composite à partir de multiples métriques
        """
        # Normaliser chaque métrique vers la plage 0-1
        normalized_scores = {
            'lpips': 1 - min(metrics['lpips'], 1.0),  # Plus bas est mieux
            'ssim': metrics['ssim'],                   # Plus haut est mieux
            'dists': metrics['dists'],                 # Plus haut est mieux
            'psnr': min(metrics['psnr'] / 50, 1.0),   # Normalisation
        }
        
        # Composition pondérée
        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):
        """
        Détermination du niveau de qualité basée sur le score
        """
        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'

Système de Traitement par Lots

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):
        """
        Évaluation par lots de répertoire
        """
        original_path = Path(original_dir)
        processed_path = Path(processed_dir)
        
        # Obtenir les paires de fichiers d'images
        image_pairs = self.get_image_pairs(original_path, processed_path)
        
        # Évaluation par lots avec traitement parallèle
        tasks = [
            self.evaluate_pair_async(orig, proc) 
            for orig, proc in image_pairs
        ]
        
        results = await asyncio.gather(*tasks, return_exceptions=True)
        
        # Générer le rapport
        report = self.generate_report(image_pairs, results)
        
        # Sauvegarder les résultats
        await self.save_report(report, output_file)
        
        return report
    
    async def evaluate_pair_async(self, original_path, processed_path):
        """
        Évaluation asynchrone d'une paire d'images
        """
        async with self.semaphore:
            # Charger les images
            img1 = await self.load_image_async(original_path)
            img2 = await self.load_image_async(processed_path)
            
            # Exécuter l'évaluation
            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):
        """
        Chargement asynchrone d'image
        """
        async with aiofiles.open(path, 'rb') as f:
            data = await f.read()
        
        # Décoder l'image avec 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):
        """
        Générer rapport d'évaluation
        """
        successful_results = [r for r in results if not isinstance(r, Exception)]
        
        # Calcul des statistiques
        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):
        """
        Sauvegarder rapport en JSON
        """
        import json
        async with aiofiles.open(output_file, 'w') as f:
            await f.write(json.dumps(report, indent=2, default=str))

Surveillance de Qualité en Temps Réel

Moniteur de Qualité en Temps Réel

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
        
        # Seuils d'alerte
        self.thresholds = {
            'composite_score': {
                'warning': 0.6,
                'critical': 0.4
            },
            'lpips': {
                'warning': 0.3,
                'critical': 0.5
            }
        }
    
    def start_monitoring(self, input_queue):
        """
        Démarrer la surveillance en temps réel
        """
        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):
        """
        Boucle principale de surveillance
        """
        while self.is_running:
            try:
                # Obtenir une paire d'images de la file
                img_pair = input_queue.get(timeout=1.0)
                
                if img_pair is None:  # Signal de terminaison
                    break
                
                # Évaluation de qualité
                result = self.evaluator.evaluate_single_pair(*img_pair)
                
                # Ajouter à l'historique
                self.quality_history.append(result)
                
                # Vérifier les alertes
                self.check_alerts(result)
                
                # Mettre à jour les statistiques
                self.update_statistics()
                
            except queue.Empty:
                continue
            except Exception as e:
                print(f"Erreur de surveillance : {e}")
    
    def check_alerts(self, result):
        """
        Vérifier les conditions d'alerte
        """
        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):
        """
        Obtenir les statistiques actuelles
        """
        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)
        }

Optimisation Automatisée de la Qualité

Ajustement Dynamique de Paramètres

class AdaptiveQualityOptimizer:
    def __init__(self, evaluator, target_quality=0.8):
        self.evaluator = evaluator
        self.target_quality = target_quality
        self.parameter_history = []
        
        # Paramètres cibles pour l'optimisation
        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):
        """
        Optimisation de paramètres vers l'objectif de qualité
        """
        best_params = self.parameters.copy()
        best_quality = 0
        
        for iteration in range(max_iterations):
            # Traiter avec les paramètres actuels
            processed_images = self.process_with_parameters(
                test_images, self.parameters
            )
            
            # Évaluation de qualité
            avg_quality = self.evaluate_batch_quality(
                test_images, processed_images
            )
            
            print(f"Itération {iteration + 1}: Qualité = {avg_quality:.3f}")
            
            # Mettre à jour le meilleur résultat
            if avg_quality > best_quality:
                best_quality = avg_quality
                best_params = self.parameters.copy()
            
            # Vérifier l'atteinte de l'objectif
            if avg_quality >= self.target_quality:
                print(f"Objectif de qualité {self.target_quality} atteint !")
                break
            
            # Mettre à jour les paramètres
            self.update_parameters(avg_quality)
            
            # Enregistrer l'historique
            self.parameter_history.append({
                'iteration': iteration,
                'parameters': self.parameters.copy(),
                'quality': avg_quality
            })
        
        return best_params, best_quality
    
    def update_parameters(self, current_quality):
        """
        Mises à jour de paramètres basées sur la qualité actuelle
        """
        quality_gap = self.target_quality - current_quality
        
        # Utiliser des paramètres plus conservateurs quand la qualité est faible
        if quality_gap > 0.1:
            # Augmenter la qualité de compression
            self.parameters['compression_quality']['current'] = min(
                100, 
                self.parameters['compression_quality']['current'] + 5
            )
            
            # Réduire l'accentuation
            self.parameters['sharpening_strength']['current'] = max(
                0.0,
                self.parameters['sharpening_strength']['current'] - 0.1
            )
        
        # Se concentrer sur l'efficacité quand la qualité est suffisamment élevée
        elif quality_gap < -0.05:
            self.parameters['compression_quality']['current'] = max(
                50,
                self.parameters['compression_quality']['current'] - 2
            )

Implémentation et Déploiement

Service d'Évaluation Dockerisé

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

WORKDIR /app

# Installer les dépendances
COPY requirements.txt .
RUN pip install -r requirements.txt

# Code d'application
COPY src/ ./src/
COPY models/ ./models/

# Point d'entrée
COPY entrypoint.sh .
RUN chmod +x entrypoint.sh

EXPOSE 8080

ENTRYPOINT ["./entrypoint.sh"]

Implémentation d'API Web

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

app = FastAPI(title="API d'Évaluation de Qualité d'Image")

# Évaluateur global
quality_evaluator = ComprehensiveQualityEvaluator()

@app.post("/evaluate/single")
async def evaluate_single_image(
    original: UploadFile = File(...),
    processed: UploadFile = File(...)
):
    """
    Évaluation d'une paire d'images unique
    """
    try:
        # Charger les images
        original_img = await load_upload_image(original)
        processed_img = await load_upload_image(processed)
        
        # Exécuter l'évaluation
        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(...)
):
    """
    Évaluation par lots
    """
    if len(files) % 2 != 0:
        raise HTTPException(
            status_code=400, 
            detail="Nombre pair de fichiers requis (paires original + traité)"
        )
    
    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)
    
    # Calcul des statistiques
    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)

Résumé

Les métriques d'évaluation de qualité d'image IA permettent une évaluation qui dépasse de loin les indicateurs numériques traditionnels dans le reflet précis de la perception humaine. Les techniques introduites dans cet article peuvent améliorer significativement la gestion de qualité pour les systèmes de traitement d'images.

Points Clés :

  1. Évaluation Multi-facettes : Évaluation complète de la qualité à travers des combinaisons de LPIPS, SSIM, et DISTS
  2. Surveillance en Temps Réel : Détection précoce de problèmes grâce à la surveillance de qualité en temps réel
  3. Optimisation Automatisée : Ajustement dynamique de paramètres vers les objectifs de qualité
  4. Évolutivité : Support d'opération à grande échelle grâce au traitement par lots et développement d'API

Liens Internes : Budgets de Qualité d'Image et Portes CI 2025 — Opérations pour Prévenir les Pannes Proactivement, Stratégie complète de compression d'images 2025 — Guide pratique pour optimiser la vitesse perçue tout en préservant la qualité, Stratégies de conversion de format 2025 — Directives pour la sélection de WebP/AVIF/JPEG/PNG

Outils associés

Articles liés