julio 6, 2021 10:00 am

Jesús

La comparación entre imágenes es uno de esos conceptos absurdamente intuitivos, fáciles y naturales para nosotros, pero que para una computadora resulta complicado. 

¿Qué buscamos típicamente cuando comparamos dos imágenes? La mayoría de las veces, saber si el contenido de ambas se parece o, en determinados casos, es el mismo.

Toma estas dos imágenes, por ejemplo:

Si te preguntara si estas imágenes son iguales, lo más probable es que me digas que sí, ¿cierto?

¿Qué hay de estas dos?

Podríamos argumentar que son la misma imagen, solo que una de ellas es la versión rotada 90° de la otra, ¿verdad?

Genial. Es muy fácil para nosotros llegar a estas conclusiones, ya que por la manera en la que percibimos el mundo tendemos a enfocarnos en el contenido, más que en los pormenores de la forma, como la rotación, inclinación o tamaño de los objetos.

En las dos comparaciones vimos la misma foto de un Ferrari… A veces más grande, a veces más pequeño, a veces rotado… Pero a fin de cuentas, el mismo bendito Ferrari.

Sin embargo, para una computadora, estas son 4 fotos totalmente diferentes.

Entonces, ¿cómo comparamos las imágenes por contenido y no por estructura usando computer vision?

¡Ah, me alegra que preguntes, porque de eso hablaremos el día de hoy!

Al final de este artículo habrás aprendido:

  • Qué es una función de hashing a alto nivel.
  • Por qué tenemos que usar un método especial de hashing llamado dHash, enfocado en el contenido de la imagen, no en los bits que la componen (hashing perceptual).
  • Por qué no podemos usar funciones de hashing tradicional como SHA-1 o MD5.
  • Cómo implementar nuestra propia función dHash.
  • Cómo usar nuestra implementación de dHash en para comparar imágenes con OpenCV.

¿Preparado? ¡Iniciemos!

¿Qué es una Función de Hashing?

A grandes rasgos, una función de hashing de imágenes es aquella a la que le pasamos una imagen, y nos retorna un código, serial o secuencia alfanumérica única basada en el contenido de la imagen.

¿Por Qué No Podemos Usar Funciones de Hashing Criptográfico como SHA-1 o MD5?

A priori, decantarse por métodos de hashing probados extensamente en un sinnúmero de contextos es la estrategia más racional. Sin embargo, el problema de funciones como SHA-1 o MD5 es que un cambio en un sólo píxel produce dos hashes totalmente distintos

Lo mismo si cambiamos las dimensiones de la imagen.

Así, aunque para nosotros las imágenes sean perceptualmente idénticas, no lo sabríamos si miráramos sus hashes.

Introducción a dHash

Entonces, si no podemos recurrir a SHA-1 o MD5, ¿qué nos queda?

La respuesta es un algoritmo bien interesante conocido como difference hash o dHash, y funciona así:

  1. 1
    Convertimos la imagen a escala de grises, lo cual acelera el cómputo del hash y hace que imágenes con el mismo contenido pero con diferente distribución cromática sean idénticas en términos de su hash.
  2. 2
    Redimensionamos la imagen a 9x8 píxeles, ignorando la relación de aspecto.
  3. 3
    Calculamos la diferencia entre píxeles adyacentes (de ahí el nombre). El resultado será una imagen de 8x8 donde resaltan las diferencias entre píxeles vecinos.
  4. 4
    Calculamos el hash de 64 bits siguiendo la ecuación P[i] > P[i + 1] = 1, si no 0.

¡Así de sencillo! Para convencerte de las bondades de dHash, a continuación resalto sus principales beneficios:

  • La relación de aspecto no altera el hash de una imagen.
  • Ajustes en el brillo o el color de la imagen afectarán marginalmente el hash de la imagen, por lo que su valor se mantendrá cercano al de la imagen inalterada.

Un último punto a tener en cuenta es la comparación de los hashes. ¿Cómo la hacemos? Típicamente medimos la distancia Hamming entre dos hashes, que no es más que el conteo de bits en la misma posición que son distintos entre sí o, en otras palabras, el XOR de dos hashes.

Por ejemplo, la distancia Hamming entre 1101 y 1110 es 2.

Si dos hashes tienen distancia Hamming 0, significa que los hashes son idénticos.

Si dos hashes tienen una distancia Hamming menor a 10, típicamente significa que las imágenes correspondientes a dichos hashes son perceptualmente iguales, por lo que una es una variación de la otra.

En la práctica, tenemos que experimentar con diferentes valores, dependiendo del contexto de nuestro problema.

Creación del Entorno de Desarrollo con Anaconda

Primero, veamos la estructura del proyecto:

El único script es datasmarts/compare_hash.py, mientras que en resources tenemos una serie de imágenes de prueba que utilizaremos para demostrar el funcionamiento de nuestra solución.

Para crear el ambiente de Anaconda, ejecuta el siguiente comando:

conda env create -f env.yml

Dicha instrucción habrá creado un ambiente opencv-image-hashing, correspondiente a este archivo de configuración:

Activa el ambiente así:

conda activate opencv-image-hashing

Cálculo y Comparación de Hashes de Imágenes en OpenCV

Abre el archivo datasmarts/compare_hash.py e inserta estas líneas para importar las dependencias del programa:

La función auxiliar dhash(), definida en el siguiente extracto, como su nombre indica, calcula el dHash de la imagen de entrada, siguiendo los pasos descritos en secciones anteriores:

Otra función auxiliar que nos hace falta es hamming(), la cual computa la distancia Hamming entre dos hashes:

Ahora definimos el menú del script, compuesto de dos parámetros:

  • -f/--first: Ruta a la primera imagen.
  • -s/--second: Ruta a la segunda imagen.

Leemos la primera imagen, la convertimos a escala de grises, y de una vez computamos su dHash:

Mostramos la primera imagen junto con su hash (en el título de la ventana):

Leemos la segunda imagen, la convertimos a escala de grises, y de una vez computamos su dHash:

Mostramos la segunda imagen junto con su hash (en el título de la ventana):

Calculamos la distancia Hamming entre los hashes de las imágenes:

Imprimimos los hashes y la distancia Hamming que los separa:

Finalmente, si la distancia Hamming entre los hashes es menor a 10, imprimimos un mensaje indicando que las imágenes son perceptualmente iguales; en caso contrario, imprimimos un mensaje diciendo que las imágenes son perceptualmente distintas:

Ejecutemos el programa así:

python datasmarts/compare_hash.py -f resources/runner_large.jpg -s resources/runner_small.jpg

Las dos imágenes que aparecerán en pantalla son estas:

Y en la consola veremos estos mensajes:

Hash de la primera imagen: 3249159351270049543
Hash de la segunda imagen: 4546196043952752391
Distancia Hamming entre los hashes: 2
Las imágenes son perceptualmente IGUALES.

Debido a que la distancia Hamming entre los dos hashes es 2 < 10, las imágenes son perceptualmente iguales, ya que una es una versión más pequeña de la otra.

Corramos otro ejemplo:

python datasmarts/compare_hash.py -f resources/runner_small.jpg -s resources/runner_deformed.jpg

Y en la consola estos mensajes:

Hash de la primera imagen: 4546196043952752391
Hash de la segunda imagen: 4546196043952754439
Distancia Hamming entre los hashes: 1
Las imágenes son perceptualmente IGUALES.

Como la segunda imagen es una versión deformada de la primera, tiene sentido que la distancia Hamming entre los hashes sea de apenas 1 bit, lo que significa que son perceptualmente iguales.

Analicemos un último caso:

python datasmarts/compare_hash.py -f resources/runner_small.jpg -s resources/runner2.jpg

Las imágenes son claramente diferentes:

Y, por tanto, sus hashes difieren por bastante:

Hash de la primera imagen: 4546196043952752391
Hash de la segunda imagen: 2708186892436996867
Distancia Hamming entre los hashes: 21
Las imágenes son perceptualmente DIFERENTES.

Resumen

Hoy aprendimos una técnica matemática para la comparación de imágenes con base a su contenido, en vez de su estructura, lo que nos permite enfocarnos en determinar si dos fotografías digitales son meras variaciones la una de la otra o si, efectivamente, se trata de escenas diferentes.

Si bien esta técnica se fundamenta en el cálculo de hashes, que son códigos o seriales que identifican inequívocamente a una imagen, tuvimos que recurrir a un algoritmo conocido como dHash, el cual cuantifica la diferencia entre píxeles adyacentes en una imagen, sin tomar en cuenta su relación de aspecto ni su información cromática.

¿Por qué no usamos algoritmos más tradicionales y mejor conocidos como SHA-1 o MD5? Pues, porque estos algoritmos son extremadamente sensibles a pequeñas variaciones a nivel de píxeles o resolución, lo que vence todo el propósito de comprar a nivel perceptual, no estructural.

Posteriormente, implementamos dHash en Python, y lo usamos para comprar diversas imágenes de ejemplo. 

¿Cómo se lleva a cabo esta comparación? Mediante una medida de distancia conocida como Hamming, que calcula el XOR entre la representación binaria de los hashes.

La lógica de esta comparación es que si la distancia entre dos hashes es menor a cierto valor (usualmente 10, aunque tenemos que tunearlo con base al contexto del problema), podemos determinar con una alta probabilidad de que dos imágenes son perceptualmente iguales, y que una es una variación de la otra. Si la distancia Hamming está por encima de dicho valor, entonces son imágenes diferentes.

¿Qué te pareció el post de hoy? ¿Lo encontraste interesante? Espero que sí, ya que en próximas entradas estudiaremos los diversos usos y aplicaciones de dHash. ¡No te las querrás perder!

Mientras, descarga el código a continuación para que continúes experimentando por cuenta propia:

¡Adiós!

Sobre el Autor

Jesús Martínez es el creador de DataSmarts, un lugar para los apasionados por computer vision y machine learning. Cuando no se encuentra bloggeando, jugando con algún algoritmo o trabajando en un proyecto (muy) cool, disfruta escuchar a The Beatles, leer o viajar por carretera.

Paso 2/2: Celebra tu NUEVO EMPLEO en Machine Learning ?

A %d blogueros les gusta esto: