Con Imagick podemos hacerlo mucho más limpio (y normalmente más rápido) que con GD: ImageMagick ya implementa SSIM como métrica de comparación, y desde PHP puedes invocarlo con getImageChannelDistortion() o compareImages(). [php.net], [php.net] A continuación te dejo una función lista para usar que: Carga 2 imágenes con Imagick. Las iguala de tamaño (opcional, para evitar el error de “Compare images failed” cuando difieren dimensiones). [https://www.php.net/manual/en/imagick.compareimages.php] Permite comparar en gris (luminancia) o en color. Permite ajustar los parámetros de SSIM (radius, sigma, k1, k2) usando los defines/artifacts de ImageMagick. [https://imagemagick.org/script/defines.php] Devuelve SSIM (0..1, más alto = más parecido) y también DSSIM (1-SSIM). Incluye una “autodetección” para evitar la confusión típica de si tu build devuelve “similarity” o “dissimilarity” como valor base (pasa en algunas combinaciones de versiones/configuraciones). [https://github.com/ImageMagick/ImageMagick/discussions/8084] --------------------------------- bool (default true) // Ajusta B al tamaño de A * - 'resizeTo' => ?int (default null) // Si se indica, reescala ambas a NxN * - 'grayscale' => bool (default true) // Comparar en gris (recomendado para SSIM “clásico”) * - 'channel' => int (default Imagick::CHANNELALL) * - 'alpha' => 'flatten'|'keep' (default 'flatten') // Qué hacer con transparencia * - 'flattenBg' => string (default 'white') // color de fondo al aplanar * - 'radius' => int (default 5) * - 'sigma' => float (default 1.5) * - 'k1' => float (default 0.01) * - 'k2' => float (default 0.03) * * @return array{ssim:float,dssim:float,raw:float} */ function ssim_imagick(string $pathA, string $pathB, array $opts = []): array { if (!extension_loaded('imagick')) { throw new RuntimeException("La extensión imagick no está cargada."); } if (!defined('Imagick::METRICSSIM')) { throw new RuntimeException("Tu Imagick/ImageMagick no expone Imagick::METRICSSIM (SSIM). Actualiza ImageMagick/Imagick."); } $autoResize = $opts['autoResize'] ?? true; $resizeTo = $opts['resizeTo'] ?? null; $grayscale = $opts['grayscale'] ?? true; $channel = $opts['channel'] ?? Imagick::CHANNELALL; $alphaMode = $opts['alpha'] ?? 'flatten'; $flattenBg = $opts['flattenBg'] ?? 'white'; $radius = (int)($opts['radius'] ?? 5); $sigma = (float)($opts['sigma'] ?? 1.5); $k1 = (float)($opts['k1'] ?? 0.01); $k2 = (float)($opts['k2'] ?? 0.03); $imA = new Imagick($pathA); $imB = new Imagick($pathB); // Si son animaciones (GIF/WebP), coge el primer frame if ($imA->getNumberImages() > 1) { $imA->setIteratorIndex(0); } if ($imB->getNumberImages() > 1) { $imB->setIteratorIndex(0); } // Normalización básica: convertir a sRGB para evitar sorpresas por perfiles/espacios $imA->transformImageColorspace(Imagick::COLORSPACESRGB); $imB->transformImageColorspace(Imagick::COLORSPACESRGB); // Manejo de alpha (transparencia) if ($alphaMode === 'flatten') { $imA = flattenOnBackground($imA, $flattenBg); $imB = flattenOnBackground($imB, $flattenBg); } // Comparación en gris (SSIM clásico suele aplicarse a un solo canal/luminancia) if ($grayscale) { $imA->transformImageColorspace(Imagick::COLORSPACEGRAY); $imB->transformImageColorspace(Imagick::COLORSPACEGRAY); } // Igualar tamaños if (is_int($resizeTo) && $resizeTo > 0) { $imA->resizeImage($resizeTo, $resizeTo, Imagick::FILTERLANCZOS, 1); $imB->resizeImage($resizeTo, $resizeTo, Imagick::FILTERLANCZOS, 1); } else { $wA = $imA->getImageWidth(); $hA = $imA->getImageHeight(); $wB = $imB->getImageWidth(); $hB = $imB->getImageHeight(); if ($wA !== $wB || $hA !== $hB) { if (!$autoResize) { // Nota: compareImages puede fallar si dimensiones difieren. [https://www.php.net/manual/en/imagick.compareimages.php] throw new RuntimeException("Las imágenes no tienen las mismas dimensiones ($wA×$hA vs $wB×$hB)."); } $imB->resizeImage($wA, $hA, Imagick::FILTERLANCZOS, 1); } } // Ajustes SSIM (artifacts/defines de ImageMagick) [3](https://imagemagick.org/script/defines.php) // En ImageMagick CLI son -define compare:ssim-radius=... etc. // En Imagick se pueden establecer como options (aplican a la operación de compare/distortion). $imA->setOption('compare:ssim-radius', (string)$radius); $imA->setOption('compare:ssim-sigma', (string)$sigma); $imA->setOption('compare:ssim-k1', (string)$k1); $imA->setOption('compare:ssim-k2', (string)$k2); // Cálculo principal: métrica SSIM vía distortion de canales [https://www.php.net/manual/en/imagick.getimagechanneldistortion.php] $raw = $imA->getImageChannelDistortion($imB, $channel, Imagick::METRICSSIM); // Autodetección: // En algunas salidas/herramientas aparece “0 (...) [1]” (donde [1] es la similitud) [https://github.com/ImageMagick/ImageMagick/discussions/8084] // Por eso comprobamos el valor contra sí misma para inferir si 'raw' es similitud o disimilitud. $clone = clone $imA; $self = $imA->getImageChannelDistortion($clone, $channel, Imagick::METRICSSIM); // Si al compararse consigo misma devuelve ~1 → raw es SSIM (similitud) // Si devuelve ~0 → raw es “dissimilarity/error” y SSIM ≈ 1 - raw $rawLooksLikeSimilarity = (abs($self - 1.0) < abs($self - 0.0)); if ($rawLooksLikeSimilarity) { $ssim = clamp01($raw); $dssim = clamp01(1.0 - $ssim); } else { $dssim = clamp01($raw); $ssim = clamp01(1.0 - $dssim); } // Limpieza $imA->clear(); $imA->destroy(); $imB->clear(); $imB->destroy(); $clone->clear(); $clone->destroy(); return ['ssim' => $ssim, 'dssim' => $dssim, 'raw' => (float)$raw]; } /** Aplana una imagen con transparencia sobre un fondo sólido. */ function flattenOnBackground(Imagick $im, string $bg = 'white'): Imagick { $flatten = new Imagick(); $flatten->newImage($im->getImageWidth(), $im->getImageHeight(), new ImagickPixel($bg)); $flatten->setImageColorspace($im->getImageColorspace()); $flatten->compositeImage($im, Imagick::COMPOSITE_OVER, 0, 0); return $flatten; } function clamp01(float $v): float { if ($v < 0.0) return 0.0; if ($v > 1.0) return 1.0; return $v; } --------------------------------- Ejemplo de uso: try { $res = ssim_imagick('img1.png', 'img2.png', [ 'grayscale' => true, // recomendado 'autoResize' => true, // ajusta B al tamaño de A si difiere 'radius' => 5, 'sigma' => 1.5, 'k1' => 0.01, 'k2' => 0.03, ]); echo "SSIM : " . $res['ssim'] . PHP_EOL; echo "DSSIM : " . $res['dssim'] . PHP_EOL; echo "RAW : " . $res['raw'] . PHP_EOL; if ($res['ssim'] >= 0.95) echo "Muy similares\n"; else echo "Diferentes\n"; } catch (Throwable $e) { echo "Error: " . $e->getMessage() . PHP_EOL; } --------------------------------- Notas importantes (para evitar sustos) Dimensiones: si no igualas tamaños, la comparación puede fallar (es un clásico). Por eso metí autoResize (true por defecto). [https://www.php.net/manual/en/imagick.compareimages.php] Parámetros SSIM: ImageMagick permite ajustar SSIM con compare:ssim-radius, compare:ssim-sigma, compare:ssim-k1, compare:ssim-k2. [https://imagemagick.org/script/defines.php] Valor devuelto: según versiones/salidas, SSIM puede mostrarse con “distortion” + valor normalizado de similitud, lo que genera confusión; la autodetección evita que tengas que pelearte con eso. [https://github.com/ImageMagick/ImageMagick/discussions/8084] Canales: por defecto uso CHANNEL_ALL, pero si comparas en gris ya es esencialmente 1 canal. Si me dices tu versión de ImageMagick (convert -version o magick -version) y tu versión de imagick (php -i | grep imagick), puedo afinar el comportamiento (por ejemplo, devolver también un mapa de diferencias con compareImages() —que devuelve la imagen de diferencia + la métrica— si lo necesitas).