前言
本文闡述了你ncnn轉換時focus報錯的原因。
轉換和實作focus子產品
$ onnx2ncnn yolov5s-sim.onnx yolov5s.param yolov5s.bin
轉換為 ncnn 模型,會輸出很多 Unsupported slice step,這是focus子產品轉換的報錯
Unsupported slice step !
Unsupported slice step !
Unsupported slice step !
Unsupported slice step !
Unsupported slice step !
Unsupported slice step !
Unsupported slice step !
Unsupported slice step !
好多人遇到這種情況,便不知所措,這些警告表明focus子產品這裡要手工修複下
打開 yolov5/models/common.py 看看focus在做些什麼
class Focus(nn.Module):
# Focus wh information into c-space
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True): # ch_in, ch_out, kernel, stride, padding, groups
super(Focus, self).__init__()
self.conv = Conv(c1 * 4, c2, k, s, p, g, act)
def forward(self, x): # x(b,c,w,h) -> y(b,4c,w/2,h/2)
return self.conv(torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1))
這其實是一次 col-major space2depth 操作,pytorch 似乎并沒有對應上層api實作(反向的 depth2space 可以用 nn.PixelShuffle),yolov5 用 stride slice 再 concat 方式實作,實乃不得已而為之的騷操作
用netron工具打開param,找到對應focus的部分
把這堆騷操作用個自定義op YoloV5Focus代替掉,修改param
- 找準輸入輸出 blob 名字,用一個自定義層 YoloV5Focus 連接配接
- param 開頭第二行,layer_count 要對應修改,但 blob_count 隻需確定大于等于實際數量即可
- 修改後使用 ncnnoptimize 工具,自動修正為實際 blob_count
替換後用 ncnnoptimize 過一遍模型,順便轉為 fp16 存儲減小模型體積
$ ncnnoptimize yolov5s.param yolov5s.bin yolov5s-opt.param yolov5s-opt.bin 65536
接下來要實作這個自定義op YoloV5Focus,wiki上的步驟比較繁多
https://github.com/Tencent/ncnn/wiki/how-to-implement-custom-layer-step-by-stepgithub.com
針對 focus 這樣,沒有權重,也無所謂參數加載的 op,繼承 ncnn::Layer 實作 forward 就可以用,注意要用 DEFINE_LAYER_CREATOR 宏定義 YoloV5Focus_layer_creator
#include "layer.h"
class YoloV5Focus : public ncnn::Layer
{
public:
YoloV5Focus()
{
one_blob_only = true;
}
virtual int forward(const ncnn::Mat& bottom_blob, ncnn::Mat& top_blob, const ncnn::Option& opt) const
{
int w = bottom_blob.w;
int h = bottom_blob.h;
int channels = bottom_blob.c;
int outw = w / 2;
int outh = h / 2;
int outc = channels * 4;
top_blob.create(outw, outh, outc, 4u, 1, opt.blob_allocator);
if (top_blob.empty())
return -100;
#pragma omp parallel for num_threads(opt.num_threads)
for (int p = 0; p < outc; p++)
{
const float* ptr = bottom_blob.channel(p % channels).row((p / channels) % 2) + ((p / channels) / 2);
float* outptr = top_blob.channel(p);
for (int i = 0; i < outh; i++)
{
for (int j = 0; j < outw; j++)
{
*outptr = *ptr;
outptr += 1;
ptr += 2;
}
ptr += w;
}
}
return 0;
}
};
DEFINE_LAYER_CREATOR(YoloV5Focus)
加載模型前先注冊 YoloV5Focus,否則會報錯找不到 YoloV5Focus
ncnn::Net yolov5;
yolov5.opt.use_vulkan_compute = true;
// yolov5.opt.use_bf16_storage = true;
yolov5.register_custom_layer("YoloV5Focus", YoloV5Focus_layer_creator);
yolov5.load_param("yolov5s-opt.param");
yolov5.load_model("yolov5s-opt.bin");