海域重启礼包码汇总 海域重启最新可用兑换码分享
2026-07-21
2026-07-24 0
我们目标是构建一个图像相似性搜索系统,其中查询图像用于查找图像数据库中的其他相似图像。
有多种方法可以完成这项任务。基于深度学习的方法(CNN、RNN、视觉转换器)是当前最先进的方法,但久经考验的传统特征检测方法 SIFT、SURF 和 ORB 在许多情况下仍然可以很好地工作。一些方法,如 MagicLeap 的 SuperGlue,使用两者的组合来完成工作。
传统的基于计算机视觉的方法
对于我们相当简单且相对较小的数据集(大约 1000 张图像),我们将首先尝试尺度不变特征变换 (SIFT) 和定向 FAST 和旋转 BRIEF (ORB) 来检测和提取图像中的不同特征,即关键点,并将它们与其他图像进行比较以查看它们是否匹配。
让我们首先尝试简单的关键点检测,看看哪种方法在这个邮票盒中效果更好。
import cv2 as cvimport matplotlib.pyplot as plt# Load imagePATH = "/home/username/venv_folder/venv_name/image.jpg"img = cv.imread(PATH, cv.IMREAD_GRAYSCALE)# Create feature detectorsorb = cv.ORB_create(nfeatures=2000)sift = cv.SIFT_create()# Detect keypoints and descriptorskp_orb, des_orb = orb.detectAndCompute(img, None)kp_sift, des_sift = sift.detectAndCompute(img, None)# Draw keypoints on imageimg_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 resultsplt.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 检测到的关键点更侧重于肖像本身。后者是首选,因为我们不想使用图章边缘进行图像匹配,因为它们不是唯一的特征。有关 SIFT 和 ORB 工作原理的更详细说明,请查看参考文档。
检测到一张图像中的关键点后,我们可以将它们与所有其他图像中的关键点进行比较。这实际上并不是最有效的方法,尤其是当您处理大量图像时,但对于我的小型图像数据集来说应该没问题。
使用 ORB 检测到的关键点可能并不总是与另一张图像正确匹配,如下图所示,其中女王肖像中的三个关键点与狮子徽章错误匹配。
为了获得更强大的匹配性能,我们需要应用一种称为空间验证的东西。这可以通过两种方法完成:
随机样本共识 (RANSAC)
广义霍夫变换
在我的上一篇文章中,我们认识了 Hough变换,其中 Hough 变换用于检测图像中的直线。这次让我们使用随机样本共识。简而言之,RANSAC 用于去除异常值关键点,这使得比较更加稳健和可靠 。RANSAC 的基本概念可以用线性回归作为示例来说明。
合计最小二乘法(和普通最小二乘法)方法找到所有数据点的最优拟合。因此,异常值可能会产生很大的影响,如黑线所示。另一方面,RANSAC 会忽略超出预定义阈值水平的异常值,并自动查找具有最高异常值的拟合。
在图像相似度搜索中,RANSAC 用于评估检测到的关键点的相对位置是否匹配。换句话说,搜索图像中的匹配关键点应与查询图像中的关键点位于相同的位置。否则,关键点将被视为异常值并被忽略。根据应用程序的不同,这可能是一个好主意,也可能是一个坏主意,因为当存在透视失真、缩放、旋转或平移差异时,使用 RANSAC 进行空间验证可能无法匹配图像。
为了演示,让我们在与之前相同的图像对上运行关键点匹配,但这次使用 RANSAC 进行空间验证。
只找到两个关键点匹配项,它们甚至不正确。这是因为 RANSAC 将位于不同相对位置的匹配关键点视为异常值并排除它们。这种方法似乎失败了,但如果目标是查找关键点及其在图像之间的相对位置匹配的重复图像,它会非常有效。
如果目标是找到对缩放、旋转和平移有一定容忍度的相似图像,我们需要将 RANSAC 与同向性(或使用 RANSAC 的同性估计)一起使用,如下图所示 [4,5]。
这一次,尽管肖像的比例和位置存在明显差异,但所有关键点都得到了正确匹配。
使用哪种方法取决于应用程序。要查找重复图像,请使用 RANSAC 来确保空间验证。要查找相似但不一定相同的图像,请使用 RANSAC 和 homography。
就我而言,我将选择后一个选项,因为我希望我的搜索检索到所有带有伊丽莎白二世女王肖像的图像。下面提供了实现此目的的完整代码。
import cv2 as cvimport numpy as npimport matplotlib.pyplot as pltimport osimport mathdef 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 avoid errors return imgGray, keypoints, descriptorsdef 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 0def 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.ravel() == 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 imageimg1 = 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 检测查询图像中的关键点,并将这些关键点与搜索图像中检测到的关键点进行比较,得出以下结果。返回的图片按照 inlier ratio score 排序(第一个得分为 100% 的图片与查询图片相同)。
在总共 941 张邮票图像中,其中包含 22 张带有女王肖像的图像,搜索返回了 21 张图像,其中 20 张是正确的。召回率和精确率可以分别计算为 91% 和 95%。
搜索总共花费了 26.4 秒(每张图像约 28 毫秒),这并不快,但对于小型图像数据库搜索来说已经足够了。为了加快搜索过程,可以使用倒排索引、视觉单词和词汇树等概念。
基于深度学习的方法
我们可以使用 OpenAI 开发的名为 CLIP 的预训练深度学习模型,而不是关键点检测和匹配。它利用卷积神经网络 (CNN) 和基于 transformer 的语言模型对图像和文本信息进行编码(在本例中,我们将仅使用图像数据)。对于图像相似性搜索,我们将使用 Facebook AI Research 开发的 FAISS。CLIP 用于创建嵌入,这些嵌入表示每张图像中的视觉信息,然后由 FAISS 存储和索引,以实现高效的图像相似性搜索 [6]。
这里已经有一个很棒的 CLIP/FAISS 指导教程,用于图像相似性搜索,我建议查看 。
当使用与以前相同的查询图像时,CLIP & FAISS图像搜索结果如下25个前25个图像。图像相似度是使用余弦相似度分数评估的,与以前一样,最高分数为 1.00 的第一个图像是查询图像。
CLIPP & FAISS与之前的ORB方法之间的搜索指标比较如下表所示。
CLIP & FAISS的召回率和精确率分数比ORB低一些,但应该注意的是,CLIP模型是预先训练的,没有使用我的自定义图像数据集进行微调。然而,最显著的区别在于查询时间:CLIP和FAISS的方法比ORB快了大约22,000倍。在处理非常大的图像数据集时,这可能是一个巨大的优势。
总 结
传统的计算机视觉和基于深度学习的方法都可用于实现高效的图像相似性搜索。传统的 SIFT、SURF 和 ORB 关键点检测方法仍然适用,并且可以提供有关图像之间关键点如何匹配的良好可视化。使用CLIP和FAISS的深度学习方法可能更难可视化,但开箱即用就表现出良好的性能,这可以通过微调模型来进一步改进。