用 OpenCV 检查图像的相似性

OpenCV 是否支持对两张图片进行比较,返回一些值(可能是百分比)来表明这两张图片的相似程度?例如,如果同一个图像传递两次,则返回100% ; 如果图像完全不同,则返回0% 。

我已经在 StackOverflow 上读到了很多类似的话题。我还用谷歌搜索了一下。可惜我想不出一个满意的答案。

223367 次浏览

这是一个巨大的话题,从3行代码到整个研究杂志的答案。

我将概述最常见的这类技术及其结果。

比较直方图

最简单和最快的方法之一。几十年前提出,作为寻找图片相似性的一种手段。这个想法是,一个森林将有很多绿色,和一个人的脸很多粉红色,或什么。所以,如果你比较两张图片和森林,你会得到一些直方图之间的相似性,因为两者都有很多绿色。

缺点: 它过于简单化了。香蕉和海滩看起来是一样的,因为它们都是黄色的。

OpenCV 方法: compareHist ()

模板匹配

这里有一个很好的例子 模板寻找良好匹配。它将被搜索的图像与被搜索的图像卷积在一起。它通常用于在较大的图像中找到较小的图像部分。

缺点: 它只返回相同的图像,相同的大小和方向良好的结果。

OpenCV 方法: match Template ()

特征匹配

被认为是最有效的图像搜索方法之一。从一幅图像中提取出许多特征,以确保即使在旋转、缩放或倾斜的情况下也能再次识别出相同的特征。该方法提取的特征可以与其他图像特征集进行匹配。另一个图像具有高比例的特征匹配的第一个被认为是描绘相同的场景。

找到这两组点之间的同形异义词还可以让你找到原始图片之间拍摄角度的相对差异或者重叠的数量。

这里有很多 OpenCV 教程/示例,还有一个不错的视频 给你。一个完整的 OpenCV 模块(Features 2d)专门用于它。

缺点: 它可能是缓慢的。它不是完美的。


OpenCV 问与答网站上,我谈到了特征描述符之间的区别,当比较整个图像和纹理描述符时,特征描述符非常有用。纹理描述符用于识别图像中的人脸或汽车等物体。

如果匹配相同的图像(相同的大小/方向)

// Compare two images by getting the L2 error (square-root of sum of squared error).
double getSimilarity( const Mat A, const Mat B ) {
if ( A.rows > 0 && A.rows == B.rows && A.cols > 0 && A.cols == B.cols ) {
// Calculate the L2 relative error between images.
double errorL2 = norm( A, B, CV_L2 );
// Convert to a reasonable scale, since L2 error is summed across all pixels of the image.
double similarity = errorL2 / (double)( A.rows * A.cols );
return similarity;
}
else {
//Images have a different size
return 100000000.0;  // Return a bad value
}

来源

有点偏离主题,但有用的是 Python numpy方法。它的健壮和快速,但只是比较像素,而不是图片包含的对象或数据(它需要相同大小和形状的图像) :

在没有 openCV 和任何计算机视觉库的情况下,一种非常简单快速的方法是规范图像数组

import numpy as np
picture1 = np.random.rand(100,100)
picture2 = np.random.rand(100,100)
picture1_norm = picture1/np.sqrt(np.sum(picture1**2))
picture2_norm = picture2/np.sqrt(np.sum(picture2**2))

在定义了两个规范化图片(或矩阵)之后,你可以对你想要比较的图片的乘法进行求和:

1)如果你比较相似的图片,总和将返回1:

In[1]: np.sum(picture1_norm**2)
Out[1]: 1.0

2)如果它们不相似,你会得到一个介于0和1之间的值(如果乘以100就是一个百分比) :

In[2]: np.sum(picture2_norm*picture1_norm)
Out[2]: 0.75389941124629822

请注意,如果你有彩色图片,你必须做到这一点在所有3个维度或只是比较灰度版本。我经常不得不将大量的图片与任意的内容进行比较,这是一个非常快速的方法。

Sam 的解决方案应该足够了。我使用了直方图差异和模板匹配相结合的方法,因为没有一种方法能够百分之百地为我工作。不过,我对直方图方法的重视程度较低。下面是我如何在简单的 python 脚本中实现的。

import cv2


class CompareImage(object):


def __init__(self, image_1_path, image_2_path):
self.minimum_commutative_image_diff = 1
self.image_1_path = image_1_path
self.image_2_path = image_2_path


def compare_image(self):
image_1 = cv2.imread(self.image_1_path, 0)
image_2 = cv2.imread(self.image_2_path, 0)
commutative_image_diff = self.get_image_difference(image_1, image_2)


if commutative_image_diff < self.minimum_commutative_image_diff:
print "Matched"
return commutative_image_diff
return 10000 //random failure value


@staticmethod
def get_image_difference(image_1, image_2):
first_image_hist = cv2.calcHist([image_1], [0], None, [256], [0, 256])
second_image_hist = cv2.calcHist([image_2], [0], None, [256], [0, 256])


img_hist_diff = cv2.compareHist(first_image_hist, second_image_hist, cv2.HISTCMP_BHATTACHARYYA)
img_template_probability_match = cv2.matchTemplate(first_image_hist, second_image_hist, cv2.TM_CCOEFF_NORMED)[0][0]
img_template_diff = 1 - img_template_probability_match


# taking only 10% of histogram diff, since it's less accurate than template method
commutative_image_diff = (img_hist_diff / 10) + img_template_diff
return commutative_image_diff




if __name__ == '__main__':
compare_image = CompareImage('image1/path', 'image2/path')
image_difference = compare_image.compare_image()
print image_difference

可以使用 VGG16等架构对预先训练好的 ImageRes 数据进行自动编码,然后计算查询和其他图像之间的距离,以找到最接近的匹配。

由于没有人发布一个完整的具体例子,这里有两个定量的方法来确定两个图像之间的相似性。一种方法用于比较具有相同尺寸的图像; 另一种方法用于比例不变和变换不同的图像。两种方法都返回 0100之间的相似度评分,其中 0代表一个完全不同的图像,而 100代表一个相同/重复的图像。对于介于两者之间的所有其他值: 分数越低,相似度越低; 分数越高,相似度越高。

方法 # 1: 结构相似性指数

为了比较两幅图像之间的差异并确定准确的差异,我们可以利用 图像质量评估: 从误差可见性到结构相似性中引入的 结构相似性指数(SSIM)。SSIM 是一种图像质量评估方法,它根据参考图像和失真图像之间局部信息的统计特性来估计结构相似性的退化程度。SSIM 值的范围在[-1,1]之间,它通常使用滑动窗口计算,其中整个图像的 SSIM 值计算为所有单个窗口结果的平均值。此方法已经在用于图像处理的 Scikit-image库中实现,可以与 pip install scikit-image一起安装。

skimage.metrics.structural_similarity()函数返回一个比较 score和一个差异图像 diffscore表示两幅图像之间的平均 SSIM 得分,较高的值表示较高的相似性。diff图像包含的实际图像差异与较暗的区域有更大的差异。较大的差异区域用黑色突出显示,而较小的差异区域用灰色突出显示。这里有一个例子:

输入图像

差异图像 ->突出显示掩码差异

比较两幅图像后的 SSIM 分数显示它们非常相似。

相似度得分: 89.462%

为了使两幅图像之间的精确差异可视化,我们可以遍历每个轮廓,使用最小阈值区域进行过滤以去除微小噪声,并用边界框突出显示差异。

限制: 虽然这种方法工作得很好,但是有一些重要的限制。两个输入图像必须具有相同的大小/尺寸,并且还存在一些问题,包括缩放、平移、旋转和失真。SSIM 也不能很好地执行模糊或嘈杂的图像。方法 # 2解决了这些问题。

密码

from skimage.metrics import structural_similarity
import cv2
import numpy as np


first = cv2.imread('clownfish_1.jpeg')
second = cv2.imread('clownfish_2.jpeg')


# Convert images to grayscale
first_gray = cv2.cvtColor(first, cv2.COLOR_BGR2GRAY)
second_gray = cv2.cvtColor(second, cv2.COLOR_BGR2GRAY)


# Compute SSIM between two images
score, diff = structural_similarity(first_gray, second_gray, full=True)
print("Similarity Score: {:.3f}%".format(score * 100))


# The diff image contains the actual image differences between the two images
# and is represented as a floating point data type so we must convert the array
# to 8-bit unsigned integers in the range [0,255] before we can use it with OpenCV
diff = (diff * 255).astype("uint8")


# Threshold the difference image, followed by finding contours to
# obtain the regions that differ between the two images
thresh = cv2.threshold(diff, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
contours = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]


# Highlight differences
mask = np.zeros(first.shape, dtype='uint8')
filled = second.copy()


for c in contours:
area = cv2.contourArea(c)
if area > 100:
x,y,w,h = cv2.boundingRect(c)
cv2.rectangle(first, (x, y), (x + w, y + h), (36,255,12), 2)
cv2.rectangle(second, (x, y), (x + w, y + h), (36,255,12), 2)
cv2.drawContours(mask, [c], 0, (0,255,0), -1)
cv2.drawContours(filled, [c], 0, (0,255,0), -1)


cv2.imshow('first', first)
cv2.imshow('second', second)
cv2.imshow('diff', diff)
cv2.imshow('mask', mask)
cv2.imshow('filled', filled)
cv2.waitKey()

方法 # 2: 密集向量表示

通常情况下,两个图像不会完全相同。它们可能会因为背景、尺寸、特性的增减或者变化(缩放、旋转、倾斜)而有所不同。换句话说,我们不能使用直接的像素到像素的方法,因为随着变化,问题从识别像素相似性转移到对象相似性。我们必须切换到深度学习的特征模型,而不是比较单个像素值。

为了确定相同和接近相似的图像,我们可以使用 sentence-transformers文库,它提供了一个简单的方法来计算图像的密集矢量表示和 OpenAI 对比语言-图像预训练(CLIP)模型,它是一个神经网络,已经训练了各种(图像,文本)对。这个想法是将所有的图像编码到矢量空间,然后找到高密度区域对应的区域的图像是相当相似的。

当两幅图像进行比较时,它们会得到一个从 01.00的评分。我们可以使用一个阈值参数来识别相似或不同的两个图像。一个较低的阈值将导致聚类中有较少的相似图像。相反,较高的阈值将导致具有更多相似图像的聚类。一个重复的图像将有一个评分的 1.00意味着两个图像是完全相同的。为了找到接近相似的图像,我们可以将阈值设置为任意值,比如 0.9。例如,如果两幅图像之间的确定得分大于 0.9,那么我们可以推断它们是近似相似的图像。


举个例子:

enter image description here

这个数据集有五张图片,注意有如何复制的花 # 1,而其他是不同的。

识别重复图像

Score: 100.000%
.\flower_1 copy.jpg
.\flower_1.jpg

花1号和它的复制品都是一样的

识别近似图像

Score: 97.141%
.\cat_1.jpg
.\cat_2.jpg


Score: 95.693%
.\flower_1.jpg
.\flower_2.jpg


Score: 57.658%
.\cat_1.jpg
.\flower_1 copy.jpg


Score: 57.658%
.\cat_1.jpg
.\flower_1.jpg


Score: 57.378%
.\cat_1.jpg
.\flower_2.jpg


Score: 56.768%
.\cat_2.jpg
.\flower_1 copy.jpg


Score: 56.768%
.\cat_2.jpg
.\flower_1.jpg


Score: 56.284%
.\cat_2.jpg
.\flower_2.jpg

我们在不同的图像之间得到了更有趣的结果。分数越高,相似度越高; 分数越低,相似度越低。使用 0.9或90% 的阈值,我们可以过滤掉近似的图像。

两幅图像的比较

Score: 97.141%
.\cat_1.jpg
.\cat_2.jpg
Score: 95.693%
.\flower_1.jpg
.\flower_2.jpg
Score: 88.914%
.\ladybug_1.jpg
.\ladybug_2.jpg
Score: 94.503%
.\cherry_1.jpg
.\cherry_2.jpg

密码

from sentence_transformers import SentenceTransformer, util
from PIL import Image
import glob
import os


# Load the OpenAI CLIP Model
print('Loading CLIP Model...')
model = SentenceTransformer('clip-ViT-B-32')


# Next we compute the embeddings
# To encode an image, you can use the following code:
# from PIL import Image
# encoded_image = model.encode(Image.open(filepath))
image_names = list(glob.glob('./*.jpg'))
print("Images:", len(image_names))
encoded_image = model.encode([Image.open(filepath) for filepath in image_names], batch_size=128, convert_to_tensor=True, show_progress_bar=True)


# Now we run the clustering algorithm. This function compares images aganist
# all other images and returns a list with the pairs that have the highest
# cosine similarity score
processed_images = util.paraphrase_mining_embeddings(encoded_image)
NUM_SIMILAR_IMAGES = 10


# =================
# DUPLICATES
# =================
print('Finding duplicate images...')
# Filter list for duplicates. Results are triplets (score, image_id1, image_id2) and is scorted in decreasing order
# A duplicate image will have a score of 1.00
# It may be 0.9999 due to lossy image compression (.jpg)
duplicates = [image for image in processed_images if image[0] >= 0.999]


# Output the top X duplicate images
for score, image_id1, image_id2 in duplicates[0:NUM_SIMILAR_IMAGES]:
print("\nScore: {:.3f}%".format(score * 100))
print(image_names[image_id1])
print(image_names[image_id2])


# =================
# NEAR DUPLICATES
# =================
print('Finding near duplicate images...')
# Use a threshold parameter to identify two images as similar. By setting the threshold lower,
# you will get larger clusters which have less similar images in it. Threshold 0 - 1.00
# A threshold of 1.00 means the two images are exactly the same. Since we are finding near
# duplicate images, we can set it at 0.99 or any number 0 < X < 1.00.
threshold = 0.99
near_duplicates = [image for image in processed_images if image[0] < threshold]


for score, image_id1, image_id2 in near_duplicates[0:NUM_SIMILAR_IMAGES]:
print("\nScore: {:.3f}%".format(score * 100))
print(image_names[image_id1])
print(image_names[image_id2])