基于 Easysearch kNN 搭建即时图片搜索服务
环境准备
启动 Easysearch 服务:
# Make sure your vm.max_map_count meets the requirement
sudo sysctl -w vm.max_map_count=262144
docker run -it --rm -p 9200:9200 \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms512m -Xmx512m" \
infinilabs/easysearch:1.3.0-24 \
-E "security.enabled=false"
安装 Python 依赖项:
pip install -r requirements.txt
启动服务器:
ES_SERVER=http://localhost:9200 python main.py
后端功能实现
我们实现的主要功能是接收用户上传的图片,然后将图片进行处理和向量化,利用 Easysearch 进行图像搜索,并将搜索结果渲染到模板页面上展示给用户。用户可以通过访问 /req
路由来上传图片,然后提交表单进行搜索。搜索结果将在模板页面 search.html 中展示。
本次服务,我们主要使用的是 Flask 和 Easysearch 这两个工具是 Flask 是一个流行的 Python Web 框架,它提供了简单而灵活的方式来构建 Web 应用程序。而 Easysearch 是一个分布式的搜索引擎,它具有强大的全文搜索和分析功能,并支持使用 kNN 检索 API 来进行向量查询。结合这两个工具,我们可以构建一个功能完善的图像搜索服务。
首先,我们使用 Flask 框架创建一个应用程序实例,并定义几个路由和视图函数。其中,/req
路由用于展示一个包含表单的页面,用户可以在该页面上上传图像。/search
路由用于处理图像搜索请求,并返回搜索结果。
在 /search
路由中,我们首先连接到 Easysearch 搜索引擎,然后获取用户上传的图像文件。接下来,我们对图像进行处理和向量化,以便能够在向量空间中进行相似度匹配。然后,我们构建一个查询体,使用 Easysearch 的查询语法来描述我们的搜索需求。通过调用 Easysearch 的 search 方法,我们可以执行搜索操作,并获取搜索结果。
在获取到搜索结果后,我们从中提取需要的字段,并将结果传递给模板进行渲染。我们使用 Flask 提供的 render_template 函数,将搜索结果传递给一个名为 search.html 的 HTML 模板。该模板可以根据搜索结果动态生成页面内容,并将结果展示给用户。
通过这个简单而高效的图像搜索服务,用户可以方便地上传图像,系统将快速地在图像库中进行搜索,并返回与上传图像相似的结果。
from PIL import Image
from elasticsearch import Elasticsearch
from flask import Flask, request, jsonify, render_template
from img2vec_pytorch import Img2Vec
import io
import os
DEFAULT_INDEX = "img-test"
app = Flask(__name__)
es = Elasticsearch(os.environ.get("ES_SERVER") or "http://localhost:9200")
def rgba2rgb(image_file):
# Open the image file
img = Image.open(image_file)
# Check if the image is in RGBA mode
if img.mode == "RGBA":
# Convert the image to RGB mode
img = img.convert("RGB")
# Create a BytesIO object and save the image to it
image_io = io.BytesIO()
img.save(image_io, format="JPEG")
# Seek to the beginning of the BytesIO object
image_io.seek(0)
return image_io
return image_file
def vectorize(input):
img2vec = Img2Vec()
try:
img = Image.open(input)
vec = img2vec.get_vec(img, tensor=True)
vec_np = vec.cpu().numpy().flatten().tolist()
return vec_np
except Exception as e:
print(f"Error processing image: {e}")
return None
def init_indicies(index: str):
if es.indices.exists(index):
return
# 初始化 kNN 索引
print(f"Initializing {index}")
es.indices.create(
index,
body={
"settings": {"index.knn": True},
"mappings": {
"properties": {
"my_vec": {
"type": "knn_dense_float_vector",
"knn": {
"dims": 512,
"model": "lsh",
"similarity": "cosine",
"L": 99,
"k": 1,
},
}
}
},
},
)
img_dir = "static/img"
for title in os.listdir(img_dir):
print(f"Indexing {title}")
my_vec = vectorize(os.path.join(img_dir, title))
body = {"title": title, "my_vec": my_vec}
es.index(index=index, body=body)
@app.route("/search", methods=["POST"])
def search_service():
# 获取表单数据
index_name = request.form.get("index_name") or DEFAULT_INDEX # 索引名
# 获取上传的图片文件
image_file = request.files.get("image")
if not index_name or not image_file:
return jsonify({"error": "Both index_name and image are required."}), 400
# 处理图片
image0 = rgba2rgb(image_file)
vector_arr = vectorize(image0)
if vector_arr is None:
return jsonify({"error": "Error processing image."}), 400
query_body = {
"size": 50,
"_source": "title",
"query": {
"bool": {
"must": [
{
"knn_nearest_neighbors": {
"field": "my_vec",
"vec": {"values": vector_arr},
"model": "lsh",
"similarity": "cosine",
"candidates": 50,
}
}
]
}
},
}
if not index_name or not vector_arr:
return jsonify({"error": "Both index_name and query are required."}), 400
# 执行搜索
response = es.search(index=index_name, body=query_body)
# 使用模板显示搜索结果
results = response["hits"]["hits"]
print([r["_source"]["title"] for r in results], len(results))
return render_template("search.html", results=results)
@app.route("/", methods=["GET"])
def home():
return render_template("home.html")
if __name__ == "__main__":
init_indicies(DEFAULT_INDEX)
app.run(port=5000, debug=True)
前端页面实现
目前需要实现的是一个即时搜索页面的前端部分。思路非常明确,实现一个简洁漂亮的页面展示功能即可。一些基础的内容就简单略过,我们下面重点描述思路以及实现。
首先,我们创建一个 HTML 文档,并指定文档类型为 HTML5。在文档头部,我们设置了页面的标题为 "Easysearch Search Service",以便清晰地表达页面的用途。
接下来,我们使用 CSS 样式定义了页面的外观和布局。在 <style>
样式中我们设置了背景图片、字体、边距和阴影效果等,以提升页面的美观性和用户体验。
<style>
#searchForm {
background-image: url("/static/background/bluewhite.jpg");
background-repeat: no-repeat;
background-size: cover;
}
body {
font-family: Arial, sans-serif;
background-color: #f9f8f8;
margin: 0;
padding: 20px;
}
.searchImage {
max-width: 600px;
max-height: 500px;
}
.container {
max-width: 100%;
margin: 0 auto;
/* background: linear-gradient(to right, #8aa0ee, #3838ee); */
background: #fff;
padding: 20px;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
h1 {
text-align: center;
margin-bottom: 20px;
color: #000;
}
h2 {
margin-bottom: 10px;
}
form {
max-width: 400px;
margin: 0 auto;
background-color: #fff;
padding: 20px;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
label {
display: block;
font-weight: bold;
margin-bottom: 5px;
}
input[type="text"],
input[type="file"] {
width: 100%;
padding: 8px;
margin-bottom: 10px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
input[type="submit"] {
width: 100%;
padding: 10px;
background-color: #4caf50;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s ease;
}
input[type="submit"]:hover {
background-color: #45a049;
}
pre {
background-color: #f5f5f5;
padding: 10px;
border-radius: 5px;
white-space: pre-wrap;
}
#result {
text-align: center;
margin-top: 20px;
}
</style>
<style>
body {
font-family: Arial, sans-serif;
/* background-color: #f2f2f2; */
margin: 0;
padding: 20px;
}
.container {
max-width: 100%;
margin: 0 auto;
background-image: url("/static/background/daziran.png");
}
.waterfall-container {
display: flex;
flex-wrap: wrap;
}
.waterfall-item {
display: inline-block;
border-radius: 5px;
box-shadow: none;
margin-bottom: 20px;
text-align: center;
width: 25%; /* 每行显示 4 个搜索结果,可以根据需要进行调整 */
padding: 10px;
box-sizing: border-box;
}
.waterfall-item img {
max-width: 100%;
max-height: 250px;
border-radius: 5px;
}
.waterfall-item p {
margin-top: 10px;
font-size: 25px;
font-weight: bold;
color: black;
background-color: white;
}
</style>
JavaScript 部分 ,我们引入了 jQuery 库和自定义的 JavaScript 文件。这些脚本将用于处理页面的交互逻辑。通过 jQuery 库,我们可以方便地处理表单的提交事件,实现异步请求和数据处理。当用户提交表单时,将发送 AJAX 请求到指定的 URL,并将索引名称和图像文件作为请求的参数。在成功返回 AJAX 响应后,我们通过解析返回的 HTML 数据,提取出图片和段落元素,并按照一定的格式进行组合。最后,将组合后的结果添加到结果容器中,以展示搜索结果。
前端 JavaScript 代码
<script src="/static/jquery-3.5.1.min.js"></script>
<script src="/static/m.js"></script>
<script>
$(document).ready(function () {
$("#searchForm").on("submit", function (event) {
event.preventDefault();
var indexName = $("#indexName").val();
// 检查索引名是否为空或未定义
if (!indexName) {
indexName = "img-test"; // 设置默认值为 "默认索引名"
}
var formData = new FormData();
formData.append("index_name", indexName);
formData.append("image", $("#image")[0].files[0]);
$.ajax({
url: "/search",
method: "POST",
processData: false, // Important!
contentType: false, // Important!
data: formData,
success: function (data) {
// Clear the previous results
$("#result").empty();
// Parse the returned HTML and extract image and paragraph elements
var parsedData = $.parseHTML(data);
// Group image and paragraph elements
var imageAndParagraphPairs = [];
var currentPair = [];
$(parsedData).each(function () {
if ($(this).is("img.searchImage")) {
if (currentPair.length === 1) {
currentPair.push(this);
imageAndParagraphPairs.push(currentPair);
currentPair = [];
} else {
currentPair.push(this);
}
} else if ($(this).is("p")) {
if (currentPair.length === 0) {
currentPair.push(this);
} else {
currentPair.push(this);
imageAndParagraphPairs.push(currentPair);
currentPair = [];
}
} else if ($(this).is("h1")) {
// Add the <h1> element back to the results
$("#resultTitle").html($(this));
}
});
// Create and append the waterfall items
$.each(imageAndParagraphPairs, function (index, pair) {
var $item = $("<div>").addClass("waterfall-item");
$.each(pair, function (i, element) {
$item.append(element);
});
$("#result").append($item);
});
},
});
});
});
</script>
页面主体部分 ,我们将内容包裹在一个名为 "container" 的 <div>
元素中。页面包含一个标题和一个搜索表单。搜索表单包括一个文件选择框用于选择图像文件。还有一个提交按钮,当用户点击该按钮时,将触发 JavaScript 代码中的事件处理程序。
搜索结果部分 ,我们使用一个 <div>
元素来显示搜索结果的标题,并使用另一个 <div>
元素作为瀑布流容器,用于展示搜索结果的图片和相关的段落。
代码如下
<body>
<div class="container">
<h1>Easycsearch Search Service</h1>
<form id="searchForm" enctype="multipart/form-data">
<label for="image">Image:</label><br />
<input type="file" id="image" name="image" /><br />
<input type="submit" value="Search" />
</form>
<div id="resultTitle"></div>
<div id="result" class="waterfall-container"></div>
</div>
</body>
最终结果如图所示
搜索前
搜索后
总结
通过这个简单的基于 Easysearch kNN 搜索服务网页 ,我们可以方便地上传图像文件,进行搜索操作,并以瀑布流的形式展示搜索结果。
项目 Github 地址:https://github.com/infinilabs/image-search-demo
关于 Easysearch
INFINI Easysearch 是一个分布式的近实时搜索与分析引擎,核心引擎基于开源的 Apache Lucene。Easysearch 的目标是提供一个轻量级的 Elasticsearch 可替代版本,并继续完善和支持更多的企业级功能。与 Elasticsearch 相比,Easysearch 更关注在搜索业务场景的优化和继续保持其产品的简洁与易用性。
官网文档:https://www.infinilabs.com/docs/latest/easysearch
下载地址:https://www.infinilabs.com/download
基于 Easysearch kNN 搭建即时图片搜索服务的更多相关文章
- 腾讯云:基于 Ubuntu 搭建 VNC 远程桌面服务
基于 Ubuntu 搭建 VNC 远程桌面服务 前言 任务时间:5min ~ 10min 必要知识 本教程假设您已学习以下 Ubuntu 基本操作: 连接 SSH 执行命令 编辑文件 如果还没有掌握 ...
- 从零搭建 ES 搜索服务(二)基础搜索
一.前言 上篇介绍了 ES 的基本概念及环境搭建,本篇将结合实际需求介绍整个实现过程及核心代码. 二.安装 ES ik 分析器插件 2.1 ik 分析器简介 GitHub 地址:https://git ...
- 从零搭建ES搜索服务(一)基本概念及环境搭建
一.前言 本系列文章最终目标是为了快速搭建一个简易可用的搜索服务.方案并不一定是最优,但实现难度较低. 二.背景 近期公司在重构老系统,需求是要求知识库支持全文检索. 我们知道普通的数据库 like ...
- Maven 仓库搜索服务和私服搭建
Maven 仓库搜索服务 使用maven进行日常开发的时候,一个常见问题就是如何寻找需要的依赖,我们可能只知道需要使用类库的项目名称,但是添加maven依赖要求提供确切的maven坐标,这时就可以使用 ...
- 【PHP】基于ThinkPHP框架搭建OAuth2.0服务
[PHP]基于ThinkPHP框架搭建OAuth2.0服务 http://leyteris.iteye.com/blog/1483403
- .net平台 基于 XMPP协议的即时消息服务端简单实现
.net平台 基于 XMPP协议的即时消息服务端简单实现 昨天抽空学习了一下XMPP,在网上找了好久,中文的资料太少了所以做这个简单的例子,今天才完成.公司也正在准备开发基于XMPP协议的即时通讯工具 ...
- 基于图片识别服务的IOS图片识别程序
由于TensorFlow提供的IOS版Demo相对于Android版识别率不高,所以开发了通过识别服务进行图片识别的IOS版程序. 该程序基于图片识别服务(http://www.cnblogs.com ...
- 基于小米即时消息云服务(MIMC)的Web IM
michat 一个基于小米即时消息云服务(MIMC)的Web IM. 源码地址github和gitee同步. 截图展示 如何使用 请先双击目录"需要安装的jars"的install ...
- 亚马逊云推出基于机器学习的企业搜索服务Kendra,剑指微软
近日,在AWS re:Invent全球大会上,亚马逊发布了五项新的基于机器学习的人工智能 (AI) 服务. 这五项服务包括机器学习驱动的企业搜索.代码审核与分析.欺诈检测.医疗转录和 AI 预测的人工 ...
- 搜索服务Solr集群搭建 使用ZooKeeper作为代理层
上篇文章搭建了zookeeper集群 那好,今天就可以搭建solr搜服服务的集群了,这个和redis 集群不同,是需要zk管理的,作为一个代理层 安装四个tomcat,修改其端口号不能冲突.8080~ ...
随机推荐
- Oracle的主键id自增
Oracle的主键id自增 可以直接用序列加触发器的方式实现 首先表里面要有个主键,没有的话用语句或者在编译器中加一下,都可以 然后创建一个序列,一般来说最常用的有这几个参数 CREATE SEQUE ...
- SQL server 游标使用实例
--创建一个游标 DECLARE my_cursor CURSOR FOR SELECT id, Bran_number, Bran_taxis FROM dbo.Base_Branch; --打开游 ...
- 【GUI软件】小红书详情数据批量采集,含笔记内容、转评赞藏等,支持多笔记同时采集!
目录 一.背景介绍 1.1 爬取目标 1.2 演示视频 1.3 软件说明 二.代码讲解 2.1 爬虫采集模块 2.2 软件界面模块 2.3 日志模块 三.获取源码及软件 一.背景介绍 1.1 爬取目标 ...
- HDU 多校 2023 Round #6 题解
HDU 多校 2023 Round #6 题解 \(\text{By DaiRuiChen007}\) A. Count Problem Link 题目大意 求有多少个长度为 \(n\),字符集大小为 ...
- Cesium教程10-把影像和天空改成背景图片
在使用Cesium引擎时,我们经常要使用大屏适配导致地球或者模型的黑色天空盒和大屏的样式不匹配造成场景不好看的情况,这样就可以用到我们修改Cesium的天空为纯色背景,与大屏更适配,直接上代码. &l ...
- 【活动访谈】发力数字基座 推动物联创新—航天科技控股集团AIRIOT4.0平台发布会活动专访
近日,由航天科技控股集团股份有限公司主办的"数字基座 智慧物联-AIRIOT4.0平台发布会"在北京圆满落幕.航天三院科技委总工程师王连宝应邀出席本次会议并接受媒体采访,共同参与访 ...
- Kubernetes Pod调度:从基础到高级实战技巧
本文深入探讨了Kubernetes中的Pod调度机制,包括基础概念.高级调度技术和实际案例分析.文章详细介绍了Pod调度策略.Taints和Tolerations.节点亲和性,以及如何在高流量情况下优 ...
- log4j日志记录级别
目录 一.日志的作用 二.log4j的日志级别和简介 三.log4j配置文件包含的节点简介 四.logger配置说明 一.日志的作用 问题追踪:通过日志不仅仅包括我们程序的一些 bug,也可以在安 ...
- 拼接sql 参数化 where userId in(@userIds)的问题
这里@userIds 如果 写成101,202,301翻译后的sql的where部分会是: where userId in('101,202,301'): 而不是期待的: where userId i ...
- 到今天了你还不会集合的Stream操作吗?你要out了
Java8的两个重大改变,一个是Lambda表达式,另一个就是本节要讲的Stream API表达式.Stream 是Java8中处理集合的关键抽象概念,它可以对集合进行非常复杂的查找.过滤.筛选等操作 ...