Come posso rilevare che due immagini sono "uguali" anche se una ha ritagli / rapporti leggermente diversi?


11

Ho due immagini diverse:

in 100px con inserisci qui la descrizione dell'immagineo 400pxinserisci qui la descrizione dell'immagine

e

in larghezza 100 inserisci qui la descrizione dell'immaginepx o 400 pxinserisci qui la descrizione dell'immagine

Come puoi vedere, i due sono chiaramente gli "stessi" da un punto di vista umano. Ora voglio rilevare programmaticamente che sono uguali. Ho usato la magia dell'immagine tramite la gemma rubino chiamata rmagickcosì:

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

Mentre questo funziona bene per le immagini che hanno lo stesso rapporto / ritaglio, non è l'ideale quando hanno un ritaglio leggermente diverso ed è stato ridimensionato alla stessa larghezza.

C'è un modo per farlo per immagini con ritaglio diverso? Sono interessato a una soluzione in cui posso dire qualcosa del genere: un'immagine è contenuta all'interno dell'altra e copre da qualche parte circa ad esempio il 90% di essa.

PS. Posso ottenere le immagini con una risoluzione più elevata se ciò aiuta (ad esempio il doppio)


2
Non sono sicuro di RMagick, ma lo comparestrumento da riga di comando di ImageMagick ha un'opzione -subimage-search.
Stefan

È interessante, come apparirebbe un comando del genere?
Niels Kristian,

2
Non l'ho mai usato da solo, forse questo aiuta: stackoverflow.com/q/29062811/477037
Stefan

Grazie, è un'ottima informazione. Tuttavia, non riesco a capire come farlo dal rubino ...
Niels Kristian,

1
Le immagini sono di bassa qualità? In caso contrario, condividi la versione più grande delle immagini, con maggiore qualità.
MH304,

Risposte:


6

Potresti dare un'occhiata alla corrispondenza delle caratteristiche. L'idea è di trovare funzionalità in due immagini e abbinarle. Questo metodo viene comunemente utilizzato per trovare un modello (ad esempio un logo) in un'altra immagine. Una caratteristica, in sostanza, può essere descritta come cose che gli umani troverebbero interessanti in un'immagine, come angoli o spazi aperti. Esistono molti tipi di tecniche di rilevamento delle funzionalità, tuttavia la mia raccomandazione è di utilizzare una trasformazione di funzionalità invariante (SIFT) come algoritmo di rilevamento delle funzionalità. SIFT è invariante alla traduzione delle immagini, ridimensionamento, rotazione, parzialmente invariante ai cambiamenti di illuminazione e robusto alla distorsione geometrica locale. Questo sembra corrispondere alle tue specifiche in cui le immagini possono avere rapporti leggermente diversi.

Date le tue due immagini fornite, ecco un tentativo di abbinare le funzionalità utilizzando il Matcher di funzionalità FLANN . Per determinare se le due immagini sono uguali, possiamo basarci su una soglia predeterminata che tiene traccia del numero di corrispondenze che superano il test del rapporto descritto in Caratteristiche distintive dell'immagine da punti chiave invarianti di David G. Lowe . Una semplice spiegazione del test è che il test del rapporto controlla se le corrispondenze sono ambigue e devono essere rimosse, è possibile trattarlo come una tecnica di rimozione anomala. Possiamo contare il numero di corrispondenze che superano questo test per determinare se le due immagini sono uguali. Ecco i risultati della corrispondenza delle funzionalità:

Matches: 42

I punti rappresentano tutte le corrispondenze rilevate mentre le linee verdi rappresentano le "corrispondenze valide" che superano il test del rapporto. Se non si utilizza il test del rapporto, verranno disegnati tutti i punti. In questo modo, è possibile utilizzare questo filtro come soglia per mantenere solo le funzioni più adatte.


L'ho implementato in Python, non ho molta familiarità con Rails. Spero che questo ti aiuti, buona fortuna!

Codice

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
Approccio super interessante, ci proverò e tornerò ...
Niels Kristian il

PS. Ho aggiornato le immagini su una scala più ampia
Niels Kristian il

1
@nathancy È così che sul tuo esempio, i punti verdi corrispondono, ma quelli blu no? Sembra che ci siano troppi punti senza eguali?
Draco Ater,

2
@DracoTuttavia, i punti blu rappresentano tutte le partite mentre disegniamo solo "buone partite" che superano il test del rapporto in verde. Se non si utilizza il test del rapporto, verranno disegnati tutti i punti ma filtriamo utilizzando il test del rapporto per disegnare le corrispondenze "migliori". In questo modo, OP può utilizzare questo test come soglia per mantenere solo le funzionalità più adatte. Quindi tutti i punti blu sono le caratteristiche che SIFT ha trovato ma noi
filtriamo

Grazie. la concorrenza è stata dura per le risposte, molte ottime :-)
Niels Kristian

4

Poiché ImageMagick è uno strumento molto vecchio, avanzato e dalle molte funzionalità, sarebbe difficile creare un'interfaccia che copra la maggior parte delle funzionalità. Per quanto sia bello, rmagick non (e nemmeno i molti tentativi fatti da Python) si avvicina alla copertura di tutte le funzionalità.

Immagino per molti casi d'uso, sarà abbastanza sicuro e molto più semplice eseguire un metodo da riga di comando e leggere da quello. In ruby ​​sarà simile a questo;

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

Tratterò cose importanti e poi parlerò di note aggiuntive.

Il comando usa magick compare per verificare se la seconda immagine ( small) è una sottoimmagine della prima ( large). Questa funzione non verifica che small sia strettamente più piccola di large (sia in altezza che in larghezza). Il numero che ho inserito per la somiglianza è 0,2 (errore del 20%) e il valore per le immagini fornite è di circa 0,15. Potresti voler mettere a punto questo! Trovo che le immagini che sono un sottoinsieme rigoroso ottengano meno di 0,01.

  • Se vuoi meno errori (numeri più piccoli) nei casi in cui hai una sovrapposizione del 90% ma la seconda immagine ha delle cose extra la prima no, puoi eseguirla una volta, quindi ritagliare la prima immagine grande in cui è contenuta l'immagine secondaria , quindi eseguirlo nuovamente con l'immagine ritagliata come "piccola" e l'immagine "piccola" originale come quella grande.
  • Se volevi davvero una bella interfaccia orientata agli oggetti in Ruby, rmagick utilizza l'API MagicCore. Questo (link a documenti) comando è probabilmente quello che vuoi usare per implementarlo, e puoi aprire un pr per rmagick o impacchettare il cext da solo.
  • L'uso di open3 avvierà un thread ( vedi documenti ). Chiusura stderrestdout non è "necessario", ma dovresti.
  • L'immagine "temp" che è il terzo argomento specifica un file su cui generare un'analisi. Con una rapida occhiata, non sono riuscito a trovare un modo per non richiederlo, ma si limita a sovrascrivere automaticamente e potrebbe essere utile salvare per il debug. Per il tuo esempio, sarebbe simile a questo;

inserisci qui la descrizione dell'immagine

  • L'output completo è nel formato 10092.6 (0.154003) @ 0,31. Il primo numero è il valore rmse su 655535, il secondo (che io uso) è percentuale normalizzata. Gli ultimi due numeri rappresentano la posizione dell'immagine originale da cui inizia l'immagine piccola.
  • Dal momento che non esiste una fonte obiettiva di verità per quanto siano "simili" le immagini, ho scelto RMSE (vedi più opzioni metriche qui ). È una misura abbastanza comune delle differenze tra i valori. Un conteggio degli errori assoluti (AE) potrebbe sembrare una buona idea, tuttavia sembra che alcuni software di ritaglio non conservino perfettamente i pixel, quindi potresti dover regolare il fuzz e non è un valore normalizzato, quindi dovresti confrontare il conteggio degli errori con le dimensioni dell'immagine e quant'altro.

1
Ecco alcune informazioni davvero fantastiche lì Carol. Grazie
Niels Kristian il

Curioso di sapere come funziona per gli altri tuoi casi!
Carol Chen,

1
Grazie per l'ottima risposta. Se potessi, ti avrei dato una ricompensa di 100p anche per questo :-)
Niels Kristian il

3

Ottieni l'istogramma di entrambe le immagini e confrontale. Funzionerebbe molto bene per ritaglio e Zoom a meno che non ci sia un cambiamento troppo drastico a causa di questi.

Questo è meglio dell'approccio attuale in cui si sottraggono direttamente le immagini. Ma questo approccio ha ancora pochi.


Grazie per il consiglio che darò un'occhiata.
Niels Kristian,

Questa non è una risposta molto utile in quanto non dimostra come raggiungere l'obiettivo. È l'equivalente di "Google questo termine e capiscilo da solo".
altro

L'istogramma è una delle prime cose che le persone imparano nell'elaborazione delle immagini. Se qualcuno deve cercarlo su Google, mi scuso profondamente.
Raviteja Narra,

3

Di solito la corrispondenza dei modelli ha un buon risultato in queste situazioni. La corrispondenza dei modelli è una tecnica per trovare aree di un'immagine che corrispondono (sono simili) a un'immagine di modello (seconda immagine). Questo algoritmo fornisce un punteggio per la migliore posizione macthed nell'immagine di origine (la seconda).

In apertura usando TM_CCOEFF_NORMED metodo , fornisce il punteggio tra 0 e 1. Se il punteggio è 1, significa che l'immagine del modello è esattamente una parte (Rect) dell'immagine di origine, ma se si ha un piccolo cambiamento nell'illuminazione o nella prospettiva tra le due immagini, il punteggio sarebbe inferiore a 1.

Ora considerando una soglia per il punteggio di somiglianza, puoi scoprire se sono uguali o meno. Tale soglia può essere ottenuta con alcuni tentativi ed errori su alcune immagini di esempio. Ho provato le tue immagini e ho ottenuto il punteggio 0,823863 . Ecco il codice (opencv C ++) e l'area comune tra le due immagini, ottenute dalla corrispondenza:

inserisci qui la descrizione dell'immagine

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);

Grazie per l'ottima risposta. Se potessi, ti avrei dato una ricompensa di 100p anche per questo :-)
Niels Kristian il

2

Considera il metodo find_similar_region . Usa la più piccola delle due immagini come immagine di destinazione. Prova vari valori per gli attributi fuzz sull'immagine e sull'immagine di destinazione.


Grazie, comunque non riesco a farlo funzionare, vero?
Niels Kristian,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.