果蔬识别系统性能优化之路

目录

  • 一级目录
    • 二级目录
      • 三级目录
    • 前情提要
      • 当前问题
    • 优化方案
      • 1. 内存化
      • 2. 原生化
      • 3. 接口化
    • 行动
    • 实现
    • 结语

一级目录

二级目录

三级目录

前情提要

超详细前端AI蔬菜水果生鲜识别应用优化之路

当前问题

  1. indexddb在webview中确实性能有限,存储量上来后每次读取数据会有明显卡顿
  2. 目前的余弦相邻算法是基于所有特征向量数据进行的计算,一旦数据量大了后计算量也是一个十分消耗性能的点
  3. 由于机器性能问题,本地化加载模型在每次进入页面后需要消耗很长一段时间进行模型的加载,体验相当不好

优化方案

1. 内存化

方式:为了解决indexddb读取速度的问题,最直接的方式就是把数据放内存对象中,提前将indexddb的数据取出来,然后在数据变化时进行同步
结果:确实省去了读取这一步会快很多,但是仍有隐患,webview分配的内存是否这种占用内存的方式

2. 原生化

本地化模型加载可以在原生层面进行模型的加载,识别,学习,相当于把整套方案在原生端实现一次,通过bridge调用原生相关方法完成识别,但目前暂无学习原生的意向,所以搁置

3. 接口化

方式:将数据存储在mysql,识别单独起一个python服务,通过接口调用,利用服务器的性能优势使识别速度提升,保证网络消耗在合理范围即可

行动

最终选择了接口化,在保证网络的情况下,识别速度在300ms内可被接受

  1. nestjs搭建服务端,分feature和img两个模块
  2. mysql搭建数据库,建立feature和img两个表,通过imgId和feature表关联,同时feature表包含storeCode字段,用来管理门店
  3. 搭建redis,代替内存化方案,实现快速读取
  4. 搭建python服务端,与nestjs服务端进行通信,进行识别结果的传输
  5. 通过IVF方式提升计算速度,保证大量特征值情况下仍然可以快速算出相似结果

实现

  1. python端,flask搭建http服务,当然后续可以改成其他和服务端通信方式,提升速度
  2. 实现识别接口恶化同步接口,用于识别图片特征值和同步数据库存储的特征向量
    app.py
from flask import Flask, request, jsonify
from flask_cors import CORS
from detect import MainDetect
from tensorflow.keras import layers, modelsapp = Flask(__name__)
CORS(app)  # 允许所有路由上的跨域请求
detector = MainDetect()@app.route('/')
def home():return "Welcome to the Vegetable Recognize App!"@app.route('/predict', methods=['POST'])
def predict():if 'file' not in request.files:return 'No file part', 400file = request.files['file']store_code = request.form["storeCode"]if file.filename == '':return 'No selected file', 400try:image_data = file.read()data = detector.classify_image(image_data, store_code)outputs = data["outputs"]time = data["time"]index = data["index"]return jsonify({"predictTime": time, "features": outputs, "index": index})except Exception as e:return jsonify({'error': str(e)}), 500@app.route('/sync', methods=['POST'])
def sync():data = request.get_json()arr = data.get('data')store_code = data.get('storeCode')detector.sync(store_code, arr)return jsonify({"message": 'ok'})

detect.py

import tensorflow as tf
from tensorflow.keras import layers, models
from tensorflow.keras.applications import MobileNet
from tensorflow.keras.applications.mobilenet import preprocess_input, decode_predictions
from PIL import Image
import numpy as np
import cv2
import time
import io
import gc
from ivf import IVFPQmodel = MobileNet(input_shape=(224, 224, 3), weights='imagenet', include_top=False, pooling='avg')class MainDetect:# 初始化def __init__(self):super().__init__()# 模型初始化self.image_id = Noneself.image_features = Noneself.model = tf.keras.models.load_model("models/custom/my-model.h5")self.ivfObj = {}def classify_image(self, image_data, store_code):# Load and preprocess imageimg = tf.image.decode_image(image_data, channels=3)img = tf.image.resize(img, [224, 224])img = tf.expand_dims(img, axis=0)  # Add batch dimension# Run model predictionstart_time = time.time()outputs = model.predict(img)# outputs = self.model.predict(outputs)# prediction = tf.divide(outputs, tf.norm(outputs))i = []if store_code + '-featureDatabase' in self.ivfObj:i = self.ivfObj[store_code + '-featureDatabase'].search(outputs)i = i.flatten().tolist()end_time = time.time()# Calculate elapsed timeelapsed_time = end_time - start_time# Flatten the outputs and return them# output_data = prediction.numpy().flatten().tolist()output_data = outputs.flatten().tolist()# Force garbage collection to free up memorydel img, outputs, end_time, start_time  # Ensure variables are deletedgc.collect()return {"outputs": output_data, "time": f"{elapsed_time * 1000:.2f}ms", "index": i}def sync(self, store_code, data):if store_code + '-featureDatabase' in self.ivfObj:del self.ivfObj[store_code + '-featureDatabase']self.ivfObj[store_code + '-featureDatabase'] = IVFPQ()for item in data:feature = item['features']self.ivfObj[store_code + '-featureDatabase'].add(np.array([feature], dtype=np.float32))return 'ok'

ivf.py

import faiss
import numpy as npclass IVFPQ:def __init__(self, d=1024, nlist=1, m=16, n_bits=8):# 创建量化器quantizer = faiss.IndexFlatL2(d)  # 使用L2距离进行量化self.index = faiss.IndexIVFPQ(quantizer, d, nlist, m, n_bits)np.random.seed(1234)xb = np.random.random((256, d)).astype('float32')  # 模拟数据库中的特征向量# 训练索引self.index.train(xb)self.index.add(xb)  # 将特征向量添加到索引中def search(self, xq, k=50):d, i = self.index.search(xq, k)return idef add(self, xb):self.index.add(xb)def sync(self, features):for i in range(len(features)):self.add(features[i])
  1. nestjs进行接口的转发和数据处理
    识别的service,调用python服务,并通过返回的索引找到正确的目标label
  /*** 预测* @param file* @param num* @param storeCode* @param justPredict*/async predict(file: Express.Multer.File, num: string = '5', storeCode: string, justPredict: Boolean = false) {const url = 'http://localhost:5000/predict'; // Python 服务的 URLconst startTime = Date.now();try {// 返回 Python 服务的响应数据const formData = new FormData();formData.append('file', file.buffer, file.originalname);formData.append('storeCode', storeCode);const response = await firstValueFrom(this.httpService.post(url, formData));const endTime = Date.now();const features = response.data.features;const index = response.data.index;if (justPredict) {return features;}// const top5 = await this.findTopNSimilar(features, parseInt(num), storeCode);const featureDatabaseStr = await this.redisService.get(`${storeCode}-featureDatabase`);if (!featureDatabaseStr) {return response.data = {...response.data,[`top${num}`]: [],features,totalTime: `${endTime - startTime}ms`,};}const featureDatabase = JSON.parse(featureDatabaseStr);const list = [];index.forEach((i: number) => {const ide = i - 256;if (ide >= 0) {const item = featureDatabase[ide];if (!list.some(l => l.label === item.label)) {list.push({ label: item.label });}}});return response.data = {...response.data,[`top${num}`]: list,features,totalTime: `${endTime - startTime}ms`,};} catch (error) {// 错误处理console.error('Error calling Python service:', error);throw error;}}

同步python端特征向量,在数据库的增删改查时进行调用

  /*** 同步redis* @param storeCode*/async syncRedis(storeCode: string) {const featureDatabase = await this.findAll(storeCode);await this.redisService.set(`${storeCode}-featureDatabase`, JSON.stringify(featureDatabase));const url = 'http://localhost:5000/sync'; // Python 服务的 URLawait firstValueFrom(this.httpService.post(url, { data: featureDatabase, storeCode }));}

结语

没规划好,其实全部用python实现应该更自然,后续有时间再更新,先上线跑跑

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

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

相关文章

【机器学习】交通勘测

交通勘测 交通勘测中的关键应用场景 交通勘测中常用的数据来源 交通勘测中的挑战 结论 🎈边走、边悟🎈迟早会好 机器学习在交通勘测中的应用非常广泛,可以用于交通流量预测、事故检测、车辆分类、道路拥堵管理等多个方面。通过结合传感…

什么是函数调用约定?

目录 前言 一、函数调用约定的主要内容 二、常见的函数调用约定 1. __cdecl(C Declaration) 2. __stdcall(Standard Call) 3. __fastcall(Fast Call) 4. __thiscall(This Call&#xff0…

【Spring Boot 3】【Web】国际化

【Spring Boot 3】【Web】国际化 背景介绍开发环境开发步骤及源码工程目录结构总结背景 软件开发是一门实践性科学,对大多数人来说,学习一种新技术不是一开始就去深究其原理,而是先从做出一个可工作的DEMO入手。但在我个人学习和工作经历中,每次学习新技术总是要花费或多或…

[数据集][目标检测]轮胎检测数据集VOC+YOLO格式4629张1类别

数据集格式:Pascal VOC格式YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数):4629 标注数量(xml文件个数):4629 标注数量(txt文件个数):4629 标注…

【SQL】删除表中重复数据的方法

很久之前我写入一张sql的数据表,它里面有很多重复的内容。然后我想只保留一条原始数据: 例如上面的时间,出现了很多重复值。 我最初用的是这种方法: SELECT * FROM table_name WHERE primary_key IN (SELECT max(primary_key)F…

ubuntu20.04 colmap安装

apt-get update apt-get install colmap 官方包网址: colmap_3.6really3.6-1_amd64.deb Debian 11 Download (pkgs.org) 官方安装非常简单,但是看网上都是手动安装教程,都麻烦的要命!我也踩了两天雷,还是看github上…

仕考网:公务员笔试和面试哪个难?

公务员笔试和面试哪个难?二者之间考察的方向不同,难度也是不同的。 笔试部分因其广泛的知识点和有限的考试时间显得难度更高一些,在笔试环节中,考生需在有限的时间内应对各种问题,而且同时还要面对激烈的竞争,在众多…

Java的内存泄漏和性能瓶颈

内存泄漏 ‌内存泄漏‌指的是程序中已分配的内存由于某种原因无法被释放或回收,导致内存的浪费和潜在的程序崩溃。在Java中,由于有垃圾回收机制(GC),直接的内存泄漏相对较少,但间接的内存泄漏仍然可能发生…

栈栈栈专题

一、基础 Leetcode 3174. 清除数字 class Solution { public:string clearDigits(string s) {string st; // 用string模拟栈的行为for(auto& v: s){if(isdigit(v)) st.pop_back();else st v;}return st;} }; 二、进阶 三、邻项消除 四、合法括号字符串 五、…

每日一题——第八十题

题目&#xff1a;输入十个整数&#xff0c;将其中最小的数与第一个数交换&#xff0c;将最大的数与最后一个数对调 #include <stdio.h> void swap(int *a, int *b) { int temp *a; *a *b; *b temp; } int main() { int numbers[10]; int i; int minIndex …

50Kg大载重长航时油电混动多旋翼无人机技术详解

50Kg大载重长航时油电混动多旋翼无人机技术是一项高度复杂且前沿的研究领域&#xff0c;它结合了燃油发动机的高能量密度和电动机的高效性&#xff0c;旨在提高无人机的续航能力和载重能力。以下是对该技术的详细解析&#xff1a; 产品轴距&#xff1a;2320mm 产品尺寸&#x…

数仓建模—维度建模之维度表

数仓建模—维度建模之维度表 维度表(Dimension Table)是数据仓库中描述业务过程中各种维度信息的表,用于提供上下文和描述性信息,以丰富事实数据的分析 维度表是维度建模的灵魂所在,在维度表设计中碰到的问题(比如维度变化、维度层次、维度一致性、维度整合和拆分等)都…

Django+Vue家居全屋定制系统的设计与实现

目录 1 项目介绍2 项目截图3 核心代码3.1 需要的环境3.2 Django接口层3.3 实体类3.4 config.ini3.5 启动类3.5 Vue 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍&#xff1a;CSDN认证博客专家&#xff0c;CSDN平台Java领域优质创作者&…

今年读过最绝的大模型神书死磕这本大模型神书!看完直接脱胎换骨!!

书名&#x1f4d6;&#xff1a;《大语言模型&#xff1a;基础与前沿》 该书深入阐述了大语言模型&#xff08;Large Language Model, LLM&#xff09;的基本概念和算法、研究前沿以及应用&#xff0c;内容全面且系统性强&#xff0c;适合&#x1f468;&#x1f3fb;‍&#x1…

踩坑记录-20240904--qt

1&#xff1a;请求接口没有数据 &#xff0c;请把本地的接口缓存清空&#xff0c;确保接口是从网络中拿数据 拿不到数据的情况下 接口判断是否从缓存中拿去数据也是false的情况 2&#xff1a;异步请求嵌套异步请求 要注意延时性的问题 因为第二个异步请求结束的时候 前面异步…

Oracle WITH简单例子

假设有一个名为 students 的表&#xff0c;包含字段 student_id、student_name、score 现在要查询成绩大于等于 80 分的学生信息以及所有学生的平均成绩。 WITH high_score_students AS (SELECT student_id, student_name, scoreFROM studentsWHERE score > 80 ) SELECT h…

【Python系列】FastApi发送Post请求

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

安装 Let‘s Encrypt certbot 生成多个域名免费 https 证书实录(linux pip 方式)

本文记录了我在华为云 EulerOS linux 云主机使用 python pip 方式安装配置 Let’s Encrypt certbot, 并为我的网站的多个域名生成免费 https 证书的整个过程, 包括 python 环境配置, 下载 certbot 及 certbot-nginx, 一次性生成多个域名的证书及注意事项, 以及最后配置 certbot…

Arduino IDE

Arduino IDE&#xff08;集成开发环境&#xff09;的安装过程是一个相对直观且易于操作的流程&#xff0c;主要步骤包括下载、安装和配置。以下将详细阐述Arduino IDE的安装过程&#xff0c;同时提供一些背景信息和注意事项&#xff0c;确保安装过程顺利进行。 一、Arduino ID…

k8s配置

一、前期准备 1、修改主机的/etc/hosts文件挟持域名 [rootk8s-master ~]# vim /etc/hosts 192.168.8.199 k8s-master 192.168.8.200 k8s-node1 192.168.8.201 k8s-node2 2、配置yum源 [rootk8s-master ~]# cd /etc/yum.repos.d/ [rootk8s-master yum.repos.d]# vim kubernetes…