# Yolov8的NCNN模型导出与部署
# 1.yolov8
导出ncnn
模型
# 1).ultralytics
直接导出ncnn
模型
from ultralytics import YOLO
# Create a model
model = YOLO('/xx/data/code/ultralytics/yolov8n-pose.pt')
# Export the model to NCNN with arguments
model.export(format='ncnn', half=True, imgsz=480)
执行上面的代码,会在当前路径创建yolov8n-pose_ncnn_model
文件夹:
.
├── metadata.yaml
├── model.ncnn.bin
├── model.ncnn.param
└── model_ncnn.py
model_ncnn.py
是ncnn
模型推理的测试文件,可以直接执行测试导出的模型是否能正常工作。
不过ultralytics
是使用pnnx
(opens new window)导出的ncnn
模型,使用c++
导入,
auto net_ = std::make_unique<ncnn::Net>();
assert(net_->load_param(param_file) == 0);
assert(net_->load_model(bin_file) == 0);
# 2).ultralytics
导出onnx
模型再转ncnn
模型
step1:
ultralytics
导出onnx
模型
from ultralytics import YOLO
# Create a model
model = YOLO('/xx/data/code/ultralytics/yolov8n-pose.pt')
# Export the model to NCNN with arguments
model.export(format='ncnn', half=True, imgsz=480)
step2:简化
onnx
模型
直接导出的onnx
模型包含的Gather/Shape
算子,在ncnn
中并不支持,直接转换会失败,可以先使用onnxsim
工具对onnx
模型进行简化。
安装onnxsim
包:
pip3 install -U pip && pip3 install onnxsim
使用:
onnxsim yolov8n-pose.onnx yolov8n-pose-sim.onnx
输出:
Here is the difference:
┏━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┓
┃ ┃ Original Model ┃ Simplified Model ┃
┡━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━┩
│ Add │ 9 │ 9 │
│ Concat │ 21 │ 21 │
│ Constant │ 166 │ 159 │
│ Conv │ 73 │ 73 │
│ Div │ 1 │ 1 │
│ Identity │ 6 │ 0 │
│ MaxPool │ 3 │ 3 │
│ Mul │ 66 │ 66 │
│ Reshape │ 10 │ 10 │
│ Resize │ 2 │ 2 │
│ Sigmoid │ 65 │ 65 │
│ Slice │ 2 │ 2 │
│ Softmax │ 1 │ 1 │
│ Split │ 10 │ 10 │
│ Sub │ 2 │ 2 │
│ Transpose │ 2 │ 2 │
│ Model Size │ 12.7MiB │ 12.7MiB │
└────────────┴────────────────┴──────────────────┘
step3:使用
onnx2ncnn
将onnx
模型转换为ncnn
模型
安装ncnn
,在ncnn
(opens new window)仓库下载ncnn
,可以下载对应平台已编译好的包,也可以自己编译,解压后在bin
路径下有我们要使用的可执行文件onnx2ncnn
。
Usage: ./onnx2ncnn [onnxpb] [ncnnparam] [ncnnbin]
转优化模型,
ncnnoptimize mobilenet.param mobilenet.bin mobilenet-opt.param mobilenet-opt.bin 65536
转二进制模型:
Usage: ./ncnn2mem [ncnnproto] [ncnnbin] [idcpppath] [memcpppath]
# like
./ncnn2mem mobilenet.param mobilenet.bin mobilenet.id.h mobilenet.mem.h
使用二进制文件:
#include <net.h>
#include <mobilenet.id.h>
#include <mobilenet.mem.h
int inference()
{
auto net = ncnn::Net();
net.load_param(model_ncnn_param_bin);
net.load_model(model_ncnn_bin);
// ...
// extract content
auto ex = net.create_extractor();
ncnn:Mat x;
ex.input(model_ncnn_param_id::BLOB_in0 ,x);
ncnn::Mat out;
ex.extract(model_ncnn_param_id::BLOB_out0 ,out);
return 0;
}
以上就可以实现导出并部署yolov8
的ncnn
模型了。下面来看下yolov8
模型的inference
过程。
# 2.yolov8
关键点检测的推理过程
# 检测框推理过程
yolov8
和yolov5
基本架构是相同的,都是backbone
输出P3, P4, P5
三级特征图, 主要的区别体现在针对关键点,实例分割,目标检测任务使用的输出头的不同。yolov8
检测框的预测方式和yolov5
有很大的不同。
yolov8
中使用的检测框预测方式和Nanodet/FCOS
算法中相似,都是一种anchor free
的方式,不需要人工筛选锚框。yolov8
还使用了Generalized Focal Loss
,只需要设置超参数reg_max
,默认为16
,然后对特征做softmax
后经1x1
卷积变成left-top-right-bottom
的坐标,不过直接回归的结果是相对于特征图每个格的中心点在特征图尺度上的坐标值。
# 关键点推理过程
关键点检测是在目标检测的输出头上加多一个分支,用来实现关键点的预测。
其中,kpts_decode
的逻辑可以参考函数:
class Pose:
def kpts_decode(self, bs, kpts):
"""Decodes keypoints."""
ndim = self.kpt_shape[1]
if self.export: # required for TFLite export to avoid 'PLACEHOLDER_FOR_GREATER_OP_CODES' bug
y = kpts.view(bs, *self.kpt_shape, -1)
a = (y[:, :, :2] * 2.0 + (self.anchors - 0.5)) * self.strides
if ndim == 3:
a = torch.cat((a, y[:, :, 2:3].sigmoid()), 2)
return a.view(bs, self.nk, -1)
else:
y = kpts.clone()
if ndim == 3:
y[:, 2::3] = y[:, 2::3].sigmoid() # sigmoid (WARNING: inplace .sigmoid_() Apple MPS bug)
y[:, 0::ndim] = (y[:, 0::ndim] * 2.0 + (self.anchors[0] - 0.5)) * self.strides
y[:, 1::ndim] = (y[:, 1::ndim] * 2.0 + (self.anchors[1] - 0.5)) * self.strides
return y
从上面的函数可以看出,关键点预测的坐标x,y
是相对于特征图尺度对应meshgrid
坐标点left-top
距离的1/2
。
# 部署
完整的部署工程可以参考https://gitee.com/lx_r/object_detection_task/tree/main/detection/yolov8-ncnn (opens new window)
# reference
1.https://github.com/pnnx/pnnx (opens new window)
2.https://github.com/Tencent/ncnn (opens new window)