resnet50,clip,Faiss+Flask简易图文搜索服务

一、实现

文件夹目录结构:

templates

        -----upload.html

faiss_app.py

前端代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Search and Show Multiple Images</title>
    <style>
        #image-container {
            display: flex;
            flex-wrap: wrap;
        }
        #image-container img {
            max-width: 150px;
            margin: 10px;
        }
    </style>
</head>
<body>
    <h1>Search Images</h1>
     <!-- 上传表单 -->
     <form id="upload-form" enctype="multipart/form-data">
        <input type="file" id="file-input" name="file" accept="image/*" required>
        <input type="submit" value="Upload">
    </form>

    <!-- 搜索框 -->
    <form id="search-form">
        <input type="text" id="search-input" name="query" placeholder="Enter search term" required>
        <input type="submit" value="Search">
    </form>

    <h2>Search Results</h2>
    <!-- 显示搜索返回的多张图片 -->
    <div id="image-container"></div>

    <!-- 使用JS处理表单提交 -->
    <script>
        document.getElementById('search-form').addEventListener('submit', async function(event) {
            event.preventDefault();  // 阻止表单默认提交行为
            
            const query = document.getElementById('search-input').value;  // 获取搜索框中的输入内容

            try {
                // 发送GET请求,将搜索关键词发送到后端
                const response = await fetch(`/search?query=${encodeURIComponent(query)}`, {
                    method: 'GET',
                });

                // 确保服务器返回JSON数据
                const data = await response.json();

                // 清空图片容器
                const imageContainer = document.getElementById('image-container');
                imageContainer.innerHTML = '';

                // 遍历后端返回的图片URL数组,动态创建<img>标签并渲染
                data.image_urls.forEach(url => {
                    const imgElement = document.createElement('img');
                    imgElement.src = url;  // 设置图片的src属性为返回的URL
                    imageContainer.appendChild(imgElement);  // 将图片添加到容器中
                });
            } catch (error) {
                console.error('Error searching for images:', error);
            }
        });
        document.getElementById('upload-form').addEventListener('submit', async function(event) {
            event.preventDefault();  // 阻止表单默认提交行为
            
            const fileInput = document.getElementById('file-input');
            const formData = new FormData();
            formData.append('file', fileInput.files[0]);  // 获取用户上传的图片文件

            try {
                // 发送POST请求,将图片发送到后端
                const response = await fetch('/search_by_images', {
                    method: 'POST',
                    body: formData
                });

                // 确保服务器返回JSON数据
                const data = await response.json();

                // 清空图片容器
                const imageContainer = document.getElementById('image-container');
                imageContainer.innerHTML = '';

                // 遍历后端返回的图片URL数组,动态创建<img>标签并渲染
                data.image_urls.forEach(url => {
                    const imgElement = document.createElement('img');
                    imgElement.src = url;  // 设置图片的src属性为返回的URL
                    imageContainer.appendChild(imgElement);  // 将图片添加到容器中
                });
            } catch (error) {
                console.error('Error uploading file:', error);
            }
        });
    </script>
</body>
</html>

后端代码:

from sentence_transformers import SentenceTransformer, util
from torchvision import models, transforms
from PIL import Image
from flask import Flask, request, jsonify, current_app, render_template, send_from_directory, url_for
from werkzeug.utils import secure_filename
import faiss
import os, glob
import numpy as np
from markupsafe import escape
import shutil

#Load CLIP model
model = SentenceTransformer('clip-ViT-B-32')
IMAGE_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.bmp'}

UPLOAD_FOLDER = 'uploads/'
IMAGES_PATH  = "C:\\Users\\cccc\\Pictures\\cls_auto_config"

def generate_clip_embeddings(images_path, model):
    image_paths = []
    # 使用 os.walk 遍历所有子目录和文件
    for root, dirs, files in os.walk(images_path):
        for file in files:
            # 获取文件的扩展名并转换为小写
            ext = os.path.splitext(file)[1].lower()
            # 判断是否是图片文件
            if ext in IMAGE_EXTENSIONS:
                image_paths.append(os.path.join(root, file)) 
    embeddings = []
    for img_path in image_paths:
        image = Image.open(img_path)
        embedding = model.encode(image)
        embeddings.append(embedding)
    
    return embeddings, image_paths

def generate_res50_embeddings(images_path):
    # Load the pretrained model
    res50_model = models.resnet50(pretrained=True)
    res50_model = res50_model.eval()

    # Define the image transformations
    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]),
    ])

    image_paths = []
    # 使用 os.walk 遍历所有子目录和文件
    for root, dirs, files in os.walk(images_path):
        for file in files:
            # 获取文件的扩展名并转换为小写
            ext = os.path.splitext(file)[1].lower()
            # 判断是否是图片文件
            if ext in IMAGE_EXTENSIONS:
                image_paths.append(os.path.join(root, file)) 
    embeddings = []
    for img_path in image_paths:
        image = Image.open(img_path)
        # Apply the transformations and get the image vector
        image = transform(image).unsqueeze(0)
        image_vector = res50_model(image).detach().numpy()
        embeddings.append(image_vector[0])
    return embeddings, image_paths

def create_faiss_index(embeddings, image_paths, output_path):

    dimension = len(embeddings[0])

    # 分情况创建Faiss索引对象
    if len(image_paths) < 39 * 256:
        # 如果条目很少,直接用最普通的L2索引
        faiss_index = faiss.IndexFlatL2(dimension)
    elif len(image_paths) < 39 * 4096:
        # 如果条目少于39 × 4096,就只用PQ量化,不使用IVF
        faiss_index = faiss.index_factory(dimension, 'OPQ64_256,PQ64x8')
    else:
        # 否则就加上IVF
        faiss_index = faiss.index_factory(dimension, 'OPQ64_256,IVF4096,PQ64x8')
    res = faiss.StandardGpuResources()
    co = faiss.GpuClonerOptions()
    co.useFloat16 = True
    faiss_index = faiss.index_cpu_to_gpu(res, 0, faiss_index, co)

    #index = faiss.IndexFlatIP(dimension)
    faiss_index = faiss.IndexIDMap(faiss_index)
    
    vectors = np.array(embeddings).astype(np.float32)

    # Add vectors to the index with IDs
    faiss_index.add_with_ids(vectors, np.array(range(len(embeddings))))
    
    # Save the index
    faiss_index = faiss.index_gpu_to_cpu(faiss_index)
    faiss.write_index(faiss_index, output_path)
    print(f"Index created and saved to {output_path}")
    
    # Save image paths
    with open(output_path + '.paths', 'w') as f:
        for img_path in image_paths:
            f.write(img_path + '\n')
    
    return faiss_index

def load_faiss_index(index_path):
    faiss_index = faiss.read_index(index_path)
    with open(index_path + '.paths', 'r') as f:
        image_paths = [line.strip() for line in f]
    print(f"Index loaded from {index_path}")
    if not faiss_index.is_trained:
            raise RuntimeError(f'从[{index_path}]加载的Faiss索引未训练')
    res = faiss.StandardGpuResources()
    co = faiss.GpuClonerOptions()
    co.useFloat16 = True
    faiss_index = faiss.index_cpu_to_gpu(res, 0, faiss_index, co)
    return faiss_index, image_paths


def retrieve_similar_images(query, model, index, image_paths, top_k=3):
    
    # query preprocess:
    if query.endswith(('.png', '.jpg', '.jpeg', '.tiff', '.bmp', '.gif')):
        query = Image.open(query)

    query_features = model.encode(query)
    query_features = query_features.astype(np.float32).reshape(1, -1)

    distances, indices = index.search(query_features, top_k)

    retrieved_images = [image_paths[int(idx)] for idx in indices[0]]

    return query, retrieved_images

def retrieve_res50_similar_images(query, index, image_paths, top_k=3):
    # query preprocess:
    if query.endswith(('.png', '.jpg', '.jpeg', '.tiff', '.bmp', '.gif')):
        image = Image.open(query)
        # Load the pretrained model
        res50_model = models.resnet50(pretrained=True)
        res50_model = res50_model.eval()

        # Define the image transformations
        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]),
        ])

        # Apply the transformations and get the image vector
        image = transform(image).unsqueeze(0)
        query_features = res50_model(image).detach().numpy()
        query_features = query_features[0]
        query_features = query_features.astype(np.float32).reshape(1, -1)

        distances, indices = index.search(query_features, top_k)

        retrieved_images = [image_paths[int(idx)] for idx in indices[0]]

        return query, retrieved_images

# 检查文件扩展名是否允许
def allowed_file(filename):
    return '.' in filename and "." + filename.rsplit('.', 1)[1].lower() in IMAGE_EXTENSIONS

def search():
    query = request.args.get('query')  # 获取搜索关键词
    safe_query = escape(query)

    if not query:
        return jsonify({"error": "No search query provided"}), 400
    index, image_paths = None, []
    OUTPUT_INDEX_PATH = f"{app.config['UPLOAD_FOLDER']}/vector.index"
    if os.path.exists(OUTPUT_INDEX_PATH):
        index, image_paths = load_faiss_index(OUTPUT_INDEX_PATH)
    else:
        # embeddings, image_paths = generate_clip_embeddings(IMAGES_PATH, model)
        embeddings, image_paths = generate_res50_embeddings(IMAGES_PATH)
        index = create_faiss_index(embeddings, image_paths, OUTPUT_INDEX_PATH)
    query, retrieved_images = retrieve_similar_images(query, model, index, image_paths, top_k=5)


    image_urls = []
    for path in retrieved_images:
        base_name = os.path.basename(path)
        shutil.copy(path, os.path.join(app.config['UPLOAD_FOLDER'], base_name))
        image_urls.append(url_for('uploaded_file_path', filename=base_name))

    return jsonify({"image_urls": image_urls})


def search_by_images():
    # 检查请求中是否有文件
    if 'file' not in request.files:
        return jsonify({"error": "No file part"}), 400
    file = request.files['file']

    # 检查文件是否为空
    if file.filename == '':
        return jsonify({"error": "No selected file"}), 400
    print(file.filename)
    if file and allowed_file(file.filename):
        filename = secure_filename(file.filename)
        filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
        file.save(filepath)

        index, image_paths = None, []
        OUTPUT_INDEX_PATH = f"{app.config['UPLOAD_FOLDER']}/images_vector.index"
        if os.path.exists(OUTPUT_INDEX_PATH):
            index, image_paths = load_faiss_index(OUTPUT_INDEX_PATH)
        else:
            embeddings, image_paths = generate_res50_embeddings(IMAGES_PATH)
            index = create_faiss_index(embeddings, image_paths, OUTPUT_INDEX_PATH)
        filepath, retrieved_images = retrieve_res50_similar_images(filepath, index, image_paths, top_k=5)

        image_urls = []
        for path in retrieved_images:
            base_name = os.path.basename(path)
            shutil.copy(path, os.path.join(app.config['UPLOAD_FOLDER'], base_name))
            image_urls.append(url_for('uploaded_file_path', filename=base_name))

        return jsonify({"image_urls": image_urls})
    else:
        return jsonify({"error": "Invalid file"}), 400


def index():
    return render_template('upload.html')

# 提供静态文件的访问路径
def uploaded_file_path(filename):
    return send_from_directory(app.config['UPLOAD_FOLDER'], filename)

if __name__ == "__main__":
    app = Flask(__name__)
    app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
    if not os.path.exists(UPLOAD_FOLDER):
        os.makedirs(UPLOAD_FOLDER)
    # 主页显示上传表单
    app.route('/')(index)
    app.route('/search', methods=['GET'])(search)
    app.route('/uploads/images/<filename>')(uploaded_file_path)
    app.route('/search_by_images', methods=['POST'])(search_by_images)
    app.run(host='0.0.0.0', port=8080, debug=True)

二、实现效果

三、参考文章

1. https://towardsdatascience.com/building-an-image-similarity-search-engine-with-faiss-and-clip-2211126d08fa

2.向量数据库Faiss的搭建与使用 - 很久8899 - 博客园

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/922468.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SFP+光模块介绍

SFP光模块介绍 1 SFP光模块简介(Small Form -Factor Pluggable)2 光模块管脚定义 1 SFP光模块简介(Small Form -Factor Pluggable) 光模块&#xff08;Optical Module&#xff09;由光电子器件、功能电路和光接口等组成&#xff0c;光电子器件包括激光发射器(Laser Transmitte…

Redis——Raft算法

Raft使用较为广泛的强一致性、去中心化、高可用的分布式协议&#xff0c;即使在网络、节点故障等情况下&#xff0c;多个节点依然能达到一致性。 其中redis、etcd等都用到了这种算法 在Redis集群中&#xff0c;采取的主从复制结构&#xff0c;当主节点宕机后&#xff0c;哨兵会…

【网络系统管理】2023年全国职业院校技能大赛:组策略--10套题组合--3

11、阻止Microsoft账户登录 (1)计算机配置\策略\Windows设置\安全设置\本地策略\安全选项 12、允许更改系统时间 (1)计算机配置\策略\Windows设置\安全设置\本地策略\用户权限分配 13、可以登录本机的用户 (1)计算机配置\策略\Windows设置\安全设置\本地策略\用户权限…

Glide源码学习

前言 开始 基本使用非常简单&#xff0c;链式调用把context对象传入&#xff0c;设置要加载的URL&#xff0c;设置要填充的ImageView控件&#xff0c;方法很简洁&#xff0c;每次都是传入的最核心的参数&#xff0c;底层加载的缓存逻辑&#xff0c;加载的引擎&#xff0c;加载…

基于RFSOC实现LFMCW雷达测距测速

雷达原理可以参考以下文章 https://zhuanlan.zhihu.com/p/508764579 一般情况下&#xff0c;雷达发射信号的模型可采用线性调频连续波&#xff08;LFMCW&#xff09; &#xff0c;发射波形的信号形式为调频连续锯齿波。线性调频的含义即调制信号频率随时间线性变化&#xff0c…

VELO SkyOW+坐垫,一起Cityride温暖你的上海之旅

随着冬季的到来&#xff0c;上海的街头巷尾弥漫着一种独特的浪漫气息&#xff0c;当金黄的落叶从空中飘落&#xff0c;铺满路边&#xff0c;只是路过就仿佛骑进了一幅世界名画。无论是沿着外滩漫游&#xff0c;还是穿行在浦东的高楼间&#xff0c;骑行的方式总能让你充分体验到…

基于lora的llama2二次预训练

基于lora的llama2二次预训练 一、为什么需要对llama2做基于lora的二次预训练? 加入中文训练语料进行llama2的二次预训练&#xff0c;这样模型就可以增加支持中文输出的能力。 二、基于lora的llama2二次预训练的目标是什么&#xff1f; 在保持预训练模型权重不变的情况下&a…

探索 Python 任务自动化的新境界:Invoke 库揭秘

文章目录 探索 Python 任务自动化的新境界&#xff1a;Invoke 库揭秘背景&#xff1a;为何选择 Invoke&#xff1f;什么是 Invoke&#xff1f;如何安装 Invoke&#xff1f;5个简单的库函数使用方法1. 定义任务2. 带参数的任务3. 运行 Shell 命令4. 任务参数化5. 列出任务 场景应…

利用Prompt工程为LLM提升推理能力

利用Prompt工程为LLM提升推理能力 基于策略的推理详解ReAct: 推理与行动思维链&#xff1a;逐步解决问题反思&#xff1a;深入分析和自我审查与代理架构的集成实际应用代码附录 众所周知&#xff0c;一个精心设计的Prompt能够显著增强大型语言模型&#xff08;LLMs&#xff09;…

C#开发合集

用C#轻松搞定m3u8视频下载与合并 嘿&#xff0c;程序员们&#xff01;今天咱们来聊聊如何用C#写个小程序&#xff0c;轻松下载和合并m3u8视频文件。没错&#xff0c;就是那种分段的流媒体视频。准备好了吗&#xff1f;让我们开始吧&#xff01; 准备工作 在动手之前&#xf…

java框架Netty网络编程——问鼎篇

Netty进阶 粘包现象 案例 服务端代码 public static void main(String[] args) {NioEventLoopGroup bossGroupnew NioEventLoopGroup(1);NioEventLoopGroup workerGroupnew NioEventLoopGroup(2);try {ServerBootstrap serverBootstrap new ServerBootstrap();serverBootstr…

堤防安全监测系统方案

一、背景情况 堤防是开发利用水资源和防治水灾害的重要工程措施之一&#xff0c;对防洪、供水、生态、发电、航运等至关重要。我国现有堤防9.8万多座&#xff0c;其中大中型堤防4700多座、小型堤防9.4万座&#xff0c;80%以上修建于上世纪50至70年代。由于堤防管护力量薄弱&am…

模型减肥秘籍:模型压缩技术 知识蒸馏

教程链接&#xff1a;模型减肥秘籍&#xff1a;模型压缩技术-课程详情 | Datawhale 知识蒸馏&#xff1a;让AI模型更轻更快 在人工智能快速发展的今天&#xff0c;我们经常需要在资源受限的设备&#xff08;如手机、IoT设备&#xff09;上运行AI模型。但这些设备的计算能力和…

golang实现TCP服务器与客户端的断线自动重连功能

1.服务端 2.客户端 生成服务端口程序: 生成客户端程序: 测试断线重连: 初始连接成功

React表单联动

Ant Design 1、dependencies Form.Item 可以通过 dependencies 属性&#xff0c;设置关联字段。当关联字段的值发生变化时&#xff0c;会触发校验与更新。 一种常见的场景&#xff1a;注册用户表单的“密码”与“确认密码”字段。“确认密码”校验依赖于“密码”字段&#x…

springboot实战(16)(Validation参数校验冲突问题、分组校验、默认分组)

目录 一、注解NotNull与NotEmpty区别。 二、Validation提供的分组校验。&#xff08;参数校验冲突问题&#xff09; &#xff08;1&#xff09;基本介绍。 &#xff08;2&#xff09;实际案例。 &#xff08;3&#xff09;大模型提问提供的方法。 1、定义分组接口。 2、在字段上…

学Linux的第九天--磁盘管理

目录 一、磁盘简介 &#xff08;一&#xff09;、认知磁盘 &#xff08;1&#xff09;结构 &#xff08;2&#xff09;物理设备的命名规则 &#xff08;二&#xff09;、磁盘分区方式 MBR分区 MBR分区类型 扩展 GPT格式 lsblk命令 使用fdisk管理分区 使用gdisk管理分…

【ubuntu+win】Win10+Ubuntu22.04双系统给ubuntu系统中的某个分区进行扩容(从400G->800G)数据无损坏

给ubuntu已分区的部分进行扩容 1. 准备扩容的空间2.进入ubuntu系统进行卸载分区3.安装图形界面的安装包4.进行对分区扩容5. 重新挂载 我的情况是这式的&#xff08;可以不看&#xff0c;直接看后面的&#xff09;&#xff1a; 刚开始买下电脑的时候&#xff0c;只装了一个 1T 的…

流式上传与分片上传的原理与实现

&#x1f680; 博主介绍&#xff1a;大家好&#xff0c;我是无休居士&#xff01;一枚任职于一线Top3互联网大厂的Java开发工程师&#xff01; &#x1f680; &#x1f31f; 在这里&#xff0c;你将找到通往Java技术大门的钥匙。作为一个爱敲代码技术人&#xff0c;我不仅热衷…

Ettus USRP X410

总线连接器: 以太网 RF频率范围: 1 MHz 至 7.2 GHz GPSDO: 是 输出通道数量: 4 RF收发仪瞬时带宽: 400 MHz 输入通道数量: 4 FPGA: Zynq US RFSoC (ZU28DR) 1 MHz to 7.2 GHz&#xff0c;400 MHz带宽&#xff0c;GPS驯服OCXO&#xff0c;USRP软件无线电设备 Ettus USRP X410集…