基于SpringBoot框架和Flask的图片差异检测与展示系统

目录

1. 项目目标

2. 功能需求

(1)图片上传功能

(2)差异检测算法

(3)后端服务

(4)前端展示

(5)阿里云服务器存储

(6)数据库记录

(7)检测提示

(8)检测时间优化

3. 项目展示

4. 数据库设计

5. 前端设计

6. Flask后端设计

7. SpringBoot后端设计

(1)阿里云工具类

(2)HTTP客户端工具类

(3)Controller

(4)Service

(5)Mapper


1. 项目目标

  • 设计并实现一个基于Web的图片差异检测与展示系统。
  • 用户可通过系统上传两张仅有几处差别的图片(template和sample),系统自动识别差异并在sample图片上用圆圈标注。
  • 利用阿里云服务器存储用户上传的图片和检测结果,实现数据的安全可靠传输与存储。

2. 功能需求

(1)图片上传功能

用户可以同时上传template和sample两张图片。

(2)差异检测算法

在Python文件中实现差异检测算法,能够准确识别图片间的不同之处。

(3)后端服务

使用Flask搭建Python后端,与SpringBoot框架相结合,处理前端请求并调用差异检测算法。

(4)前端展示

采用Vue框架搭建前端页面,实现用户友好的交互界面。

(5)阿里云服务器存储

将用户上传的图片和Python生成的检测结果保存到阿里云服务器,并返回URL给前端展示。

(6)数据库记录

数据库需记录以下信息:id、用户id、sample和template图片的URL、result图片的URL以及图片上传时间。

(7)检测提示

用户上传图片并按下检测按钮后,系统显示正在检测提示,提高用户体验。

(8)检测时间优化

确保差异检测算法具有较高的执行效率,检测时间不宜过久,以满足用户需求。

3. 项目展示

sample:

template: 

 前端页面:

 检测动画:

结果: 

如上图所示,左侧展示的是检测结果(result),而右侧展示的是模板图片(template)。在检测结果中,sample图片与template图片之间的不同之处已经被红色圆圈精确标注出来,从而清晰地指出了两者之间的差异。这意味着系统已经成功识别并圈出了sample图片相对于template图片的不同区域。

4. 数据库设计

5. 前端设计

    // 点击上传图片事件
    submit() {
      if (this.$refs.upload1.uploadFiles.length === 1 && this.$refs.upload2.uploadFiles.length === 1) {
        this.uploadBatchImage(this.fileList1, this.fileList2);
        this.uploaded = true;
      } else {
        Message({
          message: '上传失败!请保证模板和样例同时上传',
          type: 'error',
        });
      }
    },
    
    // 上传文件
    uploadBatchImage(fileList1, fileList2) {
      const loading = this.$loading({
        lock: true,
        text: '图片上传中...',
        spinner: 'el-icon-loading',
        background: 'rgba(0, 0, 0, 0.7)'
      });
      const formData = new FormData();
      // 遍历文件列表,将每个文件添加到formData中
      fileList1.forEach((file) => {
        formData.append(`files`, file.raw, file.name); // `files`是后端期望的字段名
      });
      fileList2.forEach((file) => {
        formData.append(`files`, file.raw, file.name); // `files`是后端期望的字段名
      });

      formData.append('userId', this.userId);

      request
          .post('/checker/upload', formData,
              {
                headers: {
                  'Content-Type': 'multipart/form-data',
                }
              })
          .then(response => {
            loading.close();

            this.checkerVo.id = response.data.data.id;
            this.checkerVo.sampleUrl = response.data.data.sampleUrl;
            this.checkerVo.templateUrl = response.data.data.templateUrl;
            this.checkerVo.userId = response.data.data.userId;
            console.log(this.checkerVo);
            Message({
              message: '上传成功!',
              type: 'success',
            });
          }).catch(error => {
        Message({
          message: '上传失败!',
          type: 'error',
        });
        throw error;
      });
    },
    
    // 点击差异检测事件
    quickCheck() {
      if (this.$refs.upload1.uploadFiles.length === 1 && this.$refs.upload2.uploadFiles.length === 1 && this.uploaded) {
        this.check(2);
      } else {
        Message({
          message: '检测失败!请保证模板和样例同时上传',
          type: 'error',
        });
      }
    },

    // 差异检测
    check(status) {
      const loading = this.$loading({
        lock: true,
        text: '检测中,请稍等几分钟',
        spinner: 'el-icon-loading',
        background: 'rgba(0, 0, 0, 0.7)'
      });
      request
          //向/checker/check/{status}发送消息
          .post("/checker/check/" + status, this.checkerVo)  
          .then((res) => {
            console.log(res.data.data);
            this.resultUrl = res.data.data.resultUrl;
            this.templateUrl = res.data.data.templateUrl;
            this.resultVisible = true;
            this.hasResult = true;
            loading.close();
          })
    },

6. Flask后端设计

由于算法可能涉及商业应用,出于保密考虑,不会公开算法的具体内部实现细节。在此情况下,将算法视为一个黑盒,仅对外展示如何通过Flask框架的接口来调用这个算法。这意味着只提供接口的使用方法,而不涉及算法本身的工作原理和代码实现。

如下代码,DiffQuickCheckUtil为算法实现类,已经封装成工具类,不演示内部算法。

from datetime import datetime

import cv2
from flask import Flask, request, jsonify

from utils.AliOssUtil import OSSClient
from utils.DiffQuickUtil import DiffQuickCheckUtil
from utils.DiffUtil import DiffCheckUtil
from utils.DownloadUtil import ImageDownloader

app = Flask(__name__)

@app.route('/diffQuickCheck', methods=['POST'])
def diffQuickCheck():
    # 从请求中获取参数
    data = request.get_json()
    id = data.get('id')
    user_id = data.get('user_id')
    sample_url = data.get('sample_url')
    template_url = data.get('template_url')

    downloader = ImageDownloader()
    template_image, sample_image = downloader.get_images(template_url), downloader.get_images(sample_url)

    diffQuickCheckUtil.calculate(template=template_image, sample=sample_image)

    oss_client = OSSClient(
        accessKeyId=''      # 填写你的阿里云OssId
        accessKeySecret=''  # 填写你的阿里云Oss密钥
        endpoint=''         # 填写你的地区
        bucketName=''       # 填写你的bucket名字
    )

    # 由于并发性低,使用当前时间戳作为文件名,可确保图片文件名唯一
    objectName = f'output/user_{user_id}/{datetime.now().strftime("%Y%m%d%H%M%S")}.jpg'
    localFile = './static/output/quickresult.jpg'

    try:
        # 尝试上传文件到oss
        oss_client.upload_file(objectName, localFile)
        fileLink = oss_client.generate_file_link(objectName)
        print(fileLink)

        # 如果上传成功,返回成功信息
        return jsonify({
            "code": 200,
            "msg": "success",
            "data": {
                "id": id,
                "result_url": fileLink
            }
        })

    except Exception as e:
        # 如果发生异常,打印异常信息并返回错误信息
        print(f"An error occurred: {e}")
        return jsonify({
            "code": 500,
            "msg": "Failed to upload the file to OSS.",
            "data": {
                "userId": id,
                "error": str(e)
            }
        })


if __name__ == '__main__':
    diffQuickCheckUtil = DiffQuickCheckUtil(saveName="./static/output/quickresult.jpg")
    app.run(host='0.0.0.0', port=12345)

7. SpringBoot后端设计

(1)阿里云工具类

@Data
@AllArgsConstructor
@Slf4j
public class AliOssUtil {

    private String endpoint;
    private String accessKeyId;
    private String accessKeySecret;
    private String bucketName;

    /**
     * 文件上传
     *
     * @param bytes
     * @param objectName
     * @return
     */
    public String upload(byte[] bytes, String objectName) {

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        try {
            // 创建PutObject请求。
            ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(bytes));
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }

        //文件访问路径规则 https://BucketName.Endpoint/ObjectName
        StringBuilder stringBuilder = new StringBuilder("https://");
        stringBuilder
                .append(bucketName)
                .append(".")
                .append(endpoint)
                .append("/")
                .append(objectName);

        log.info("文件上传到:{}", stringBuilder.toString());

        return stringBuilder.toString();
    }
}

(2)HTTP客户端工具类

HTTP客户端工具类,用于向Flask发送消息

public class HttpClientUtil {

    static final int TIMEOUT_MSEC = 5 * 100000000;

    //省略其他方式发送请求

    /**
     * 发送POST方式请求 
     */
    public static String doPost4Json(String url, Map<String, String> paramMap) throws IOException {
        // 创建Httpclient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpResponse response = null;
        String resultString = "";

        try {
            // 创建Http Post请求
            HttpPost httpPost = new HttpPost(url);

            if (paramMap != null) {
                //构造json格式数据
                JSONObject jsonObject = new JSONObject();
                for (Map.Entry<String, String> param : paramMap.entrySet()) {
                    jsonObject.put(param.getKey(), param.getValue());
                }
                StringEntity entity = new StringEntity(jsonObject.toString(), "utf-8");
                //设置请求编码
                entity.setContentEncoding("utf-8");
                //设置数据类型
                entity.setContentType("application/json");
                httpPost.setEntity(entity);
            }

            httpPost.setConfig(builderRequestConfig());

            // 执行http请求
            response = httpClient.execute(httpPost);

            resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
        } catch (Exception e) {
            throw e;
        } finally {
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return resultString;
    }

    /**
     * @return {@link RequestConfig }
     */
    private static RequestConfig builderRequestConfig() {
        return RequestConfig.custom()
                .setConnectTimeout(TIMEOUT_MSEC)
                .setConnectionRequestTimeout(TIMEOUT_MSEC)
                .setSocketTimeout(TIMEOUT_MSEC).build();
    }

}

(3)Controller

@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/checker")
public class CheckerController {

    private final ICheckerService checkService;

    /**
     * 上传图片到数据库
     *
     * @param uploadDTO
     * @return {@link Result }
     */
    @PostMapping("/upload")
    public Result upload(@ModelAttribute UploadDTO uploadDTO) throws IOException {
        CheckerVO checkerVO = checkService.upload(uploadDTO);
        if (checkerVO != null) {
            return Result.success(checkerVO);
        } else {
            return Result.error("上传失败");
        }
    }

    /**
     * 图片差异检测
     *
     * @param checkerVo
     * @return {@link Result }<{@link String }>
     */
    @PostMapping("/check/{status}")
    public Result<Map<String,String>> check(@RequestBody CheckerVO checkerVo, @PathVariable Integer status) throws IOException {
        String resultUrl = checkService.check(checkerVo, status);
        Map<String,String> map = new HashMap<>();
        map.put("resultUrl",resultUrl);
        map.put("templateUrl",checkerVo.getTemplateUrl());

        if (resultUrl != null) {
            return Result.success(map);
        } else {
            return Result.error("检测失败");
        }

    }

}

(4)Service

@Service
@RequiredArgsConstructor
public class CheckerServiceImpl extends ServiceImpl<CheckerMapper, Checker> implements ICheckerService {

    private final CheckerMapper checkerMapper;
    private final AliOssUtil aliOssUtil;
    private final DiffAlgorithmProperties diffAlgorithmProperties;

    /**
     * 上传图片到数据库
     */
    @Override
    public CheckerVO upload(UploadDTO uploadDTO) {
        try {
            //原始文件名
            String originalFilename0 = uploadDTO.getFiles().get(0).getOriginalFilename();
            String originalFilename1 = uploadDTO.getFiles().get(1).getOriginalFilename();
            //截取原始文件名的后缀   dfdfdf.png
            String extension0 = originalFilename0.substring(originalFilename0.lastIndexOf("."));
            String extension1 = originalFilename1.substring(originalFilename1.lastIndexOf("."));
            //构造新文件名称
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
            // 获取当前日期时间并格式化
            LocalDateTime localDateTime = LocalDateTime.now();
            String now = localDateTime.format(formatter);
            Integer userId = uploadDTO.getUserId();
            String objectName0 = "input/user_" + userId + "/template_" + now + extension0;
            String objectName1 = "input/user_" + userId + "/sample_" + now + extension1;
            //文件的请求路径
            String filePath0 = aliOssUtil.upload(uploadDTO.getFiles().get(0).getBytes(), objectName0);
            String filePath1 = aliOssUtil.upload(uploadDTO.getFiles().get(1).getBytes(), objectName1);
            //构建实体类,写入数据库
            Checker checker = new Checker();
            checker.setUserId(uploadDTO.getUserId());
            checker.setSampleUrl(filePath1);
            checker.setTemplateUrl(filePath0);
            checker.setInsertTime(localDateTime);
            checkerMapper.insert(checker);
            return BeanUtil.copyProperties(checker, CheckerVO.class);
        } catch (IOException e) {
            log.error("上传失败:{}", e);
        }
        return null;
    }

    /**
     * 差异检测
     */
    @Override
    public String check(CheckerVO checkerVo, Integer status) {
        Map map = new HashMap();
        map.put("id", checkerVo.getId());
        map.put("user_id", checkerVo.getUserId());
        map.put("sample_url", checkerVo.getSampleUrl());
        map.put("template_url", checkerVo.getTemplateUrl());
        String addr;
        if(status==1){
            addr = "http://" + diffAlgorithmProperties.getIp() + ":" + diffAlgorithmProperties.getPort() + "/diffCheck";
        }else if(status==2){
            addr = "http://" + diffAlgorithmProperties.getIp() + ":" + diffAlgorithmProperties.getPort() + "/diffQuickCheck";
        }else{
            return null;
        }

        try {
            String userCoordinate = HttpClientUtil.doPost4Json(addr, map);
            JSONObject jsonObject = new JSONObject(userCoordinate);
            if (jsonObject.getInt("code") == 200) {
                //解析出resultUrl和id
                JSONObject data = jsonObject.getJSONObject("data");
                String resultUrl = data.getStr("result_url");
                Long id = data.getLong("id");
                //更新数据库
                Checker checker = new Checker();
                checker.setId(id);
                if(status==1) {
                    checker.setResultUrl(resultUrl);
                } else if (status==2) {
                    checker.setQuickResultUrl(resultUrl);
                }
                checkerMapper.updateById(checker);
                return resultUrl;
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        return null;
    }


}

(5)Mapper

采用了MyBatisPlus简化代码。

@Mapper
public interface CheckerMapper extends BaseMapper<Checker> {
}

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

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

相关文章

Java:正则表达式 matches

文章目录 正则表达式作用基本用法小结代码 案例&#xff1a;校验用户输入的电话&#xff0c;邮箱&#xff0c;是否合法\\.是什么意思 黑马学习笔记 正则表达式 由一些特定的字符组成&#xff0c;代表的是一个规则 作用 用来校验数据格式是否合法在一段文本中查找满足要求的内…

Elasticsearch:无状态世界中的数据安全

作者&#xff1a;来自 Elastic Henning Andersen 在最近的博客文章中&#xff0c;我们宣布了支持 Elastic Cloud Serverless 产品的无状态架构。通过将持久性保证和复制卸载到对象存储&#xff08;例如 Amazon S3&#xff09;&#xff0c;我们获得了许多优势和简化。 从历史上…

Web3D 技术发展瓶颈在哪里?

Web3D 技术的发展瓶颈主要集中在以下几个方面&#xff1a; 1、性能和优化&#xff1a;尽管现代浏览器和硬件逐步提高了性能&#xff0c;但高质量的3D渲染仍可能导致性能瓶颈。特别是在移动设备上&#xff0c;图形渲染和计算可能会受到限制。建议合理控制好项目资源量&#xff…

实验记录 | 点云处理 | K-NN算法3种实现的性能比较

引言 K近邻&#xff08;K-Nearest Neighbors, KNN&#xff09;算法作为一种经典的无监督学习算法&#xff0c;在点云处理中的应用尤为广泛。它通过计算点与点之间的距离来寻找数据点的邻居&#xff0c;从而有效进行点云分类、聚类和特征提取。本菜在复现点云文章过程&#xff…

详解React setState调用原理和批量更新的过程

1. React setState 调用的原理 setState目录 1. React setState 调用的原理2. React setState 调用之后发生了什么&#xff1f;是同步还是异步&#xff1f;3. React中的setState批量更新的过程是什么&#xff1f; 具体的执行过程如下&#xff08;源码级解析&#xff09;&#x…

基于SpringBoot+Vue+MySQL的宿舍维修管理系统

系统展示 前台界面 管理员界面 维修员界面 学生界面 系统背景 在当今高校后勤管理的日益精细化与智能化背景下&#xff0c;宿舍维修管理系统作为提升校园生活品质、优化资源配置的关键环节&#xff0c;其重要性日益凸显。随着学生规模的扩大及住宿条件的不断提升&#xff0c;宿…

人机交互系统中的人脸讲话生成系统调研

《Human-Computer Interaction System: A Survey of Talking-Head Generation》 图片源&#xff1a;https://github.com/Yazdi9/Talking_Face_Avatar 目录 前言摘要一、背景介绍二、人机交互系统体系结构2.1. 语音模块2.2. 对话系统模块2.3. 人脸说话动作生成 三 人脸动作生成…

来啦| LVMH路威酩轩25届校招智鼎高潜人才思维能力测验高分攻略

路威酩轩香水化妆品(上海)有限公司是LVMH集团于2000年成立&#xff0c;负责集团旗下的部分香水化妆品品牌在中国的销售包括迪奥、娇兰、纪梵希、贝玲妃、玫珂菲、凯卓、帕尔马之水以及馥蕾诗等。作为目前全球最大的奢侈品集团LVMH 集团秉承悠久的历史&#xff0c;不断打破常规&…

【微处理器系统原理和应用设计第六讲】片上微处理器系统系统架构

一、概念辨析 首先来厘清以下概念&#xff1a;微处理器&#xff0c;微控制器&#xff0c;单片机&#xff0c;片上微处理器系统 &#xff08;1&#xff09;微处理器&#xff1a;即MPU&#xff08;Microprocessor Unit&#xff09;&#xff0c;微处理器是一种计算机的中央处理单…

如何打造个性化大学生聊天室?Java SpringBoot Vue实战,2025最新设计指南

✍✍计算机毕业编程指导师** ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java…

【深度学习】向量化

1. 什么是向量化 向量化通常是消除代码中显示for循环语句的技巧&#xff0c;在深度学习实际应用中&#xff0c;可能会遇到大量的训练数据&#xff0c;因为深度学习算法往往在这种情况下表现更好&#xff0c;所以代码的运行速度非常重要&#xff0c;否则如果它运行在一个大的数据…

【Linux】翻山越岭——进程地址空间_c语言父子进程地址空间

文章目录 一、是什么 写时拷贝 二、为什么三、怎么做 区域划分和调整 一、是什么 回顾我们学习C/C时的地址空间&#xff1a; 有了这个基本框架&#xff0c;我们对于语言的学习更加易于理解&#xff0c;但是地址空间究竟是什么❓我们对其并不了解&#xff0c;是不是内存呢&…

海外云服务器安装 MariaDB10.6.X (Ubuntu 18.04 记录篇二)

本文首发于 秋码记录 MariaDB 的由来&#xff08;历史&#xff09; 谈起新秀MariaDB&#xff0c;或许很多人都会感到陌生吧&#xff0c;但若聊起享誉开源界、业界知名的关系型数据库——Mysql&#xff0c;想必混迹于互联网的人们&#xff08;coder&#xff09;无不知晓。 其…

MonoHuman: Animatable Human Neural Field from Monocular Video 精读

一、共享双向变形模块 1. 模块的核心思想 共享双向变形模块的核心目标是解决从单目视频中生成不同姿态下的3D人体形状问题。因为视频中的人物可能处于各种动态姿态下&#xff0c;模型需要能够将这些不同姿态的几何形状进行变形处理&#xff0c;以适应标准的姿态表示并生成新的…

SVN下载安装使用方法

目录 &#x1f315;SVN是什么&#xff1f;&#x1f319;SVN跟Git比的优势&#x1f319;SVN的用处 &#x1f315;下载安装使用方法 &#x1f315;&#x1f319;⭐ &#x1f315;SVN是什么&#xff1f; 代码版本管理工具 它能记住你每次的修改 查看所有的修改记录 恢复到任何历…

【Linux网络】详解TCP协议(1)

&#x1f389;博主首页&#xff1a; 有趣的中国人 &#x1f389;专栏首页&#xff1a; Linux网络 &#x1f389;其它专栏&#xff1a; C初阶 | C进阶 | 初阶数据结构 小伙伴们大家好&#xff0c;本片文章将会讲解 TCP协议 的相关内容。 如果看到最后您觉得这篇文章写得不错&am…

【大数据】深入浅出Hadoop,干货满满

【大数据】深入浅出Hadoop 文章脉络 Hadoop HDFS MapReduce YARN Hadoop集群硬件架构 假设现在有一个PB级别的数据库表要处理。 在单机情况下&#xff0c;只能升级你的内存、磁盘、CPU&#xff0c;那么这台机器就会变成 “超算”&#xff0c;成本太高&#xff0c;商业公司肯…

通过卷积神经网络(CNN)识别和预测手写数字

一&#xff1a;卷积神经网络&#xff08;CNN&#xff09;和手写数字识别MNIST数据集的介绍 卷积神经网络&#xff08;Convolutional Neural Networks&#xff0c;简称CNN&#xff09;是一种深度学习模型&#xff0c;它在图像和视频识别、分类和分割任务中表现出色。CNN通过模仿…

8. GIS数据分析师岗位职责、技术要求和常见面试题

本系列文章目录&#xff1a; 1. GIS开发工程师岗位职责、技术要求和常见面试题 2. GIS数据工程师岗位职责、技术要求和常见面试题 3. GIS后端工程师岗位职责、技术要求和常见面试题 4. GIS前端工程师岗位职责、技术要求和常见面试题 5. GIS工程师岗位职责、技术要求和常见面试…

【高等代数笔记】线性空间(一到四)

3. 线性空间 令 K n : { ( a 1 , a 2 , . . . , a n ) ∣ a i ∈ K , i 1 , 2 , . . . , n } \textbf{K}^{n}:\{(a_{1},a_{2},...,a_{n})|a_{i}\in\textbf{K},i1,2,...,n\} Kn:{(a1​,a2​,...,an​)∣ai​∈K,i1,2,...,n}&#xff0c;称为 n n n维向量 规定&#xff08;规定…