java elasticsearch 实现以图搜图效果

news2024/12/24 9:18:25

前言:
现在需要用java+elasticsearch的方式实现以图搜图的效果,根据下面的文章内容做了一点修改
相关文章:https://blog.csdn.net/m0_52640724/article/details/129357847

一、相关环境

java:jdk11
elasticsearch:7.17.3
windows:win10
linux:centos7.9

二、引入算法

此算法是使用pytorch计算图片的正弦值,匹配图片正弦值的内容
将下面链接中的算法下载后即可,放入 D:/test/ 文件夹
无需配置相关算法环境
算法下载地址

三、创建表和索引

避免重复生成内容,将算法生成的正弦值存入mysql表中,设置mysql和es数据同步

1、mysq中创建file_vector表

在这里插入图片描述

2、创建es索引

PUT /file_vector
{
  "mappings": {
    "properties": {
      "vectorList": {
        "type": "dense_vector",
        "dims": 1024
      },
      "url" : {
        "type" : "keyword"
      },
      "fileId": {
          "type": "keyword"
      }
    }
  }
}

四、java项目引入依赖

本项目使用的是maven,直接在pom文件中引入依赖即可

注意:由于环境不一致,在本地开发过程中引入的是windows版本依赖,在linux环境中引入的是linux版本依赖,如果linux为centos8以上,似乎windows版本依赖也可行

<!--elasticsearch依赖     开始-->
        <dependency>
            <groupId>co.elastic.clients</groupId>
            <artifactId>elasticsearch-java</artifactId>
            <version>7.17.3</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.12.3</version>
        </dependency>
        <dependency>
            <groupId>jakarta.json</groupId>
            <artifactId>jakarta.json-api</artifactId>
            <version>2.0.1</version>
        </dependency>
        <!--elasticsearch依赖     结束-->
        <!--提取图片正弦值依赖开始   windows环境依赖-->
<!--        <dependency>-->
<!--            <groupId>ai.djl.pytorch</groupId>-->
<!--            <artifactId>pytorch-engine</artifactId>-->
<!--            <version>0.19.0</version>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>ai.djl.pytorch</groupId>-->
<!--            <artifactId>pytorch-native-cpu</artifactId>-->
<!--            <version>1.10.0</version>-->
<!--            <scope>runtime</scope>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>ai.djl.pytorch</groupId>-->
<!--            <artifactId>pytorch-jni</artifactId>-->
<!--            <version>1.10.0-0.19.0</version>-->
<!--        </dependency>-->
        <!--提取图片正弦值依赖结束  windows环境依赖 -->
        <!--提取图片正弦值依赖开始   linux环境依赖-->
        <dependency>
            <groupId>ai.djl.pytorch</groupId>
            <artifactId>pytorch-engine</artifactId>
            <version>0.16.0</version>
        </dependency>
        <dependency>
            <groupId>ai.djl.pytorch</groupId>
            <artifactId>pytorch-native-cpu-precxx11</artifactId>
            <classifier>linux-x86_64</classifier>
            <version>1.9.1</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>ai.djl.pytorch</groupId>
            <artifactId>pytorch-jni</artifactId>
            <version>1.9.1-0.16.0</version>
            <scope>runtime</scope>
        </dependency>
        <!--提取图片正弦值依赖结束 linux环境依赖 -->

五、调用算法

将第二步中的算法放入对应的文件夹中
在下面代码中,windows版本下算法路径为 D:/test/faceModel.pt ,也可自行更改

//获取图片正弦值
    @Override
    public Predictor<Image, float[]> getVectorData() {
        Model model; //模型
        Predictor<Image, float[]> predictor; //predictor.predict(input)相当于python中model(input)
        int IMAGE_SIZE = 224;
        try {
            model = Model.newInstance("faceModel");
            //这里的model.pt是上面代码展示的那种方式保存的
//            model.load(FileInfoServiceImpl.class.getClassLoader().getResourceAsStream("faceModel.pt"));
            model.load(new FileInputStream("D:/test/faceModel.pt"));
//            model.load(new FileInputStream("/usr/local/dm/algorithm/faceModel.pt"));
            Transform resize = new Resize(IMAGE_SIZE);
            Transform toTensor = new ToTensor();
            Transform normalize = new Normalize(new float[]{0.485f, 0.456f, 0.406f}, new float[]{0.229f, 0.224f, 0.225f});
            //Translator处理输入Image转为tensor、输出转为float[]
            Translator<Image, float[]> translator = new Translator<Image, float[]>() {
                @Override
                public NDList processInput(TranslatorContext ctx, Image input) throws Exception {
                    NDManager ndManager = ctx.getNDManager();
                    System.out.println("input: " + input.getWidth() + ", " + input.getHeight());
                    NDArray transform = normalize.transform(toTensor.transform(resize.transform(input.toNDArray(ndManager))));
//                    System.out.println(transform.getShape());
                    NDList list = new NDList();
                    list.add(transform);
                    return list;
                }

                @Override
                public float[] processOutput(TranslatorContext ctx, NDList ndList) throws Exception {
                    return ndList.get(0).toFloatArray();
                }
            };
            predictor = new Predictor<>(model, translator, Device.cpu(), true);

            return predictor;

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

六、预处理文件数据

将 D:/test/photo/ 文件夹中放入图片,调用接口批量生成图片的正弦值存入表中

public void addFileVector111() {
        try {
            File file = new File("D:/test/photo/");
            for (File listFile : file.listFiles()) {
                InputStream inputStream = new FileInputStream("D:/test/photo/" + listFile.getName());
                Predictor<Image, float[]> vectorData = getVectorData();
                float[] vector = vectorData.predict(ImageFactory.getInstance().fromInputStream(inputStream));
                if (vector == null) {
                    log.error("生成正弦值内容失败");
                    continue;
                }

                Gson gson = new Gson();
                String s = gson.toJson(vector);
                String newSub = s.substring(1, s.length() - 1);
                //存储fileVector表
                FileVector f = new FileVector();
                f.setVectorList(newSub);
                f.setUrl(listFile.getAbsolutePath());
                f.setStatus("1");
                int i = fileVectorDao.insertSelective(f);
                if (i <= 0) continue;
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("添加图片正弦值失败" + e);
        }
    }

七、同步数据到es

原本mysql数据同步到es用的是canal,似乎canal无法传输text类型的文件,则改为通过程序同步

 @Override
    public ApiResult addEsFileVectorList() {
        ElasticsearchClient esClient = null;
        Long sqlLimitNum = 1000L;
        Boolean flag = true;
        try {
            long beginTime = System.currentTimeMillis();
            Integer successNum = 0;
            Long beginFileVectorId = 0L;
            Long endFileVectorId = sqlLimitNum;

            while (flag) {
                List<FileVector> fileVectorList = fileVectorDao.selectFileVectorList(beginFileVectorId, sqlLimitNum);
                if (fileVectorList != null && fileVectorList.size() > 0) {
                    BulkRequest.Builder br = new BulkRequest.Builder();
                    List<Long> successFileVecIdList = new ArrayList<>();//成功的同步id记录

                    for (FileVector f : fileVectorList) {
                        String[] strArray = f.getVectorList().split(",");
                        Float[] floatArray = Arrays.stream(strArray).map(Float::parseFloat).toArray(Float[]::new);

                        //存储es数据
                        Map<String, Object> jsonMap = new HashMap<>();
                        jsonMap.put("fileId", f.getFileId());
                        jsonMap.put("vectorList", floatArray);
                        jsonMap.put("url", f.getUrl());
                        br.operations(op -> op
                                .index(idx -> idx
                                        .index("file_vector")
                                        .id(f.getFileVectorId().toString())
                                        .document(jsonMap)
                                )
                        );
                        successFileVecIdList.add(f.getFileVectorId());
                    }
                    if (successFileVecIdList != null && successFileVecIdList.size() > 0) {
                        esClient = this.getEsClient();
                        BulkResponse bulk = esClient.bulk(br.build());
                        if (bulk.errors()) {
                            System.out.println("有部分数据操作失败");
                            for (BulkResponseItem item : bulk.items()) {
                                if (item.error() != null) {
                                    //如果失败需要将失败的id保存
                                    Long failFileVectorId = Long.valueOf(String.valueOf(item.id()));
                                    successFileVecIdList.remove(failFileVectorId);
                                }
                            }
                        }
                    }

					//修改file_vector表中同步状态
                    if (successFileVecIdList != null && successFileVecIdList.size() > 0)
                        fileVectorDao.updateStatusByFileIdList(successFileVecIdList, "0");
                        
                    successNum += successFileVecIdList.size();
                    beginFileVectorId = endFileVectorId + 1;
                    endFileVectorId = endFileVectorId + sqlLimitNum;
                } else {
                    flag = false;
                }
            }

            long endTime = System.currentTimeMillis();
            System.out.println("用时:" + (endTime - beginTime) + "ms");
            return ApiResult.success("同步成功,共执行" + successNum + "条记录");
        } catch (Exception e) {
            e.printStackTrace();
            log.error("批量同步es_file_vector失败" + e);
        } finally {
            try {
                esClient._transport().close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return ApiResult.error("同步失败");
    }

八、查询数据

接收一张图片,调用算法获取图片正弦值,调用es获取匹配数据
可自行设置匹配图片匹配阈值,下面代码中设置的是0.8

public static List<SearchResult> search1(InputStream input) {
        ElasticsearchClient client = null;
        try {
            float[] vector = getVectorList().predict(ImageFactory.getInstance().fromInputStream(input));
            System.out.println(Arrays.toString(vector));

            // 连接Elasticsearch服务器
            client = getEsClient();
            Script.Builder script = new Script.Builder();
            script.inline(_1 -> _1
                    .lang("painless")
                    .source("cosineSimilarity(params.queryVector, doc['vectorList'])")
                    .params("queryVector", JsonData.of(vector)));

            FunctionScoreQuery.Builder funQueryBuilder = new FunctionScoreQuery.Builder();
            funQueryBuilder.query(_1 -> _1.matchAll(_2 -> _2));
            funQueryBuilder.functions(_1 -> _1
                    .scriptScore(_2 -> _2
                            .script(script.build())));

            SearchResponse<Map> search = client.search(_1 -> _1
                            .index("file_vector")
                            .query(funQueryBuilder.build()._toQuery())
                            .source(_2 -> _2.filter(_3 -> _3.excludes("vector")))
                            .size(100)
                            .minScore(0.8)  //此处是设置返回匹配最低分数
                    , Map.class
            );

            List<SearchResult> list = new ArrayList<>();
            List<Hit<Map>> hitsList = search.hits().hits();
            for (Hit<Map> mapHit : hitsList) {
                float score = mapHit.score().floatValue();
                String url = (String) mapHit.source().get("url");
                SearchResult aa = new SearchResult(url, score);
                list.add(aa);
            }
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                client._transport().close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    //生成es连接
    private static ElasticsearchClient getEsClient() {
        try {
            //调用es有同步和异步之分,下列方法是同步阻塞调用
            // Create the low-level client
            RestClient restClient = RestClient.builder(
                    new HttpHost(ES_IP, ES_PORT)).build();

            // Create the transport with a Jackson mapper
            ElasticsearchTransport transport = new RestClientTransport(
                    restClient, new JacksonJsonpMapper());

            // And create the API client
            ElasticsearchClient client = new ElasticsearchClient(transport);

            return client;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

九、演示效果

通过设置不同的阈值,匹配的精确程度也不一样,如果设置阈值为0.9,只会返回构图完全一样的图片,设置为0.8,则会实现下图效果
在这里插入图片描述

十、后续可优化点

1、在上面的流程设计中,是通过java程序同步的es,java程序设置定时任务同步,时效性会比较差,mysql中无法存放float[]格式数据,看是否有其他方案提高同步时效性
2、图片阈值方面的设置还需要根据具体场景具体分析,阈值太低容易误读文件,阈值太高容易漏查文件
大家有什么好的解决方案欢迎留言探讨。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

行情不好进阶困难?那是因为你没有选对方向

关注“软件测试藏经阁”微信公众号&#xff0c;回复暗号【软件测试】&#xff0c;即可获取氪肝整理的全套测试资源 IT行情彻底崩盘了&#xff1f; 相信凡是在抖音关注过互联网相关内容的同学&#xff0c;应该会经常听到这句话吧&#xff01;没错就是号称“某蛙之父”的那个人天…

【LeetCode】HOT 100(13)

题单介绍&#xff1a; 精选 100 道力扣&#xff08;LeetCode&#xff09;上最热门的题目&#xff0c;适合初识算法与数据结构的新手和想要在短时间内高效提升的人&#xff0c;熟练掌握这 100 道题&#xff0c;你就已经具备了在代码世界通行的基本能力。 目录 题单介绍&#…

JavaScript中getElementById与querySelector区别

JavaScript中getElementById与querySelector区别 1、getElement(s)Byxxxx 的用法1.1 getElementById() 方法1.2 getElementsByClassName() 方法1.3 getElementsByTagName() 方法 2、querySelector() 和 querySelectorAll() 用法2.1 querySelector() 方法2.2 querySelectorAll()…

Big O示例与Python数据结构的Big O

在Big-O示例部分的第一部分&#xff0c;我们将介绍各种Big-O函数的各种迭代。 让我们从一些简单的例子开始&#xff0c;探索大O是什么。 O(1) Constant def func_constant(values):Prints first item in a list of values.print(values[0])func_constant([1,2,3])# 1请注意&a…

避雷器计数器测试仪

原理 图1所示为JS动作记数器的原理接线图。图1&#xff08;a&#xff09;为JS动作记数器的基本结构&#xff0c;即所谓的双阀片式结构。当避雷器动作时&#xff0c;放电电流流过阀片R1&#xff0c;在R1上的压降经阀片R2给电容器C充电&#xff0c;然后C再对电磁式记数器的电感线…

easyX基本概念(注释版)

0.前言 本次我给您带来easyX库系列的博文&#xff0c;本系列博文目的在于对原easyX库文档进行一个补充和注解&#xff0c;重在补充测试样例和实践。 easyX库本身并不值得过于学习&#xff0c;但是作为有C语言基础的C爱好者&#xff0c;学习easyX能让您对IT技术更加感兴趣。用…

AI完成音频创作,击败99%作者

使用AI完成音频创作 &#xff0c;击败99%同类创作者 &#xff0c;享受持续广告变现收益 &#xff0c;下面我们来看下如何使用把~ 音频生成和投放可以分为以下两步骤&#xff1a; 使用AI效能公众号完成内容创作&#xff1b;利用喜马拉雅云剪辑发布内容 1. 内容生成 打开AI效能公…

Altium Designer 15 (AD15)新建元件库

1. 连接线 1.1 设置连接线的默认颜色&#xff0c;宽度&#xff1a;点击图标 --》按Tab键 1.2 默认使用蓝色&#xff0c;通用 2. 调出来元件库&#xff0c;然后按照元件的英文名字搜元件&#xff0c;拖到左边画图区域就可以了 3. 自己画一个元器件&#xff0c;自定义元器件 3.1…

6.14 消息队列

目录 消息队列 消息队列结构 消息队列使用步骤 消息队列创建/打开-msgget 消息队列创建/打开 - 示例 消息发送 – msgsnd 消息格式 消息发送 - 示例 笔记 消息队列 消息队列是System V IPC对象的一种 消息队列由消息队列ID来唯一标识 消息队列就是一个消息的列表。…

解决Idea中日志文件log4j.xml中http//jakarta.apache.org/log4j爆红,报错此 uri is not registered

在Idea中&#xff0c;配置log4j.xml出现“http //jakarta.apache.org/log4j/ uri is not registered”的错误信息&#xff0c;解决步骤如下&#xff1a; 1、原始的log4j.xml配置文件&#xff1a; <?xml version"1.0" encoding"GB2312" ?> <!…

ESP32(Micro Python)LVGL 四路ADC

本程序布局与上一个程序相同&#xff0c;引脚不重合&#xff0c;可以在不更换外设的情况下切换程序。由于仪表盘显示的数值范围不可调&#xff0c;实际显示的值为测量值占量程的百分比。 代码如下 import lvgl as lv import time from espidf import VSPI_HOST from ili9XX…

chatgpt赋能python:Python如何在输入之前等待30秒

Python如何在输入之前等待30秒 作为一名编程工程师&#xff0c;程序的性能和用户体验都是非常重要的。在用户输入数据之前等待一段时间可以帮助我们避免不必要的错误和提高程序的稳定性。本文将介绍如何使用Python等待30秒在输入数据。 使用Python的time模块 Python的time模…

【强烈推荐】 十多款2023年必备国内外王炸级AI工具 (免费 精品 好用) 让你秒变神一样的装逼佬感受10倍生产力 (4) AI办公

&#x1f680; 个人主页 极客小俊 ✍&#x1f3fb; 作者简介&#xff1a;web开发者、设计师、技术分享博主 &#x1f40b; 希望大家多多支持一下, 我们一起进步&#xff01;&#x1f604; &#x1f3c5; 如果文章对你有帮助的话&#xff0c;欢迎评论 &#x1f4ac;点赞&#x1…

SLAM实战项目(2) — ROS下运行ORB-SLAM2稠密地图重建

目录 1 运行步骤 (1) 创建工作空间 (2) 修改CmakeList.txt (3) 编译 (4) 下载bag文件 (4) 编写roslaunch文件 2 运行报错 报错1&#xff1a; 报错2&#xff1a; 报错3&#xff1a; 报错4&#xff1a; ROS学习文档&#xff1a;Introduction Autolabor-ROS机器人入门…

Bert模型精讲

1.Autoregressive语言模型与Autoencoder语言模型 1.1 语言模型概念介绍 Autoregressice语言模型&#xff1a;指的是依据前面(或后面)出现的单词来预测当前时刻的单词&#xff0c;代表有ElMo, GPT等。 Autoencoder语言模型&#xff1a;通过上下文信息来预测被mask的单词&…

Linux GCC,GDB,Shell脚本,Vim的简单使用

这里写目录标题 GCC命令GDB命令Shell脚本VIM指令 GCC命令 GCC&#xff08;GNU Compiler Collection&#xff0c;GNU编译器套件&#xff09;是由GNU开发的编程语言译器 编译一个简单的.c程序&#xff1a; 四步分开写&#xff1a; gcc -E -o hello.i hello.c // 预处理 gcc -…

chatgpt赋能python:Python怎么绕过登录爬取数据

Python怎么绕过登录爬取数据 在进行网站爬取时&#xff0c;经常会发现需要登录才能访问所需要的数据&#xff0c;这给我们的爬虫程序带来了一定的难度。本文就介绍一些Python绕过登录的方法&#xff0c;让你获取到所需的数据。 1. Session维持登录状态 当我们登录一个网站时…

运维实践 | 运维打工人必备 CentOS-Linux/Stream-8 服务器系统基础安装与配置实践...

欢迎关注「全栈工程师修炼指南」公众号 点击 &#x1f447; 下方卡片 即可关注我哟! 设为「星标⭐」每天带你 基础入门 到 进阶实践 再到 放弃学习&#xff01; 专注 企业运维实践、网络安全、系统运维、应用开发、物联网实战、全栈文章 等知识分享 “ 花开堪折直须折&#xf…

【LeetCode】python 主要元素 摩根投票法

目录 题目&#xff1a; 题解&#xff1a; 1.纯暴力&#xff08;字典&#xff09; 2. 摩根投票法 题目&#xff1a; 数组中占比超过一半的元素称之为主要元素。给你一个 整数 数组&#xff0c;找出其中的主要元素。若没有&#xff0c;返回 -1 。请设计时间复杂度为 O(N) 、空…

电脑中的个人私密视频该怎么加密?

因为电脑内存空间较大&#xff0c;所以很多人喜欢将拍摄的视频存放在电脑中&#xff0c;这其中就会有一些比较私密&#xff0c;不适合被其他人看到的视频。为了保护这些视频的安全&#xff0c;我们需要合适的方法对其进行加密保护。下面我们就一起来了解一下电脑中的个人私密视…