在上篇文章中,我们已经成功将 PyTorch 模型转换为了 NCNN 模型,那么接下来就是将 NCNN 模型部署在嵌入式开发板上并执行图像分类任务和目标检测任务了。
本文使用的嵌入式 Linux 开发板是百问网的 100ASK-6ULL-V11(即:官网提到的 i.MX6ULL-PRO),基于 NXP i.MX6ULL 处理器,比较适合新手入门使用。
一、调用交叉编译工具链部署 NCNN 框架
之前我们已经拉取 NCNN 仓库并安装相关依赖,本文不再赘述,直接进入编译环节。
1.1 为 100ASK-6ULL-V11 开发板添加编译配置
在toolchains
目录下,我们可以看到很多其它开发板的编译配置文件:
参照其它开发板的配置文件,为 100ASK-6ULL-V11 开发板添加配置文件arm-buildroot-gnueabihf.toolchain.cmake
:
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_C_COMPILER "arm-buildroot-linux-gnueabihf-gcc")
set(CMAKE_CXX_COMPILER "arm-buildroot-linux-gnueabihf-g++")
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_C_FLAGS "-march=armv7-a -mfloat-abi=hard -mfpu=neon")
set(CMAKE_CXX_FLAGS "-march=armv7-a -mfloat-abi=hard -mfpu=neon")
# cache flags
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}" CACHE STRING "c flags")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}" CACHE STRING "c++ flags")
1.2 生成makefile
文件并开始编译
在配置好 i.MX6ULL 开发板所需的交叉编译工具链后,就可以使用cmake
命令来生成编译所需的makefile
文件了,为了方便,我们在后续编译的同时生成样例程序:
mkdir -p build-imx6ull # 在工程根目录中新建 build-imx6ull 文件夹
cd build-imx6ull
cmake -DCMAKE_BUILD_TYPE=Release -DNCNN_SIMPLEOCV=ON -DCMAKE_TOOLCHAIN_FILE=../toolchains/arm-buildroot-gnueabihf.toolchain.cmake -DNCNN_BUILD_EXAMPLES=ON ..
开始编译:
make -j4
在编译测试用例时,可能会出现库格式错误的提示,此时需要设置交叉编译环境下的库归档工具并重新编译:
export ar=arm-buildroot-linux-gnueabihi-gcc-ar
make -j4
1.3 查看编译结果
编译完成后,在build-imx6ull
目录下,我们可以看到benchmark
目录和example
目录,前者目录中的benchncnn
就是 NCNN 的基准测试工具,后者目录中是编译完成的样例程序:
二、基准测试和执行图像分类 & 目标检测任务
编译完成后便可把相关文件复制到开发板中进行测试。
2.1 基准测试
把build-imx6ull/benchmark
目录下的benchncnn
复制到开发板的/home/root/ncnn/benchmark
目录下,同时把工程根目录的benchmark
目录下所有文件也复制到开发板的/home/root/ncnn/benchmark
目录下,在开发板中运行命令:
./benchncnn
稍后即可看到基准测试的结果:
基准测试的结果是单个模型单次推理的耗时,因此数值越小意味着算力越强,考虑到这个开发板是一个 ARM v7 入门级的开发板,这样的性能已经超乎预料了。
2.2 图像分类任务
将build-imx6ull/examples
目录下的所有文件复制到开发板的/home/root/ncnn/examples
目录下,这些文件是编译完成的样例程序,用于调用对应的深度神经网络模型。
调用深度神经网络需要模型的架构和参数,经过预训练的模型的架构文件 (*.bin) 和参数文件 (*.param) 可以从 https://github.com/nihui/ncnn-assets/tree/master/models 下载,与编译完成的样例程序放在同一目录下即可。
使用 ILSVRC2012 验证集的ILSVRC2012_val_00020804.JPEG
作为测试图片:
运行以下命令调用 SqueezeNet 模型执行图像分类任务:
./squeezenet ./ILSVRC2012_val_00020804.JPEG
另外,在上篇中,我们已经成功将 Inception-v3 的 PyTorch 模型转换为了 NCNN 模型,那么我们可以仿照其它样例程序写一份调用 Inception-v3 模型的的代码并编译:
// Tencent is pleased to support the open source community by making ncnn available.
//
// Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
//
// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except
// in compliance with the License. You may obtain a copy of the License at
//
// https://opensource.org/licenses/BSD-3-Clause
//
// Unless required by applicable law or agreed to in writing, software distributed
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
#include "net.h"
#include <algorithm>
#if defined(USE_NCNN_SIMPLEOCV)
#include "simpleocv.h"
#else
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#endif
#include <stdio.h>
#include <vector>
static int detect_squeezenet(const cv::Mat& bgr, std::vector<float>& cls_scores)
{
ncnn::Net squeezenet;
squeezenet.opt.use_vulkan_compute = true;
// the ncnn model https://github.com/nihui/ncnn-assets/tree/master/models
if (squeezenet.load_param("inception_v3.param"))
exit(-1);
if (squeezenet.load_model("inception_v3.bin"))
exit(-1);
ncnn::Mat in = ncnn::Mat::from_pixels_resize(bgr.data, ncnn::Mat::PIXEL_BGR, bgr.cols, bgr.rows, 299, 299);
const float mean_vals[3] = {104.f, 117.f, 123.f};
in.substract_mean_normalize(mean_vals, 0);
ncnn::Extractor ex = squeezenet.create_extractor();
ex.input("input", in);
ncnn::Mat out;
ex.extract("output", out);
cls_scores.resize(out.w);
for (int j = 0; j < out.w; j++)
{
cls_scores[j] = out[j];
}
return 0;
}
static int print_topk(const std::vector<float>& cls_scores, int topk)
{
// partial sort topk with index
int size = cls_scores.size();
std::vector<std::pair<float, int> > vec;
vec.resize(size);
for (int i = 0; i < size; i++)
{
vec[i] = std::make_pair(cls_scores[i], i);
}
std::partial_sort(vec.begin(), vec.begin() + topk, vec.end(),
std::greater<std::pair<float, int> >());
// print topk and score
for (int i = 0; i < topk; i++)
{
float score = vec[i].first;
int index = vec[i].second;
fprintf(stderr, "%d = %f\n", index, score);
}
return 0;
}
int main(int argc, char** argv)
{
if (argc != 2)
{
fprintf(stderr, "Usage: %s [imagepath]\n", argv[0]);
return -1;
}
const char* imagepath = argv[1];
cv::Mat m = cv::imread(imagepath, 1);
if (m.empty())
{
fprintf(stderr, "cv::imread %s failed\n", imagepath);
return -1;
}
std::vector<float> cls_scores;
detect_squeezenet(m, cls_scores);
print_topk(cls_scores, 3);
return 0;
}
将编译好的程序和转换好的 NCNN 模型文件复制到开发板后,运行以下命令即可调用 Inception-v3 模型执行图像分类任务:
./inception_v3 ./ILSVRC2012_val_00020804.JPEG
不过这个图像分类结果 emmm...
正确的类别是143🤣
2.3目标检测任务
下载squeezenet_ssd_voc.bin
和squeezenet_ssd_voc.param
,之后执行命令:
./squeezenetssd ./ILSVRC2012_val_00020804.JPEG
得到目标检测的结果:
目标检测的效果倒是还不错。
参考资料
[1] 嵌入式Linux入门级板卡的神经网络框架ncnn移植与测试-米尔i.MX6UL开发板
[2] 在全志d1开发板上玩ncnn
[3] pytorch模型的部署(系列一)--ncnn的编译和使用
版权属于:Angus
本文链接:https://blog.angustar.com/archives/deploying-DNNs-on-embedded-development-boards-2.html
所有原创文章采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。 您可以自由的转载和修改,但请务必注明文章来源并且不可用于商业目的。