Solución:
Te sugiero que eches un vistazo a la distancia del movimiento de tierra (EMD) entre las imágenes. Esta métrica da una idea de lo difícil que es transformar una imagen en escala de grises normalizada en otra, pero se puede generalizar para imágenes en color. Un muy buen análisis de este método se puede encontrar en el siguiente artículo:
robotics.stanford.edu/~rubner/papers/rubnerIjcv00.pdf
Se puede hacer tanto en la imagen completa como en el histograma (que es realmente más rápido que el método de imagen completa). No estoy seguro de qué método permite una comparación de imágenes completa, pero para la comparación de histogramas puede utilizar el cv.CalcEMD2 función.
El único problema es que este método no define un porcentaje de similitud, sino una distancia por la que puede filtrar.
Sé que este no es un algoritmo de trabajo completo, pero sigue siendo una base para él, así que espero que ayude.
EDITAR:
Aquí hay una parodia de cómo funciona el EMD en principio. La idea principal es tener dos matrices normalizadas (dos imágenes en escala de grises divididas por su suma), y definir una matriz de flujo que describa cómo se mueve el gris de un píxel al otro desde la primera imagen para obtener la segunda (se puede definir incluso para uno no normalizado, pero es más difícil).
En términos matemáticos, la matriz de flujo es en realidad un tensor cuadridimensional que da el flujo desde el punto (i, j) de la imagen anterior al punto (k, l) de la nueva, pero si aplana sus imágenes, puede transformarlo. a una matriz normal, solo que un poco más difícil de leer.
Esta matriz de flujo tiene tres restricciones: cada término debe ser positivo, la suma de cada fila debe devolver el mismo valor del píxel de destino y la suma de cada columna debe devolver el valor del píxel inicial.
Ante esto hay que minimizar el costo de la transformación, dado por la suma de los productos de cada flujo desde (i, j) a (k, l) para la distancia entre (i, j) y (k, l).
Parece un poco complicado en palabras, así que aquí está el código de prueba. La lógica es correcta, no estoy seguro de por qué el solucionador scipy se queja al respecto (quizás debería buscar openOpt o algo similar):
#original data, two 2x2 images, normalized
x = rand(2,2)
x/=sum(x)
y = rand(2,2)
y/=sum(y)
#initial guess of the flux matrix
# just the product of the image x as row for the image y as column
#This is a working flux, but is not an optimal one
F = (y.flatten()*x.flatten().reshape((y.size,-1))).flatten()
#distance matrix, based on euclidean distance
row_x,col_x = meshgrid(range(x.shape[0]),range(x.shape[1]))
row_y,col_y = meshgrid(range(y.shape[0]),range(y.shape[1]))
rows = ((row_x.flatten().reshape((row_x.size,-1)) - row_y.flatten().reshape((-1,row_x.size)))**2)
cols = ((col_x.flatten().reshape((row_x.size,-1)) - col_y.flatten().reshape((-1,row_x.size)))**2)
D = np.sqrt(rows+cols)
D = D.flatten()
x = x.flatten()
y = y.flatten()
#COST=sum(F*D)
#cost function
fun = lambda F: sum(F*D)
jac = lambda F: D
#array of constraint
#the constraint of sum one is implicit given the later constraints
cons = []
#each row and columns should sum to the value of the start and destination array
cons += [ {'type': 'eq', 'fun': lambda F: sum(F.reshape((x.size,y.size))[i,:])-x[i]} for i in range(x.size) ]
cons += [ {'type': 'eq', 'fun': lambda F: sum(F.reshape((x.size,y.size))[:,i])-y[i]} for i in range(y.size) ]
#the values of F should be positive
bnds = (0, None)*F.size
from scipy.optimize import minimize
res = minimize(fun=fun, x0=F, method='SLSQP', jac=jac, bounds=bnds, constraints=cons)
la variable res contiene el resultado de la minimización … pero como dije, no estoy seguro de por qué se queja de una matriz singular.
El único problema con este algoritmo es que no es muy rápido, por lo que no es posible hacerlo bajo demanda, pero hay que realizarlo con paciencia en la creación del conjunto de datos y almacenar en algún lugar los resultados.
Se está embarcando en un problema masivo, conocido como “recuperación de imágenes basada en contenido” o CBIR. Es un campo masivo y activo. Todavía no hay algoritmos terminados o enfoques estándar, aunque hay muchas técnicas, todas con diferentes niveles de éxito.
Incluso la búsqueda de imágenes de Google no hace esto (todavía); hacen búsquedas de imágenes basadas en texto; por ejemplo, buscan texto en una página que es como el texto que buscó. (Y estoy seguro de que están trabajando en el uso de CBIR; es el santo grial para muchos investigadores de procesamiento de imágenes)
Si tiene una fecha límite ajustada o necesita hacer esto y trabajar pronto … ¡Ay!
Aquí hay un montón de artículos sobre el tema:
http://scholar.google.com/scholar?q=content+based+image+retrieval
Por lo general, deberá hacer algunas cosas:
- Extraiga características (ya sea en puntos de interés locales, o globalmente, o de alguna manera, SIFT, SURF, histogramas, etc.)
- Agrupar / construir un modelo de distribuciones de imágenes
Esto puede involucrar descriptores de características, esencias de imágenes, aprendizaje de múltiples instancias. etc.
Escribí un programa para hacer algo muy similar hace quizás 2 años usando Python / Cython. Más tarde lo reescribí en Go para obtener un mejor rendimiento. La idea base proviene de findimagedupes IIRC.
Básicamente, calcula una “huella digital” para cada imagen y luego compara estas huellas digitales para que coincidan con imágenes similares.
La huella digital se genera cambiando el tamaño de la imagen a 160×160, convirtiéndola a escala de grises, agregando algo de desenfoque, normalizándola y luego cambiando su tamaño a monocromo de 16×16. Al final tienes 256 bits de salida: esa es tu huella digital. Esto es muy fácil de hacer usando convert
:
convert path[0] -sample 160x160! -modulate 100,0 -blur 3x99
-normalize -equalize -sample 16x16 -threshold 50% -monochrome mono:-
(Los [0]
en path[0]
se utiliza para extraer solo el primer fotograma de GIF animados; si no está interesado en tales imágenes, simplemente puede eliminarlas).
Después de aplicar esto a 2 imágenes, tendrá 2 huellas digitales (256 bits), fp1
y fp2
.
La puntuación de similitud de estas 2 imágenes se calcula aplicando XOR a estos 2 valores y contando los bits establecidos en 1. Para realizar este recuento de bits, puede utilizar el bitsoncount()
función de esta respuesta:
# fp1 and fp2 are stored as lists of 8 (32-bit) integers
score = 0
for n in range(8):
score += bitsoncount(fp1[n] ^ fp2[n])
score
será un número entre 0 y 256 que indica qué tan similares son sus imágenes. En mi aplicación, lo divido por 2,56 (normalizo a 0-100) y he descubierto que las imágenes con una puntuación normalizada de 20 o menos suelen ser idénticas.
Si desea implementar este método y usarlo para comparar muchas imágenes, fuertemente sugiero que use Cython (o simplemente C) tanto como sea posible: XORing y el conteo de bits es muy lento con enteros de Python puros.
Lo siento mucho, pero ya no puedo encontrar mi código Python. En este momento solo tengo una versión de Go, pero me temo que no puedo publicarlo aquí (estrechamente integrado en algún otro código, y probablemente un poco feo ya que fue mi primer programa serio en Go …).
También hay una muy buena función de “buscar por similitud” en GQView / Geeqie; su fuente está aquí.