以图搜图:基于深度特征的商品与场景检索

FreeGuideOnline 最新 2026-06-24

bash pip install torch torchvision PIL numpy matplotlib faiss-cpu


- `torch` + `torchvision`:提供预训练模型和图片预处理。
- `Pillow`:图片读取与处理。
- `faiss-cpu`:向量相似度搜索(若需GPU加速可安装`faiss-gpu`)。
- `matplotlib`:结果可视化。

### 4. 特征提取:使用预训练ResNet

我们将使用在ImageNet上预训练的ResNet-50作为特征提取器,移除最后的分类层,取倒数第二层输出作为特征向量。这样提取的特征泛化能力强,适合通用图像检索。

```python
import torch
import torch.nn as nn
from torchvision import models, transforms
from PIL import Image

# 定义特征提取器
class FeatureExtractor(nn.Module):
    def __init__(self):
        super().__init__()
        base_model = models.resnet50(weights='ResNet50_Weights.DEFAULT')
        # 移除最后的全连接层和平均池化层,保留到layer4输出
        self.features = nn.Sequential(*list(base_model.children())[:-2])
        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))  # 全局平均池化

    def forward(self, x):
        x = self.features(x)
        x = self.avgpool(x)
        return torch.flatten(x, 1)  # 输出形状: (batch, 2048)

# 实例化模型并设为评估模式,不更新梯度
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = FeatureExtractor().to(device)
model.eval()

输入图片需要经过标准化预处理:

transform = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406],
                         std=[0.229, 0.224, 0.225])
])

def extract_features(image_path):
    """单张图片特征提取"""
    img = Image.open(image_path).convert('RGB')
    img_tensor = transform(img).unsqueeze(0).to(device)  # 增加batch维度
    with torch.no_grad():
        features = model(img_tensor)
    return features.cpu().numpy().flatten()  # 转为numpy数组

这样,extract_features("bag.jpg") 会返回一个2048维的浮点向量。

5. 构建特征数据库并建立FAISS索引

假设我们的图库中有1000张商品图片。我们需要预计算所有图片的特征,并存入FAISS索引中,以便实时查询。

import faiss
import numpy as np
import os

# 准备图库图片路径列表
image_dir = 'product_images'
image_paths = [os.path.join(image_dir, f) for f in os.listdir(image_dir) if f.endswith(('.jpg', '.png'))]

# 批量提取特征
feature_list = []
for path in image_paths:
    feat = extract_features(path)
    feature_list.append(feat)

# 转为(n, 2048)的矩阵,注意类型为float32
feature_matrix = np.array(feature_list).astype('float32')

# 创建FAISS索引
dim = feature_matrix.shape[1]  # 特征维度
index = faiss.IndexFlatL2(dim)  # 使用L2欧氏距离
index.add(feature_matrix)       # 将特征矩阵添加到索引中

# 可选:保存索引和路径映射,以便后续使用
faiss.write_index(index, "image_index.faiss")
import pickle
with open("paths.pkl", "wb") as f:
    pickle.dump(image_paths, f)

此时,index 索引已经包含了全部图库特征,使用L2距离衡量相似度。对于商品检索,也可以尝试余弦相似度(FAISS中可先对向量做L2归一化,再用内积搜索,相当于余弦相似度)。

6. 执行相似图像查询

当用户输入一张查询图片时,我们提取其特征,然后从索引中找出最近的K张图。

def search(query_image_path, k=5):
    # 提取查询图特征
    query_feat = extract_features(query_image_path)
    query_feat = query_feat.astype('float32').reshape(1, -1)

    # 搜索最近邻
    distances, indices = index.search(query_feat, k)

    # 打印结果
    print("查询图片:", query_image_path)
    for i, idx in enumerate(indices[0]):
        print(f"Top {i+1}: {image_paths[idx]}  距离: {distances[0][i]:.4f}")
    return indices[0], distances[0]

# 测试
result_indices, _ = search("query_bag.jpg", k=5)

7. 可视化搜索结果

用matplotlib并排展示查询图和Top-K结果,直观感受效果。

import matplotlib.pyplot as plt

def show_results(query_path, result_indices, num_results=5):
    fig, axes = plt.subplots(1, 1+num_results, figsize=(15, 5))
    axes[0].imshow(Image.open(query_path))
    axes[0].set_title("Query")
    axes[0].axis('off')

    for i, idx in enumerate(result_indices[:num_results]):
        img = Image.open(image_paths[idx])
        axes[i+1].imshow(img)
        axes[i+1].set_title(f"#{i+1}")
        axes[i+1].axis('off')
    plt.tight_layout()
    plt.show()

show_results("query_bag.jpg", result_indices)