如何检测两个具有不同裁剪比例的图片是否相同?

12

我有两张不同尺寸的图片:

100px

enter image description here enter image description here

和400px

enter image description here enter image description here

从人的角度来看,这两个显然是“相同”的。现在我想检测它们是否相同。我一直在使用 ruby gem 中的 image magic 库 rmagick 来实现:

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

对于具有相同比例和裁剪的图像,这种方法效果很好,但当它们具有略微不同的裁剪并被调整为相同的宽度时,就不是理想的方法。

有没有一种方法可以处理具有不同裁剪的图像?我感兴趣的解决方案是:一个图像包含在另一个图像内,并覆盖大约90%的区域。

注:如果有需要的话,我可以获得更高分辨率的图像(例如双倍大小)。


2
不确定RMagick,但是ImageMagick的compare命令行工具有一个-subimage-search开关。 - Stefan
这很有趣,这样的命令会是什么样子? - Niels Kristian
2
我自己从未使用过,也许这可以帮助:https://dev59.com/dYjca4cB1Zd3GeqP1cIw - Stefan
谢谢,这是一条很棒的信息。然而我无法从 Ruby 中找出如何实现它... - Niels Kristian
1
这些图片质量低吗?如果不是,请分享更高质量的大图。 - MeiH
完成了,查看更新后的图片... - Niels Kristian
5个回答

7
你可能想要了解一下特征匹配。其思想是在两幅图像中找到特征并进行匹配。这种方法通常用于在另一幅图像中找到模板(例如标志)。特征本质上可以被描述为人类在图像中发现的有趣的事物,例如拐角或开放空间。虽然有许多类型的特征检测技术,但我的建议是使用尺度不变特征变换(SIFT)作为特征检测算法。SIFT对图像平移、缩放、旋转具有不变性,对光照变化部分具有不变性,并且对局部几何失真具有鲁棒性。这似乎符合您的规格,因为图像可以具有稍微不同的比例。
给定你提供的两张图片,我们使用FLANN特征匹配器来尝试匹配特征点。为了确定这两张图片是否相同,我们可以通过预设阈值来跟踪通过David G. Lowe描述的比率测试的匹配数量。简单地说,比率测试检查匹配是否模糊,并应该被移除,可以将其视为异常值去除技术。我们可以计算通过此测试的匹配数以确定这两张图片是否相同。以下是特征匹配结果:

Matches: 42

这些点代表所有检测到的匹配项,而绿色线条表示通过比率测试的“好匹配项”。如果不使用比率测试,则会绘制所有点。通过这种方式,您可以将此过滤器用作阈值,仅保留最佳匹配特征。


我用Python实现了它,对Rails不是很熟悉。希望这有所帮助,祝好运!
代码
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
非常有趣的方法,我会试一试并回来... - Niels Kristian
1
@nathancy,你的例子是这样的吗?绿色的点匹配了,但蓝色的没有?看起来有太多的不匹配的点了? - Draco Ater
2
@DracoAter 很好的问题,蓝点代表所有匹配点,而我们只绘制通过比率测试的“好匹配点”为绿色。如果您不使用比率测试,则会绘制所有点,但我们使用比率测试进行过滤以绘制“更好”的匹配点。通过这种方式,OP可以将此测试用作阈值,仅保留最佳匹配特征。因此,所有蓝点都是SIFT找到的特征,但我们进行过滤以保留绘制为绿色的好特征。 - nathancy
谢谢。这次比赛的答案都很出色,竞争非常激烈 :-) - Niels Kristian

4
由于ImageMagick非常古老,先进且功能丰富,因此构建覆盖大部分功能的界面将会很困难。尽管rmagick非常出色,但它(以及Python所尝试的许多方法)都无法涵盖所有功能。
我认为,在许多用例中,执行命令行方法并从中读取会更加安全和容易。在Ruby中,这将看起来像这样;
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

我将介绍重要的内容,然后谈论一些额外的注意事项。

该命令使用magick compare检查第二张图片(small)是否是第一张图片(large)的子图像。此函数不检查严格小于large(高度和宽度均如此)。我为相似性设置的数字是0.2(20%误差),对于您提供的图像,该值约为0.15。您可能需要微调此参数!我发现严格子集的图像得分低于0.01。

  • 如果你想在有90%重叠但第二张图片比第一张多一些东西的情况下减少错误(更小的数字),你可以先运行一次,然后将第一张大图裁剪到包含子图像的位置,然后使用裁剪后的图像作为“小”图像,原始的“小”图像作为大图像再次运行。
  • 如果你真的想在Ruby中使用一个好的面向对象接口,rmagick使用MagicCore API。这个(链接到文档) 命令可能是你想要使用来实现它的,并且你可以打开一个pr到rmagick或自己打包cext。
  • 使用open3会启动一个线程(请参见文档)。关闭stderrstdout不是“必需的”,但你应该这样做。
  • 作为第三个参数的“temp”图像指定了一个文件来输出分析结果。经过快速查找,我没有找到不需要它的方法,但它确实会自动覆盖并且保存调试信息可能很有用。对于你的例子,它应该是这样的;

enter image description here

完整的输出格式为10092.6(0.154003)@ 0,31。第一个数字是655535个RMSE值,第二个数字(我使用的)是标准化百分比。最后两个数字表示原始图像的位置,从中开始小图像。 由于没有客观的真实来源来说明图像的“相似”,因此我选择了RMSE(请在这里查看更多指标选项)。它是一种衡量值之间差异的相当普遍的方法。绝对误差计数(AE)可能看起来是一个好主意,但是似乎一些裁剪软件不能完美地保留像素,因此您可能需要调整模糊度,并且它不是标准化值,因此您必须将错误计数与图像大小等进行比较。

1
那是一些非常棒的信息,Carol。谢谢。 - Niels Kristian
好奇这对你的其他情况如何运作! - Carol Chen
1
谢谢你提供非常棒的回答。如果可以的话,我也会给你100分奖励 :-) - Niels Kristian

3
通常,在这些情况下,模板匹配可以得到良好的结果。模板匹配是一种技术,用于查找与模板图像(第二个图像)匹配(相似)的图像区域。该算法为源图像中最佳匹配位置(第二个图像)给出一个分数。
在opencv中使用TM_CCOEFF_NORMED方法,得分介于0和1之间。如果得分为1,则表示模板图像恰好是源图像(第二个图像)的一部分(矩形),但如果两个图像之间的光线或透视有轻微变化,则得分将低于1。
现在,通过考虑相似度得分的阈值,您可以找出它们是否相同。该阈值可以通过对几个样本图像进行试验和错误来获得。我尝试了您的图像,并得到了得分0.823863。以下是代码(opencv C++)和匹配到的两个图像之间的公共区域:

enter image description here

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

谢谢你的超棒回答。如果可以的话,我也会给你100p的奖励 :-) - Niels Kristian

2
考虑使用find_similar_region方法。将两张图片中较小的那张作为目标图片。尝试在图像和目标图像上使用模糊属性的各种值。

1
获取两幅图像的直方图并进行比较。除非由于裁剪和缩放引起了过于剧烈的变化,否则这种方法将非常有效。这比当前的直接相减的方法要好得多。不过,这种方法仍然有一些问题。

谢谢您的建议,我会仔细看一下。 - Niels Kristian
这并不是一个非常有用的答案,因为它没有展示如何实现目标。这相当于说“谷歌一下这个术语然后自己想办法”。 - anothermh
直方图是图像处理中人们学习的第一件事情之一。如果有人不得不去谷歌搜索它,那么我深表歉意。 - Raviteja Narra

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接