使用 Milvus 实现高效图片查重功能

本文将介绍如何利用 Milvus 向量数据库构建一个高效的图片查重系统,通过传入图片就能快速从已有数据中找出匹配度高的相似图片。

一.什么是图片查重?

图片查重指的是通过算法识别出内容相同或高度相似的图片,即使它们可能存在尺寸、格式、轻微编辑等差异。传统的基于文件名或元数据的查重方法效果极差,而基于内容的图片查重则能真正识别视觉上相似的图片。

二. 技术原理

基于 Milvus 的图片查重系统主要依赖以下关键技术:

  1. 图片特征提取:使用深度学习模型将图片转换为固定维度的特征向量,捕捉图片的视觉特征
  2. 向量相似度搜索:通过计算向量之间的距离(相似度)来判断图片的相似程度
  3. 高效向量数据库:Milvus 提供的高性能向量索引和搜索能力,支持亿级数据的毫秒级检索

核心流程:

  • 预处理:将所有图片转换为特征向量并存储到 Milvus
  • 查重阶段:对输入图片提取特征向量,在 Milvus 中搜索相似度高于阈值的向量,找到对应的图片

三 .实现步骤

1. 核心代码实现

下面是完整的图片查重系统实现,包含特征提取和 Milvus 操作:

2. 代码解析

核心组件
  1. 图片特征提取器(ImageFeatureExtractor)
class ImageFeatureExtractor:"""图片特征提取器,将图片转换为特征向量"""def __init__(self):# 使用预训练的ResNet50模型self.model = models.resnet50(pretrained=True)# 移除最后一层全连接层,保留特征提取部分self.model = torch.nn.Sequential(*list(self.model.children())[:-1])self.model.eval()  # 切换到评估模式# 确保使用适当的设备(GPU如果可用)self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")self.model.to(self.device)# 图片预处理流程,与模型训练时保持一致self.transform = 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])])logger.info(f"特征提取器初始化完成,使用设备: {self.device}")def extract(self, image_path):"""提取单张图片的特征向量"""try:# 打开图片并转换为RGB格式img = Image.open(image_path).convert('RGB')# 预处理img_tensor = self.transform(img).unsqueeze(0)  # 添加批次维度img_tensor = img_tensor.to(self.device)# 提取特征with torch.no_grad():  # 不计算梯度,加速推理features = self.model(img_tensor)# 展平为一维向量并归一化feature_vector = features.squeeze().cpu().numpy()normalized_vector = feature_vector / np.linalg.norm(feature_vector)return normalized_vectorexcept Exception as e:logger.error(f"提取图片 {image_path} 特征失败: {str(e)}")return Nonedef batch_extract(self, image_paths):"""批量提取图片特征"""features = []valid_paths = []for path in image_paths:feat = self.extract(path)if feat is not None:features.append(feat)valid_paths.append(path)return valid_paths, features
  • 使用预训练的 ResNet50 模型提取图片特征
  • 对图片进行标准化处理(Resize、裁剪、归一化)
  • 支持单张和批量图片特征提取
  • 自动选择 GPU/CPU 设备加速处理

class MilvusImageChecker:"""基于Milvus的图片查重工具"""def __init__(self, host=Config.MILVUS_HOST, port=Config.MILVUS_PORT, collection_name=Config.COLLECTION_NAME):self.host = hostself.port = portself.collection_name = collection_nameself.collection = None# 连接Milvus并初始化集合self.connect()self.init_collection()def connect(self):"""连接到Milvus服务器"""try:connections.connect(alias="default",host=self.host,port=self.port)logger.info(f"成功连接到Milvus服务器: {self.host}:{self.port}")except Exception as e:logger.error(f"连接Milvus服务器失败: {str(e)}")raisedef init_collection(self):"""初始化集合,如不存在则创建"""if utility.has_collection(self.collection_name):self.collection = Collection(self.collection_name)logger.info(f"已加载集合: {self.collection_name}")return# 定义集合字段fields = [FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True),FieldSchema(name="image_path", dtype=DataType.VARCHAR, max_length=512),FieldSchema(name="feature_vector", dtype=DataType.FLOAT_VECTOR, dim=Config.VECTOR_DIM),FieldSchema(name="upload_time", dtype=DataType.INT64, description="图片上传时间戳,用于去重时保留最早/最新版本")]# 创建集合schemaschema = CollectionSchema(fields, "用于图片查重的集合,存储图片路径和特征向量",enable_dynamic_field=False)# 创建集合self.collection = Collection(self.collection_name, schema)logger.info(f"成功创建集合: {self.collection_name}")# 创建索引,优化查询性能index_params = {"index_type": "HNSW",  # 适用于高维向量的高效索引"metric_type": "IP",   # 内积,适用于归一化向量的相似度计算"params": {"M": 16,           # HNSW参数,影响索引质量和查询速度"efConstruction": 200  # 构建索引时的参数}}self.collection.create_index(field_name="feature_vector",index_params=index_params)logger.info("集合索引创建完成")def insert_images(self, image_paths, upload_timestamps=None):"""插入图片特征到Milvus参数:image_paths: 图片路径列表upload_timestamps: 上传时间戳列表,用于去重时判断保留哪个版本"""if not image_paths:logger.warning("没有图片路径可插入")return []# 处理时间戳(默认使用当前时间)if upload_timestamps is None:current_ts = int(os.path.getmtime(image_paths[0])) if image_paths else 0upload_timestamps = [current_ts] * len(image_paths)# 批量提取特征extractor = ImageFeatureExtractor()valid_paths, features = extractor.batch_extract(image_paths)if not valid_paths:logger.warning("没有有效图片可插入")return []# 准备插入数据data = [valid_paths,  # image_path字段[f.tolist() for f in features],  # feature_vector字段upload_timestamps[:len(valid_paths)]  # upload_time字段]# 执行插入try:insert_result = self.collection.insert(data)self.collection.flush()  # 刷新到磁盘logger.info(f"成功插入 {len(valid_paths)} 张图片,ID范围: {insert_result.primary_keys}")return insert_result.primary_keysexcept Exception as e:logger.error(f"插入图片失败: {str(e)}")return []def check_duplicates(self, image_path, threshold=Config.DEFAULT_THRESHOLD, top_k=Config.DEFAULT_TOP_K):"""检查指定图片是否存在重复或高度相似的图片参数:image_path: 待检查的图片路径threshold: 相似度阈值,高于此值认为是相似图片top_k: 返回的最大相似图片数量返回:相似图片列表,按相似度降序排列"""# 提取查询图片特征extractor = ImageFeatureExtractor()query_vector = extractor.extract(image_path)if query_vector is None:logger.error("无法提取查询图片特征,查重失败")return []# 加载集合到内存(如果尚未加载)if not self.collection.is_loaded:self.collection.load()logger.info(f"集合 {self.collection_name} 已加载到内存")# 配置搜索参数search_params = {"metric_type": "IP","params": {"ef": 64}  # 搜索时的参数,影响查询精度和速度}# 执行相似度搜索try:results = self.collection.search(data=[query_vector.tolist()],anns_field="feature_vector",param=search_params,limit=top_k,output_fields=["image_path", "upload_time"])# 处理搜索结果,过滤低于阈值的结果duplicates = []for hits in results:for hit in hits:similarity = hit.distanceif similarity >= threshold:duplicates.append({"image_path": hit.entity.get("image_path"),"similarity": float(similarity),"milvus_id": hit.id,"upload_time": hit.entity.get("upload_time")})# 按相似度降序排序duplicates.sort(key=lambda x: x["similarity"], reverse=True)logger.info(f"找到 {len(duplicates)} 张相似图片 (阈值: {threshold})")return duplicatesexcept Exception as e:logger.error(f"查重搜索失败: {str(e)}")return []def delete_duplicates(self, duplicate_ids):"""删除指定ID的重复图片记录"""if not duplicate_ids:return Truetry:self.collection.delete(f"id in {duplicate_ids}")self.collection.flush()logger.info(f"成功删除 {len(duplicate_ids)} 条重复记录")return Trueexcept Exception as e:logger.error(f"删除重复记录失败: {str(e)}")return False
  1. Milvus 查重工具(MilvusImageChecker)
    • 负责与 Milvus 服务器的连接和交互
    • 初始化集合并创建高效索引(使用 HNSW 索引)
    • 提供图片特征插入、重复图片查询和删除功能
    • 支持批量操作,提高处理效率
关键技术点
  • 特征向量归一化:确保内积(IP)可以直接作为余弦相似度使用
  • 合适的索引选择:使用 HNSW 索引平衡查询速度和精度
  • 相似度阈值:可根据业务需求调整,值越高表示要求越相似
  • 时间戳管理:记录图片上传时间,便于去重时选择保留最早或最新版本

五 .重点

  1. 模型选择

    • 追求精度:可使用更复杂的模型如 ResNet101 或 Vision Transformer
    • 追求速度:可使用轻量级模型如 MobileNet 或 EfficientNet
  2. 索引优化

    • 对于大规模数据,可调整 HNSW 索引的 MefConstruction 参数
    • 可尝试不同索引类型(如 IVF_FLAT、IVF_SQ8)找到性能平衡点
  3. 阈值调整

    • 对于严格查重(完全相同的图片),可将阈值设为 0.98 以上
    • 对于相似图片检索,可将阈值设为 0.85-0.95 之间
  4. 分布式部署

    • 对于超大规模图片库,可使用 Milvus 集群提高吞吐量和可靠性

六 应用场景

  • 内容管理系统:自动检测并去重上传的图片
  • 电商平台:识别盗图和相似商品图片
  • 版权保护:追踪未经授权使用的图片
  • 相册管理:自动整理相似照片,减少冗余

总结

基于 Milvus 的图片查重系统能够高效处理海量图片数据,通过特征向量和相似度搜索技术,实现了精准的重复图片识别。相比传统方法,它具有以下优势:

  • 识别真正视觉相似的图片,不受文件名或格式影响
  • 支持亿级图片的快速检索,毫秒级响应
  • 可灵活调整相似度阈值,适应不同业务需求
  • 易于扩展和集成到现有系统中

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/91385.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/91385.shtml
英文地址,请注明出处:http://en.pswp.cn/web/91385.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

诱导多能干细胞(iPSC)的自述

自十七年前诱导多能干细胞(也称iPS细胞或iPSC)技术出现以来,干细胞生物学和再生医学取得了巨大进展。人类iPSC已广泛用于疾病建模、药物发现和细胞疗法开发。新的病理机制已被阐明,源自iPSC筛选的新药正在研发中,并且首…

基于深度学习的医学图像分析:使用DeepLabv3+实现医学图像分割

前言 医学图像分析是计算机视觉领域中的一个重要应用,特别是在医学图像分割任务中,深度学习技术已经取得了显著的进展。医学图像分割是指从医学图像中识别和分割出特定的组织或器官,这对于疾病的诊断和治疗具有重要意义。近年来,D…

Lombok 字段魔法:用 @FieldDefaults 解锁“隐身+锁死”双重特效

前言 项目里总有这样一种神秘现象:明明只是几个字段,却堆满 private final,每次都得机械敲上一遍。有的同事一边敲一边默念“代码规范不能丢”,表情严肃得像在写遗嘱。可惜,规范虽好,手指遭殃。 于是,Lombok 悄然登场,肩扛简洁大旗,手握注解神器,@FieldDefaults 正…

小白如何自学网络安全,零基础入门到精通,看这一篇就够了!

小白如何自学网络安全,零基础入门到精通,看这一篇就够了! 小白人群想学网安但是不知道从哪入手?一篇文章告诉你如何在4个月内吃透网安课程,掌握网安技术 一、基础阶段 1.了解网安相关基础知识 了解中华人民共和国网…

前端 vue 第三方工具包详解-小白版

恭喜你迈入Vue世界!😄 对于前端小白,掌握这些常用第三方包能极大提升开发效率和项目质量。以下是Vue生态中必备的第三方包及小白友好式用法解析:🧱 一、基础工具包(每个项目必装) 1. Vue Router…

解决mac下git pull、push需要输入密码

解决方法: 1.强制配置 SSH 自动加载钥匙串 编辑 SSH 配置文件 vi ~/.ssh/configHost *AddKeysToAgent yes # 自动将密钥添加到 ssh-agentUseKeychain yes # 明确使用钥匙串存储密码IdentityFile ~/.ssh/id_rsa # 替换为你的私钥路径2.修复 Sh…

内存网格、KV存储和Redis的概念、使用场景及异同

基本概念 内存网格 (In-Memory Data Grid - IMDG) 内存网格是一种分布式内存数据存储技术,具有以下特点:分布式架构 数据跨多个服务器节点分布存储提供线性扩展能力内存优先 主要数据存储在内存中,提供微秒级访问延迟支持持久化作为备份企业级…

【C++算法】87.BFS解决最短路径问题_为高尔夫比赛砍树

文章目录题目链接:题目描述:解法C 算法代码:题目链接: 675. 为高尔夫比赛砍树 题目描述: 解法 注意:砍树要从低到高砍。 砍掉1,从1到5到2 砍掉2,从2到5到3 砍掉3,从3到5…

JavaScript内存管理完全指南:从入门到精通

文章目录JavaScript内存管理完全指南:从入门到精通1. 哪些数据类型属于引用类型(复杂数据类型)?2. 为什么引用类型要存储在堆中?3. 引用类型的内存存储示例示例 1:对象(Object)示例 …

Linux网络-------3.应⽤层协议HTTP

1.HTTP协议 虽然我们说,应⽤层协议是我们程序猿⾃⼰定的.但实际上,已经有⼤佬们定义了⼀些现成的,⼜⾮常好⽤的应⽤层协议,供我们直接参考使⽤.HTTP(超⽂本传输协议)就是其中之⼀。 在互联⽹世界中,HTTP(HyperText Transfer Protocol,超⽂本…

05 GWAS表型数据处理原理

表型数据处理 • 质量性状 – 二分类:可用0 / 1, 1 / 2 数值表示 – 多分类:哑变量赋值,0/1 • 数量性状 – 尽量符合正太分布 – 剔除异常表型值样本 – 多年多点重复观测 – 对于阈值性状,分级数量化或哑变量赋值 R中 shapiro.t…

【Cpolar实现内网穿透】

Cpolar实现内网穿透业务需求第一步:准备工作1、关闭安全软件2、下载所需软件第二步:Nginx的配置第三步:使用cpolar实现内网穿透1、进入 https://dashboard.cpolar.com/get-started 注册,登录,完成身份证的实名认证2、下…

基于 JavaWeb+MySQL 的学院党费缴费系统

基于 JavaWeb 的学院党费缴费系统第 1 章绪论1.1 项目背景当今互联网发展及其迅速,互联网的便利性已经遍及到各行各业,惠及到每一个人,传统的缴费方式都需要每个人前往缴费点陆续排队缴费,不仅浪费大量了个人时间,而且…

LCGL基本使用

LVGC简介 light video Graphics Library (1)纯c与语言编程,将面向对象的思想植入c语言。 (2)轻量化图形库资源,人机交互效果好,在(ios Android QT)移植性较好,但是这些平台对硬件要求较高 lcgc工程搭建 工程源码的获取 获取工程结构 https://github.com/lvgl/lv_po…

嵌入式第十六课!!!结构体与共用体

一、结构体结构体是一种数据类型,它的形式是这样的:struct 结构体名{ 结构体成员语句1;结构体成员语句2;结构体成员语句3;};举个例子:struct Student {int id;char name[20];float score…

java web 实现简单下载功能

java web 实现简单下载功能 项目结构├── src\ │ ├── a.txt │ └── com\ │ └── demo\ │ └── web\ │ ├── Cookie\ │ ├── download\ │ ├── homework\ │ ├── serv…

虚幻基础:模型穿模

能帮到你的话,就给个赞吧 😘 文章目录模型穿模模型之间的阻挡是否正确设置模型是角色的组件:角色的组件不会与场景中其他的物体发生阻挡但可以发生重叠模型穿模 模型之间的阻挡是否正确设置 模型是角色的组件:角色的组件不会与场…

【Linux】linux基础开发工具(二) 编译器gcc/g++、动静态库感性认识、自动化构建-make/Makefile

文章目录一、gcc/g介绍二、gcc编译选项预处理编译汇编链接三个细节三、动静态库感性认识动静态库的优缺点四、自动化构建-make/Makefile背景知识初步上手Makefilemakefile的推导过程makefile语法一、gcc/g介绍 我们之前介绍了编辑器vim,可以让我们在linux上linux系统…

CentOS 7 上使用 Docker 安装 Jenkins 完整教程

目录 前言 准备工作 系统要求 检查系统信息 更新系统 安装Docker 第一步:卸载旧版本Docker(如果存在) 第二步:安装必要的软件包 第三步:添加Docker官方仓库 第四步:安装Docker CE 第五步:启动Docker服务 第六步:验证Docker安装 第七步:配置Docker用户权限…

30.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--单体转微服务--公共代码--用户上下文会话

在前面的文章中,我们会看到使用ContextSession来获取当前用户的UserId和UserName。这篇文章我们就一起来看看如何实现ContextSession。 一、ContextSession的实现 我们在公共类库SP.Common中创建一个名为ContextSession的类,用于获取当前请求的用户信息。…