¿Cómo detecto que dos imágenes son "iguales" incluso si una tiene un recorte / relación ligeramente diferente?


11

Tengo dos imágenes diferentes:

en ingrese la descripción de la imagen aquí100 px con o 400 pxingrese la descripción de la imagen aquí

y

en 100px de ancho ingrese la descripción de la imagen aquío 400pxingrese la descripción de la imagen aquí

Como puede ver, los dos son claramente "iguales" desde el punto de vista humano. Ahora quiero detectar programáticamente que son lo mismo. He estado usando magia de imágenes a través de la gema de rubí llamada rmagickasí:

img1 = Magick::Image.from_blob(File.read("image_1.jpeg")).first
img2 = Magick::Image.from_blob(File.read("image_2.jpeg")).first

if img1.difference(img2).first < 4000.0 # I have found this to be a good threshold, but does not work for cropped images
  puts "they are the same!!!"
end

Si bien esto funciona bien para imágenes que tienen la misma relación / recorte, no es ideal cuando tienen un recorte ligeramente diferente y se ha redimensionado al mismo ancho.

¿Hay alguna manera de hacerlo para imágenes con diferentes recortes? Estoy interesado en una solución en la que pueda decir algo como: una imagen está contenida dentro de la otra y cubre en algún lugar, por ejemplo, el 90% de la misma.

PD. Puedo obtener las imágenes en mayor resolución si eso ayuda (por ejemplo, el doble)


2
No estoy seguro acerca de RMagick, pero la compareherramienta de línea de comandos de ImageMagick tiene un -subimage-searchinterruptor.
Stefan

Eso es interesante, ¿cómo se vería un comando como ese?
Niels Kristian

2
Nunca lo usé yo mismo, tal vez esto ayude: stackoverflow.com/q/29062811/477037
Stefan

Gracias, esa es una gran información. Sin embargo, no puedo entender cómo hacer esto desde Ruby ...
Niels Kristian

1
¿Las imágenes son de baja calidad? En caso negativo, comparta una versión más grande de las imágenes, con más calidad.
MH304

Respuestas:


6

Es posible que desee echar un vistazo a la coincidencia de características. La idea es encontrar características en dos imágenes y combinarlas. Este método se usa comúnmente para encontrar una plantilla (por ejemplo, un logotipo) en otra imagen. Una característica, en esencia, puede describirse como cosas que los humanos encontrarían interesantes en una imagen, como esquinas o espacios abiertos. Existen muchos tipos de técnicas de detección de características, sin embargo, mi recomendación es utilizar una transformación de características invariantes de escala (SIFT) como algoritmo de detección de características. SIFT es invariante para la traducción de imágenes, escalado, rotación, parcialmente invariable para los cambios de iluminación y robusto para la distorsión geométrica local. Esto parece coincidir con su especificación donde las imágenes pueden tener proporciones ligeramente diferentes.

Dadas sus dos imágenes proporcionadas, aquí hay un intento de hacer coincidir las características utilizando el comparador de características de FLANN . Para determinar si las dos imágenes son iguales, podemos basarlo en un umbral predeterminado que rastrea el número de coincidencias que pasan la prueba de relación descrita en Características de imagen distintivas de puntos clave invariables a escala por David G. Lowe . Una explicación simple de la prueba es que la prueba de proporción verifica si las coincidencias son ambiguas y deben eliminarse, puede tratarse como una técnica de eliminación atípica. Podemos contar el número de coincidencias que pasan esta prueba para determinar si las dos imágenes son iguales. Aquí están los resultados de coincidencia de características:

Matches: 42

Los puntos representan todas las coincidencias detectadas, mientras que las líneas verdes representan las "coincidencias buenas" que pasan la prueba de relación. Si no utiliza la prueba de razón, se dibujarán todos los puntos. De esta manera, puede usar este filtro como umbral para mantener solo las mejores características coincidentes.


Lo implementé en Python, no estoy muy familiarizado con Rails. Espero que esto ayude, buena suerte!

Código

import numpy as np
import cv2

# Load images
image1 = cv2.imread('1.jpg', 0)
image2 = cv2.imread('2.jpg', 0)

# Create the sift object
sift = cv2.xfeatures2d.SIFT_create(700)

# Find keypoints and descriptors directly
kp1, des1 = sift.detectAndCompute(image2, None)
kp2, des2 = sift.detectAndCompute(image1, None)

# FLANN parameters
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks=50)   # or pass empty dictionary
flann = cv2.FlannBasedMatcher(index_params,search_params)
matches = flann.knnMatch(des1,des2,k=2)

# Need to draw only good matches, so create a mask
matchesMask = [[0,0] for i in range(len(matches))]

count = 0
# Ratio test as per Lowe's paper (0.7)
# Modify to change threshold 
for i,(m,n) in enumerate(matches):
    if m.distance < 0.15*n.distance:
        count += 1
        matchesMask[i]=[1,0]

# Draw lines
draw_params = dict(matchColor = (0,255,0),
                   # singlePointColor = (255,0,0),
                   matchesMask = matchesMask,
                   flags = 0)

# Display the matches
result = cv2.drawMatchesKnn(image2,kp1,image1,kp2,matches,None,**draw_params)
print('Matches:', count)
cv2.imshow('result', result)
cv2.waitKey()

2
Enfoque súper interesante, le daré una vuelta y volveré ...
Niels Kristian

PD. Actualicé las imágenes a mayor escala
Niels Kristian

1
@nathancy ¿Es así que en su ejemplo, los puntos verdes coinciden, pero los azules no? Parece que hay demasiados puntos sin igual?
Draco Ater

2
@DracoAter buena pregunta, los puntos azules representan todas las coincidencias, mientras que solo dibujamos "buenas coincidencias" que pasan la prueba de proporción en verde. Si no usa la prueba de razón, se dibujarán todos los puntos, pero los filtraremos usando la prueba de razón para dibujar las "mejores" coincidencias. De esta manera, OP puede usar esta prueba como un umbral para mantener solo las mejores características coincidentes. Entonces, todos los puntos azules son las características que encontró SIFT, pero filtramos para mantener los buenos dibujados en verde
nathancy

Gracias. la competencia fue dura en las respuestas, muchas excelentes :-)
Niels Kristian

4

Debido a que ImageMagick es una herramienta muy antigua, avanzada y con muchas funciones, sería difícil construir una interfaz que cubra la mayoría de las funciones. Por grandioso que sea, rmagick no (y tampoco los muchos intentos que ha hecho Python) se acerca a cubrir todas las características.

Me imagino que para muchos casos de uso, será lo suficientemente seguro y mucho más fácil simplemente ejecutar un método de línea de comando y leerlo. En rubí se verá así;

require 'open3'

def check_subimage(large, small)
    stdin, stdout, stderr, wait_thr = Open3.popen3("magick compare -subimage-search -metric RMSE #{large} #{small} temp.jpg")
    result = stderr.gets
    stderr.close
    stdout.close
    return result.split[1][1..-2].to_f < 0.2
end

if check_subimage('a.jpg', 'b.jpg')
    puts "b is a crop of a"
else
    puts "b is not a crop of a"
end

Cubriré cosas importantes y luego hablaré sobre notas adicionales.

El comando usa magick compare para verificar si la segunda imagen ( small) es una subimagen de la primera (large ). Esta función no comprueba que pequeño es estrictamente más pequeño que grande (altura y anchura). El número que puse para la similitud es 0.2 (error del 20%), y el valor de las imágenes que proporcionó es de aproximadamente 0.15. Es posible que desee afinar esto! Encuentro que las imágenes que son un subconjunto estricto obtienen menos de 0.01.

  • Si desea menos errores (números más pequeños) en casos en los que tiene una superposición del 90% pero la segunda imagen tiene algunas cosas adicionales que la primera no, puede ejecutarla una vez, luego recortar la primera imagen grande donde está contenida la subimagen , ejecútelo nuevamente con la imagen recortada como la "pequeña" y la imagen original "pequeña" como la grande.
  • Si realmente desea una interfaz orientada a objetos agradable en Ruby, rmagick utiliza la API MagicCore. Esto (enlace a documentos) comando es probablemente lo que desea usar para implementarlo, y puede abrir una pr para rickick o empaquetar el cext usted mismo.
  • El uso de open3 iniciará un hilo ( ver documentos ). Cierre stderry stdoutno es "necesario" pero se supone que debes hacerlo.
  • La imagen "temporal" que es el tercer argumento especifica un archivo para generar un análisis. Con un vistazo rápido, no pude encontrar una manera de no requerirlo, pero simplemente se sobrescribe automáticamente y podría ser bueno para guardarlo para la depuración. Para su ejemplo, se vería así;

ingrese la descripción de la imagen aquí

  • La salida completa está en el formato de 10092.6 (0.154003) @ 0,31. El primer número es el valor rmse de 655535, el segundo (que uso) es el porcentaje normalizado. Los dos últimos números representan la ubicación de la imagen original desde la que comienza la imagen pequeña.
  • Como no hay una fuente objetiva de verdad sobre cuán "similares" son las imágenes, elegí RMSE (vea más opciones de métricas aquí ). Es una medida bastante común de las diferencias entre los valores. Un conteo de errores absolutos (AE) puede parecer una buena idea, sin embargo, parece que algunos softwares de recorte no conservan los píxeles perfectamente, por lo que es posible que tenga que ajustar el fuzz y no es un valor normalizado, por lo que tendría que comparar el conteo de errores con el tamaño de la imagen y demás.

1
Esa es una gran información allí Carol. Gracias
Niels Kristian

¡Curioso por saber cómo funciona esto para sus otros casos!
Carol Chen

1
Gracias por la excelente respuesta. Si pudiera, también te habría dado una recompensa de 100p por esta :-)
Niels Kristian

3

Obtenga el histograma de ambas imágenes y compárelas. Esto funcionaría muy bien para recortar y Zoom a menos que haya un cambio demasiado drástico debido a esto.

Esto es mejor que el enfoque actual en el que está restando directamente las imágenes. Pero este enfoque todavía tiene pocos.


Gracias por el consejo, lo echaré un vistazo.
Niels Kristian

Esta no es una respuesta muy útil, ya que no demuestra cómo lograr el objetivo. Es el equivalente de "Google este término y descúbrelo tú mismo".
otro

El histograma es una de las primeras cosas que las personas aprenden en el procesamiento de imágenes. Si alguien tiene que buscarlo en Google, me disculpo profundamente.
Raviteja Narra

3

Por lo general, la coincidencia de plantillas tiene un buen resultado en estas situaciones. La coincidencia de plantillas es una técnica para encontrar áreas de una imagen que coinciden (son similares) a una imagen de plantilla (segunda imagen). Este algoritmo proporciona una puntuación para la mejor posición macthed en la imagen de origen (la segunda).

En opencv usando TM_CCOEFF_NORMED método , da el puntaje entre 0 y 1. Si el puntaje es 1, eso significa que la imagen de la plantilla es exactamente una parte (Rect) de la imagen de origen, pero si tiene un pequeño cambio en la iluminación o la perspectiva entre Las dos imágenes, la puntuación sería inferior a 1.

Ahora, al considerar un umbral para el puntaje de similitud, puede averiguar si son iguales o no. Ese umbral se puede obtener mediante alguna prueba y error en algunas imágenes de muestra. Probé sus imágenes y obtuve el puntaje 0.823863 . Aquí está el código (opencv C ++) y el área común entre las dos imágenes, obtenidas por la coincidencia:

ingrese la descripción de la imagen aquí

Mat im2 = imread("E:/1/1.jpg", 1);
//Mat im2;// = imread("E:/1/1.jpg", 1);
Mat im1 = imread("E:/1/2.jpg", 1);

//im1(Rect(0, 0, im1.cols - 5, im1.rows - 5)).copyTo(im2);

int result_cols = im1.cols - im2.cols + 1;
int result_rows = im1.rows - im2.rows + 1;

Mat result = Mat::zeros(result_rows, result_cols, CV_32FC1);

matchTemplate(im1, im2, result, TM_CCOEFF_NORMED);

double minVal; double maxVal;
Point minLoc; Point maxLoc;
Point matchLoc;

minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc, Mat());

cout << minVal << " " << maxVal << " " << minLoc << " " << maxLoc << "\n";
matchLoc = maxLoc;

rectangle(im1, matchLoc, Point(matchLoc.x + im2.cols, matchLoc.y + im2.rows), Scalar::all(0), 2, 8, 0);
rectangle(result, matchLoc, Point(matchLoc.x + im2.cols, matchLoc.y + im2.rows), Scalar::all(0), 2, 8, 0);

imshow("1", im1);
imshow("2", result);
waitKey(0);

Gracias por la excelente respuesta. Si pudiera, también te habría dado una recompensa de 100p por esta :-)
Niels Kristian

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.