如何检测两个图像是否相同但裁剪比例不同?
How to detect if two images are the same with different cropping ratios?
我有两张不同尺寸的图片:
100px
和 400px
正如你所看到的,从人类的角度来看,这两者显然是“相同的”。现在我想检测它们是否相同。我一直在通过名为 rmagick
的 ruby gem 使用图像魔术,如下所示:
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
虽然这适用于具有相同 ratio/cropping 的图像,但当它们的裁剪略有不同并且已调整为相同宽度时,它并不理想。
有没有办法对不同裁剪的图片进行处理?我对一个解决方案很感兴趣,我可以这样说:一个图像包含在另一个图像中并覆盖周围的某个地方,例如90%。
PS。如果有帮助,我可以获得更高分辨率的图像(例如双)
考虑 find_similar_region 方法。使用两个图像中较小的一个作为目标图像。尝试图像和目标图像上模糊属性的各种值。
获取两张图片的直方图并进行比较。这对于裁剪和缩放非常有效,除非因为这些而发生太大的变化。
这比当前直接减去图像的方法要好。但是这种做法还是很少。
您可能想看看特征匹配。这个想法是在两个图像中找到特征并匹配它们。此方法通常用于在另一幅图像中查找模板(例如徽标)。本质上,特征可以描述为人类会在图像中发现有趣的事物,例如角落或开放空间。有许多类型的特征检测技术,但我的建议是使用 scale-invariant 特征变换 (SIFT) 作为特征检测算法。 SIFT 对图像平移、缩放、旋转具有不变性,对光照变化具有部分不变性,对局部几何失真具有鲁棒性。这似乎符合您的规范,其中图像的比例可能略有不同。
鉴于您提供的两张图片,这里尝试使用 FLANN feature matcher. To determine if the two images are the same, we can base it off some predetermined threshold which tracks the number of matches that pass the ratio test described in Distinctive Image Features from Scale-Invariant Keypoints by David G. Lowe 来匹配特征。对该测试的一个简单解释是,比率测试检查匹配是否有歧义并且应该被删除,您可以将其视为异常值删除技术。我们可以计算通过此测试的匹配项数,以确定两张图像是否相同。这是特征匹配结果:
Matches: 42
圆点代表检测到的所有匹配项,而绿线代表通过比率测试的 "good matches"。如果您不使用比率测试,那么将绘制所有点。这样,您可以将此过滤器用作阈值以仅保留最匹配的功能。
我在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()
由于 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
) 的子图像。此函数不检查小是否严格小于大(高度和宽度)。我为相似度输入的数字是 0.2(20% 误差),您提供的图像的值约为 0.15。您可能需要对此进行微调!我发现严格子集的图像小于 0.01。
- 如果你想在有 90% 重叠但第二张图像有一些第一张没有的额外内容的情况下减少错误(较小的数字),你可以 运行 它一次,然后裁剪第一个大图像到包含子图像的位置,然后 运行 再次使用裁剪图像作为 "small" 图像,原始 "small" 图像作为大图像。
- 如果你真的想要在 Ruby 中有一个很好的面向对象的界面,rmagick 使用 MagicCore API。 This (link to docs) command 大概就是你想用来实现它的,你可以打开一个pr给rmagick或者自己打包cext。
- 使用 open3 将启动一个线程 (see docs)。关闭
stderr
和 stdout
不是 "necessary" 但你应该这样做。
- 作为第三个参数的 "temp" 图像指定了一个文件来输出分析结果。快速浏览一下,我找不到不需要它的方法,但它确实会自动覆盖并且可以很好地保存以供调试。对于您的示例,它看起来像这样;
- 完整输出的格式为 10092.6 (0.154003) @ 0,31。第一个数字是 655535 中的 rmse 值,第二个(我使用的)是归一化百分比。最后两个数字表示小图像开始的原始图像的位置。
- 由于 "similar" 图像没有 objective 真实来源,我选择了 RMSE(查看更多度量选项 here)。这是衡量价值观差异的一种相当普遍的衡量标准。绝对错误计数 (AE) 似乎是个好主意,但似乎某些裁剪软件无法完美保留像素,因此您可能需要调整模糊度并且它不是标准化值,因此您必须比较错误计数与图像的大小和诸如此类的东西。
通常模板匹配在这些情况下会有很好的结果。模板匹配是一种用于查找与模板图像(第二图像)匹配(相似)的图像区域的技术。该算法给出了源图像(第二个)中最佳加工位置的分数。
在opencv中使用TM_CCOEFF_NORMED方法,给出0到1之间的分数。如果分数为1,则表示模板图像恰好是源图像的一部分(Rect),但是如果两个图像之间的光照或透视有一点变化,则分数将低于 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);
我有两张不同尺寸的图片:
100px
和 400px
正如你所看到的,从人类的角度来看,这两者显然是“相同的”。现在我想检测它们是否相同。我一直在通过名为 rmagick
的 ruby gem 使用图像魔术,如下所示:
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
虽然这适用于具有相同 ratio/cropping 的图像,但当它们的裁剪略有不同并且已调整为相同宽度时,它并不理想。
有没有办法对不同裁剪的图片进行处理?我对一个解决方案很感兴趣,我可以这样说:一个图像包含在另一个图像中并覆盖周围的某个地方,例如90%。
PS。如果有帮助,我可以获得更高分辨率的图像(例如双)
考虑 find_similar_region 方法。使用两个图像中较小的一个作为目标图像。尝试图像和目标图像上模糊属性的各种值。
获取两张图片的直方图并进行比较。这对于裁剪和缩放非常有效,除非因为这些而发生太大的变化。
这比当前直接减去图像的方法要好。但是这种做法还是很少。
您可能想看看特征匹配。这个想法是在两个图像中找到特征并匹配它们。此方法通常用于在另一幅图像中查找模板(例如徽标)。本质上,特征可以描述为人类会在图像中发现有趣的事物,例如角落或开放空间。有许多类型的特征检测技术,但我的建议是使用 scale-invariant 特征变换 (SIFT) 作为特征检测算法。 SIFT 对图像平移、缩放、旋转具有不变性,对光照变化具有部分不变性,对局部几何失真具有鲁棒性。这似乎符合您的规范,其中图像的比例可能略有不同。
鉴于您提供的两张图片,这里尝试使用 FLANN feature matcher. To determine if the two images are the same, we can base it off some predetermined threshold which tracks the number of matches that pass the ratio test described in Distinctive Image Features from Scale-Invariant Keypoints by David G. Lowe 来匹配特征。对该测试的一个简单解释是,比率测试检查匹配是否有歧义并且应该被删除,您可以将其视为异常值删除技术。我们可以计算通过此测试的匹配项数,以确定两张图像是否相同。这是特征匹配结果:
Matches: 42
圆点代表检测到的所有匹配项,而绿线代表通过比率测试的 "good matches"。如果您不使用比率测试,那么将绘制所有点。这样,您可以将此过滤器用作阈值以仅保留最匹配的功能。
我在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()
由于 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
) 的子图像。此函数不检查小是否严格小于大(高度和宽度)。我为相似度输入的数字是 0.2(20% 误差),您提供的图像的值约为 0.15。您可能需要对此进行微调!我发现严格子集的图像小于 0.01。
- 如果你想在有 90% 重叠但第二张图像有一些第一张没有的额外内容的情况下减少错误(较小的数字),你可以 运行 它一次,然后裁剪第一个大图像到包含子图像的位置,然后 运行 再次使用裁剪图像作为 "small" 图像,原始 "small" 图像作为大图像。
- 如果你真的想要在 Ruby 中有一个很好的面向对象的界面,rmagick 使用 MagicCore API。 This (link to docs) command 大概就是你想用来实现它的,你可以打开一个pr给rmagick或者自己打包cext。
- 使用 open3 将启动一个线程 (see docs)。关闭
stderr
和stdout
不是 "necessary" 但你应该这样做。 - 作为第三个参数的 "temp" 图像指定了一个文件来输出分析结果。快速浏览一下,我找不到不需要它的方法,但它确实会自动覆盖并且可以很好地保存以供调试。对于您的示例,它看起来像这样;
- 完整输出的格式为 10092.6 (0.154003) @ 0,31。第一个数字是 655535 中的 rmse 值,第二个(我使用的)是归一化百分比。最后两个数字表示小图像开始的原始图像的位置。
- 由于 "similar" 图像没有 objective 真实来源,我选择了 RMSE(查看更多度量选项 here)。这是衡量价值观差异的一种相当普遍的衡量标准。绝对错误计数 (AE) 似乎是个好主意,但似乎某些裁剪软件无法完美保留像素,因此您可能需要调整模糊度并且它不是标准化值,因此您必须比较错误计数与图像的大小和诸如此类的东西。
通常模板匹配在这些情况下会有很好的结果。模板匹配是一种用于查找与模板图像(第二图像)匹配(相似)的图像区域的技术。该算法给出了源图像(第二个)中最佳加工位置的分数。
在opencv中使用TM_CCOEFF_NORMED方法,给出0到1之间的分数。如果分数为1,则表示模板图像恰好是源图像的一部分(Rect),但是如果两个图像之间的光照或透视有一点变化,则分数将低于 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);