用 DINOv2 做图片相似检索:从图片向量化到 pgvector 入库

DINOv2 可以把图片转换成向量,用于商品图相似检索、图片查重和以图搜图。本文记录一个基于 DINOv2、PostgreSQL、pgvector 和 HNSW 索引的最小落地方案。

文章目录

最近在做图片相似检索时,我把问题拆成了两个部分:

  1. 如何把图片转换成可计算的向量?
  2. 如何把这些向量存起来,并支持快速相似搜索?

如果只是做文本检索,可以用 BGE、text-embedding 这类文本 embedding 模型。 但如果对象是商品图、主图、海报、截图,就需要使用视觉模型提取图片特征。

这里可以优先评估 DINOv2

DINOv2 的作用可以简单理解为:

把一张图片转换成一个高维向量。图片越相似,向量距离通常越近。

然后再用向量数据库或向量索引做相似度搜索。

本文记录一个最小可落地方案:

DINOv2 + PostgreSQL + pgvector + HNSW

1. DINOv2 适合解决什么问题

DINOv2 是 Meta AI 发布的自监督视觉模型。 它可以从图片中提取通用视觉特征,不依赖人工标注的分类标签。

在工程中,它常用于以下场景:

  • 图片相似检索
  • 商品图查重
  • 以图搜图
  • 相似商品召回
  • 图片聚类
  • 图片异常检测的前置特征提取
  • 多张图片的视觉相似度计算

例如在商品系统里,可能会遇到这些问题:

这张商品图有没有重复上传?
这张主图和库里哪些图片相似?
不同商户是否上传了相同或近似图片?
商品换了标题,但图片是不是同一张?
平台上是否存在大量重复铺货图片?

这些问题本质上都可以转成一个向量检索问题:

输入一张图片
  -> 提取图片向量
  -> 到向量库里搜索相近向量
  -> 返回相似图片

2. 整体流程

一个图片相似检索系统通常分成两条链路:

  1. 图片入库流程
  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/14384
ViT-B/14768
ViT-L/141024
ViT-G/141536

所以数据库里的 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 判定为弱相似

这种规则必须通过真实数据验证。

建议准备一批样本:

完全相同图片
轻微压缩图片
裁剪图片
加水印图片
同商品不同角度图片
同类目但不同商品图片
完全无关图片

然后观察它们的向量距离分布。

更可靠的做法是建立一个小型评估集:

查询图片正确相似图错误相似图备注
AA1、A2B1、C1同款商品
BB1A1、C1相似背景但不同商品
CC1、C2A1、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、商品信息和业务规则一起工作的结果。


参考资料

你可能还感兴趣