提问人:Boris 提问时间:7/18/2012 最后编辑:nathancyBoris 更新时间:11/21/2023 访问量:252226
检查图像与 OpenCV 的相似性
Checking images for similarity with OpenCV
问:
OpenCV 是否支持两个图像的比较,返回一些值(可能是一个百分比)来指示这些图像的相似程度?例如,如果相同的图像被传递两次,则返回 100%,如果图像完全不同,则返回 0%。
我已经在 StackOverflow 上阅读了很多类似的主题。我也做了一些谷歌搜索。可悲的是,我无法想出一个令人满意的答案。
答:
这是一个巨大的话题,从 3 行代码到整个研究杂志的答案。
我将概述最常见的此类技术及其结果。
比较直方图
最简单和最快的方法之一。几十年前提出,作为寻找图片相似性的一种手段。这个想法是,森林会有很多绿色,人脸会有很多粉红色,或者其他什么。因此,如果您将两张图片与森林进行比较,您会在直方图之间获得一些相似性,因为两者都有很多绿色。
缺点:它太简单了。香蕉和海滩看起来是一样的,因为两者都是黄色的。
OpenCV 方法:compareHist()
模板匹配
这里有一个很好的例子 matchTemplate 找到良好的匹配。它将搜索图像与被搜索的图像卷积在一起。它通常用于在较大的图像部分中查找较小的图像部分。
缺点:它只在相同的图像、相同的大小和方向下返回良好的结果。
OpenCV 方法:matchTemplate()
功能匹配
被认为是进行图像搜索的最有效方法之一。从图像中提取许多特征,以确保即使在旋转、缩放或倾斜时也能再次识别相同的特征。以这种方式提取的特征可以与其他影像特征集进行匹配。另一张图像的特征与第一个图像匹配的比例很高,则该图像被视为描绘了相同的场景。
找到两组点之间的同源性还可以让您找到原始图片之间拍摄角度的相对差异或重叠量。
这里有许多关于此的 OpenCV 教程/示例,以及一个不错的视频。整个 OpenCV 模块 (features2d) 专用于它。
缺点:它可能很慢。它并不完美。
在 OpenCV Q&A 网站上,我谈论的是特征描述符和纹理描述符之间的区别,前者在比较整个图像时非常有用,后者用于识别图像中的人脸或汽车等对象。
评论
absdiff
如果用于匹配相同的图像(相同的大小/方向)
// 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, NORM_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
}
}
有点偏离主题,但有用的是 pythonic 方法。它强大而快速,但只是比较像素,而不是图片包含的对象或数据(并且它需要相同大小和形状的图像):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:
In[1]: np.sum(picture1_norm**2) Out[1]: 1.0
如果它们不相似,您将得到一个介于 0 和 1 之间的值(如果乘以 100,则为百分比):
In[2]: np.sum(picture2_norm*picture1_norm) Out[2]: 0.75389941124629822
请注意,如果您有彩色图片,则必须在所有 3 个维度上执行此操作,或者只是比较灰度版本。我经常需要将大量图片与任意内容进行比较,这是一种非常快速的方法。
评论
山姆的解决方案应该就足够了。我使用了直方图差异和模板匹配的组合,因为没有一种方法 100% 对我有用。不过,我对直方图方法的重视程度较低。这是我在简单的 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
评论
可以使用自动编码器在预先训练的 ImageRes 数据上使用 VGG16 等架构来完成此类任务;然后计算查询与其他图像之间的距离,以找到最接近的匹配项。
由于没有人发布完整的具体示例,因此这里有两种定量方法来确定两个图像之间的相似性。一种比较相同尺寸图像的方法;另一个用于标度不变和变换无差别图像。这两种方法都返回 to 之间的相似度分数,其中表示完全不同的图像和表示相同/重复的图像。对于介于两者之间的所有其他值:分数越低,相似度越低;分数越高,越相似。0
100
0
100
方法#1:结构相似性指数(SSIM)
为了比较差异并确定两个图像之间的确切差异,我们可以利用图像质量评估中引入的结构相似性指数 (SSIM):从错误可见性到结构相似性。SSIM是一种图像质量评估方法,它根据参考图像和失真图像之间局部信息的统计特性来估计结构相似性的退化。SSIM 值的范围在 [-1, 1] 之间扩展,通常使用滑动窗口进行计算,其中整个图像的 SSIM 值计算为所有单个窗口结果的平均值。此方法已在用于图像处理的 scikit-image 库中实现,可以与 一起安装。pip install scikit-image
skimage.metrics.structural_similarity()
函数返回比较和差值图像 .表示两个图像之间的平均 SSIM 分数,值越高表示相似度越高。图像包含实际图像差异,较暗区域具有更大的差异。较大的差异区域以黑色突出显示,而较小的差异以灰色突出显示。下面是一个示例:score
diff
score
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:密集向量表示
通常,两个图像不会完全相同。它们可能具有略有不同的背景、尺寸、特征添加/减法或变换(缩放、旋转、倾斜)的变化。换句话说,我们不能使用直接的像素到像素的方法,因为随着变化,问题从识别像素相似性转移到对象相似性。我们必须切换到深度学习特征模型,而不是比较单个像素值。
为了确定相同和近乎相似的图像,我们可以使用句子转换器
库,它提供了一种简单的方法来计算图像的密集向量表示,以及 OpenAI 对比语言-图像预训练 (CLIP) 模型,这是一个已经在各种(图像、文本)对上训练的神经网络。这个想法是将所有图像编码到矢量空间中,然后找到与图像非常相似的区域相对应的高密度区域。
当比较两张图像时,它们会得到一个介于 .我们可以使用阈值参数来识别两个图像的相似或不同。较低的阈值将导致集群中具有较少的相似图像。相反,较高的阈值将导致具有更多相似图像的聚类。重复的图像将具有表示两个图像完全相同的分数。为了找到近似的图像,我们可以将阈值设置为任意值,例如 .例如,如果两张图像之间的确定分数大于,我们可以得出结论,它们是近乎相似的图像。0
1.00
1.00
0.9
0.9
举个例子:
这个数据集有五张图像,请注意花 #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
我们在不同的图像之间得到了更有趣的结果。分数越高,越相似;分数越低,相似度越低。使用阈值或 90%,我们可以过滤掉近似的图像。0.9
仅两张图像之间的比较
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])
评论