yolov8实战之torchserve服务化:使用yolov8x来预打标

前言

最近在做一个目标检测的任务,部署在边缘侧,对于模型的速度要求比较严格(yolov8n这种),所以模型的大小不能弄太大,所以原模型的性能受限,更多的重点放在增加数据上。实测yolov8x在数据集上的效果比小模型要好不少,所以想法是用yolov8x来预打标,然后选择一些置信度高的样本加到训练集来训练yolov8n,减少标注的成本。原始数据是在ceph上,比较直观的方式就是一张张读,然后一张张推理。这样效率不高,毕竟GPU适合组batch去推理,所以为了效率就需要自己去组成batch然后推理,然后再把batch的结果再分开对应到单张图上,虽然并不难,还是挺繁琐的,这也是我以前的做法。其实可以不需要这么麻烦,这种batching操作很多的推理服务都会帮我们做掉,我们只需要并发去请求就好了,和做单个没什么区别,要加其他的模型进行组合逻辑也是非常方便。GroundingDINO(一种开集目标检测算法)服务化,根据文本生成检测框_CodingInCV的博客-CSDN博客这个里面我们使用torchserve来实现了算法的服务化,这里我们依旧还是使用torchserve。基础就不做介绍了,可以读上面这篇。与GroundingDINO不同的是,这里我们会启用batch操作,而GroudingDINO里没有支持batch。

导出onnx模型

为了方便起见,我们使用onnx模型,避免去处理yolov8的pytorch环境问题,官网提供了导出的方式:Detect - Ultralytics YOLOv8 Docs
为了支持动态的batch, 我们导出时要以dynamic的方式导出,我这里对导出做了一点修改,只让batch为动态,而输入尺寸固定, 修改engine/exporter.py:
image.png
为了支持我们新增的dynamic_batch参数,我们还需要再default.yaml中增加这个参数,具体可以参考:yolov8训练进阶:新增配置参数_CodingInCV的博客-CSDN博客
image.png
然后自行写脚本转换:

from ultralytics import YOLOmodel = YOLO('yolov8x6404/weights/last.pt')  # initialize
model.export(format = "onnx", opset = 11, simplify = True,dynamic_batch=True, imgsz=640)  # export to onnx

导出的模型将和输入的模型在同一个路径。

自定义handler

handler的写法

在GroundingDINO(一种开集目标检测算法)服务化,根据文本生成检测框_CodingInCV的博客-CSDN博客我们没有提到怎么写自己的模型handler,所谓模型handler就是告诉torchserve我们的模型如何载入、前处理和后处理。官方教程:Custom Service — PyTorch/Serve master documentation
torchserve自身带了一些handler:
BaseHandler: handler的基类,我们可以继承这个,也可以不继承,如果不继承则至少要实现initializehandle方法。
image.png

我们可以继承他们来实现自己的,也可以不继承,这里以不继承来实现,通用性比较强,不管什么模型都可以搞定,主要就是实现一个类,这个类至少要实现initializehandle方法:
initialize 就是初始化模型,这个方法必须有一个输入参数context(serve/ts/context.py at master · pytorch/serve (github.com)), 从这个参数我们可以拿到比如模型的路径、显卡号等信息。
handle 是接收输入请求和返回处理结果的接口,具有2个参数,第一个参数是输入请求,第二个参数也是context。
对于每个模型我们可以将推理过程拆分为三个过程(方法):preprocess、inference、postprocess,即前处理、推理、后处理,我们的handler只要实现这三个方法,然后依次在handle中调用即可,最后把输出按要求组合起来,handle的返回值必须是list of list,也就是数组的数组,外层list的长度等于输入的batch数(torchserve可以自动组batch),内层的list是单个请求的输出,里面的元素可以是dict,完整代码如下:

import logging
import os,sys
import onnxruntime as ort
import base64
import numpy as np
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
try:from common.common import resize_image
except:from common import resize_image
import cv2logger = logging.getLogger(__name__)
console_logger = logging.StreamHandler(sys.stdout)
console_logger.setLevel(logging.DEBUG)
console_logger.setFormatter(logging.Formatter("%(asctime)s %(name)s [%(levelname)s] %(message)s"))
logger.addHandler(console_logger)class YOLOV8Handler(object):def __init__(self):self.context = Noneself.initialized = Falseself.model = Noneself.input_name = Noneself.input_shape = Noneself.conf_thres = 0.45self.iou_thres = 0.45self.class2label = {0: "body",1: "head",}self.device = Nonedef initialize(self, context):#  load the modellogger.info("initialize grounding dino handler")self.context = contextself.manifest = context.manifestproperties = context.system_propertiesmodel_dir = properties.get("model_dir")# Read model serialize/pt fileserialized_file = self.manifest['model']['serializedFile']model_pt_path = os.path.join(model_dir, serialized_file)if not os.path.isfile(model_pt_path):raise RuntimeError("Missing the model file")# get deviceavailable_providers =  ort.get_available_providers()provide_options = {}if "CUDAExecutionProvider" in available_providers:self.device = str(properties.get("gpu_id"))provide_options["device_id"] = self.deviceprivider = "CUDAExecutionProvider"logger.info("using gpu {}".format(self.device))else:privider = "CPUExecutionProvider"self.model = ort.InferenceSession(model_pt_path, providers=[privider], provider_options=[provide_options])self.initialized = True# get input shapeself.input_name = self.model.get_inputs()[0].nameself.input_shape = self.model.get_inputs()[0].shapelogger.info("model loaded successfully")def preprocess(self, data):logger.info("preprocess data")preprocessed_data = []preprocessed_params = []network_input_height = self.input_shape[2]network_input_width = self.input_shape[3]for row in data:input = row.get("data") or row.get("body")if isinstance(input, dict) and "image" in input:image = input["image"]else:logger.error("No image found in the request")assert False, "No  image found in the request"if isinstance(image, str):# if the image is a string of bytesarray.image = base64.b64decode(image)# If the image is sent as bytesarrayif isinstance(image, (bytearray, bytes)):image = cv2.imdecode(np.frombuffer(image, dtype=np.uint8), cv2.IMREAD_ANYCOLOR)else:logger.error("No caption or image found in the request")assert False, "No caption or image found in the request"image_h, image_w, _ = image.shapeimage, newh, neww, top, left  = resize_image(image, keep_ratio=True, dst_width=network_input_width, dst_height=network_input_height)image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)preprocessed_data.append(image)preprocessed_params.append((newh, neww, top, left, image_h, image_w))logger.info("preprocess data done")preprocessed_data = np.array(preprocessed_data).astype(np.float32)preprocessed_data /= 255.0preprocessed_data = np.transpose(preprocessed_data, (0, 3, 1, 2))return preprocessed_data, preprocessed_paramsdef inference(self, data, *args, **kwargs):logger.info("inference data")outputs = self.model.run(None, {self.input_name: data})return outputs[0]def postprocess_one(self, output, request_param):newh, neww, top, left, image_h, image_w = request_paramx_factor = image_w / newwy_factor = image_h / newhoutputs = outputoutputs = np.transpose(np.squeeze(outputs))boxes = {}for row in outputs:classes_scores = row[4:]max_score = np.max(classes_scores)if max_score < self.conf_thres:continueclass_id = np.argmax(classes_scores)x, y, w, h = row[0], row[1], row[2], row[3]# Calculate the scaled coordinates of the bounding boxx1 = x - w / 2y1 = y - h / 2x1 = x1-lefty1 = y1-topx2 = x1 + wy2 = y1 + h# Scale the coordinates according to the original imagex1 = x1 * x_factory1 = y1 * y_factorx2 = x2 * x_factory2 = y2 * y_factorif class_id not in boxes:boxes[class_id] = [[],[]]boxes[class_id][0].append([x1, y1, x2, y2])boxes[class_id][1].append(float(max_score))# NMSnms_boxes = []for class_id in boxes:candidate_boxes, scores = boxes[class_id]indices = cv2.dnn.NMSBoxes(candidate_boxes, scores, self.conf_thres, self.iou_thres)for index in indices:nms_boxes.append((candidate_boxes[index], scores[index], self.class2label[class_id]))return nms_boxesdef postprocess(self, data):outputs, request_params = databoxes = []for i in range(len(outputs)):output = outputs[i]request_param = request_params[i]nms_boxes = self.postprocess_one(output, request_param)boxes.append(nms_boxes)return boxesdef handle(self, data, context):self.context = contextimage, request_params = self.preprocess(data)outputs = self.inference(image)boxes_batch = self.postprocess((outputs, request_params))results = []for boxes in boxes_batch:ret = []for box, score, label in boxes:ret.append({"box": box, "score": score, "label": label})results.append(ret)return results

注意:为了实现batch操作,我们实现的接口都应该是对batch来的,而不是只对一张图。

调试handler

我们可以模仿context的内容来初始化handler, 然后调用handle方法来调试结果是否正常。

if __name__=="__main__":import addictcontext = addict.Dict()context.system_properties = {"gpu_id": 0,"model_dir": "./weights"}context.manifest = {"model": {"serializedFile": "yolov8x.onnx"}}handler = YOLOV8Handler()handler.initialize(context)image_path = "./body.png"with open(image_path, "rb") as f:image = f.read()data = [{"data": {"image": image}},{"data": {"image": image}}]outputs = handler.handle(data, context)print(outputs)

镜像制作

在GroundingDINO(一种开集目标检测算法)服务化,根据文本生成检测框_CodingInCV的博客-CSDN博客中镜像的基础上安装onnxruntime-gpu, 或者在启动时安装

转换模型

这个操作和上一篇文章一样,只是权重文件和需要handler修改一下,不赘述:

docker run --rm -it -v $(pwd):/data -w /data torchserve:groundingdino bash -c "torch-model-archiver --model-name yolov8x --version 1.0 --serialized-file weights/yolov8x.onnx --handler yolov8/yolov8_handler.py --extra-files common/*.py"

启动服务

与上一篇服务化不同,我们启动时不载入所有模型,而是通过post接口去开启,方便设置模型的batch size, 其中端口号根据需要设置。

docker run -d --name groundingdino -v $(pwd)/model_store:/model_store -p 8080:8080 -p 8081:8081 -p 8082:8082 torchserve:groundingdino bash -c "pip install onnxruntime-gpu && torchserve --start --foreground --model-store /model_store

使用Management API载入模型

Management API — PyTorch/Serve master documentation
可以用curl也可以用postman, 如

curl -X POST "localhost:8081/models?url=yolov8x.mar&batch_size=8&max_batch_delay=50"

如果需要再修改batchsize, 要先调用卸载模型的接口写在,然后再调用上面的接口。
通过上面的操作,torchserve会帮我们组batch, 最大为8.

调用

import json
import base64
import requests
import threadpoolurl = "http://localhost:8080/predictions/yolov8x"
headers = {"Content-Type": "application/json"}def request_worker(arg):image_path = "./b03492798d5b44eeb70856b9253386df.jpeg"data = {"image": base64.b64encode(open(image_path, "rb").read()).decode("utf-8")}response = requests.post(url, headers=headers, json=data)print(response.text)if __name__ == "__main__":pool = threadpool.ThreadPool(24)requests_task = threadpool.makeRequests(request_worker, range(100))[pool.putRequest(req) for req in requests_task]pool.wait()

这里,我们用多线程模仿了高并发的去调用模型,这样torchserve就可以自动的根据负载情况来组成batch了,提高模型的吞吐量。类似的,我们就可以方便的使用多线程去读取数据然后调用模型来得到预打标的结果,而不用去处理模型的依赖、组batch等逻辑,也可以很方便的提供给其他需要的同事来使用。

结语

本文简述了将yolov8服务化的过程,服务化后,我们可以方便的用模型来进行数据的预打标、分享模型给他人使用。
f77d79a3b79d6d9849231e64c8e1cdfa~tplv-dy-resize-origshort-autoq-75_330.jpeg

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

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

相关文章

Golang Gorm 一对多关系 关系表创建

一对多关系 我们先从一对多开始多表关系的学习因为一对多的关系生活中到处都是&#xff0c;例如&#xff1a; 老板与员工女神和添狗老师和学生班级与学生用户与文章 在创建的时候先将没有依赖的创建。表名称ID就是外键。外键要和关联的外键的数据类型要保持一致。 package ma…

【Linux】进程状态|僵尸进程|孤儿进程

前言 本文继续深入讲解进程内容——进程状态。 一个进程包含有多种状态&#xff0c;有运行状态&#xff0c;阻塞状态&#xff0c;挂起状态&#xff0c;僵尸状态&#xff0c;死亡状态等等&#xff0c;其中&#xff0c;阻塞状态还包含深度睡眠和浅度睡眠状态。 个人主页&#xff…

【Linux网络】Cookie和session的关系

目录 一、Cookie 和 session 共同之处 二、Cookie 和 session 区别 2.1、cookie 2.2、session 三、cookie的工作原理 四、session的工作原理 一、Cookie 和 session 共同之处 Cookie 和 Session 都是用来跟踪浏览器用户身份的会话方式。 二、Cookie 和 session 区别 2.…

Linux下的Shell基础——正则表达式入门(四)

前言&#xff1a; 正则表达式使用单个字符串来描述、匹配一系列符合某个语法规则的字符串。在很多文本编辑器里&#xff0c;正则表达式通常被用来检索、替换那些符合某个模式的文本。 在Linux 中&#xff0c;grep&#xff0c;sed&#xff0c;awk 等文本处理工具都支持…

redis持久化机制 事务详解

目录 前言&#xff1a; 持久化机制 RDB&#xff08;Redis DataBase&#xff09; 手动触发 save bgsave 自动触发 RDB特点 AOF&#xff08;append only file&#xff09; 缓冲区刷新策略 重写机制 aof重写流程 混合持久化 事务 事务操作命令 WATCH WATCH实现原…

分布式计算框架:Spark、Dask、Ray

目录 什么是分布式计算 分布式计算哪家强&#xff1a;Spark、Dask、Ray 2 选择正确的框架 2.1 Spark 2.2 Dask 2.3 Ray 什么是分布式计算 分布式计算是一种计算方法&#xff0c;和集中式计算是相对的。 随着计算技术的发展&#xff0c;有些应用需要非常巨大的计算能力才…

亿赛通电子文档安全管理系统 RCE漏洞复现(QVD-2023-19262)

0x01 产品简介 亿赛通电子文档安全管理系统&#xff08;简称&#xff1a;CDG&#xff09;是一款电子文档安全加密软件&#xff0c;该系统利用驱动层透明加密技术&#xff0c;通过对电子文档的加密保护&#xff0c;防止内部员工泄密和外部人员非法窃取企业核心重要数据资产&…

VIT 和Swin Transformer

VIT&#xff1a;https://blog.csdn.net/qq_37541097/article/details/118242600 Swin Transform&#xff1a;https://blog.csdn.net/qq_37541097/article/details/121119988 一、VIT 模型由三个模块组成&#xff1a; Linear Projection of Flattened Patches(Embedding层) Tran…

C语言基础之——数组

前言&#xff1a;本篇文章&#xff0c;我们将对一维数组&#xff0c;和二维数组进行展开式的讲解&#xff0c;并进行实际应用。 目录 一.一维数组 1.一维数组的创建和初始化 &#xff08;1&#xff09;数组的创建 &#xff08;2&#xff09;数组的初始化 2.一维数组的使用…

二叉树中的最大路径和-递归

路径 被定义为一条从树中任意节点出发&#xff0c;沿父节点-子节点连接&#xff0c;达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点&#xff0c;且不一定经过根节点。 路径和 是路径中各节点值的总和。 给你一个二叉树的根节点 root…

AI智能语音机器人的基本业务流程

先画个图&#xff0c;了解下AI语音机器人的基本业务流程。 上图是一个AI语音机器人的业务流程&#xff0c;简单来说就是首先要配置话术&#xff0c;就是告诉机器人在遇到问题该怎么回答&#xff0c;这个不同公司不同行业的差别比较大&#xff0c;所以一般每个客户都会配置其个性…

华为OD机试 - 最佳植树距离 - 二分查找(Java 2023 B卷 100分)

目录 一、题目描述二、输入描述三、输出描述四、备注说明五、二分查找六、解题思路七、Java算法源码八、效果展示1、输入2、输出3、说明 一、题目描述 按照环保公司要求&#xff0c;小明需要在沙化严重的地区进行植树防沙工作&#xff0c;初步目标是种植一条直线的树带。 由于…

微信小程序——van-field中的left-icon属性自定义

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

vue 简单实验 v-for 循环

1.代码 <script src"https://unpkg.com/vuenext" rel"external nofollow" ></script> <div id"list-rendering"><ol><li v-for"todo in todos">{{ todo.text }}</li></ol> </div> &…

Jenkins自动化部署Vue项目

1、新建item&#xff0c;选择 Freestyle project 2、源码管理选择git&#xff0c;输入git仓库地址和授权账号&#xff0c;并指明要部署的分支 3、构建选择 Execute shell&#xff0c;输入vue项目打包命令 命令示例&#xff1a; source /etc/profile node -v npm config set re…

【stable-diffusion使用扩展+插件和模型资源(上】

文章目录 前言一、插件推荐1.qrcode-monster2.sd-webui-openpose-editor3.sd-webui-depth-lib4.roop&#xff08;换脸插件&#xff09;5.sd-webui-qrcode-toolkit&#xff08;艺术二维码&#xff09;5.光源控制6.二次元转真人7.动态视频转场&#xff08;loopback-wave&#xff…

无涯教程-PHP - preg_replace()函数

preg_replace() - 语法 mixed preg_replace (mixed pattern, mixed replacement, mixed string [, int limit [, int &$count]] ); preg_replace()函数的操作与POSIX函数ereg_replace()相同&#xff0c;不同之处在于可以在模式和替换输入参数中使用正则表达式。 可选的输…

社科院与美国杜兰大学金融管理硕士项目——畅游于金融世界

随着社会经济的不断发展&#xff0c;职场竞争愈发激烈&#xff0c;很多同学都打算通过报考研究生来实现深造&#xff0c;提升自己的综合能力和竞争优势&#xff0c;获得优质的证书。而对于金融专业的学生和在职人员来说&#xff0c;社科院与美国杜兰大学金融管理硕士项目是一个…

【Hello Algorithm】堆和堆排序

本篇博客简介&#xff1a; 讲解堆和堆排序相关算法 堆和堆排序 堆堆的概念堆的性质堆的表示形式堆的增加删除堆的最大值 堆排序堆排序思路时间复杂度为N的建堆方法已知一个近乎有序的数组 使用最佳排序方法排序 堆 堆的概念 这里注意&#xff01;&#xff01;&#xff01; 这…

ELK + Kibana + Logstash实现可视化日志

&#x1f61c;作 者&#xff1a;是江迪呀✒️本文关键词&#xff1a;elasticsearch、kibana、logstash、日志收集、日志可视化☀️每日 一言&#xff1a;坚持就是胜利啊&#xff0c;哥~ 一、前言 面试官&#xff1a;在日常开发工作中你们是如何查看日志的呢&#x…