由于本人使用的系统是win10,所以记录也是在win10下使用ncnn。
网上很多介绍ncnn的配置教程都是从头开始构建编译,其实官方已经编译好一些版本的ncnn,直接下载就行了,没有必要从头开始编译。
官方github: https://github.com/Tencent/ncnn
从官方仓库的页面,点击releases
我用的是vs2019,shared是动态链接版本,我下的就是shared版本
解压后随便存放在哪个盘都行,然后记得在环境变量的path把bin路径添加进去,像我就放在D盘下,并且由于我opencv版本的关系,我用的是x64,所以我添加的路径就为:D:\C++_lib\ncnn-20210720-windows-vs2019-shared\x64\bin
官方教程中是使用caffe作为例子,但是我没有使用过caffe,所以我用的是pytorch模型转onnx的方式来验证ncnn是否能正常工作。
还有关于pytorch转onnx,这个我就不多说了,很复杂,坑巨多,自求多福,如果在pytorch里面直接定义了很多骚操作,那么多半转onnx要凉,唯一的办法就是绕开某些操作。这个话题不是本篇重点,跳过。说一点就是:输入和输出一定要命名啊:几个输出就命名几个,这样在用ncnn的load的时候才能直接提取出来。一般导出onnx的代码举例如下:
import torch
import torchvision.models as models
model = models.resnet50()
model.fc = torch.nn.Linear(2048, 2)
batch_size = 1 # 批处理大小
input_shape = (3, 244, 224) # 输入数据,改成自己的输入shape
# #set the model to inference mode
model.eval()
x = torch.randn(batch_size, *input_shape) # 生成张量
export_onnx_file = "test.onnx" # 目的ONNX文件名
torch.onnx.export(model,
x,
export_onnx_file,
verbose=True,
opset_version=10,
do_constant_folding=True, # 是否执行常量折叠优化
input_names=["input"], # 输入名
output_names=["output"], # 输出名
dynamic_axes={"input": {0: "batch_size"}, # 批处理变量
"output": {0: "batch_size"}})
我使用了pytorch官方自带的resnet50,并将输出修改为两分类。(没有加载预训练权重,主要是懒。。。。)
运行之后,我们就可以得到onnx模型了,之后的操作也就告别了pytorch框架了,可以随心所欲了,现在需要将onnx格式的模型转成ncnn能够加载的.bin和.param。
在onnx模型所在的目录下,使用命令:
onnx2ncnn test.onnx model.param model.bin
如果什么都没输出,那恭喜成功了!
由于利用opencv来读取图片,然后再利用ncnn的from_pixels_resize函数将opencv格式的图片转为ncnn格式的图片。所以会需要用到opencv,至于怎么下载opencv,网上一搜都有,就不说了。
还有ncnn内部用到了vulkan,所以在包含目录也要添加vulkande的include目录,下载链接:https://vulkan.lunarg.com/sdk/home#windows
下载后点击安装,过程很简单。
将onnx转成ncnn能使用的格式后,接下来就是在vs2019配置ncnn了。
在包含目录添加(根据自己实际安装目录)
D:\C++_lib\opencv\build\include
D:\C++_lib\ncnn-20210720-windows-vs2019-shared\x64\include\ncnn
D:\C++_lib\VulkanSDK\1.2.189.2\Include
在库目录添加(根据自己实际安装目录)
D:\C++_lib\opencv\build\x64\vc15\lib
D:\C++_lib\ncnn-20210720-windows-vs2019-shared\x64\lib
点击连接器-输入-附加依赖项,添加
opencv_world450.lib
opencv_world450d.lib
ncnn.lib
测试代码:
#include
#include
#include "net.h"
//这个函数是官方提供的用于打印输出的tensor
void pretty_print(const ncnn::Mat& m)
{
for (int q = 0; q < m.c; q++)
{
const float* ptr = m.channel(q);
for (int y = 0; y < m.h; y++)
{
for (int x = 0; x < m.w; x++)
{
printf("%f ", ptr[x]);
}
ptr += m.w;
printf("\n");
}
printf("------------------------\n");
}
}
int main()
{
//使用opencv以灰度图读取图片
cv::Mat img = cv::imread("E://code//test//image.jpg");
//获取图片的宽
int w = img.cols;
//获取图片的高
int h = img.rows;
//cv::imshow("aa", img);
//cv::waitKey();
//将OpenCV的图片转为ncnn格式的图片,并且将图片缩放到224×224之间
ncnn::Mat in = ncnn::Mat::from_pixels_resize(img.data, ncnn::Mat::PIXEL_GRAY, w, h, 224, 224);
float mean[1] = { 128.f };
float norm[1] = { 1 / 128.f };
//对图片进行归一化,将像素归一化到-1~1之间
in.substract_mean_normalize(mean, norm);
//定义模型的网络
ncnn::Net net;
//加载模型
net.load_param("E://code//test//model.param");
net.load_model("E://code//test//model.bin");
ncnn::Extractor ex = net.create_extractor();
ex.set_light_mode(true);
//设置线程个数
ex.set_num_threads(4);
//将图片放入到网络中,进行前向推理
ex.input("input", in);
ncnn::Mat feat;
//获取网络的输出结果
ex.extract("output", feat);
pretty_print(feat);
return 0;
}
上面只是一段测试代码,也不用纠结为什么放缩到-1到1之间,你喜欢也可以放缩到0到1之间。
需要注意的是,我是x64模式下导入头文件的,所以最好在vs里面也设为x64模式,不然会出现冲突。
如果正常输出一个二分类结果,那就没有什么问题了,可以接下来愉快的学习ncnn了。