圖片查重從設計到實現(7) :使用 Milvus 實現高效圖片查重功能

使用 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的類,用于獲取當前請求的用戶信息。…