最近在做图片相似检索时,我把问题拆成了两个部分:
- 如何把图片转换成可计算的向量?
- 如何把这些向量存起来,并支持快速相似搜索?
如果只是做文本检索,可以用 BGE、text-embedding 这类文本 embedding 模型。 但如果对象是商品图、主图、海报、截图,就需要使用视觉模型提取图片特征。
这里可以优先评估 DINOv2。
DINOv2 的作用可以简单理解为:
把一张图片转换成一个高维向量。图片越相似,向量距离通常越近。
然后再用向量数据库或向量索引做相似度搜索。
本文记录一个最小可落地方案:
DINOv2 + PostgreSQL + pgvector + HNSW
1. DINOv2 适合解决什么问题
DINOv2 是 Meta AI 发布的自监督视觉模型。 它可以从图片中提取通用视觉特征,不依赖人工标注的分类标签。
在工程中,它常用于以下场景:
- 图片相似检索
- 商品图查重
- 以图搜图
- 相似商品召回
- 图片聚类
- 图片异常检测的前置特征提取
- 多张图片的视觉相似度计算
例如在商品系统里,可能会遇到这些问题:
这张商品图有没有重复上传?
这张主图和库里哪些图片相似?
不同商户是否上传了相同或近似图片?
商品换了标题,但图片是不是同一张?
平台上是否存在大量重复铺货图片?
这些问题本质上都可以转成一个向量检索问题:
输入一张图片
-> 提取图片向量
-> 到向量库里搜索相近向量
-> 返回相似图片
2. 整体流程
一个图片相似检索系统通常分成两条链路:
- 图片入库流程
- 图片搜索流程
整体结构如下:
图片入库流程:
商品图片
-> 加载 DINOv2 模型
-> 图片向量化
-> 存储向量和图片信息
-> PostgreSQL + pgvector + HNSW 索引
图片搜索流程:
查询图片
-> 加载 DINOv2 模型
-> 图片向量化
-> 向量相似度检索
-> 返回相似图片信息
其中 DINOv2 负责“提取图片特征”,pgvector 负责“存储和检索向量”。
3. 图片入库流程
图片入库的目标是把每张图片转换成一条可检索的数据。
3.1 输入商品图片
输入可以是:
- 本地图片文件
- OSS / S3 图片地址
- 商品主图 URL
- 后台上传图片
- 爬取后的图片资源
入库前建议先做基础清洗:
- 过滤无法下载的图片
- 过滤过小图片
- 统一图片格式
- 处理透明背景
- 去除明显损坏图片
- 记录图片 hash
图片 hash 很重要。 它可以先做一层精确去重,例如 MD5、SHA256、感知哈希等。
但 hash 只能处理完全相同或非常接近的图片。 如果图片经过裁剪、压缩、加水印、换背景,仍然需要依赖向量相似检索。
3.2 加载 DINOv2 模型
DINOv2 有不同规格的模型。 如果使用 ViT-B/14,常见输出特征维度是 768 维。
也就是说,一张图片最终会被转换成类似这样的向量:
[0.012, -0.083, 0.241, ..., 0.056]
这个向量长度是 768。
需要注意:不同 DINOv2 模型输出维度不同。
常见情况:
| 模型规格 | 向量维度 |
|---|---|
| ViT-S/14 | 384 |
| ViT-B/14 | 768 |
| ViT-L/14 | 1024 |
| ViT-G/14 | 1536 |
所以数据库里的 vector 维度必须和模型输出维度一致。
如果使用 768 维向量,pgvector 字段应该定义为:
vector(768)
如果后续换成 1024 维模型,原来的表结构和索引都需要调整。
3.3 图片向量化
向量化过程可以理解为:
原始图片
-> resize / crop / normalize
-> 输入 DINOv2
-> 提取图片特征
-> 得到 embedding 向量
工程上需要注意两点。
第一,入库图片和查询图片要使用同一套预处理逻辑。 否则同一张图片在入库和查询时可能得到不同分布的向量。
第二,向量通常需要做归一化。 如果后续使用余弦相似度,归一化可以让向量距离计算更稳定。
3.4 存储向量和图片信息
入库时不应该只存向量,还要存业务信息。
例如:
CREATE TABLE goods_image_vectors (
id BIGSERIAL PRIMARY KEY,
goods_id BIGINT NOT NULL,
image_url TEXT NOT NULL,
image_md5 VARCHAR(64),
embedding VECTOR(768),
created_at TIMESTAMP DEFAULT NOW()
);
如果是商品图片检索,通常还会加这些字段:
goods_id
merchant_id
brand_id
category_id
image_url
image_md5
platform
created_at
updated_at
这样查询时就可以结合业务条件过滤。
例如:
只查同品牌图片
只查同类目图片
只查某个商户下面的图片
只查已上架商品图片
这对检索质量很重要。
纯向量相似只能回答“视觉上像不像”。 但业务系统通常还需要回答“在指定业务范围内像不像”。
4. 图片搜索流程
搜索流程和入库流程基本对称。
4.1 输入查询图片
用户上传一张查询图片,或者系统拿某个商品主图作为查询图片。
然后执行同样的预处理:
查询图片
-> 图片预处理
-> DINOv2 向量化
-> 得到查询向量
4.2 向量相似度检索
拿到查询向量后,就可以在 PostgreSQL 里做相似度搜索。
如果使用 pgvector,可以用类似下面的 SQL:
SELECT
id,
goods_id,
image_url,
embedding <=> '[...]' AS distance
FROM goods_image_vectors
ORDER BY embedding <=> '[...]'
LIMIT 20;
这里的 <=> 通常用于 cosine distance。
含义是:
distance 越小,图片越相似
实际业务里一般不会只做纯向量排序,还会加过滤条件:
SELECT
id,
goods_id,
image_url,
embedding <=> :query_vector AS distance
FROM goods_image_vectors
WHERE category_id = :category_id
AND merchant_id <> :current_merchant_id
ORDER BY embedding <=> :query_vector
LIMIT 20;
这样可以减少无关结果,提高业务可用性。
4.3 返回相似图片信息
搜索结果通常不只返回图片 URL,还应该返回:
商品 ID
商品标题
商户 ID
品牌 ID
类目 ID
图片地址
相似度分数
距离值
命中的业务原因
例如:
{
"goods_id": 10001,
"image_url": "https://example.com/a.jpg",
"distance": 0.083,
"similarity": 0.917
}
如果使用 cosine distance,可以简单转换成:
similarity = 1 - distance
但这个分数不要直接当成绝对判断。 不同数据集、不同模型、不同图片类型下,阈值都需要重新评估。
5. 为什么需要 HNSW 索引
如果数据量很小,直接全表排序也能跑。
但当图片数量达到几十万、几百万时,这种 SQL 会变慢:
ORDER BY embedding <=> :query_vector
LIMIT 20
因为数据库需要计算大量向量距离。
这时就需要向量索引。
pgvector 支持 HNSW 索引。 HNSW 是一种近似最近邻搜索算法,适合高维向量检索。
可以创建类似这样的索引:
CREATE INDEX goods_image_vectors_embedding_hnsw_idx
ON goods_image_vectors
USING hnsw (embedding vector_cosine_ops);
HNSW 的作用是:
牺牲一点点精确度
换取更快的召回速度
在图片检索场景里,这通常是可以接受的。 因为业务上一般不是只看第 1 个结果,而是看 top 10、top 20、top 50 里是否有足够相关的图片。
6. 相似度阈值怎么定
图片相似检索不能只靠感觉定阈值。
例如:
distance < 0.05 判定为高度相似
distance < 0.10 判定为可能相似
distance < 0.20 判定为弱相似
这种规则必须通过真实数据验证。
建议准备一批样本:
完全相同图片
轻微压缩图片
裁剪图片
加水印图片
同商品不同角度图片
同类目但不同商品图片
完全无关图片
然后观察它们的向量距离分布。
更可靠的做法是建立一个小型评估集:
| 查询图片 | 正确相似图 | 错误相似图 | 备注 |
|---|---|---|---|
| A | A1、A2 | B1、C1 | 同款商品 |
| B | B1 | A1、C1 | 相似背景但不同商品 |
| C | C1、C2 | A1、B1 | 同商品不同压缩率 |
最后根据业务目标选择阈值:
- 查重:阈值应该更严格
- 相似推荐:阈值可以放宽
- 风控审核:宁可多召回,再人工确认
- 自动拦截:必须非常严格
7. DINOv2 的优势和限制
7.1 优势
DINOv2 的主要优势是通用视觉特征能力较强。
它不依赖具体商品类目,也不需要为每个类目重新训练模型。 对很多中小规模图片检索系统来说,可以先直接用预训练模型做 MVP。
适合优先验证的场景:
- 商品主图相似检索
- 重复图片识别
- 相似素材召回
- 图片聚类
- 视觉异常初筛
7.2 限制
DINOv2 不是万能图片理解模型。
它主要提取视觉特征,不负责理解复杂业务语义。
例如:
两张图片视觉上很像,但商品规格不同
两张图片是同一商品,但角度差异很大
图片里文字不同,但整体构图类似
商品主体很小,背景占比很大
图片经过大量拼接、贴字、加边框
这些情况可能影响召回结果。
所以图片检索系统不能只依赖 DINOv2。 实际落地时,通常还要结合:
- 图片 hash
- OCR 文本
- 商品标题
- 品牌
- 类目
- 价格
- 商户信息
- 业务规则
- 人工审核结果
也就是说,DINOv2 解决的是“视觉相似”,不是完整的“商品相同”。
8. 推荐的落地架构
一个比较实用的架构可以这样设计:
图片入库服务
-> 下载图片
-> 图片清洗
-> 计算 image_md5
-> DINOv2 提取向量
-> 写入 PostgreSQL + pgvector
图片检索服务
-> 接收查询图片
-> DINOv2 提取查询向量
-> pgvector 向量召回
-> 业务条件过滤
-> 返回相似图片
离线评估任务
-> 抽样查询
-> 统计 top-k 命中率
-> 分析 bad case
-> 调整阈值和过滤条件
如果数据量继续变大,可以再考虑把向量检索迁移到专门的向量数据库,例如 Milvus、Qdrant、Elasticsearch 向量检索等。
但在早期阶段,PostgreSQL + pgvector 的优势很明显:
- 部署简单
- 数据和向量可以放在一起
- 方便加业务过滤条件
- 适合 MVP 和中小规模系统
- 运维成本比单独引入向量数据库低
9. 常见误区
9.1 只看图片相似度,不看业务字段
两张图片视觉上相似,不代表业务上一定是同一个商品。
例如:
同款包装,不同规格
同品牌,不同型号
同背景模板,不同商品
同一张海报,卖点文字不同
所以检索时最好结合类目、品牌、商户、标题等字段。
9.2 以为向量维度越大越好
更大的模型通常表达能力更强,但也意味着:
- 推理更慢
- 向量存储更大
- 索引更大
- 查询成本更高
- 部署资源更高
如果 ViT-B/14 的 768 维已经够用,就没有必要一开始上更大的模型。
9.3 不做评估,直接拍脑袋定阈值
图片相似度阈值一定要基于真实数据。
不同业务的阈值差异很大:
商品查重:要求严格
相似推荐:可以宽松
图片聚类:看整体分布
风控审核:可以多召回
自动拦截:必须保守
不要直接复制别人的阈值。
10. 结论
DINOv2 适合用来做图片向量化,尤其适合商品图相似检索、图片查重和以图搜图。
一个最小可用方案是:
DINOv2 提取图片向量
-> PostgreSQL + pgvector 存储向量
-> HNSW 索引加速检索
-> 根据 distance 返回相似图片
如果图片规模不大,PostgreSQL + pgvector 足够支撑早期验证。 如果后续数据量和并发继续增长,再考虑迁移到专门的向量数据库。
选型时要记住一点:
DINOv2 负责提取视觉特征,pgvector 负责相似检索,业务规则负责最终判断。
不要把“视觉相似”直接等同于“业务相同”。 真正可用的图片检索系统,通常是视觉向量、图片 hash、OCR、商品信息和业务规则一起工作的结果。
评论 / 0
共 0 条你可能是第一个留下评论的人