游乐游手机版
首页/AI热点日报/热点详情

基于计算机视觉的图像相似性搜索实现方案

类型:热点整理2026-07-23
针对小规模图像数据集,采用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则更聚焦于肖像主体。显然,ORB更符合我们的需求——避免使用边缘特征进行匹配,因为这些特征缺乏唯一性。关于SIFT与ORB的具体原理,可查阅官方文档,此处不再赘述。

完成查询图片的关键点检测后,下一步是将这些关键点与数据库中所有图片的关键点进行比对。对于大规模数据集,这种暴力匹配方式效率低下,但针对我们约1000张图片的小规模数据集,性能尚可接受。

然而,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—

来源:https://www.eefocus.com/article/2056218.html

相关热点

继续查看同栏目近期热点。

延伸阅读

补充最近整理过的热点入口。