# 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.pyncnn模型推理的测试文件,可以直接执行测试导出的模型是否能正常工作。

不过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        │ 99                │
│ Concat     │ 2121               │
│ Constant   │ 166159              │
│ Conv       │ 7373               │
│ Div        │ 11                │
│ Identity   │ 60                │
│ MaxPool    │ 33                │
│ Mul        │ 6666               │
│ Reshape    │ 1010               │
│ Resize     │ 22                │
│ Sigmoid    │ 6565               │
│ Slice      │ 22                │
│ Softmax    │ 11                │
│ Split      │ 1010               │
│ Sub        │ 22                │
│ Transpose  │ 22                │
│ Model Size │ 12.7MiB        │ 12.7MiB          │
└────────────┴────────────────┴──────────────────┘

step3:使用onnx2ncnnonnx模型转换为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;
}

以上就可以实现导出并部署yolov8ncnn模型了。下面来看下yolov8模型的inference过程。

# 2.yolov8关键点检测的推理过程

# 检测框推理过程

yolov8yolov5基本架构是相同的,都是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)