即使一个图像的裁剪/比率略有不同,我如何检测到两个图像“相同”?


11

我有两个不同的图像:

在100px 在此处输入图片说明或400px在此处输入图片说明

宽度为100 在此处输入图片说明像素或400像素在此处输入图片说明

如您所见,从人类的角度来看,两者显然是“相同的”。现在,我想以编程方式检测它们是否相同。我一直在通过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%的位置。

PS。如果有帮助,我可以以更高的分辨率获得图像(例如,双倍)


2
不确定RMagick,但是ImageMagick的compare命令行工具有一个-subimage-search开关。
Stefan

很有意思,这样的命令看起来如何?
Niels Kristian

2
我自己从未使用过,也许这会有所帮助:stackoverflow.com/q/29062811/477037
Stefan

谢谢,这是非常重要的信息。我不知道如何从红宝石中做到这一点……
Niels Kristian

1
图像质量低吗?如果没有,请以更大的质量共享更大版本的图像。
MH304

Answers:


6

您可能需要看一下功能匹配。这个想法是在两个图像中找到特征并匹配它们。此方法通常用于在其他图像中查找模板(例如徽标)。本质上,特征可以描述为人类会在图像中发现有趣的事物,例如角落或开放空间。有很多类型的特征检测技术,但是我的建议是使用尺度不变特征变换(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

PS。我更大规模地更新了图像
Niels Kristian

1
@nathancy在您的示例中,绿色点是否匹配,而蓝色点不匹配?看起来有太多不匹配的点?
Draco Ater

2
@DracoAter好问题,蓝色圆点表示所有匹配项,而我们仅绘制通过绿色比率测试的“良好匹配项”。如果您不使用比率测试,则将绘制所有点,但是我们将使用比率测试进行过滤以绘制“更好”的匹配项。这样,OP可以将此测试用作阈值,以仅保留最匹配的功能。因此,所有蓝点都是SIFT发现的功能,但我们进行过滤以保留以绿色绘制的好颜色
nathancy

谢谢。竞争很难找到答案,很多很棒:-)
Niels Kristian

4

由于ImageMagick是非常古老,先进且功能众多的工具,因此很难构建一个涵盖大多数功能的界面。尽其所能,rmagick并没有(而且python所做的许多尝试也没有)接近涵盖所有功能。

我想对于许多用例来说,仅执行命令行方法并从中读取内容将足够安全且容易得多。在红宝石中将看起来像这样;

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。此功能不会检查小尺寸是否严格小于大尺寸(高度和宽度)。我为相似性输入的数字是0.2(错误20%),而您提供的图像的价值约为0.15。您可能需要对此进行微调!我发现作为严格子集的图像得到的图像小于0.01。

  • 如果您希望在重叠率达到90%的情况下减少错误(较小的数字),而第二张图像有一些多余的东西,而第一张图像则没有,您可以运行一次,然后将第一张大图像裁剪到包含子图像的位置,然后再次运行,将裁剪后的图像作为“小”图像,将原始“小”图像作为大图像。
  • 如果您真的想在Ruby中使用漂亮的面向对象的接口,则rmagick使用MagicCore API。这个(链接到docs)命令可能是您要用来实现它的命令,您可以打开pr来rmagick或自己打包cext。
  • 使用open3将启动一个线程(请参阅docs)。关闭stderrstdout不是“必要的”,但你应该。
  • 第三个arg的“临时”图像指定一个文件,用于将分析输出到该文件。快速浏览,我找不到不要求它的方法,但是它只是自动覆盖,可以很好地保存以进行调试。以您的示例为例:

在此处输入图片说明

  • 完整输出的格式为10092.6(0.154003)@ 0.31。第一个数字是655535中的均方根值,第二个数字(我使用的是标准化百分比)。最后两个数字代表小图像从其开始的原始图像的位置。
  • 由于没有关于“相似”图像的真实度的客观来源,因此我选择了RMSE(请参见此处的更多指标选项)。这是值之间差异的相当普遍的度量。绝对错误计数(AE)似乎是个好主意,但是似乎某些裁剪软件不能完全保留像素,因此您可能需要调整模糊度,而且它不是归一化的值,因此您必须比较错误计数与图像的大小之类的。

1
那是Carol的一些非常有用的信息。谢谢
Niels Kristian

很好奇这在您的其他情况下如何运作!
卡罗尔·陈

1
非常感谢您的答复。如果可以的话,我也为您提供了100便士的奖励:-)
Niels Kristian

3

获取两个图像的直方图并进行比较。除非有太大的改变,否则这对于裁剪和缩放非常有效。

这比直接减去图像的当前方法要好。但是这种方法仍然很少。


感谢您的建议,我将对其进行介绍。
Niels Kristian

这不是一个非常有用的答案,因为它没有演示如何实现目标。相当于“这个词用Google自己弄清楚”。
anothermh

直方图是人们在图像处理中首先学习的东西之一。如果有人必须用谷歌搜索,那我深表歉意。
Raviteja Narra

3

通常,在这些情况下,模板匹配会有很好的效果。模板匹配是一种用于查找与模板图像(第二张图像)匹配(相似)的图像区域的技术。该算法为源图像中的最佳位置进行评分(第二个)。

在opencv中,使用TM_CCOEFF_NORMED方法,将得分设为 0到1之间。如果得分为1,则表示模板图像恰好是源图像的一部分(矩形),但是如果您在两张图片,得分将低于1。

现在,通过考虑相似性得分的阈值,您可以找出它们是否相同。该阈值可以通过对一些样本图像进行反复试验而获得。我尝试了您的图像并获得了0.823863的分数 。这是代码(opencv C ++)和两个图像之间的公共区域,通过匹配获得:

在此处输入图片说明

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

非常感谢您的答复。如果可以的话,我也为您提供了100便士的奖励:-)
Niels Kristian

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.