Clip结合Faiss+Flask简易版文搜图服务

一、实现

使用目录结构:

templates

        ---upload.html

 faiss_app.py

前端代码:upload.html

<!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="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);
            }
        });
    </script>
</body>
</html>

后端代码 faiss_app.py:

from sentence_transformers import SentenceTransformer, util
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\\xxxx\\Pictures\\"

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 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 allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_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)
        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 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.run(host='0.0.0.0', port=8080, debug=True)

二、效果

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

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

相关文章

Linux驱动开发快速入门——字符设备驱动(直接操作寄存器设备树版)

Linux驱动开发快速入门——字符设备驱动 前言 笔者使用开发板型号&#xff1a;正点原子的IMX6ULL-alpha开发板。ubuntu版本为&#xff1a;20.04。写此文也是以备忘为目的。 字符设备驱动 本小结将以直接操作寄存器的方式控制一个LED灯&#xff0c;可以通过read系统调用可以…

概念解读|K8s/容器云/裸金属/云原生...这些都有什么区别?

随着容器技术的日渐成熟&#xff0c;不少企业用户都对应用系统开展了容器化改造。而在容器基础架构层面&#xff0c;很多运维人员都更熟悉虚拟化环境&#xff0c;对“容器圈”的各种概念容易混淆&#xff1a;容器就是 Kubernetes 吗&#xff1f;容器云又是什么&#xff1f;容器…

《机器人控制器设计与编程》考试试卷**********大学2024~2025学年第(1)学期

消除误解&#xff0c;课程资料逐步公开。 复习资料&#xff1a; Arduino-ESP32机器人控制器设计练习题汇总_arduino编程语言 题-CSDN博客 试卷样卷&#xff1a; 开卷考试&#xff0c;时间&#xff1a; 2024年11月16日 001 002 003 004 005 ……………………装………………………

本地音乐服务器(三)

6. 删除音乐模块设计 6.1 删除单个音乐 1. 请求响应设计 2. 开始实现 首先在musicmapper新增操作 Music findMusicById(int id);int deleteMusicById(int musicId); 其次新增相对应的.xml代码&#xff1a; <select id"findMusicById" resultType"com.exa…

如何在项目中用elementui实现分页器功能

1.在结构部分复制官网代码&#xff1a; <template> 标签: 这是 Vue 模板的根标签&#xff0c;包含所有的 HTML 元素和 Vue 组件。 <div> 标签: 这是一个普通的 HTML 元素&#xff0c;包裹了 el-pagination 组件。它没有特别的意义&#xff0c;只是为了确保 el-pagi…

VB.Net笔记-更新ing

1.1 设置默认VS的开发环境为VB.NET&#xff08;2024/11/18&#xff09; 1.2 新建一个“Hello&#xff0c;world”的窗体&#xff08;2024/11/18&#xff09; 1.3 计算圆面积的小程序&#xff08;2024/11/18&#xff09; 显示/隐式 声明 &#xff08;2024/11/18&#xff0…

每日一练:【优先算法】双指针之移动零(easy)

双指针概念介绍 常见的双指针有两种形式&#xff0c;一种是对撞指针&#xff0c;一种是左右指针。 对撞指针&#xff1a;一般用于顺序结构中&#xff0c;也称左右指针。 • 对撞指针从两端向中间移动。一个指针从最左端开始&#xff0c;另一个从最右端开始&#xff0c;然后逐渐…

树状数组 Color the ball hdu 1556 线段树 洛谷p3372

目录 前言 树状数组 lowbit函数 直观表述 代码 运行结果 树状数组构建代码 树状数组的应用 单点修改和&#xff08;单点&#xff09;区间查询 结合差分数组区间修改 ,单点查询 差分数组 Color the ball hdu 1556 问题描述 问题分析 代码 线段树 洛谷p3372 问题描述 问题…

学习笔记022——Ubuntu 安装 MySQL8.0版本踩坑记录

目录 1、查看可安装 MySQL 版本 2、Ubuntu安装 MySQL8.0 3、MySQL8.0 区分大小写问题 4、MySQL8.0 设置sql_mode 5、MySQL8.0 改端口33060&#xff08;个人遇到问题&#xff09; 1、查看可安装 MySQL 版本 ## 列出可用的MySQL版本&#xff08;列出所有可用的MySQL版本以…

【数据结构】树——链式存储二叉树的基础

写在前面 书接上文&#xff1a;【数据结构】树——顺序存储二叉树 本篇笔记主要讲解链式存储二叉树的主要思想、如何访问每个结点、结点之间的关联、如何递归查找每个结点&#xff0c;为后续更高级的树形结构打下基础。不了解树的小伙伴可以查看上文 文章目录 写在前面 一、链…

qt之QFTP对文件夹(含嵌套文件夹和文件)、文件删除下载功能

一、前言 主要功能如下&#xff1a; 1.实现文件夹的下载和删除&#xff0c;网上很多资料都是单独对某个路径的文件操作的&#xff0c;并不能对文件夹操作 2.实现目标机中含中文名称自动转码&#xff0c;有些系统编码方式不同&#xff0c;下载出来的文件会乱码 3.实现ftp功能…

集群聊天服务器(7)数据模块

目录 Mysql数据库代码封装头文件与源文件 Mysql数据库代码封装 业务层代码不要直接写数据库&#xff0c;因为业务层和数据层的代码逻辑也想完全区分开。万一不想存储mysql&#xff0c;想存redis的话&#xff0c;就要改动大量业务代码。解耦合就是改起来很方便。 首先需要安装m…

手机远程控制电脑,让办公更快捷

在数字化办公的浪潮下&#xff0c;远程控制软件已成为连接工作与生活的桥梁。它使得用户能够通过一台设备&#xff08;主控端&#xff09;来操作另一台设备&#xff08;被控端&#xff09;&#xff0c;无论它们是否位于同一局域网内。这种软件广泛应用于远程办公、手机远程控制…

面向FWA市场!移远通信高性能5G-A模组RG650V-NA通过北美两大重要运营商认证

近日&#xff0c;全球领先的物联网整体解决方案供应商移远通信宣布&#xff0c;其旗下符合3GPP R17标准的新一代5G-A模组RG650V-NA成功通过了北美两家重要运营商认证。凭借高速度、大容量、低延迟、高可靠等优势&#xff0c;该模组可满足CPE、家庭/企业网关、移动热点、高清视频…

72项!湖北省2024年度第二批省级科技计划项目拟立项项目公示!

本期精选 SCI&EI ●IEEE 1区TOP 计算机类&#xff08;含CCF&#xff09;&#xff1b; ●EI快刊&#xff1a;最快1周录用&#xff01; 知网(CNKI)、谷歌学术期刊 ●7天录用-检索&#xff08;100%录用&#xff09;&#xff0c;1周上线&#xff1b; 免费稿件评估 免费匹配…

LeetCode 热题 100 回顾

目录 一、哈希部分 1.两数之和 &#xff08;简单&#xff09; 2.字母异位词分组 &#xff08;中等&#xff09; 3.最长连续序列 &#xff08;中等&#xff09; 二、双指针部分 4.移动零 &#xff08;简单&#xff09; 5.盛最多水的容器 &#xff08;中等&#xff09; 6…

Chrome 浏览器 131 版本开发者工具(DevTools)更新内容

Chrome 浏览器 131 版本开发者工具&#xff08;DevTools&#xff09;更新内容 一、使用 Gemini 调试 CSS Chrome DevTools 现在推出了一个新的实验性 AI 辅助面板&#xff0c;可以与 Gemini 聊天并获得帮助来调试 CSS。 在 Elements 面板中&#xff0c;右键点击一个元素并选…

网络编程-002-UDP通信

1.UDP通信的简单介绍 1.1不需要通信握手,无需维持连接,网络带宽需求较小,而实时性要求高 1.2 包大小有限制,不发大于路径MTU的数据包 1.3容易丢包 1.4 可以实现一对多,多对多 2.客户端与服务端=发送端与接收端 代码框架 收数据方一般都是客户端/接收端 3.头文件 #i…

websocket身份验证

websocket身份验证 前言 上一集我们就完成了websocket初始化的任务&#xff0c;那么我们完成这个内容之后就应该完成一个任务&#xff0c;当客户端与服务端连接成功之后&#xff0c;客户端应该主动发起一个身份认证的消息。 身份认证proto 我们看一眼proto文件的内容。 我…

初识C++(1)

C是在C语言的基础之上&#xff0c;容纳进去了面向对象编程思想&#xff0c;并增加了许多有用的库以及编程范式等。 在C语言中&#xff0c;变量、函数和类的名称存在于全局作用域中&#xff0c;因此可能会发生许多冲突。比如&#xff1a; #include<stdio.h> #include<…