Torch、Java、Milvus快速搭建以图搜图系统

1 原理概述

以图搜图大致原理(口水话版)

以图搜图,即通过一张图片去匹配数据库中的图片,找到最相似的N张图。在我们普通的搜索系统中,文字匹配的搜索单纯的MySQL数据库就能实现简单的搜索,但是图片就存在很多难点。

1、首先要解决的是图片怎么表达的问题,肯定不会是每个像素点去匹配,而是对图像提取特征。在传统的数字图像处理中,图像的特征有很多:颜色特征、纹理特征、关键点特征、几何特征,可以将具有代表性的特征提取处理归一化后形成一个多维向量去表示图片。在深度学习如火如荼的时代,卷积神经网络能更好的做到特征提取这个工作。

2、特征提取到了,自然而然的就是将每个图片的特征(即一个向量)存入数据库,要搜索一张图片时就去数据库匹配。第二个问题就是如何去匹配图片,两个向量相等?当然不是。我们用距离来表达两个向量的相似程度,距离越近就越相似。距离用得最多的就是欧式距离和余弦距离(简单来说区别就是欧氏距离体现数值上的差异、余弦距离体现方向上的相对差异)。

3、怎么判断两个图片是否相似解决了,通过距离!第三个问题:来一张图时去数据库查询怎么查?一个一个匹配,最后排个序?当然不是!MySQL可以建索引,这个好像建索引也无从下手。这里就需要借助向量搜索引擎了。目前开源的向量搜索引擎还是有很多的,这里采用Milvus这个开源项目实现向量搜索引擎,详细了解的去自行百度。

2、ResNet提取深度特征向量

环境:Pytorch1.1 python3.6 cuda9.0 采用pretrainedmodels库快速搭建ResNet(pip安装即可)

几行代码搭建出一个特征提取网络

from torch.autograd import Variable
import torch
import torch.nn as nn
import torchvision.transforms as transforms
import pretrainedmodels
from PIL import ImageTARGET_IMG_SIZE = 224
img_to_tensor = transforms.ToTensor()
def get_seresnet50():encoder = pretrainedmodels.se_resnet50()model = nn.Sequential(encoder.layer0,encoder.layer1,encoder.layer2,encoder.layer3,encoder.layer4,encoder.avg_pool   # 平均池化,张成一个[batchSize,2048]的特征向量)for param in model.parameters():param.requires_grad = Falsemodel.cuda()   # 使用GPU,CPU版去掉model.eval()return model# 特征提取
def extract_feature(model, imgpath):img = Image.open(imgpath)  # 读取图片img = img.resize((TARGET_IMG_SIZE, TARGET_IMG_SIZE))tensor = img_to_tensor(img)  # 将图片矩阵转化成tensortensor = tensor.cuda()  # GPUtensor = torch.unsqueeze(tensor, 0)result = model(Variable(tensor))result_npy = result.data.cpu().numpy()[0].ravel().tolist()return result_npy

利用serverSocket搭建服务器端,Java端通信,调用python的特征提取。

import socket
import threading
import json
from model import extract_feature, get_seresnet50def main():# 创建服务器套接字serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 获取本地主机名称host = socket.gethostname()# 设置一个端口port = 12345# 将套接字与本地主机和端口绑定serversocket.bind((host, port))# 设置监听最大连接数serversocket.listen(10)# 模型创建model = get_seresnet50()print("等待连接")while True:# 获取一个客户端连接clientsocket, addr = serversocket.accept()print("连接地址:%s" % str(addr))try:t = ServerThreading(model, clientsocket)  # 为每一个请求开启一个处理线程t.start()except Exception as identifier:print(identifier)passserversocket.close()passclass ServerThreading(threading.Thread):def __init__(self, model, clientsocket, recvsize=1024 * 1024, encoding="utf-8"):threading.Thread.__init__(self)self.model = modelself._socket = clientsocketself._recvsize = recvsizeself._encoding = encodingpassdef run(self):print("开启线程.....")try:# 接受数据msg = ''while True:# 读取recvsize个字节rec = self._socket.recv(self._recvsize)# 解码msg += rec.decode(self._encoding)# 文本接受是否完毕,因为python socket不能自己判断接收数据是否完毕,# 所以需要自定义协议标志数据接受完毕if msg.strip().endswith('over'):msg = msg[:-4]break# 解析json格式的数据# 调用神经网络模型处理请求res = extract_feature(self.model, msg)sendmsg = json.dumps(res)print(sendmsg)# 发送数据self._socket.send(("%s" % sendmsg).encode(self._encoding))except Exception as identifier:self._socket.send("500".encode(self._encoding))print(identifier)passfinally:self._socket.close()print("任务结束.....")if __name__ == "__main__":main()

3、Java端调用Python函数提取特征

package top.maolaoe.imgsearch.service;import org.springframework.stereotype.Service;import java.io.*;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;/*** 通过socket调用python获得图片的特征向量*/
@Service
public class FeatureService {private String HOST = "192.168.1.103";private final int PORT = 12345;public List<Float> remoteCall(String path){// 访问服务进程的套接字
//        System.out.println("调用远程接口:host=>"+HOST+",port=>"+PORT);try(Socket socket = new Socket(HOST, PORT)) {// 初始化套接字,设置访问服务的主机和进程端口号,HOST是访问python进程的主机名称,可以是IP地址或者域名,PORT是python进程绑定的端口号// 获取输出流对象OutputStream os = socket.getOutputStream();PrintStream out = new PrintStream(os);// 发送内容out.print(path);// 告诉服务进程,内容发送完毕,可以开始处理out.print("over");// 获取服务进程的输入流InputStream is = socket.getInputStream();BufferedReader br = new BufferedReader(new InputStreamReader(is,"utf-8"));String tmp = null;StringBuilder sb = new StringBuilder();// 读取内容while((tmp=br.readLine())!=null)sb.append(tmp).append('\n');// 解析结果tmp = sb.toString().substring(1, sb.length()-2);String[] split = tmp.split(",");List<Float> list = new ArrayList<Float>(split.length);for (int i = 0; i < split.length; i++) {list.add(Float.valueOf(split[i]));split[i] = null;}
//            System.out.println(list);
//            System.out.println(list.size());return list;} catch (IOException e) {e.printStackTrace();}return null;}public static void main(String[] args) throws IOException {FeatureService featureService = new FeatureService();featureService.remoteCall("E:\\data\\tx.jpg");}}

4、安装启动milvus向量搜索引擎

官方教程

注意修改配置文件中的内存大小以适应自己的机器,否则docker启动时报错。

5、编写milvus插入和搜索向量的方法

引入依赖:milvus中的guava容易与其他包冲突,单独引入

<dependency><groupId>io.milvus</groupId><artifactId>milvus-sdk-java</artifactId><exclusions><exclusion><groupId>com.google.guava</groupId><artifactId>guava</artifactId></exclusion></exclusions><version>0.8.2</version>
</dependency>

提供search和insert功能

package top.maolaoe.imgsearch.service;import com.google.gson.JsonObject;
import io.milvus.client.*;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class MilvusService {private MilvusClient client = new MilvusGrpcClient();private String collectionName;MilvusService(String host, int port, String collectionName, int nlist){this.collectionName = collectionName;//建立连接ConnectParam connectParam = new ConnectParam.Builder().withHost(host).withPort(port).build();try {client.connect(connectParam);//连接正常则创建collectionResponse responseCollect = createCollect(collectionName, 2048, 1024, MetricType.IP);System.out.println(responseCollect.getMessage());//创建索引if(createIndex(nlist)){System.out.println("创建索引");}else {System.out.println("索引创建失败");}} catch (ConnectFailedException e) {System.out.println("连接失败!");}}MilvusService(String host, int port, String collectionName){this(host, port, collectionName, 1000);}MilvusService(){this("localhost", 19530, "imgsearch");}/*** 插入特征向量* @param features* @return*/public List<Long> insertFeatures(List<List<Float>> features){//先判断是否正常连接boolean connected = client.isConnected();if(!connected){System.out.println("连接失败!!");}//插入特征向量InsertParam insertParam = new InsertParam.Builder(collectionName).withFloatVectors(features).build();InsertResponse insertResponse = client.insert(insertParam);client.flush(collectionName);boolean flag = insertResponse.ok();if(!flag){System.out.println("插入失败");return null;}List<Long> vectorIds = insertResponse.getVectorIds();return vectorIds;}/*** 查询相似的特诊向量* @param vectorsToSearch* @param topK* @return*/public SearchResponse searchFeature(List<List<Float>> vectorsToSearch, long topK, int nprobe){//先判断是否正常连接boolean connected = client.isConnected();if(!connected){System.out.println("连接失败!!");}JsonObject indexParamsJson = new JsonObject();indexParamsJson.addProperty("nprobe", nprobe);   //nprobe代表选择最近的多少个聚类去比较。SearchParam searchParam =new SearchParam.Builder(collectionName).withFloatVectors(vectorsToSearch).withParamsInJson(indexParamsJson.toString()).withTopK(topK).build();SearchResponse searchResponse = client.search(searchParam);return searchResponse;}public SearchResponse searchFeature(List<List<Float>> vectorsToSearch, long topK){return searchFeature(vectorsToSearch, topK, 15);}/*** 创建数据库表* @param collectionName 表的名称* @param dimension 向量维度* @param indexFileSize 单个文件的大小值* @param metricType* @return*/private Response createCollect(String collectionName, int dimension, int indexFileSize,MetricType metricType){CollectionMapping collectionMapping = new CollectionMapping.Builder(collectionName, dimension).withIndexFileSize(indexFileSize).withMetricType(metricType).build();Response response = client.createCollection(collectionMapping);return response;}// 创建索引,指定聚类数private boolean createIndex(int nlist){final IndexType indexType = IndexType.IVF_SQ8;JsonObject indexParamsJson = new JsonObject();indexParamsJson.addProperty("nlist", nlist);   //nlist代表聚类数,根据数据量多少设置Index index =new Index.Builder(collectionName, indexType).withParamsInJson(indexParamsJson.toString()).build();Response createIndexResponse = client.createIndex(index);return createIndexResponse.ok();}}

6、其他处理

接下来就是细枝末节上的处理,

我是前端上传图片时保存到本地,然后发送图片路径给python端提取特征返回Java端。

Java端有了特征向量就调用milvus的方法获取最近的topK条特征向量的ID,再根据ID查询数据库获取图片的路径,然后展示到前端。

导入VOC2007的数据集,5000张图片,传入一张猫的图片,搜索结果还是比较满意的。

当然,项目只是简单搭建完成,,精度上还有待优化,参数的调整还有待优化,,项目中还有大量bug没处理,Python端BIO的方式问题太多,batchSize只为1并发度太低,,

有时间再完整的搭建一下项目。

Torch、Java、Milvus快速搭建以图搜图系统相关推荐

  1. 7 招教你轻松搭建以图搜图系统!

    作者 | 小龙 责编 | 胡巍巍 当您听到"以图搜图"时,是否首先想到了百度.Google 等搜索引擎的以图搜图功能呢?事实上,您完全可以搭建一个属于自己的以图搜图系统:自己建立图 ...

  2. python+milvus实现一个以图搜图系统

    目录 引言 说明 准备数据 训练数据 安装minlvus(docker-compose方式) python集成milvus+towhee python后端启动方式 启动前端 查询数据 引言 当您听到& ...

  3. Milvus实战| 以图搜视频系统

    以图搜视频,顾名思义就是拿一张图片去视频底库里面搜索包含相似镜头的视频.以图搜视频中一个关键的步骤就是视频向量化,视频向量化即在视频中抽取关键帧,对每帧视频进行特征提取,将其转化为结构化的向量.至此, ...

  4. 使用Milvus搭建以图搜图服务

    使用Milvus搭建以图搜图服务 介绍 安装Milvus Java调用Milvus插入.查询 引入Maven依赖 创建Milvus客户端 实现Milvus插入向量数据 实现Milvus 查询向量 结尾 ...

  5. 搭建自己的以图搜图系统(二):深入优化搭建生产级别的图搜系统

    概述 本文是"搭建自己的以图搜图系统"系列的第二篇,在第一篇内容中我们了解了如何利用"机器学习框架 Towhee ¹"和"向量数据库 Milvus ² ...

  6. 推荐系统工程篇之搭建以图搜图服务

    基于内容的召回在推荐系统中是比较常见的召回策略,常见有基于用户或物品的标签召回或者基于用户的年龄,地域等召回,一般该策略的实现是基于开源软件 Elasticseach 实现的.虽然召回的结果都比较合理 ...

  7. 基于 Milvus 的以图搜图系统 2.0

    Milvus 以图搜图 1.0 版本自发布以来便受到广大用户的欢迎.近日,Zilliz 推出了 Milvus 以图搜图系统 2.0 版.本文将介绍 Milvus 以图搜图系统 2.0 版的主要更新内容 ...

  8. Java调用Pytorch实现以图搜图(附源码)

    Java调用Pytorch实现以图搜图 设计技术栈: 1.ElasticSearch环境: 2.Python运行环境(如果事先没有pytorch模型时,可以用python脚本创建模型): 1.运行效果 ...

  9. 工程篇之搭建以图搜图服务

    基于内容的召回在推荐系统中是比较常见的召回策略,常见有基于用户或物品的标签召回或者基于用户的年龄,地域等召回,一般该策略的实现是基于开源软件 Elasticseach 实现的.虽然召回的结果都比较合理 ...

最新文章

  1. python requests 示例_python的requests模块实现登陆示例
  2. Three.js学习笔记 – “我和小伙伴都惊呆了”的特效和Three.js初探
  3. 用 Python 控制你的鼠标和键盘,然后爱怎么玩怎么玩
  4. 终极解密输入网址按回车到底发生了什么?
  5. nc和telnet配合使用
  6. 【Scratch案例实操】Scratch小狗散步 scratch编程案例教学 scratch创意编程 少儿编程教案
  7. 前端处理emoji表情的编码解码
  8. 使用Tableau进行基础图表制作
  9. iOS - 添加代码片段(Code Snippets)
  10. DAO层,Service层,Controller层的作用
  11. python pi表示_python 算pi
  12. Pywinauto基础03--控件操作
  13. 从0开始搭建SQL Server AlwaysOn 第三篇(配置AlwaysOn)
  14. 文件上传之伪Ajax方式上传
  15. js+html绘制文字居中,CreateJs系列教程之 EaselJs_2_绘制文字(Text)
  16. java获取word固定位置的值_java 实现保存Word文档中指定位置的数据,又保存整篇文档...
  17. 那些除百度以外你可能不知道的小众搜索引擎
  18. ​​​​​​​Log日志级别从高到低排序和log4j配置
  19. 邀请别人进入队伍rust_rust怎么和好友一起 | 手游网游页游攻略大全
  20. SQLScout——AndroidStudio插件(Sqlite神器)

热门文章

  1. mongodb课程介绍
  2. 数据库(MySql 8.0)详细学习笔记
  3. 【EHub_tx1_tx2_E100】 ROS_ Melodic + Astra S(如何在该环境下打开摄像机获取rgb/深度图/点云)
  4. Java介绍、应用、前景
  5. 如何给自己的论文免费查重
  6. 亚马逊测评需要注意哪些问题
  7. HiAI模型集成应用场景和开发指南
  8. c语言建筑工地管理系统,建筑工地信息管理系统
  9. .Net轻松处理亿级数据--clickhouse及可视化界面安装介绍
  10. paypal tp 对接_tp5实现paypal支付