图像检索系统

📅 2026/6/29 19:59:01
图像检索系统
项目背景一个基于深度学习的图像管理与检索平台。支持本地上传和网络搜图自动用YOLO检测物体并打标签用CLIP做图文语义匹配实现以文搜图。GitHub 仓库https://github.com/LuckLuffy/SVD-image-compressor项目流程上传图片 → YOLO检测物体自动打标签 → CLIP编码成512维向量 → 存入SQLite并加入FAISS索引。搜索的时候输入自然语言 → CLIP编码 → FAISS搜相似向量 → 同时做关键词匹配 → 两路结果融合排序。核心问题图片上传、存储、缩略图生成自动检测物体并生成标签和描述用自然语言搜索图片实现SVD/QR有损压缩并评估质量前端做成两个Tab页关键词CLIP、FAISS、YOLO、ONNX、FastAPI、向量检索整体结构├── 核心引擎 (engine.py) ← CLIPYOLOFAISS搜索全部集中 ├── 压缩算法 (core/compressor.py) ← SVD实现 ├── API层 (main.py) ← FastAPI路由15个端点 ├── 数据库层 (database.py models.py) ← 异步SQLAlchemy3张表 └── 前端 (frontend/) ← React 19搜集/检索双标签页第1步图片入库流水线上传一张图片后系统自动跑完一整条流水线用户拖拽图片 │ ├── 1. 格式校验 → 大小限制(20MB) ├── 2. 存原图 生成300px缩略图 ├── 3. 算 pHash/dHash (感知哈希用于去重) ├── 4. CLIP编码 → 512维向量 ├── 5. YOLO检测物体 → 自动标签 [cat,couch] ├── 6. 生成描述 包含cat和couch的图片 ├── 7. (可选) SVD压缩 ├── 8. 写入SQLite └── 9. 更新FAISS索引对应后端代码就是这一个函数——main.py里的upload_image()。每个步骤都包了try/except某一步挂了不影响入库。数据库表设计-- 3张表的关系image_records-- 图片主表 (路径、标签JSON、嵌入向量BLOB、检测结果JSON)search_history-- 搜索历史 (查询文本、结果ID列表、搜索类型)compression_records-- 压缩历史 (v1遗留算法、PSNR、压缩率)标签为什么存JSON列而不是建关联表标准的做法是建tags表和image_tags关联表。但本项目的场景是每张图就3-10个标签几百到几千张图的规模。JSON列读一次就全拿到不用JOIN前端拿到直接JSON.parse()就能用。省掉的N1查询比规范化的收益更大。第2步CLIP多模态嵌入为什么用CLIPCLIP是OpenAI训练的把图像和文字映射到同一个向量空间的模型。在这个空间里一只猫的照片这段文字和一张猫图的向量距离很近。图像 → ViT编码器 → 512维向量 ─┐ ├── 余弦相似度 → 0~1 文本 → Transformer → 512维向量 ─┘两个向量越接近说明图文越匹配——这就是以文搜图的基础。为什么用ONNX而不是PyTorch维度PyTorchONNX Runtime安装大小~2GB~200MB启动时间5-10秒1秒推理速度相当相当依赖复杂度CUDA、cuDNN就一个 onnxruntime 包对于演示/教学项目ONNX的轻量和快速启动碾压PyTorch。代价只是需要提前把模型导出成ONNX格式。具体实现# engine.py: embed_image() — 图像编码imgImage.open(path).convert(RGB).resize((224,224),Image.LANCZOS)arrnp.array(img,dtypenp.float32)/255.0arr(arr-mean)/std# CLIP专用归一化arrnp.transpose(arr,(2,0,1))[np.newaxis,...]# HWC → NCHW# ONNX推理 → L2归一化vec_image_session.run(None,{input_name:arr})[0][0]returnvec/np.linalg.norm(vec)# 归一化后内积余弦相似度文本编码也是同理——BPE分词 → token IDs → ONNX推理 → L2归一化。图像和文本的向量在同一个空间可以直接比较。第3步FAISS向量检索为什么用FAISSFAISS是Meta开源的向量检索引擎。自己手写的话——512维向量算余弦相似度要遍历所有图片——1000张还行10万张就慢了。FAISS内部用了高度优化的矩阵运算。索引选择IndexFlatIP# IndexFlatIP 内积(Inner Product)索引# 对L2归一化后的向量内积就等于余弦相似度_faiss_indexfaiss.IndexFlatIP(512)# 512维_faiss_index.add(vectors)# 添加所有向量D,I_faiss_index.search(query_vec,k)# 搜最相似的k个IndexFlatIP是精确搜索不是近似O(N)复杂度。图片量小于10万时完全够用——单次搜索不到10毫秒。索引持久化# 每次入库后把索引写到磁盘faiss.write_index(_faiss_index,faiss_index.bin)# 启动时直接从磁盘加载比从DB重建快50倍_faiss_indexfaiss.read_index(faiss_index.bin)如果每次启动都从数据库读embedding_blob重建索引1000张图要5秒。持久化后启动耗时降到100毫秒以内。第4步YOLO目标检测模型选择用YOLOv8n——nano版本ONNX格式只有12MB。在CPU上也能跑检测80类COCO物体。预处理和后处理原图(任意尺寸) → Letterbox填充到640×640 → RGB → /255 → (1,3,640,640) │ ▼ ONNX推理 → 输出 [1, 84, 8400] │ 84 4(bbox) 80(类别分数) │ 8400 特征网格点数 ▼ 解码bbox → 阈值过滤(confidence≥0.5) → NMS去重 → 映射回原图坐标核心代码# engine.py: detect_objects()def_yolo_postprocess(preds,w0,h0,r,pl,pt):# cxcywh → xyxyxyxy[:,0]boxes[:,0]-boxes[:,2]/2# x1xyxy[:,2]boxes[:,0]boxes[:,2]/2# x2# 映射回原图坐标去掉padding和缩放xyxy[:,[0,2]](xyxy[:,[0,2]]-pl)/r xyxy[:,[1,3]](xyxy[:,[1,3]]-pt)/r# NMS同类框不互相抑制keep_simple_nms(xyxy,confs,cls_ids)Windows中文路径兼容OpenCV的cv2.imread()不支持中文路径。解决方案是绕过去# 用 np.fromfile imdecode 代替 imreadimgcv2.imdecode(np.fromfile(image_path,np.uint8),cv2.IMREAD_COLOR)第5步混合搜索策略纯CLIP搜索的问题——搜红色的车可能返回各种颜色的车CLIP对颜色不够敏感。纯关键词搜索的问题——搜温馨的家庭照片根本匹配不到任何标签。所以两路并行然后融合用户输入 蓝天白云下的风景 │ ├──→ CLIP文本编码 → 512维向量 → FAISS搜索 → 语义结果 │ ├──→ 分词[蓝天,白云,风景] │ ├── 标签精确匹配: 每命中0.6 │ └── 描述包含匹配: 每命中0.3 │ → 关键词结果 │ └──→ 合并策略 - 关键词结果优先进精确匹配置信度高 - 语义结果补充分数≥0.18才纳入 - ID已存在 → 叠加50%语义分 → 排序取top_k分数公式relevance0.6× 语义相似度0.3× 标签匹配度0.1× 结构特征分权重在config.py里配置。第6步SVD/QR图像压缩这是项目最初的起源——从MATLAB课程作业迁移过来的压缩算法。SVD压缩原理对每个颜色通道做奇异值分解只保留前k个最大的奇异值# core/compressor.py: compress_svd()forchinrange(c):# 逐通道R, G, Bchannelimage[:,:,ch].astype(np.float64)U,S,Vtsvd(channel,full_matricesFalse)k_effmin(k,len(S))# 重构只取前k个分量compressed[:,:,ch](U[:,:k_eff]*S[:k_eff]) Vt[:k_eff,:]Eckart-Young定理保证——SVD截断是所有秩为k的矩阵中F-范数误差最小的。k1时只剩模糊轮廓k50中等质量k300接近原图。质量指标指标公式怎么读MSE(1/N)Σ(I-I’)²越小越好0无损PSNR20·log₁₀(255/√MSE)40dB优秀30-40dB良好压缩率k×(mn1)/(m×n)越小压缩越强PSNRinf无穷大说明MSE0完全无损——JSON不支持Infinity所以后端做了特殊处理。第7步前端双标签页React 19 TypeScript Vite两个Tab标签页标签展示内容图片搜集拖拽上传区 SVD压缩选项 网络搜图区 图片网格 标签筛选图像检索搜索栏 搜索结果网格(含匹配分数和类型) 搜索历史 一键重搜图片搜集页上传区支持拖拽选择后显示预览图。可选SVD压缩复选框 k值滑块。网络搜图用ddgs或LoremFlickr不需要API Key搜出来点导入就下载并入库。检索页输入自然语言 → 返回匹配图片 → 每张显示相关性百分比和匹配类型badge——标签匹配是绿色、语义匹配是黄色、标签描述是蓝色。详情弹窗点开图片后可以编辑描述失焦自动保存、手动添加/删除标签、查看YOLO检测到的物体列表和置信度、下载原图或压缩图。懒加载降级CLIP、YOLO、网络搜图全都不是启动时加载的而是首次使用时才初始化_initFalse_sessionNonedef_init_module():global_init,_sessionif_init:return_sessionload_model()# 耗时操作_initTruedefdo_work():_init_module()if_sessionisNone:returnfallback_value# 模型不可用 → 降级return_session.run(...)CLIP模型缺失 → 语义搜索跳过 → 关键词搜索顶上 → 系统仍然可用。技术栈清单模块用的什么为什么选它后端框架FastAPI写代码少、自动Swagger文档、异步支持好数据库SQLite SQLAlchemy 2.0(异步)SQLite零配置aiosqlite不阻塞事件循环向量检索FAISS (IndexFlatIP)Meta开源优化过的矩阵运算10万数据精确搜索够快图文嵌入CLIP ViT-B/32 (ONNX)图文统一向量空间ONNX免PyTorch依赖目标检测YOLOv8n (ONNX)12MB超轻量CPU可跑图像处理Pillow NumPy标准组合压缩算法NumPy (手写SVD/QR)从MATLAB迁移纯教学向前端React 19 TypeScript Vite组件化开发TS有类型提示网络搜图ddgs / LoremFlickr免费不需要API Key项目反思ONNX自包含解决了环境问题— 不需要用户装PyTorchpip install后下载模型就能跑懒加载让启动秒开— CLIPYOLO近1GB的模型都在首次使用时才加载不影响API响应混合搜索比单一方案好— 关键词做精确匹配、CLIP做语义扩展两路互补FAISS索引持久化是必要的— 从磁盘加载比从DB重建快50倍如果对你有帮助可以给个 Star ⭐欢迎提 Issue 讨论。GitHubhttps://github.com/LuckLuffy/SVD-image-compressor