LoginSignup

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

More than 3 years have passed since last update.

ncnn(1) ライブラリのビルドと、推論アプリケーションの作成

Posted at

この記事について

ncnnという、Tencent製の推論に特化したDeep Learningフレームワークを使用してみます。
今回は、ncnnライブラリをビルドして、MobileNetで推論処理をしてみようと思います。

コードはこちら
https://github.com/take-iwiw/NcnnMultiPlatformProject/tree/master/NcnnSimpleProject

ncnnについて

  • C++から使える
  • 一般的なモデル形式(MXNet, ONNX)をncnn用モデル形式(*.param, *.bin)に変換して使用
    • PyTorchやTensorFlowからの変換も可能らしい。とりあえずONNXから変換できるので安心?
  • モバイルプラットフォームがメインターゲット
  • ビルド自体は、PC(Windows, Linxu)、Android、 iOS、Raspberry Pi 3、nVIDIA Jetson、その他ARM環境をサポート
  • GPU(vulkan) サポートもしているっぽい
  • 依存ライブラリなし <- これ大事! 実行バイナリとモデルファイルだけで動きます。
  • 速い (https://mc.ai/how-to-convert-ncnn-models/ )
  • GitHubが所々中国語。。。
  • すでに色々なプロダクトで採用されている (https://github.com/Tencent/ncnn/wiki/application-with-ncnn-inside )

ncnnのビルド

ncnnを取り込む方針

外部ライブラリを使用したプロジェクトの運用としては、以下の2つが考えられると思います。

  1. 自分のソースコードと一緒にビルドする
  2. 外部ライブラリだけ別途ビルドして、ライブラリ(*.a, *.so)化する

外部ライブラリがさらに他のライブラリに依存していたりする場合は、1の方がビルドが楽になることが多いですが、ncnnの場合は必要なのはncnnのライブラリとヘッダファイルだけです。そのため、今回は2の方針で進めようと思います。

ncnnのビルド

https://github.com/Tencent/ncnn/wiki/how-to-build 通りに進めるだけです。
protobuf云々の所は今の時点では無視してOKです。CMakeでプロジェクト生成時にwarningが出ると思いますが、それも無視してOKです。(変換ツール用のwarningなので)。

まず、以下コマンドでコードを取得します。一応現時点(2019/05/05)の最新リリースバージョンに固定しておきます。

ncnnソースコード取得(win/linux共通)
git clone https://github.com/Tencent/ncnn.git
cd ncnn
git checkout 20190320

Windows(Visual Studio)の場合

cmake-guiでVisual Studio用のプロジェクトを生成します。
今回は適当にc:\work\build_ncnn というフォルダに作ります。
パス設定後、ConfigureとGenerateをクリックします。
Configureでは所定のVisual Studioバージョンを選択します。
Configure時にwarningが出ますが、今の時点では無視してOKです。

image.png

生成されたncnn.sln をダブルクリックしてVisual Studioを開きます。
ソリューション構成をReleaseにします。(これは、後で作成するアプリケーションの設定とそろえる必要があります)
ソリューションエクスプローラ内のALL_BUILD を右クリックして、Build します。
ビルド完了後、INSTALL を右クリックして、Build します。

ncnnライブラリと必要なヘッダファイルが build_ncnn\install\libbuild_ncnn\install\include に生成されます。
これを適当なパスにコピーしておきます。
今回は、後で作るアプリケーション用プロジェクトの近くに、以下のように配置しました。
ExternalLibs/ncnn_prebuilt/x64_vs2017/include
ExternalLibs/ncnn_prebuilt/x64_vs2017/ncnn.lib

Linuxの場合

ncnnライブラリ生成コマンド
mkdir build && cd build
cmake ..
cmake ..  -DCMAKE_BUILD_TYPE=Release
make -j2
make install

Linux(Ubuntu)で上記コマンド実行で、ncnnライブラリと必要なヘッダファイルが build_ncnn\install\libbuild_ncnn\install\include に生成されます。
これを適当なパスにコピーしておきます。
今回は、後で作るアプリケーション用プロジェクトの近くに、以下のように配置しました。
ExternalLibs/ncnn_prebuilt/x64_linux/include
ExternalLibs/ncnn_prebuilt/x64_linux/libncnn.a

ncnnを使ったアプリケーションで必要なのは、ここで生成したヘッダファイル(.h)とライブラリファイル(.a/.lib)だけです。

ncnnを使用したアプリケーションの作成

ちょうど手元にあったので、Edge TPUのチュートリアルで使ったオウム をMobilenetで識別してみます。

Windows用とLinux用に分けるのが面倒なのでCMakeでまとめてしまいます。そのために、これまで生成したncnnライブラリの保存場所を変えておきました。
また、画像の読み込みにOpenCVを使用します。バージョンはOpenCV3.4.xです。Windowsの場合は、OpenCVConfig.cmake のあるパス(e.g. C:\opencv\build) を環境変数OpenCV_Dir に設定しておいてください。 Linuxの場合はldconfigで登録されていればOKです。

CMakeでプロジェクトを作る

CMakeLists.txt
cmake_minimum_required(VERSION 2.8.10)

# Create executable file
project(NcnnSimpleProject)
add_executable(NcnnSimpleProject
    Main.cpp
)

# For NCNN
set(PATH_TO_EXTERNAL_LIBS ${PROJECT_SOURCE_DIR}/../ExternalLibs)
if(WIN32)
    target_include_directories(NcnnSimpleProject PUBLIC ${PATH_TO_EXTERNAL_LIBS}/ncnn_prebuilt/x64_vs2017/include)
    target_link_libraries(NcnnSimpleProject ${PATH_TO_EXTERNAL_LIBS}/ncnn_prebuilt/x64_vs2017/ncnn.lib)
else()
    target_include_directories(NcnnSimpleProject PUBLIC ${PATH_TO_EXTERNAL_LIBS}/ncnn_prebuilt/x64_linux/include)   
    target_link_libraries(NcnnSimpleProject ${PATH_TO_EXTERNAL_LIBS}/ncnn_prebuilt/x64_linux/libncnn.a)
endif()

# For OpenCV
find_package(OpenCV REQUIRED)
if(OpenCV_FOUND)
    target_include_directories(NcnnSimpleProject PUBLIC ${OpenCV_INCLUDE_DIRS})
    target_link_libraries(NcnnSimpleProject ${OpenCV_LIBS})
endif()

# Copy resouce
file(COPY ${PROJECT_SOURCE_DIR}/../resource/ DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/resource/)
add_definitions(-DRESOURCE_DIR="${CMAKE_CURRENT_BINARY_DIR}/resource/")

ソースコードとしてMain.cpp だけを使い、NcnnSimpleProjectという実行ファイルを作ります。
Windows or Linuxに応じて、取り込むncnnライブラリとヘッダファイルを切り分けています。
その後、OpenCVの設定をし、最後に画像とモデルが入ったresourceフォルダを実行バイナリが出力される場所にコピーしています。

ソースコード

Main.cpp
#include <stdio.h>
#include <opencv2/opencv.hpp>
#include "net.h"

#define MODEL_WIDTH 224
#define MODEL_HEIGHT 224
//#define MODEL_CHANNEL ncnn::Mat::PIXEL_GRAY
#define MODEL_CHANNEL ncnn::Mat::PIXEL_BGR

int main()
{
    /*** Load ncnn model (probably, need this only once) ***/
    ncnn::Net net;
    net.load_param(RESOURCE_DIR"ncnn_mobilenet.param");
    net.load_model(RESOURCE_DIR"ncnn_mobilenet.bin");

    /*** Read image using OpenCV ***/
    cv::Mat image = cv::imread(RESOURCE_DIR"parrot.jpg", (MODEL_CHANNEL == ncnn::Mat::PIXEL_GRAY) ? CV_LOAD_IMAGE_GRAYSCALE : CV_LOAD_IMAGE_COLOR);
    cv::imshow("Display", image);

    /*** Prepare input image Mat for ncnn ***/
    ncnn::Mat ncnnMat = ncnn::Mat::from_pixels_resize(image.data, MODEL_CHANNEL, image.cols, image.rows, MODEL_WIDTH, MODEL_HEIGHT);
    float mean[3] = { 128.f, 128.f, 128.f };
    float norm[3] = { 1 / 128.f, 1 / 128.f, 1 / 128.f };
    ncnnMat.substract_mean_normalize(mean, norm);

    /*** Prepare inference ***/
    ncnn::Extractor ex = net.create_extractor();
    ex.set_light_mode(true);
    ex.set_num_threads(4);
    ex.input("data", ncnnMat);

    /*** Run inference ***/
    ncnn::Mat ncnnOut;
    ex.extract("mobilenetv20_output_flatten0_reshape0", ncnnOut);
    int outputNum = ncnnOut.w;
    float *results = new float[outputNum];
    for (int i = 0; i < outputNum; i++) {
        results[i] = ((float*)ncnnOut.data)[i];
    }

    float maxScore = 0;
    int maxIndex = -1;
    for (int i = 0; i < outputNum; i++) {
        if (maxScore < results[i]) {
            maxScore = results[i];
            maxIndex = i;
        }
    }

    printf("Result = %d (%.3f)\n", maxIndex, maxScore);

    cv::waitKey(0);
    return 0;
}

ソースコードは上記の通りです。めちゃくちゃ簡単です。
https://github.com/Tencent/ncnn/wiki のサンプルコードとほぼ同じです。
通常のユースケースでは、モデル読み込みは1回だけ、その後画像は適宜読み込むと思いますので、その順番だけ変えています。

モデル

今回使用したモデルは、ONNX形式のMobileNetをhttps://github.com/onnx/models/tree/master/models/image_classification/mobilenet から頂き、onnx2ncnnツールで変換したncnn_mobilenet.paramncnn_mobilenet.bin を使用しました。(https://github.com/take-iwiw/NcnnMultiPlatformProject/tree/master/resource )
ncnn_mobilenet.param にモデル構造、ncnn_mobilenet.bin にweightパラメータが入っているようです。
ex.inputex.extract で入出力ノードを指定する必要がありますが、この名前はncnn_mobilenet.param を参照して探しました。
モデル変換については別途記事にまとめようと思います。
とにかく今回は変換済みのモデルがあるものとして進めます。

実行結果

実行結果
Result = 88 (15.902)

実行すると、オウムの画像と上記のような結果が出力されます。
ImageNet1000クラスの内、最も確度が高いインデックスを出力しています。カッコ内が確度です。
(なぜ1以上の値なのかと思ったら、出力の時点ではSoftmaxがかかっていないようです。)

https://github.com/onnx/models/blob/master/models/image_classification/synset.txt を見ると、88番(89行目)はmacawなので、正しく識別出来ています。

Visual Studioでの注意点

Visual Studio起動後、所定のプロジェクト(NcnnSimpleProject) を右クリックして「スタートアッププロジェクトに設定」してください。
ncnnライブラリを生成したのと同じバージョンの同じ構成(Release/Debug)でビルドしてください。でないとエラーが出ます。

マルチプラットフォーム対応なプロジェクト

今回はWindowsとPC Linuxを対象にしましたが、Raspberry Piでも同じようにできます。
また、Visual Studioではバージョンや構成(Release/Debug)によって使用するライブラリを切り替える必要があります。
本記事では簡単のためここら辺は省略しましたが、対応したプロジェクトを

にアップロードしておきます。
今後、JetsonやGPUに対応したサンプルコードも増やしていきたいと思っています。

0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up