Ncnn使用详解(1)——PC端
阅读原文时间:2023年07月12日阅读:2

写在前面

本系列文章的内容

关于ncnn会有两篇文章(一篇介绍pc端使用,一篇介绍android端使用),主要是因为之前在使用ncnn的时候网上的资料比较少,可能是大佬们的主要精力都放在了算法优化上,所以相关的基础实践资料就感觉比较少,本系列文章将详细介绍ncnn从源码编译到最终在Android端的应用流程,希望可以帮到有需要的同仁。

相关链接(主要参考)

ncnn主页: https://github.com/Tencent/ncnn

环境说明

系统:Ubuntu16.04

编译源码

从github下载源码

git clone https://github.com/Tencent/ncnn

下载完后的目录结构应该是这样:

然后对ncnn源码进行编译(在ncnn根目录下执行):

mkdir build
cd build
cmake ..
make -j4    

然后就可以编译成功了,相关截图:

在电脑上实现ncnn加载caffe模型对目标进行分类

参考 这里

准备caffe网络和模型

caffe 的网络和模型通常是搞深度学习的研究者训练出来的,一般来说训练完会有

train.prototxt
deploy.prototxt
snapshot_10000.caffemodel

部署的时候只需要 TEST 过程,所以有 deploy.prototxt 和 caffemodel 就足够了

alexnet 的 deploy.prototxt 可以在 这里下载

alexnet 的 caffemodel 可以在 这里下载

转换ncnn网络和模型

caffe 自带了工具可以把老版本的 caffe 网络和模型转换为新版(ncnn的工具只认识新版),这里介绍一种比较笨但是比较快捷的方法:
首先将你需要转换的prototxt和caffemodel放在你电脑的caffe/build/tools目录下,然后终端进入caffe/build/tools,执行命令:

./upgrade_net_proto_text old_deploy.prototxt new_deploy.prototxt
./upgrade_net_proto_binary old.caffemodel new.caffemodel

执行完成之后你就可以在caffe/build/tools下找到你的new_deploy.prototxt和new.caffemodel文件了。
注意完成之后打开你的new_deploy.prototxt文件看一下,因为一般每次只需要做一个数据样本的识别,所以如果第一个 dim 不为1,要将其设为
1,类似于这样:

layer {
  name: "data"
  type: "Input"
  top: "data"
  input_param { shape: { dim: 1 dim: 3 dim: 227 dim: 227 } }
}

使用 caffe2ncnn 工具转换为 ncnn 的网络描述和模型

终端进入ncnn/build/toos/caffe(需要提前把上面转化的new_deploy.prototxt和new.caffemodel放到ncnn/build/tools/caffe下),执行如下命令:

caffe2ncnn new_deploy.prototxt new.caffemodel demo.param demo.bin

执行完成之后在ncnn/build/tools下就可以看到生成的param和bin文件了,文件名你可以根据你的需要设置。

去除可见字符串(可选)

用nihui大神的原文介绍:
有 param 和 bin 文件其实已经可以用了,但是 param 描述文件是明文的,如果放在 APP分发出去容易被窥探到网络结构(说得好像不明文就看不到一样) 使用 ncnn2mem 工具转换为二进制描述文件和内存模型,生成
alexnet.param.bin 和两个静态数组的代码文件:

ncnn2mem demo.param demo.bin demo.id.h demo.mem.h

我下面的所有使用为了方便都使用的是没有去除可见字符串的param和bin,如果你有去除可见字符串的需求,可以在ncnn的examples中找到相应去除了可见字符串文件的使用方法。

实现在电脑上使用ncnn

编写代码

使用你喜欢的编辑器编写c语言代码,我这里不做过多语言介绍,直接给一个demo代码,我会在代码中给出关键代码的注释(保证你认真看完代码和注释就会懂ncnn的大概原理),请大家从main函数入口开始阅读,相信更多的读者喜欢这种直接show
code的方式:

#include <stdio.h>
#include <algorithm>
#include <vector>
#include"gesture.id.h"
#include "net.h"

//使用ncnn,传入的参数第一个是你需要预测的数据,第二个参数是各个类别的得分vector,注意传入的是地址,这样才能在这个函数中改变其值
static int detect_squeezenet( float *data, std::vector<float>& cls_scores)
{
    //实例化ncnn:Net,注意include "net.h",不要在意这时候因为找不到net.h文件而include<net.h>报错,后文会介绍正确的打开方式
    ncnn::Net squeezenet;
    //加载二进制文件,也是照写,后面会介绍对应文件应该放的正确位置
    int a=squeezenet.load_param("demo.param");
    int b=squeezenet.load_param_bin("demo.bin");
    //实例化Mat,前三个参数是维度,第四个参数是传入的data,维度的设置根据你自己的数据进行设置,顺序是w、h、c
    ncnn::Mat in = ncnn::Mat(550,  8, 2, data);

    //实例化Extractor
    ncnn::Extractor ex = squeezenet.create_extractor();
    ex.set_light_mode(true);
    //注意把"data"换成你deploy中的数据层名字
    int d= ex.input("data", in);

    ncnn::Mat out;
    //这里是真正的终点,不多说了,只能仰天膜拜nihui大牛,重点是将prob换成你deploy中最后一层的名字
    int c=ex.extract("prob", out);

    //将out中的值转化为我们的cls_scores,这样就可以返回不同类别的得分了
    cls_scores.resize(out.w);
    for (int j=0; j<out.w; j++)
    {

        cls_scores[j] = out[j];
    }

    return 0;
}
int main(int argc, char** argv)
{
    //注意,这里的argv是之后从终端输入的参数,我这里是数据源的路径,因为我是从两个文件中生成一个总的数据,所以用了argv[1]和argv[2],你也可以自己根据需求改变
    const char* imagepath1 = argv[1];
    const char* imagepath2=argv[2];

    FILE *fopeni=NULL;
    FILE *fopenq=NULL;

    fopeni=fopen(imagepath1,"r");
    fopenq=fopen(imagepath2,"r");

    //这是我的数据,i和q相当于图片的两个通道
    float i[4400];
    float q[4400];
    float data[8800];
    int count=4400;

    for (int j = 0; j < count; ++j)
    {
      fscanf(fopeni,"%f",&i[j]);
      fscanf(fopenq,"%f",&q[j]);
    }   
    //这是将iq(相当于图片的两个通道的数据)转化为一个一维向量,需要特别注意的是数据维度的顺序
    for (int j = 0; j < 8800; ++j)
    {
        if (j<4400)
        {
            data[j]=i[j];
        }else{
            data[j]=q[j-4400];
        }

    }

    char a[13]={'A','B','C','F','G','H','I','J','K','L','M','N','O'};

    //注意,这里是调用ncnn的代码
    std::vector<float> cls_scores;//用来存储最终各类别的得分
    //这个函数的实现在上面,快去看
    detect_squeezenet(data, cls_scores);
    for (int i = 0; i < cls_scores.size(); ++i)
    {
         printf("%c : %f\n", a[i],cls_scores[i]);
    }

     return 0;
}

代码中我展示了最简单的ncnn使用场景,你可以根据自己的需要加入不同的其他代码

编译并运行我们写的代码

首先将你刚才写好的代码文件(假设命名为demo.cpp)放在ncnn/examples目录下,然后打开ncnn/examples目录下的CMakeLists.txt文件,增加这两行:

add_executable(demo demo.cpp)
target_link_libraries(demo ncnn)

最终的CMakeLists.txt文件类似这样:

find_package(OpenCV QUIET COMPONENTS core highgui imgproc imgcodecs)
if(NOT OpenCV_FOUND)
    find_package(OpenCV REQUIRED COMPONENTS core highgui imgproc)
endif()

include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../src)
include_directories(${CMAKE_CURRENT_BINARY_DIR}/../src)
add_executable(squeezenet squeezenet.cpp)

target_link_libraries(squeezenet ncnn ${OpenCV_LIBS})

add_executable(fasterrcnn fasterrcnn.cpp)
target_link_libraries(fasterrcnn ncnn ${OpenCV_LIBS})

add_executable(demo demo.cpp)
target_link_libraries(demo ncnn)

add_subdirectory(ssd)

然后打开ncnn根目录下的CMakeLists.txt文件,将编译examples语句的注释打开(默认是被注释掉的),如图:

更改后保存退出,现在就可以终端进入ncnn/build后执行:

make

执行完毕后你就可以在ncnn/build/examples下找到你的可执行文件了(demo)

执行可执行文件

首先将你之前生成的.param和.bin文件复制到ncnn/build/examples目录下,然后终端cd到ncnn/build/examples,执行:

./demo data_path1 data_path2

然后你就可以看到奇迹了~

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器