首页 > 人工智能 >计算机视觉实现图像相似性搜索

计算机视觉实现图像相似性搜索

来源:互联网 2026-07-30 09:49:15

针对小规模图像数据集,采用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—

侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述

热游推荐

更多
湘ICP备14008430号-1 湘公网安备 43070302000280号
All Rights Reserved
本站为非盈利网站,不接受任何广告。本站所有软件,都由网友
上传,如有侵犯你的版权,请发邮件给xiayx666@163.com
抵制不良色情、反动、暴力游戏。注意自我保护,谨防受骗上当。
适度游戏益脑,沉迷游戏伤身。合理安排时间,享受健康生活。