上篇文章中,我们已经成功将 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.binsqueezenet_ssd_voc.param,之后执行命令:

./squeezenetssd ./ILSVRC2012_val_00020804.JPEG

得到目标检测的结果:

目标检测的效果倒是还不错。

参考资料

[1] 嵌入式Linux入门级板卡的神经网络框架ncnn移植与测试-米尔i.MX6UL开发板

[2] 在全志d1开发板上玩ncnn

[3] pytorch模型的部署(系列一)--ncnn的编译和使用

最后修改:2023 年 07 月 29 日
如果觉得我的文章对你有用,请随意赞赏