YOLOv8-Pose NCNN安卓部署

news/2024/11/29 20:59:59/

YOLOv8Pose_NCNN_0">YOLOv8-Pose NCNN安卓部署

前言

YOLOv8-Pose NCNN安卓部署
在这里插入图片描述

目前的帧率可以稳定在30帧左右,下面是这个项目的github地址:https://github.com/gaoxumustwin/ncnn-android-yolov8-pose

介绍

在做YOLOv8-Pose NCNN安卓部署的时候,在github上发现了https://github.com/eecn/ncnn-android-yolov8-pose已经有大佬实现了这一操,并且也给出了它是如何导出ncnn模型的;

该作者是使用YOLOv8的官方给出的NCNN模型导出的方式,我按照这个方式导出ncnn模型,并观察其网络结构如下:

在这里插入图片描述

这个网络结构有一些错综复杂,感觉是有大量的计算图追踪,但是其安卓部署的时候速度在20帧左右也还可以接受,并且YOLOv8的官方给出了NCNN推理的python代码

https://github.com/ultralytics/ultralytics/tree/a007668e1fa8d5d586e6daa3924d65cfb139b8ac/examples/YOLOv8-NCNN-Python-Det-Pose-Cls-Seg-Obb

由于之前看到了三木君大佬,在23年的时候在知乎发布的一篇文章,只导出模型的Backbone + Neck,这种思路在玩瑞芯微设备的时候多次接触,三木君大佬也给出了原因:

  • 只导出模型,不导出后处理方便对模型进行公平的测速,MMYOLO 里面的算法多种多样,而且大多是 Backbone + Neck 结构,这样测速才能较为公平体现模型的速度差异。
  • 有些模型需要部署到嵌入式设备,导出后处理的话相当与会有海量的加减乘除要计算,由于模型应用会通过置信度过滤很多检测结果,会导致大多计算都是多余的。
  • 有些推理后端对后处理算子支持很差,可能会无法转换到对应后端的模型,或者转换后的模型无法充分利用 BPU/TPU/NPU/GPU 加速等等问题存在。
  • 很多要部署的模型都会用 c/c++ 重写后处理加速,后处理代码可以多线程计算,异步计算效率会更高。

同时在CSDN上发现了有佬根据上面的思路编写了NCNN代码并且给出了NCNN代码的github地址

此时只需要参照前面的yolov8安卓部署实现把这个只导出模型的Backbone + NeckYOLOv8-Pose NCNN代码进行简单改写就可以实现

环境

我使用的ultralytics版本如下:

pip install ultralytics==8.2.98

安装好ultralytics后,后面的操纵需要更改源码,所以需要知道ultralytics安装的路径,可以使用下面的方式进行查询

>>> import ultralytics
>>> ultralytics.__version__
'8.2.98'
>>> ultralytics.__path__
pathto\ultralytics

修改

模型导出时的网络修改

修改pathto\ultralytics\nn\modules\head.py文件中的POSE类,在其forward函数中加如下代码

if self.export or torch.onnx.is_in_onnx_export():results = self.forward_pose_export(x)return tuple(results)

同时在POSE类加上新如下函数

def forward_pose_export(self, x):results = []for i in range(self.nl):dfl = self.cv2[i](x[i]).permute(0, 2, 3, 1).contiguous()cls = self.cv3[i](x[i]).permute(0, 2, 3, 1).contiguous()kpt = self.cv4[i](x[i]).permute(0, 2, 3, 1).contiguous()results.append(torch.cat((cls, dfl, kpt), -1))return results

修改后的整体代码效果如下:

class Pose(Detect):"""YOLOv8 Pose head for keypoints models."""def __init__(self, nc=80, kpt_shape=(17, 3), ch=()):"""Initialize YOLO network with default parameters and Convolutional Layers."""super().__init__(nc, ch)self.kpt_shape = kpt_shape  # number of keypoints, number of dims (2 for x,y or 3 for x,y,visible)self.nk = kpt_shape[0] * kpt_shape[1]  # number of keypoints totalc4 = max(ch[0] // 4, self.nk)self.cv4 = nn.ModuleList(nn.Sequential(Conv(x, c4, 3), Conv(c4, c4, 3), nn.Conv2d(c4, self.nk, 1)) for x in ch)def forward(self, x):"""Perform forward pass through YOLO model and return predictions."""if self.export or torch.onnx.is_in_onnx_export():results = self.forward_pose_export(x)return tuple(results)bs = x[0].shape[0]  # batch sizekpt = torch.cat([self.cv4[i](x[i]).view(bs, self.nk, -1) for i in range(self.nl)], -1)  # (bs, 17*3, h*w)x = Detect.forward(self, x)if self.training:return x, kptpred_kpt = self.kpts_decode(bs, kpt)return torch.cat([x, pred_kpt], 1) if self.export else (torch.cat([x[0], pred_kpt], 1), (x[1], kpt))def forward_pose_export(self, x):results = []for i in range(self.nl):dfl = self.cv2[i](x[i]).permute(0, 2, 3, 1).contiguous()cls = self.cv3[i](x[i]).permute(0, 2, 3, 1).contiguous()kpt = self.cv4[i](x[i]).permute(0, 2, 3, 1).contiguous()results.append(torch.cat((cls, dfl, kpt), -1))return results

这种代码修改的导出方式并不影响训练的过程,仅在导出的时候会起到效果

更换激活函数并重头开始训练

YOLOv8默认使用的激活函数是SiLU,为了能更快的提升速度这里我换成了计算更高效的ReLU,并且更换激活函数后在转化成NCNN模型进行优化的时候会将卷积和ReLU融合之后,推理速度进一步得到了一些提升

更换激活函数后,需要对原有的Pytorch模型进行重新训练再导出ONNX

修改pathto\ultralytics\nn\modules\conv.py中的第39行左右的default_act = nn.SiLU() 修改为 default_act = nn.ReLU()

修改完成后使用训练脚本进行训练

from ultralytics import YOLOmodel = YOLO('yolov8n-pose.yaml').load('yolov8n-pose.pt')  results = model.train(data='coco-pose.yaml', epochs=100, imgsz=640, workers=4, batch=64, project='Pose_runs', name='pose_n_relu')

**注意: **

1、 .load(‘yolov8n-pose.pt’)时加载预训练模型,虽然预训练的激活函数不一样,但是我测试了以下发现加上预训练模型前面的epoch的结果损失很

2、上面的数据使用的是coco2017数据集,训练环境为4090显卡,训练10个epoch接近一个小时

3、coco-pose.yaml、yolov8n-pose.yaml均是ultralytics默认配置文件

导出的ONNX名字修改

如果需要修改输出的名称则要去修改 pathto\ultralytics\engine\exporter.py 中的 export_onnx函数

ONNX导出

编写一个export.py的导出onnx的python代码

from ultralytics import YOLO# load a pretrained model
model = YOLO('pathto\best.pt') # 训练得到的权重# export onnx
model.export(format='onnx', opset=11, simplify=True, dynamic=False, imgsz=640)

NCNN转化和优化

下面是onnx转化为NCNN的代码和对NCNN模型进行fp16的优化

./onnx2ncnn yolov8n-pose.onnx yolov8n-pose-sim.param yolov8n-pose-sim.bin
./ncnnoptimize yolov8n-pose-sim.param  yolov8n-pose-sim.bin yolov8n-pose-opt-fp16.param yolov8n-pose-opt-fp16.bin 1

在使用ncnnoptimize对relu激活函数的onnx模型进行优化的时候,你会发现relu和卷积会进行算子融合

(base) gx@RUKN0DC:/mnt/e/ubuntu_20.04/ncnn/build/install/bin$ ./ncnnoptimize yolov8pose>yolov8pose-relu.param yolov8pose>yolov8pose-relu.bin yolov8pose>yolov8pose-relu-opt.param yolov8pose>yolov8pose-relu-opt.bin 1
fuse_convolution_activation /model.0/conv/Conv /model.0/act/Relu
fuse_convolution_activation /model.1/conv/Conv /model.1/act/Relu
fuse_convolution_activation /model.2/cv1/conv/Conv /model.2/cv1/act/Relu
fuse_convolution_activation /model.2/m.0/cv1/conv/Conv /model.2/m.0/cv1/act/Relu
fuse_convolution_activation /model.2/m.0/cv2/conv/Conv /model.2/m.0/cv2/act/Relu
fuse_convolution_activation /model.2/cv2/conv/Conv /model.2/cv2/act/Relu
fuse_convolution_activation /model.3/conv/Conv /model.3/act/Relu
fuse_convolution_activation /model.4/cv1/conv/Conv /model.4/cv1/act/Relu
fuse_convolution_activation /model.4/m.0/cv1/conv/Conv /model.4/m.0/cv1/act/Relu
fuse_convolution_activation /model.4/m.0/cv2/conv/Conv /model.4/m.0/cv2/act/Relu
fuse_convolution_activation /model.4/m.1/cv1/conv/Conv /model.4/m.1/cv1/act/Relu
fuse_convolution_activation /model.4/m.1/cv2/conv/Conv /model.4/m.1/cv2/act/Relu
fuse_convolution_activation /model.4/cv2/conv/Conv /model.4/cv2/act/Relu
fuse_convolution_activation /model.5/conv/Conv /model.5/act/Relu
fuse_convolution_activation /model.6/cv1/conv/Conv /model.6/cv1/act/Relu
fuse_convolution_activation /model.6/m.0/cv1/conv/Conv /model.6/m.0/cv1/act/Relu
fuse_convolution_activation /model.6/m.0/cv2/conv/Conv /model.6/m.0/cv2/act/Relu
fuse_convolution_activation /model.6/m.1/cv1/conv/Conv /model.6/m.1/cv1/act/Relu
fuse_convolution_activation /model.6/m.1/cv2/conv/Conv /model.6/m.1/cv2/act/Relu
fuse_convolution_activation /model.6/cv2/conv/Conv /model.6/cv2/act/Relu
fuse_convolution_activation /model.7/conv/Conv /model.7/act/Relu
fuse_convolution_activation /model.8/cv1/conv/Conv /model.8/cv1/act/Relu
fuse_convolution_activation /model.8/m.0/cv1/conv/Conv /model.8/m.0/cv1/act/Relu
fuse_convolution_activation /model.8/m.0/cv2/conv/Conv /model.8/m.0/cv2/act/Relu
fuse_convolution_activation /model.8/cv2/conv/Conv /model.8/cv2/act/Relu
fuse_convolution_activation /model.9/cv1/conv/Conv /model.9/cv1/act/Relu
fuse_convolution_activation /model.9/cv2/conv/Conv /model.9/cv2/act/Relu
fuse_convolution_activation /model.12/cv1/conv/Conv /model.12/cv1/act/Relu
fuse_convolution_activation /model.12/m.0/cv1/conv/Conv /model.12/m.0/cv1/act/Relu
fuse_convolution_activation /model.12/m.0/cv2/conv/Conv /model.12/m.0/cv2/act/Relu
fuse_convolution_activation /model.12/cv2/conv/Conv /model.12/cv2/act/Relu
fuse_convolution_activation /model.15/cv1/conv/Conv /model.15/cv1/act/Relu
fuse_convolution_activation /model.15/m.0/cv1/conv/Conv /model.15/m.0/cv1/act/Relu
fuse_convolution_activation /model.15/m.0/cv2/conv/Conv /model.15/m.0/cv2/act/Relu
fuse_convolution_activation /model.15/cv2/conv/Conv /model.15/cv2/act/Relu
fuse_convolution_activation /model.16/conv/Conv /model.16/act/Relu
fuse_convolution_activation /model.18/cv1/conv/Conv /model.18/cv1/act/Relu
fuse_convolution_activation /model.18/m.0/cv1/conv/Conv /model.18/m.0/cv1/act/Relu
fuse_convolution_activation /model.18/m.0/cv2/conv/Conv /model.18/m.0/cv2/act/Relu
fuse_convolution_activation /model.18/cv2/conv/Conv /model.18/cv2/act/Relu
fuse_convolution_activation /model.19/conv/Conv /model.19/act/Relu
fuse_convolution_activation /model.21/cv1/conv/Conv /model.21/cv1/act/Relu
fuse_convolution_activation /model.21/m.0/cv1/conv/Conv /model.21/m.0/cv1/act/Relu
fuse_convolution_activation /model.21/m.0/cv2/conv/Conv /model.21/m.0/cv2/act/Relu
fuse_convolution_activation /model.21/cv2/conv/Conv /model.21/cv2/act/Relu
fuse_convolution_activation /model.22/cv2.0/cv2.0.0/conv/Conv /model.22/cv2.0/cv2.0.0/act/Relu
fuse_convolution_activation /model.22/cv2.0/cv2.0.1/conv/Conv /model.22/cv2.0/cv2.0.1/act/Relu
fuse_convolution_activation /model.22/cv3.0/cv3.0.0/conv/Conv /model.22/cv3.0/cv3.0.0/act/Relu
fuse_convolution_activation /model.22/cv3.0/cv3.0.1/conv/Conv /model.22/cv3.0/cv3.0.1/act/Relu
fuse_convolution_activation /model.22/cv4.0/cv4.0.0/conv/Conv /model.22/cv4.0/cv4.0.0/act/Relu
fuse_convolution_activation /model.22/cv4.0/cv4.0.1/conv/Conv /model.22/cv4.0/cv4.0.1/act/Relu
fuse_convolution_activation /model.22/cv2.1/cv2.1.0/conv/Conv /model.22/cv2.1/cv2.1.0/act/Relu
fuse_convolution_activation /model.22/cv2.1/cv2.1.1/conv/Conv /model.22/cv2.1/cv2.1.1/act/Relu
fuse_convolution_activation /model.22/cv3.1/cv3.1.0/conv/Conv /model.22/cv3.1/cv3.1.0/act/Relu
fuse_convolution_activation /model.22/cv3.1/cv3.1.1/conv/Conv /model.22/cv3.1/cv3.1.1/act/Relu
fuse_convolution_activation /model.22/cv4.1/cv4.1.0/conv/Conv /model.22/cv4.1/cv4.1.0/act/Relu
fuse_convolution_activation /model.22/cv4.1/cv4.1.1/conv/Conv /model.22/cv4.1/cv4.1.1/act/Relu
fuse_convolution_activation /model.22/cv2.2/cv2.2.0/conv/Conv /model.22/cv2.2/cv2.2.0/act/Relu
fuse_convolution_activation /model.22/cv2.2/cv2.2.1/conv/Conv /model.22/cv2.2/cv2.2.1/act/Relu
fuse_convolution_activation /model.22/cv3.2/cv3.2.0/conv/Conv /model.22/cv3.2/cv3.2.0/act/Relu
fuse_convolution_activation /model.22/cv3.2/cv3.2.1/conv/Conv /model.22/cv3.2/cv3.2.1/act/Relu
fuse_convolution_activation /model.22/cv4.2/cv4.2.0/conv/Conv /model.22/cv4.2/cv4.2.0/act/Relu
fuse_convolution_activation /model.22/cv4.2/cv4.2.1/conv/Conv /model.22/cv4.2/cv4.2.1/act/Relu
Input layer images without shape info, shape_inference skipped
Input layer images without shape info, estimate_memory_footprint skipped

下面是ONNX、NCNN模型以及NCNN优化后的三个模型结构简单对比

在这里插入图片描述

下面是不同的激活函数最后NCNN优化后的网络结构简单对比

在这里插入图片描述

安卓代码的修改

参考这两个代码进行修改

https://github.com/eecn/ncnn-android-yolov8-pose

https://github.com/Rachel-liuqr/yolov8s-pose-ncnn

有以下几个修改的地方:

  1. 将sigmoid函数修改为了使用快速指数fast_exp的sigmoid
  2. 将 cv::dnn::NMSBoxes 修改了使用纯C++代码的实现

具体的代码过程,有兴趣的可以去查看

本人水平高,代码应该还有更大的优化空间!!

参考资料

https://github.com/eecn/ncnn-android-yolov8-pose

https://github.com/ultralytics/ultralytics/tree/a007668e1fa8d5d586e6daa3924d65cfb139b8ac/examples/YOLOv8-NCNN-Python-Det-Pose-Cls-Seg-Obb
https://blog.csdn.net/Rachel321/article/details/130381788

https://github.com/Rachel-liuqr/yolov8s-pose-ncnn

https://zhuanlan.zhihu.com/p/622596922


http://www.ppmy.cn/news/1550987.html

相关文章

2024年11月27日Github流行趋势

项目名称:screenshot-to-code 项目维护者:abi clean99 sweep-ai kachbit vagusX项目介绍:通过上传截图将其转换为整洁的代码(支持HTML/Tailwind/React/Vue)。项目star数:62,429项目fork数:7,614…

QT 实现窗口关闭动画

1.实现效果 两种关闭窗口动画效果。 下坠式关闭动画。 中心式关闭动画 2.实现思路 创建了两个动画并行执行,一个是控制窗口尺寸的动画,一个是控制透明度的动画。 先创建一个closeAnimation动画,然后创建opacityAnimation动画,创建一个QParallelAnimationGroup并行动画…

Table 滚动条始终停靠在可视区域的底部

1. 话题引入 存在这样一个场景:当页面尺寸发生变化时,希望滚动条能够随之动态调整,始终展示在 table 的可视区域的最下方,而不是整个 table 本身的最底部。 这种行为可以提升用户的使用体验,尤其是在处理大数据表格时…

裸金属服务器和专属主机的区别是什么?

在当今互联网时代,人们越来越重视服务器的使用。裸金属服务器和专属主机是两种常见的服务器形式。裸金属服务器和云主机有什么区别呢? 一、定义和概念 裸金属服务器和云主机都是租用物理服务器的一种方式。 裸金属服务器是指没有安装虚拟化技术的物理…

使用 Python 剪辑视频的播放速度

要使用 Python 调整视频的播放速度,可以利用 moviepy 库中的 fx(特效)模块来实现这一功能。通过 moviepy.editor 中的 VideoFileClip 类和 fx.speedx 函数,可以轻松地调整视频的播放速度。 安装 moviepy 首先,确保已…

电脑上的ip地址可以改吗?如何改变ip地址

在现代网络环境中,IP地址作为设备在网络中的唯一标识,扮演着至关重要的角色。无论是日常上网冲浪,还是进行专业的网络操作,IP地址都与我们息息相关。那么,电脑上的IP地址可以改吗?答案是肯定的。接下来&…

linux系统中常用文件日常使用命令记录

我们办公机是Ubuntu系统; 记录下工作中经常使用的几个文件或命令或一些零碎的知识点: (该文档会持续更新) 查看系统信息: uname -a cat /etc/product-info cat /etc/os-version 存放系统启停脚本 /etc/init.d/ 存放源…

豆包MarsCode算法题:三数之和问题

问题描述 思路分析 1. 排序数组 目的: 将数组 arr 按升序排序,这样可以方便地使用双指针找到满足条件的三元组,同时避免重复的三元组被重复计算。优势: 数组有序后,处理两个数和 target - arr[i] 的问题可以通过双指针快速找到所有可能的组…