yolov5模型轉(zhuǎn)換NCNN模型部署
寫作原因:最近看了下nihui大佬的ncnn,練習著將yolov5訓練的模型轉(zhuǎn)換成ncnn模型并部署,同時借鑒了網(wǎng)上優(yōu)秀的博文,記錄一下,如有不對的地方,請多多指教。
說明:pytorch模型轉(zhuǎn)換成onnx模型,及onnx模型簡化和轉(zhuǎn)ncnn模型在引用的文章中都有詳細的說明,可移步至引用文章中查看。
先來看下ncnn模型,兩個,一個是param一個是bin,需要修改的是param。

圖1
其實yolov5 v1-v5版本在訓練完后,使用onnx2ncnn.exe將簡化后的onnx模型轉(zhuǎn)換成ncnn模型時主要出現(xiàn)這個問題。V6版本在輸出上和前5個版本有一點不同,這里針對1-5版本。

圖2

圖3
根據(jù)nihui大佬的方案看,主要做兩步工作,一是刪除這里報錯的網(wǎng)絡層,然后自定義一個新層作為替代,第二步是修改輸出參數(shù),做完這兩步就可以使用了。
下面說下修改的是什么,這樣就可以知道自己的模型應該修改哪里了。
一、param部分參數(shù)說明
??7767517???#?文件頭?魔數(shù)
??75?83?????#?層數(shù)量??輸入輸出blob數(shù)量
????????????#?下面有75行
??Input????????????data?????????????0?1?data?0=227?1=227?2=3
??Convolution??????conv1????????????1?1?data?conv1?0=64?1=3?2=1?3=2?4=0?5=1?6=1728
??ReLU?????????????relu_conv1???????1?1?conv1?conv1_relu_conv1?0=0.000000
??Pooling??????????pool1????????????1?1?conv1_relu_conv1?pool1?0=0?1=3?2=2?3=0?4=0
??Convolution??????fire2/squeeze1x1?1?1?pool1?fire2/squeeze1x1?0=16?1=1?2=1?3=1?4=0?5=1?6=1024
??...
??層類型????????????層名字???輸入blob數(shù)量?輸出blob數(shù)量??輸入blob名字?輸出blob名字???參數(shù)字典
??
??參數(shù)字典,每一層的意義不一樣:
??數(shù)據(jù)輸入層?Input????????????data?????????????0?1?data?0=227?1=227?2=3???圖像寬度×圖像高度×通道數(shù)量
??卷積層????Convolution??...???0=64?????1=3??????2=1????3=2?????4=0????5=1????6=1728???????????
???????????0輸出通道數(shù)?num_output()?;?1卷積核尺寸?kernel_size();??2空洞卷積參數(shù)?dilation();?3卷積步長?stride();?
?????????? 4卷積填充pad_size();?????? 5卷積偏置有無bias_term();?? 6卷積核參數(shù)數(shù)量 weight_blob.data_size();
??????????????????????????????????????????????????????????????C_OUT?*?C_in?*?W_h?*?W_w?=?64*3*3*3?=?1728
??池化層????Pooling??????0=0???????1=3???????2=2????????3=0???????4=0
??????????????????????0池化方式:最大值、均值、隨機?????1池化核大小?kernel_size();?????2池化核步長?stride();?
??????????????????????3池化核填充?pad();???4是否為全局池化?global_pooling();
??激活層????ReLU???????0=0.000000?????下限閾值?negative_slope();
???????????ReLU6??????0=0.000000?????1=6.000000?上下限
??
??綜合示例:
??0=1?1=2.5?-23303=2,2.0,3.0
??
??數(shù)組關鍵字?:?-23300?
??-(-23303)?-?23300?=?3?表示該參數(shù)在參數(shù)數(shù)組中的index
??后面的第一個參數(shù)表示數(shù)組元素數(shù)量,2表示包含兩個元素
代碼 1
二、對 ncnn.param 進行修正
算子替換 
圖4 替換前

圖5 替換后
說明:為什么要這么做,nihui大佬對此的解釋如下:
nihuinihuinihuinihuinihuinihuinihuinihuinihuinihuinihuinihuinihuinihuinihuinihuinihuinihuinihuinihuini
轉(zhuǎn)換為 ncnn 模型,會輸出很多 Unsupported slice step,這是focus模塊轉(zhuǎn)換的報錯。
Unsupported?slice?step?!
Unsupported?slice?step?!
Unsupported?slice?step?!
Unsupported?slice?step?!
Unsupported?slice?step?!
Unsupported?slice?step?!
Unsupported?slice?step?!
Unsupported?slice?step?!
代碼 2
好多人遇到這種情況,便不知所措,這些警告表明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))
代碼 3
這其實是一次 col-major space2depth 操作,pytorch 似乎并沒有對應上層api實現(xiàn)(反向的 depth2space 可以用 nn.PixelShuffle),yolov5 用 stride slice 再 concat 方式實現(xiàn),實乃不得已而為之的騷操作。
替換后修改的參數(shù)含義
由代碼1可知,param第二行的第一個數(shù)據(jù)是層數(shù)量,按niuhui大佬的意思需要替換的行數(shù)為4-13行,總共10行,因此第二行的第一個參數(shù)就是238-10=228,然后,新增加一層替換,因此最后第二行第一個參數(shù)就變成了228+1=229。
然后就是就是新增加一行的輸入、輸出參數(shù)修改了,由代碼1中參數(shù)說明可知,input層的輸出是iamges,原來第14層的輸入為205,因此增加層的輸入輸出就確定了,分別為images、205。
動態(tài)尺寸推理 
圖6
說明:為什么要修改這里,nihui大佬的解釋是
u版yolov5 是支持動態(tài)尺寸推理,但是ncnn天然支持動態(tài)尺寸輸入,無需reshape或重新初始化,給多少就算多少。u版yolov5 將最后 Reshape 層把輸出grid數(shù)寫死了,導致檢測小圖時會出現(xiàn)檢測框密密麻麻布滿整個畫面,或者根本檢測不到東西。
解決方案就是將reshape層的輸出grid數(shù)量改為 -1 便可以自適應。
