04-图像向量生成、特征入库、相似度匹配原理
图像向量生成、特征入库、相似度匹配原理
作者:黒漂技术佬 | 从一张图片到向量检索,全流程拆解
图像向量生成的完整流程
把一张图片变成向量,不是一步到位的魔法,而是四步流水线:
原始图片 → ①预处理 → ②模型提取 → ③特征向量 → ④归一化 → 入库向量
第①步:图像预处理
原始图片千奇百怪——有的 4000×3000 高分辨率,有的 640×480 低分辨率;有的是 RGB 三通道,有的是 RGBA 四通道。模型不能吃"原生态"图片,你得先把它统一成模型能接受的格式。
预处理通常包括:
- 缩放(Resize):统一到模型输入尺寸,比如 224×224(ResNet)、256×256
- 归一化(Normalize):像素值从 [0, 255] 映射到 [0, 1] 或标准化到均值0方差1
- 通道处理:确保 RGB 三通道,去掉透明通道
- 裁剪(Crop):中心裁剪或随机裁剪,聚焦目标区域
# PyTorch 预处理示例
from torchvision import transforms
preprocess = transforms.Compose([
transforms.Resize(256), # 缩放到256×256
transforms.CenterCrop(224), # 中心裁剪到224×224
transforms.ToTensor(), # 转为Tensor,值域[0,1]
transforms.Normalize( # 标准化
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]
)
])
mean 和 std 这组数值是 ImageNet 数据集的统计值,ResNet 训练时就用的这套归一化参数,推理时也得用同一套——这叫"训练和推理对齐",不对齐结果会跑偏。
第②步:特征提取模型
预处理后的图片送入深度学习模型,模型输出一个向量。不同模型输出的向量维度和特性不同:
| 模型 | 输出维度 | 特点 | 适用场景 |
|---|---|---|---|
| ResNet50 | 2048 → 可截取512 | 通用图像特征,准确度高 | 服务端通用视觉检索 |
| MobileNetV3 | 1280 → 可截取256 | 轻量化,推理快 | 嵌入式端、边缘设备 |
| CLIP(ViT-B/32) | 512 | 图文多模态,语义能力强 | 跨模态检索、零样本分类 |
ResNet50 是经典选择——在 ImageNet 上训练过,对通用物体(商品、动物、场景)的特征提取能力很强。2048维太长时,可以截取前512维或加一层降维。
MobileNetV3 是嵌入式端的首选——模型体积只有几MB,推理速度是 ResNet 的 5-10倍,代价是特征区分度稍弱。在无人售货柜的边缘计算盒子上,MobileNet 是务实选择。
CLIP 是 OpenAI 的多模态模型——同一个向量空间里,图片和文本共享语义。你可以用文字描述搜索图片,也可以用图片搜索相关文本。在具身智能场景中,机器人既看画面又听指令,CLIP 的多模态能力就派上用场了。
第③步:提取特征向量
模型通常输出的是一个高维向量。以 ResNet50 为例,去掉最后的分类层,倒数第二层的输出就是 2048 维特征向量。
import torch
import torchvision.models as models
# 加载ResNet50,去掉分类层
model = models.resnet50(pretrained=True)
model = torch.nn.Sequential(*list(model.children())[:-1]) # 去掉最后的FC层
model.eval()
# 提取特征
img_tensor = preprocess(img).unsqueeze(0) # 加batch维度
with torch.no_grad():
feature = model(img_tensor) # 输出: [1, 2048, 1, 1]
vector = feature.squeeze().numpy() # [2048]
model.children()[:-1] 这一步把 ResNet 最后的全连接分类层去掉——分类层输出的是"这是猫还是狗"的类别概率,我们需要的是倒数第二层的特征向量,它才是图像的"指纹"。
第④步:向量归一化
特征向量提取出来后,强烈建议做一次L2归一化:
import numpy as np
vector = vector / np.linalg.norm(vector) # L2归一化
归一化后向量长度变为1,所有向量落在单位球面上。好处是:
- 余弦相似度 = 内积:归一化后,内积计算结果和余弦相似度一致,省去分母计算
- 数值稳定:向量长度统一,避免长向量"压制"短向量
- 检索加速:FAISS 的
IndexFlatIP直接用内积就是余弦相似度
特征提取模型选择策略
| 场景 | 推荐模型 | 理由 |
|---|---|---|
| 无人售货柜(嵌入式) | MobileNetV3 | 轻量、快速、够用 |
| 工业视觉检测(服务器) | ResNet50 | 特征区分度高,缺陷识别准确 |
| 具身智能(多模态) | CLIP | 图文统一语义,支持指令理解 |
| 智慧农业(大规模) | ResNet50 + 降维 | 服务端算力充足,降维节省存储 |
嵌入式端选轻量模型,服务端选强模型,多模态选 CLIP——别在 4GB 内存的小板子上跑 ResNet50,也别在 64GB 内存的服务器上委屈自己用 MobileNet。
向量入库流程
图像 → 预处理 → 模型提取 → 归一化 → 写入向量数据库
# Milvus 入库示例
from pymilvus import Collection
# 逐个商品图片提取特征并入库
for product in products:
img = load_image(product.image_path)
vector = extract_feature(img) # 提取 + 归一化
collection.insert([
{"id": product.id, "vector": vector, "category": product.category}
])
每个向量入库时,除了向量本身,还应该附带元数据(商品ID、类别、名称等)。这些元数据后续用于混合查询——先按类别过滤,再在范围内做向量检索。
相似度匹配流程
查询图片 → 预处理 → 模型提取 → 归一化 → 向量检索 → TopK结果
# Milvus 检索示例
query_img = capture_from_camera() # 摄像头拍照
query_vector = extract_feature(query_img) # 提取 + 归一化
results = collection.search(
data=[query_vector],
anns_field="vector",
param={"metric_type": "IP", "params": {"nprobe": 16}},
limit=5,
expr="category == '饮料'" # 可选:元数据过滤
)
for hit in results[0]:
print(f"商品ID: {hit.id}, 相似度: {hit.distance}")
metric_type="IP" 表示用内积度量——因为向量已归一化,内积等于余弦相似度,计算最快。
完整实战代码:ResNet + FAISS
无人售货柜场景,用 ResNet50 提取特征 + FAISS 检索的完整代码:
import faiss
import numpy as np
import torch
import torchvision.models as models
import torchvision.transforms as transforms
from PIL import Image
# ===== 1. 模型准备 =====
model = models.resnet50(pretrained=True)
model = torch.nn.Sequential(*list(model.children())[:-1])
model.eval()
preprocess = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
def extract_feature(image_path):
"""提取图像特征向量(归一化)"""
img = Image.open(image_path).convert('RGB')
tensor = preprocess(img).unsqueeze(0)
with torch.no_grad():
feat = model(tensor).squeeze().numpy()
# L2归一化
feat = feat / np.linalg.norm(feat)
return feat.astype('float32')
# ===== 2. 建库:商品图片特征入库 =====
product_images = {
0: "cola_330ml.jpg",
1: "sprite_330ml.jpg",
2: "chips_lays.jpg",
3: "chips_pringles.jpg",
# ...更多商品
}
vectors = []
product_ids = []
for pid, path in product_images.items():
vec = extract_feature(path)
vectors.append(vec)
product_ids.append(pid)
vectors = np.array(vectors) # shape: [N, 2048]
# 创建FAISS索引(内积,因为向量已归一化)
dim = vectors.shape[1]
index = faiss.IndexFlatIP(dim)
index.add(vectors)
# ===== 3. 检索:摄像头拍照识别商品 =====
query_vec = extract_feature("captured_product.jpg")
D, I = index.search(query_vec.reshape(1, -1), k=3)
print("Top3 相似商品:")
for i in range(3):
print(f" 商品ID: {product_ids[I[0][i]}, 相似度: {D[0][i]:.4f}")
这段代码从零到跑通不超过 20 行。无人售货柜实际部署时,加上预处理优化和索引调参,检索精度和速度都能达到生产级。
关键工程细节
- 向量维度越高,存储和检索成本越大。2048维够精确但太胖,建议降到512维(加一层PCA或线性映射),存储减4倍,检索速度提4倍
- 归一化是必须的,别偷懒跳过。不归一化的向量,相似度计算结果不稳定
- 模型选型跟部署环境匹配,别在嵌入式端硬塞 ResNet50,推理一次500ms,用户体验直接崩
- 入库图片要覆盖多角度多光照,同一商品拍正面、侧面、俯视各一张,检索鲁棒性才够
更多推荐


所有评论(0)