针对小规模图像数据集,采用ORB特征检测与RANSAC空间验证实现图像相似性搜索。通过RANSAC剔除异常匹配点,并结合单应性矩阵估计,有效提升对旋转、缩放和平移的容忍度,从而准确检索相似图像。
图像相似性搜索,本质上是通过一张查询图像在数据库中找到视觉相似的图像。这一过程看似简单,实际落地时存在多种技术路线。基于深度学习的方法——CNN、RNN、视觉Transformer——是目前的主流选择;但传统特征检测算法,如SIFT、SURF、ORB,在特定场景下依然表现优异。甚至像MagicLeap的SuperGlue,将两者结合使用。
考虑到数据集规模较小(约1000张图片),先尝试经典的SIFT和ORB算法。它们能够检测并提取图像中的关键点,然后与其他图像逐一比对,判断是否匹配。哪种方法更合适?以一张邮票盒子中的图片为例,执行简单的关键点检测观察效果。
长期稳定更新的攒劲资源: >>>点此立即查看<<<
import cv2 as cv
import matplotlib.pyplot as plt
# Load image
PATH = "/home/username/venv_folder/venv_name/image.jpg"
img = cv.imread(PATH, cv.IMREAD_GRAYSCALE)
# Create feature detectors
orb = cv.ORB_create(nfeatures=2000)
sift = cv.SIFT_create()
# Detect keypoints and descriptors
kp_orb, des_orb = orb.detectAndCompute(img, None)
kp_sift, des_sift = sift.detectAndCompute(img, None)
# Draw keypoints on image
img_orb = cv.drawKeypoints(img, kp_orb, None, color=(0, 255, 0), flags=0)
img_sift = cv.drawKeypoints(img, kp_sift, None, color=(255, 0, 0), flags=cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
# Plot results
plt.figure(figsize=(10, 8))
plt.subplot(1, 3, 1), plt.imshow(img, cmap='gray'), plt.title(f'Original image in grayscale')
plt.axis('off')
plt.subplot(1, 3, 2), plt.imshow(img_sift, cmap='gray'), plt.title(f'SIFT Keypoints ({len(kp_sift)})')
plt.axis('off')
plt.subplot(1, 3, 3), plt.imshow(img_orb, cmap='gray'), plt.title(f'ORB Keypoints ({len(kp_orb)})')
plt.axis('off')
plt.tight_layout()
plt.show()
这里使用印有伊丽莎白二世女王肖像的邮票来演示差异。SIFT沿着邮票边缘检测出大量关键点,而ORB检测到的关键点更集中在肖像区域。显然,后者更符合需求——我们不需要用邮票边缘进行匹配,因为这些特征缺乏唯一性。至于SIFT和ORB的工作原理,可参考官方文档,此处不展开说明。
检测到一张图像的关键点后,接着与所有其他图像的关键点进行比对。对于大数据集,这种方法的效率较低,但处理千张级别的图片集,问题不大。
不过,使用ORB检测到的关键点未必总能正确匹配。例如下图,女王肖像中的三个关键点被错误匹配到狮子徽章上。
(图片描述:ORB匹配错误示例)
为了获得更稳健的匹配性能,需要引入空间验证。常用方法有两种:随机样本一致性(RANSAC)和广义霍夫变换。此前讨论过霍夫变换检测直线,此处采用RANSAC。简单来说,RANSAC能够剔除异常值关键点,使比对更可靠。其基本思想可以通过线性回归来类比。
(图片描述:RANSAC与最小二乘法对比示意图)
普通最小二乘法拟合所有数据点,因此异常值影响较大(如黑线)。而RANSAC会忽略超出预设阈值的异常值,自动找到最吻合的拟合线。在图像相似度搜索中,RANSAC用于评估检测到的关键点相对位置是否一致——搜索图像中的匹配关键点,应与查询图像中的关键点处于相同位置,否则被视为异常值剔除。不过,这种方法在遇到透视畸变、缩放、旋转或平移差异时,反而可能失效。
使用刚才那对图片运行带RANSAC空间验证的关键点匹配,观察效果。
(图片描述:RANSAC匹配结果,仅找到两个错误匹配)
只找到两个匹配,且都不正确。因为RANSAC将位置不同的匹配点全部当作异常值排除。这种方法看似笨拙,但若目标是查找重复图像(完全一致),反而非常有效。
如果要查找对缩放、旋转和平移有一定容忍度的相似图像,需要将RANSAC与单应性矩阵结合使用(即用RANSAC进行单应性估计),效果如下图所示。
(图片描述:RANSAC+单应性矩阵的正确匹配结果)
这次,尽管肖像的比例和位置差异较大,所有关键点都正确匹配。具体使用哪种方法,取决于应用场景。查找重复图像,直接使用RANSAC空间验证;查找相似但不完全相同的图像,则使用RANSAC加单应性矩阵。本文选择后者,因为希望搜索结果能召回所有带女王肖像的邮票。完整代码如下。
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
import os
import math
def extract_orb_descriptors(image_path):
"""Extract ORB keypoints and descriptors."""
imgGray = cv.imread(image_path, cv.IMREAD_GRAYSCALE)
if imgGray is None:
raise ValueError(f"Error loading image: {image_path}")
orb = cv.ORB_create(nfeatures=2000)
keypoints, descriptors = orb.detectAndCompute(imgGray, None)
if descriptors is None:
return imgGray, keypoints, np.array([]) # Return empty array to a void errors
return imgGray, keypoints, descriptors
def are_images_similar(inliers, total_matches, min_matches=15, ratio=0.5):
"""Determines if images are similar based on inlier percentage."""
similarity = len(inliers) > min_matches or len(inliers) / total_matches > ratio
return similarity, len(inliers), (len(inliers) / total_matches * 100) if total_matches > 0 else 0
def match_images_with_ransac(img1_path, folder_path):
"""Matches query image with all images in a folder using ORB + BFMatcher + RANSAC."""
img1, kp1, des1 = extract_orb_descriptors(img1_path)
if des1.size == 0:
print("No descriptors found in query image.")
return
matches_list = []
for img_name in os.listdir(folder_path):
img2_path = os.path.join(folder_path, img_name)
if not img2_path.lower().endswith(('png', 'jpg', 'jpeg')):
continue # Skip non-image files
img2, kp2, des2 = extract_orb_descriptors(img2_path)
if des2.size == 0:
continue
# Use Brute Force Matcher with Hamming distance
bf = cv.BFMatcher(cv.NORM_HAMMING)
matches = bf.knnMatch(des1, des2, k=2)
# Apply Lowe's ratio test
good_matches = [m for m, n in matches if m.distance < 0.85 * n.distance]
if len(good_matches) < 4:
continue
# Convert keypoints to NumPy array
src_pts = np.float32([kp1[m.queryIdx].pt for m in good_matches]).reshape(-1, 1, 2)
dst_pts = np.float32([kp2[m.trainIdx].pt for m in good_matches]).reshape(-1, 1, 2)
# Compute Homography using RANSAC
H, mask = cv.findHomography(src_pts, dst_pts, cv.RANSAC, 5.0)
if mask is None:
continue
inliers = np.where(mask.ra vel() == 1)[0]
similarity, inlier_count, inlier_ratio = are_images_similar(inliers, len(good_matches))
if similarity:
matches_list.append((inlier_count, inlier_ratio, img2_path, img2))
# Sort similar images by inlier ratio in descending order
matches_list.sort(key=lambda x: x[1], reverse=True)
# Print all similar images
if matches_list:
print(f"Similar Images ({len(matches_list)}):")
#for img_path, inlier_count, inlier_ratio, _ in matches_list:
for inlier_count, inlier_ratio, img_path, _ in matches_list:
print(f"{img_path} - Inliers: {inlier_count}, Ratio: {inlier_ratio:.3}%")
else:
print("No similar images found in the folder.")
return
# Determine grid size
num_images = len(matches_list)
if num_images >= 5:
num_cols = 5
else:
num_cols = num_images
num_rows = math.ceil(num_images / num_cols) # Dynamic number of rows
# Display all similar images in subplots
fig, axes = plt.subplots(num_rows, num_cols, figsize=(8, 2 * num_rows))
# Flatten axes array for easier iteration
axes = axes.flatten() if num_rows > 1 else [axes]
for ax, (inlier_count, inlier_ratio, _, img) in zip(axes, matches_list):
ax.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
ax.set_title(f"{inlier_ratio:.2f}%", fontsize=10)
ax.axis('off')
# Hide any unused subplots
for ax in axes[num_images:]:
ax.axis('off')
plt.tight_layout()
plt.show()
img1_path = '/home/username/venv_folder/venv_name/image.jpg'
folder_path = '/home/username/venv_folder/venv_name/image_folder'
# Display Search image
img1 = cv.imread(img1_path)
img1 = cv.cvtColor(img1, cv.COLOR_BGR2RGB)
plt.figure(figsize=(8,4))
plt.imshow(img1) #, cmap='gray')
plt.suptitle("Search image", fontsize=15)
plt.axis('off')
plt.show()
match_images_with_ransac(img1_path, folder_path)
使用ORB检测查询图像的关键点,再与搜索图像的关键点进行比对,结果如下。返回的图像按照内点比率排序(得分100%的即为查询图像本身)。
(图片描述:ORB+RANSAC+单应性矩阵的搜索结果,显示21张相似图片)
在总共941张邮票图像中,包含22张带有女王肖像的图片,搜索返回了21张,其中20张正确。召回率和精确率分别为91%和95%。
(图片描述:召回率和精确率图表)
整个搜索耗时26.4秒(平均每张约28毫秒),速度不算快,但对小规模数据库足够。若想提升速度,可引入倒排索引、视觉单词和词汇树等概念。
如果不希望涉及关键点检测和匹配,另一条路径是使用OpenAI的CLIP模型。该模型结合卷积神经网络和Transformer语言模型,能对图像和文本信息进行编码(此处仅使用图像数据)。在图像相似性搜索方面,可搭配Facebook的FAISS库。CLIP负责生成每张图像的嵌入向量,FAISS负责存储和索引,实现高效搜索。
(已有成熟的CLIP+FAISS教程可供参考,此处不赘述。)
使用同样的查询图像运行CLIP+FAISS,前25个搜索结果如下。相似度采用余弦相似度评估,最高分1.00的仍然是查询图像本身。
(图片描述:CLIP+FAISS搜索结果)
两种方法的指标对比如下。
(图片描述:ORB与CLIP+FAISS的召回率、精确率、查询时间对比表格)
CLIP+FAISS的召回率和精确率略低于ORB,但需注意CLIP模型是预训练的,并未针对自定义数据集进行微调。最显著的差异在于查询时间:CLIP+FAISS比ORB快了约22000倍。当数据集规模增大时,这一优势将成为决定性因素。
传统计算机视觉与深度学习方法均能实现高效的图像相似性搜索。SIFT、SURF、ORB等经典方法依然可用,并能直观展示关键点匹配过程。CLIP+FAISS这类深度学习方法,虽然结果可视化难度较大,但开箱即用表现良好,通过微调还可进一步提升。
—THE END—
侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述