0
点赞
收藏
分享

微信扫一扫

54、以centerface为例子,学习如何将pythorch模型转ncnn模型,并进行模型解析


基本思想:想学习一下centerface作者如何将pytorch模型转成ncnn模型,同时解析ncnn对应的数据,自己尝试解析一下,毕竟centerface提供了ncnn解析的demo,有参考答案,学习成本较低;自己不看答案,自己尝试解析和分析,学习一下

一、下载源码:

git clone https://github.com/Star-Clouds/CenterFace.git

然后执行prj-python代码,测试可用

54、以centerface为例子,学习如何将pythorch模型转ncnn模型,并进行模型解析_python

然后开始转将onnx转成ncnn模型,进行ncnn的对应代码开发,开发过程参考python代码解析过程

二、首先使用netron查看onnx模型

输入节点&输出节点:输入节点为input [10,3,32,32] 则输出节点为 537 [10,1,8,8], 538 [10,2,8,8], 539 [10,2,8,8] ,540 [10,10,8,8],实际的输入图片大小因为不一定是[batch,channel,width,height]

1)、onnx的开头部分

54、以centerface为例子,学习如何将pythorch模型转ncnn模型,并进行模型解析_#include_02

2)、onnx的结尾部分

54、以centerface为例子,学习如何将pythorch模型转ncnn模型,并进行模型解析_深度学习_03

 3)、将onnx模型转成ncnn模型之后

ubuntu@ubuntu:~/ncnn/build/tools/onnx$ ./onnx2ncnn ~/CenterFace/models/onnx/centerface.onnx ~/CenterFace/models/onnx/centerface.param  ~/CenterFace/models/onnx/centerface.bin

4)、ncnn的输入图

54、以centerface为例子,学习如何将pythorch模型转ncnn模型,并进行模型解析_#include_04

5)、 ncnn的输出图

54、以centerface为例子,学习如何将pythorch模型转ncnn模型,并进行模型解析_python_05

这里使用简单的c++代码测试一下输出;代码设置的输入暂且按照onnx的输入维度[*,3,32,32],输出维度 537 [*,1,8,8], 538 [*2,8,8], 539 [*,2,8,8] ,540 [*,10,8,8]是相互对应的

CMakeLists.txt

cmake_minimum_required(VERSION 3.16)
project(untitled3)


set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fopenmp")

include_directories(${CMAKE_SOURCE_DIR}/include)
#导入ncnn
add_library(libncnn STATIC IMPORTED)
set_target_properties(libncnn PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/lib/libncnn.a)
find_package(OpenCV REQUIRED)
set(CMAKE_CXX_STANDARD 11)

add_executable(untitled3 main.cpp)
target_link_libraries(untitled3 libncnn ${OpenCV_LIBS})

测试代码

#include <iostream>

#include "ncnn/net.h"
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace std;
using namespace ncnn;
using namespace cv;
int main() {


cv::Mat bgr = cv::imread("/home/ubuntu/CenterFace/prj-python/000388.jpg");
ncnn::Net net;


net.load_param("/home/ubuntu/CLionProjects/untitled1/centerface.param");
net.load_model("/home/ubuntu/CLionProjects/untitled1/centerface.bin");

int targetWidth=32;
int targetHeight=32;


ncnn::Mat in = ncnn::Mat::from_pixels_resize(bgr.data, ncnn::Mat::PIXEL_BGR, bgr.cols, bgr.rows, targetHeight, targetHeight);

ncnn::Extractor ex = net.create_extractor();

ex.input("input.1", in);

ncnn::Mat out0;
ex.extract("537", out0);
std::cout<<"537 c: "<<out0.c<<" h: "<<out0.h<<" w: "<<out0.w<<std::endl;
ncnn::Mat out1;
ex.extract("538", out1);
std::cout<<"538 c: "<<out1.c<<" h: "<<out1.h<<" w: "<<out1.w<<std::endl;
ncnn::Mat out2;
ex.extract("539", out2);
std::cout<<"539 c: "<<out2.c<<" h: "<<out2.h<<" w: "<<out2.w<<std::endl;
ncnn::Mat out3;
ex.extract("540", out3);
std::cout<<"540 c: "<<out3.c<<" h: "<<out3.h<<" w: "<<out3.w<<std::endl;


return 0;
}

测试结果(仅作为测试输入维度和输出维度是否保持一致)

/home/ubuntu/CLionProjects/untitled1/cmake-build-debug/untitled3
537 c: 1 h: 8 w: 8
538 c: 2 h: 8 w: 8
539 c: 2 h: 8 w: 8
540 c: 10 h: 8 w: 8

Process finished with exit code 0

三、也可以使用onnx模型查看网络centerface.onnx模型 ,并且喂数据给onnx模型,以测试实际图片输入大小,其后处理的数据输出维度

54、以centerface为例子,学习如何将pythorch模型转ncnn模型,并进行模型解析_神经网络_06

onnx的节点存储是以Protobuf协议存储和解析的,具体参考的​​6、caffe之probuff序列化消息使用_sxj731533730-​​

其中所有onnx节点都存储在​​https://github.com/onnx/onnx/blob/master/onnx/onnx.proto​​ 文件中,先使用脚本输出一下centerface.onnx模型,学习一下onnx如何展示和修改

import onnx
from onnx import numpy_helper

onnx_model = onnx.load(r'F:\temp\CenterFace\models\onnx\centerface.onnx')
onnx.checker.check_model(onnx_model)

print("input",onnx_model.graph.input) # 打印所有的输入node 模型最后一行的输出结点,都是输入结点
print("-------------------------")
print("output",onnx_model.graph.output)
print("-------------------------")
onnx_weights = {}

for initializer in onnx_model.graph.initializer:
W = numpy_helper.to_array(initializer)
onnx_weights[initializer.name] = W
print("head.feat.weight",onnx_weights["head.feat.weight"])

输出模型的具体node和weight

"C:\Program Files\Python36\python.exe" F:/temp/CenterFace/prj-python/parse.py
input [name: "input.1"
type {
tensor_type {
elem_type: 1
shape {
dim {
dim_value: 10
}
dim {
dim_value: 3
}
dim {
dim_value: 32
....
--------------------------------------------
output [name: "537"
type {
tensor_type {
elem_type: 1
shape {
dim {
dim_value: 10
}
dim {
dim_value: 1
}
dim {
dim_value: 8
}
dim {
dim_value: 8
}
}
}
}
......
---------------------------------------------
head.feat.weight [[[[-2.59152144e-01 -1.80263311e-01 -1.85323477e-01]
[-2.19007164e-01 -1.45827949e-01 -1.12009451e-01]
[-8.42200741e-02 -1.66336104e-01 -7.49235898e-02]]
......
[[-1.78118706e-01 -3.68947327e-01 -1.58845276e-01]
[-1.42003670e-01 -3.75836372e-01 -1.88246742e-01]
[-2.32190832e-01 -2.99981654e-01 -7.48115778e-02]]

[[ 3.00025735e-02 3.56723703e-02 4.48376499e-02]
[-7.23649189e-02 -1.17065869e-01 6.24058060e-02]
[-8.92813876e-03 -3.08007263e-02 3.03953327e-02]]]]

例如通过喂数据1024*681 (事例图片大小)的图片,可以看出

import onnx
import math

input_size =(1024,681)
model = onnx.load_model("/home/ubuntu/CenterFace/models/onnx/centerface.onnx")
d = model.graph.input[0].type.tensor_type.shape.dim
print(d)
rate = (int(math.ceil(input_size[0]/d[2].dim_value)),int(math.ceil(input_size[1]/d[3].dim_value)))
print("rare",rate)
d[0].dim_value = 1
d[2].dim_value *= rate[0]
d[3].dim_value *= rate[1]
for output in model.graph.output:
d = output.type.tensor_type.shape.dim
print(d)
d[0].dim_value = 1
d[2].dim_value *= rate[0]
d[3].dim_value *= rate[1]

onnx.save_model(model,"centerface_1024_681.onnx" )

 修改的模型,也可以正常推理使用

54、以centerface为例子,学习如何将pythorch模型转ncnn模型,并进行模型解析_pytorch_07


注:onnx的模型将宽度进行扩展,在转成ncnn模型之后,可以比对后端处理进行数据输出

四、然后就比对这官方python代码进行c++ ncnn代码开发(官方提供了prj-ncnn源码,不想看 我想自力更生 ,,,)

centerface提供的python代码在debug模式到这一行,例如:heatmap输出结果为

54、以centerface为例子,学习如何将pythorch模型转ncnn模型,并进行模型解析_python_08

heatmap存放的是置信度且是维度是1 的二维数组;scale存放的是坐标点 且维度是2的二维数组;offset存放的是偏移量 且维度是2的二维数组;lms 面部关键点且维度是10的二维数组,即可以通过python的单步调试输出所得也可以通过onnx模型在netron模型中得到,这些信息

54、以centerface为例子,学习如何将pythorch模型转ncnn模型,并进行模型解析_神经网络_09

然后根据输出结果后续python代码去仿照开发c++代码即可,本菜鸡仿照python写的代码 

#include<iostream>

#include "ncnn/benchmark.h"
#include "ncnn/cpu.h"
#include "ncnn/datareader.h"
#include "ncnn/net.h"
#include "ncnn/gpu.h"
#include "ncnn/layer.h"
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
#include <vector>
using namespace cv;
using namespace std;
using namespace ncnn;

using namespace std;

int main()
{

cv::Mat bgr= imread("/home/ubuntu/ncnn/build/tools/onnx/prj/000388.jpg");
ncnn::Net detector;

detector.load_param("/home/ubuntu/ncnn/build/tools/onnx/prj/centerfaceC.param");
detector.load_model("/home/ubuntu/ncnn/build/tools/onnx/prj/centerfaceC.bin");

ncnn::Mat in = ncnn::Mat::from_pixels_resize(bgr.data, ncnn::Mat::PIXEL_BGR2RGB,\
bgr.cols, bgr.rows, bgr.cols, bgr.rows);



ncnn::Extractor ex = detector.create_extractor();
ex.set_num_threads(8);
ex.input("input.1", in);
ncnn::Mat heatmap;
ex.extract("537", heatmap);
ncnn::Mat scale;
ex.extract("538", scale);
ncnn::Mat offset;
ex.extract("539", offset);
ncnn::Mat landmark;
ex.extract("540", landmark);
// heatmap, scale, offset, lms
float threshold=0.35;
vector<cv::Point> vec_c;
vector<float> confidences;
for (int c=0; c<heatmap.c; c++)
{
const float* ptr_heatmap = heatmap.channel(c);
for (int y=0; y<scale.h; y++)
{
for (int x=0; x<scale.w; x++)
{
//std::cout<< ptr[x]<<std::endl;
if(ptr_heatmap[x]>threshold){
vec_c.push_back(Point(x,y)); // python 代码中 np.where() 返回的坐标是行列信息 对应的是 ---> c0>y c1>x c++ 存放坐标 x y
confidences.push_back(ptr_heatmap[x]);
}

}
ptr_heatmap += heatmap.w;
//std::cout<<std::endl;
}
// std::cout<<std::endl;
}
//vec.x ---> c0 || vec.y --->c1 对应的维度是1(ncnn 的通道数为1)的二维数组 存的是对应坐标点的置信度信息

vector<float> vec_scale[2];
for (int c=0; c<scale.c; c++)
{
const float* ptr_scale = scale.channel(c);
for (int y=0; y<scale.h; y++)
{
for (int x=0; x<scale.w; x++)
{
//std::cout<< ptr[x]<<std::endl;

vec_scale[c].push_back(ptr_scale[x]); //将每个二位数据拉伸成一维数组 总共两个一维数组


}
ptr_scale += scale.w;
//std::cout<<std::endl;
}
// std::cout<<std::endl;
}
//vec_scale[0]---> scale0 || vec_scale[1]---> scale1 对应的维度是2(ncnn 的通道数为2)的二维数组 存的是对应坐标点


vector<float> vec_offset[2];
for (int c=0; c<offset.c; c++)
{
const float* ptr_offset = offset.channel(c);
for (int y=0; y<offset.h; y++)
{
for (int x=0; x<offset.w; x++)
{
//std::cout<< ptr[x]<<std::endl;

vec_offset[c].push_back(ptr_offset[x]);


}
ptr_offset += offset.w;
//std::cout<<std::endl;
}
// std::cout<<std::endl;
}
//vec_offset[0]---> offset0 || vec_offset[1]---> offset1 对应的维度是2(ncnn 的通道数为2)的二维数组 存的是对应坐标点偏移量
//数据每行的宽度
vector<float> vec_landmark[10];
for (int c=0; c<landmark.c; c++)
{
const float* ptr_landmark = landmark.channel(c);
for (int y=0; y<scale.h; y++)
{
for (int x=0; x<scale.w; x++)
{
//std::cout<< ptr[x]<<std::endl;

vec_landmark[c].push_back(ptr_landmark[x]);

}
ptr_landmark+= heatmap.w;
//std::cout<<std::endl;
}
// std::cout<<std::endl;
}


int dataWidth=heatmap.w;
vector<vector<float>> vec_lms;
vector<int> indices;
vector<cv::Rect> boxes;

if(vec_c.size())
{

for(int i=0;i<vec_c.size();i++)
{

float s0=exp(vec_scale[0][vec_c[i].x+vec_c[i].y*dataWidth])*4;
float s1=exp(vec_scale[1][vec_c[i].x+vec_c[i].y*dataWidth])*4;
float o0=vec_offset[0][vec_c[i].x+vec_c[i].y*dataWidth];
float o1=vec_offset[1][vec_c[i].x+vec_c[i].y*dataWidth];
confidences;
float x0=max(0.0,(vec_c[i].x+o0+0.5)*4-s0/2.0);
float y0=max(0.0,(vec_c[i].y+o1+0.5)*4-s1/2.0);
x0=min( x0,(float)bgr.cols);
y0=min( y0,(float)bgr.rows);
float x1=min( x0+s0,(float)bgr.cols);
float y1=min( y0+s1,(float)bgr.rows);
cv::Rect temp((int)x0,(int)y0,(int)(x1-x0),(int)(y1-y0));

boxes.push_back(temp);


vector<float> vec_lm;
for(int j=0;j<5;j++) //landmark 10e
{
vec_lm.push_back(vec_landmark[0+j*2+1][vec_c[i].x+vec_c[i].y*dataWidth]*s0+x0);
vec_lm.push_back(vec_landmark[0+j*2][vec_c[i].x+vec_c[i].y*dataWidth]*s1+x1);
//circle(bgr,cv::Point(vec_lm[-2],vec_lm[-1]),1,Scalar(0,0,0),1);
}
vec_lms.push_back(vec_lm);

//cv::rectangle(bgr, Point((int)x0, (int)y0), Point((int)x1, (int)y1), Scalar(255, 0, 0), 1, LINE_8, 0);

}

for(int i=0;i<boxes.size();i++)
{
cv::rectangle(bgr, Point((int)boxes[i].x, (int)boxes[i].y), Point(((int)boxes[i].x+(int)boxes[i].width), ((int)boxes[i].y+(int)boxes[i].height)), Scalar(255, 0, 0), 1, LINE_8, 0);
}

for(int i=0;i<vec_lms.size();i++)
{
for (int j=0;j<5;j++)
{
cv::circle(bgr, Point((int)vec_lms[i][j*2], (int)vec_lms[i][j*2+1]) ,1, Scalar(255, 0, 0), 1, LINE_8, 0);

}
}

}

imshow("Display", bgr);
waitKey(0 );


std::cout<<"hello world"<<std::endl;
return 0;
}

第一部分解析的结果图,<暂不考虑代码优化和效率问题,具体看官方的demo>, 继续学习解析onnx-ncnn的模型 

54、以centerface为例子,学习如何将pythorch模型转ncnn模型,并进行模型解析_python_10

完整解析,<暂不考虑代码优化和效率问题,具体看官方的demo>

#include<iostream>

#include "ncnn/benchmark.h"
#include "ncnn/cpu.h"
#include "ncnn/datareader.h"
#include "ncnn/net.h"
#include "ncnn/gpu.h"
#include "ncnn/layer.h"
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
#include <vector>
using namespace cv;
using namespace std;
using namespace ncnn;

using namespace std;

int main()
{

cv::Mat bgr= imread("/home/ubuntu/CenterFace/prj-python/000388.jpg");
ncnn::Net detector;

detector.load_param("/home/ubuntu/CLionProjects/untitled1/centerface.param");
detector.load_model("/home/ubuntu/CLionProjects/untitled1/centerface.bin");

ncnn::Mat in = ncnn::Mat::from_pixels_resize(bgr.data, ncnn::Mat::PIXEL_BGR2RGB,\
bgr.cols, bgr.rows, bgr.cols, bgr.rows);



ncnn::Extractor ex = detector.create_extractor();
ex.set_num_threads(8);
ex.input("input.1", in);
ncnn::Mat heatmap;
ex.extract("537", heatmap);
ncnn::Mat scale;
ex.extract("538", scale);
ncnn::Mat offset;
ex.extract("539", offset);
ncnn::Mat landmark;
ex.extract("540", landmark);
// heatmap, scale, offset, lms
float threshold=0.35;
vector<cv::Point> vec_c;
vector<float> confidences;
for (int c=0; c<heatmap.c; c++)
{
const float* ptr_heatmap = heatmap.channel(c);
for (int y=0; y<scale.h; y++)
{
for (int x=0; x<scale.w; x++)
{
//std::cout<< ptr[x]<<std::endl;
if(ptr_heatmap[x]>threshold){
vec_c.push_back(Point(x,y)); // python 代码中 np.where() 返回的坐标是行列信息 对应的是 ---> c0>y c1>x c++ 存放坐标 x y
confidences.push_back(ptr_heatmap[x]);
}

}
ptr_heatmap += heatmap.w;
//std::cout<<std::endl;
}
// std::cout<<std::endl;
}
//vec.x ---> c0 || vec.y --->c1 对应的维度是1(ncnn 的通道数为1)的二维数组 存的是对应坐标点的置信度信息

vector<float> vec_scale[2];
for (int c=0; c<scale.c; c++)
{
const float* ptr_scale = scale.channel(c);
for (int y=0; y<scale.h; y++)
{
for (int x=0; x<scale.w; x++)
{
//std::cout<< ptr[x]<<std::endl;

vec_scale[c].push_back(ptr_scale[x]); //将每个二位数据拉伸成一维数组 总共两个一维数组


}
ptr_scale += scale.w;
//std::cout<<std::endl;
}
// std::cout<<std::endl;
}
//vec_scale[0]---> scale0 || vec_scale[1]---> scale1 对应的维度是2(ncnn 的通道数为2)的二维数组 存的是对应坐标点


vector<float> vec_offset[2];
for (int c=0; c<offset.c; c++)
{
const float* ptr_offset = offset.channel(c);
for (int y=0; y<offset.h; y++)
{
for (int x=0; x<offset.w; x++)
{
//std::cout<< ptr[x]<<std::endl;

vec_offset[c].push_back(ptr_offset[x]);


}
ptr_offset += offset.w;
//std::cout<<std::endl;
}
// std::cout<<std::endl;
}
//vec_offset[0]---> offset0 || vec_offset[1]---> offset1 对应的维度是2(ncnn 的通道数为2)的二维数组 存的是对应坐标点偏移量
//数据每行的宽度
vector<float> vec_landmark[10];
for (int c=0; c<landmark.c; c++)
{
const float* ptr_landmark = landmark.channel(c);
for (int y=0; y<scale.h; y++)
{
for (int x=0; x<scale.w; x++)
{
//std::cout<< ptr[x]<<std::endl;

vec_landmark[c].push_back(ptr_landmark[x]);

}
ptr_landmark+= landmark.w;
//std::cout<<std::endl;
}
// std::cout<<std::endl;
}


int dataWidth=heatmap.w;
vector<vector<float>> vec_lms;
vector<int> indices;
vector<cv::Rect> boxes;

if(vec_c.size())
{

for(int i=0;i<vec_c.size();i++)
{

float s0=exp(vec_scale[0][vec_c[i].x+vec_c[i].y*dataWidth])*4;
float s1=exp(vec_scale[1][vec_c[i].x+vec_c[i].y*dataWidth])*4;
float o0=vec_offset[0][vec_c[i].x+vec_c[i].y*dataWidth];
float o1=vec_offset[1][vec_c[i].x+vec_c[i].y*dataWidth];
confidences;
float x0=max(0.0,(vec_c[i].x+o0+0.5)*4-s0/2.0);
float y0=max(0.0,(vec_c[i].y+o1+0.5)*4-s1/2.0);
x0=min( x0,(float)bgr.cols);
y0=min( y0,(float)bgr.rows);
float x1=min( x0+s0,(float)bgr.cols);
float y1=min( y0+s1,(float)bgr.rows);
cv::Rect temp((int)x0,(int)y0,(int)(x1-x0),(int)(y1-y0));

boxes.push_back(temp);


vector<float> vec_lm;
for(int j=0;j<5;j++) //landmark 10e
{
vec_lm.push_back(vec_landmark[0+j*2+1][vec_c[i].x+vec_c[i].y*dataWidth]*s0+x0);
vec_lm.push_back(vec_landmark[0+j*2][vec_c[i].x+vec_c[i].y*dataWidth]*s1+y0);
//circle(bgr,cv::Point(vec_lm[-2],vec_lm[-1]),1,Scalar(0,0,0),1);
}
vec_lms.push_back(vec_lm);

//cv::rectangle(bgr, Point((int)x0, (int)y0), Point((int)x1, (int)y1), Scalar(255, 0, 0), 1, LINE_8, 0);

}


// 缺少一个nms返回值index,然后筛选对应的index 给box和landmark
for(int i=0;i<boxes.size();i++)
{
cv::rectangle(bgr, Point((int)boxes[i].x, (int)boxes[i].y), Point(((int)boxes[i].x+(int)boxes[i].width), ((int)boxes[i].y+(int)boxes[i].height)), Scalar(255, 0, 0), 1, LINE_8, 0);
}

for(int i=0;i<vec_lms.size();i++)
{
for (int j=0;j<5;j++)
{
cv::circle(bgr, Point((int)vec_lms[i][j*2], (int)vec_lms[i][j*2+1]) ,1, Scalar(255, 0, 0), 1, LINE_8, 0);

}
}

}

imshow("Display", bgr);
imwrite("demo.jpg",bgr);
waitKey(0 );


std::cout<<"hello world"<<std::endl;
return 0;
}

测试结果,没写nms的代码

54、以centerface为例子,学习如何将pythorch模型转ncnn模型,并进行模型解析_pytorch_11

具体还是看原作者的解析和demo,centerface模型解析还是比较简单,因为op转ncnn没有出现意外,可以直接使用onnx2ncnn转换,有些网络需要手动修改的,进行尝试matting的onnx模型解析预热;"飞哥前辈已经实现"

感谢飞哥的解惑和帮助

参考:​​ONNX学习笔记 - 知乎​​

​​python关于onnx模型的一些基本操作_CFH1021的博客-​​

举报

相关推荐

0 条评论