Como detecto que duas imagens são “iguais” mesmo que uma tenha uma proporção / corte ligeiramente diferente?

11

Eu tenho duas imagens diferentes:

em 100 insira a descrição da imagem aquipx ou 400 pxinsira a descrição da imagem aqui

e

na largura de insira a descrição da imagem aqui100 px ou 400 pxinsira a descrição da imagem aqui

Como você pode ver, os dois são claramente os "mesmos" do ponto de vista humano. Agora eu quero detectar programaticamente que eles são os mesmos. Eu tenho usado magia de imagem através da gema de rubi chamada rmagickassim:

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

Embora isso funcione bem para imagens com a mesma proporção / corte, não é ideal quando elas têm cortes ligeiramente diferentes e foram redimensionadas para a mesma largura.

Existe uma maneira de fazer isso com imagens com cortes diferentes? Estou interessado em uma solução em que posso dizer algo como: Uma imagem está contida na outra e cobre algo em torno de, por exemplo, 90% dela.

PS. Posso obter as imagens em alta resolução, se isso ajudar (por exemplo, o dobro)

Niels Kristian
fonte
2
Não tenho certeza sobre o RMagick, mas a compareferramenta de linha de comando do ImageMagick possui uma -subimage-searchopção.
Stefan
Isso é interessante, como seria um comando como esse?
Niels Kristian
2
Nunca usei sozinho, talvez isso ajude: stackoverflow.com/q/29062811/477037
Stefan
Obrigado, essa é uma ótima informação. No entanto, não consigo descobrir como fazer isso com o ruby ​​...
Niels Kristian
11
As imagens são de baixa qualidade? Se não, compartilhe uma versão maior de imagens, com mais qualidade.
MH304

Respostas:

6

Você pode dar uma olhada na correspondência de recursos. A idéia é encontrar recursos em duas imagens e combiná-los. Esse método é comumente usado para encontrar um modelo (por exemplo, um logotipo) em outra imagem. Um recurso, em essência, pode ser descrito como coisas que os humanos considerariam interessantes em uma imagem, como cantos ou espaços abertos. Existem muitos tipos de técnicas de detecção de recursos por aí, no entanto, minha recomendação é usar uma transformação de recurso invariável em escala (SIFT) como um algoritmo de detecção de recursos. O SIFT é invariável à conversão, redimensionamento, rotação da imagem, parcialmente invariável às mudanças de iluminação e robusto à distorção geométrica local. Isso parece corresponder à sua especificação, onde as imagens podem ter proporções ligeiramente diferentes.

Dadas as duas imagens fornecidas, aqui está uma tentativa de combinar os recursos usando o combinador de recursos da FLANN . Para determinar se as duas imagens são iguais, podemos basear algum limiar predeterminado que rastreia o número de correspondências que passam no teste de proporção descrito em Recursos de imagem distintiva de pontos-chave invariantes em escala por David G. Lowe . Uma explicação simples do teste é que o teste de proporção verifica se as correspondências são ambíguas e devem ser removidas; você pode tratá-lo como uma técnica de remoção externa. Podemos contar o número de correspondências que passam neste teste para determinar se as duas imagens são iguais. Aqui estão os resultados da correspondência de recursos:

Matches: 42

Os pontos representam todas as correspondências detectadas, enquanto as linhas verdes representam as "boas correspondências" que passam no teste de proporção. Se você não usar o teste de proporção, todos os pontos serão sorteados. Dessa forma, você pode usar esse filtro como limite para manter apenas os melhores recursos correspondentes.


Eu o implementei em Python, não estou muito familiarizado com o Rails. Espero que isso ajude, boa sorte!

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()
nathancy
fonte
2
Abordagem super interessante, vou dar uma volta e voltar ...
Niels Kristian
PS. Atualizei as imagens em uma escala maior
Niels Kristian
11
@nathancy É assim que, no seu exemplo, os pontos verdes correspondem, mas os azuis não? Parece que há muitos pontos incomparáveis?
Draco Ater 04/02
2
@DracoAter boa pergunta, os pontos azuis representam todas as correspondências, enquanto apenas desenhamos "boas correspondências" que passam no teste de proporção em verde. Se você não usar o teste de proporção, todos os pontos serão sorteados, mas filtramos usando o teste de proporção para desenhar as correspondências "melhores". Dessa maneira, o OP pode usar esse teste como um limite para manter apenas os melhores recursos correspondentes. Portanto, todos os pontos azuis são os recursos que a SIFT encontrou, mas filtramos para manter os bons desenhados em verde
nathancy
Obrigado. a competição foi dura nas respostas, muitas ótimas :-)
Niels Kristian
4

Como o ImageMagick é muito antigo, avançado e uma ferramenta com muitos recursos, seria difícil criar uma interface que cubra a maioria dos recursos. Por melhor que seja, o rmagick não chega nem perto de cobrir todos os recursos.

Eu imagino que, para muitos casos de uso, será seguro o suficiente e muito mais fácil simplesmente executar um método de linha de comando e ler a partir dele. Em rubi, será assim;

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

Vou cobrir coisas importantes e depois falar sobre anotações adicionais.

O comando usa magick compare para verificar se a segunda imagem ( small) é uma sub-imagem da primeira ( large). Esta função não verifica se pequeno é estritamente menor que grande (altura e largura). O número que coloquei para a semelhança é 0,2 (erro de 20%) e o valor para as imagens que você forneceu é de cerca de 0,15. Você pode ajustar isso! Acho que as imagens que são um subconjunto estrito recebem menos de 0,01.

  • Se você deseja menos erro (números menores) nos casos em que há sobreposição de 90%, mas a segunda imagem possui algumas coisas extras, a primeira não pode ser executada uma vez e, em seguida, recortar a primeira imagem grande no local em que a sub-imagem está contida , execute-o novamente com a imagem cortada como a "pequena" e a imagem "pequena" original como a grande.
  • Se você realmente queria uma interface agradável orientada a objetos no Ruby, o rmagick usa a API MagicCore. Esse comando (link para o docs) é provavelmente o que você deseja usar para implementá-lo, e você pode abrir um arquivo pr para rmagick ou empacotar o cext por conta própria.
  • O uso do open3 iniciará um thread ( consulte a documentação ). Fechar stderre stdoutnão é "necessário", mas você deveria.
  • A imagem "temp", que é o terceiro argumento, especifica um arquivo no qual a análise é enviada. Com uma rápida olhada, não consegui encontrar uma maneira de não exigir isso, mas ela é substituída automaticamente e pode ser boa para economizar na depuração. Para o seu exemplo, seria assim;

insira a descrição da imagem aqui

  • A saída completa está no formato 10092.6 (0.154003) @ 0,31. O primeiro número é o valor rmse de 655535, o segundo (que eu uso) é a porcentagem normalizada. Os dois últimos números representam a localização da imagem original a partir da qual a imagem pequena começa.
  • Como não há uma fonte objetiva de verdade sobre como as imagens são "semelhantes", escolhi o RMSE (veja mais opções de métricas aqui ). É uma medida bastante comum de diferenças entre valores. Uma contagem absoluta de erros (AE) pode parecer uma boa idéia, no entanto, parece que alguns softwares de corte não preservam perfeitamente os pixels; portanto, você pode precisar ajustar o fuzz e não é um valor normalizado; portanto, é necessário comparar a contagem de erros com o tamanho da imagem e outros enfeites.
Carol Chen
fonte
11
Isso é realmente uma grande informação lá Carol. Obrigado
Niels Kristian
Curioso para saber como isso funciona nos seus outros casos!
Carol Chen
11
Obrigado pela super ótima resposta. Se eu pudesse, eu também daria 100p de recompensa por este :-)
Niels Kristian
3

Obtenha o histograma das duas imagens e compare-as. Isso funcionaria muito bem para o corte e o zoom, a menos que haja uma alteração muito drástica por causa disso.

Isso é melhor do que a abordagem atual em que você está subtraindo diretamente as imagens. Mas essa abordagem ainda tem poucas.

Raviteja Narra
fonte
Obrigado pelo conselho, vou dar uma olhada.
Niels Kristian
Esta não é uma resposta muito útil, pois não demonstra como atingir o objetivo. É o equivalente a "Google este termo e descubra você mesmo".
anothermh
O histograma é uma das primeiras coisas que as pessoas aprendem no processamento de imagens. Se alguém precisar pesquisar no Google, peço desculpas profundamente.
Raviteja Narra
3

Normalmente, a correspondência de modelos tem um bom resultado nessas situações. A correspondência de modelo é uma técnica para encontrar áreas de uma imagem que correspondem (são semelhantes) a uma imagem de modelo (segunda imagem). Esse algoritmo fornece uma pontuação para a melhor posição destacada na imagem de origem (a segunda).

No opencv, usando o método TM_CCOEFF_NORMED , fornece a pontuação entre 0 e 1. Se a pontuação for 1, significa que a imagem do modelo é exatamente uma parte (Rect) da imagem de origem, mas se houver uma pequena alteração no raio ou na perspectiva entre nas duas imagens, a pontuação seria menor que 1.

Agora, considerando um limite para a pontuação de similaridade, você pode descobrir se são iguais ou não. Esse limite pode ser obtido por tentativa e erro em algumas imagens de amostra. Eu tentei suas imagens e obtive a pontuação 0.823863 . Aqui está o código (opencv C ++) e a área comum entre as duas imagens, obtida pela correspondência:

insira a descrição da imagem aqui

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);
MH304
fonte
Obrigado pela super ótima resposta. Se eu pudesse, eu também daria 100p de recompensa por este :-)
Niels Kristian
2

Considere o método find_similar_region . Use a menor das duas imagens como imagem de destino. Tente vários valores para os atributos de fuzz na imagem e na imagem de destino.

Um usuário RMagick
fonte
Obrigado, no entanto, não posso fazê-lo funcionar - você pode?
Niels Kristian