この記事について
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つが考えられると思います。
- 自分のソースコードと一緒にビルドする
- 外部ライブラリだけ別途ビルドして、ライブラリ(*.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)の最新リリースバージョンに固定しておきます。
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です。
生成されたncnn.sln
をダブルクリックしてVisual Studioを開きます。
ソリューション構成をReleaseにします。(これは、後で作成するアプリケーションの設定とそろえる必要があります)
ソリューションエクスプローラ内のALL_BUILD
を右クリックして、Build
します。
ビルド完了後、INSTALL
を右クリックして、Build
します。
ncnnライブラリと必要なヘッダファイルが build_ncnn\install\lib
とbuild_ncnn\install\include
に生成されます。
これを適当なパスにコピーしておきます。
今回は、後で作るアプリケーション用プロジェクトの近くに、以下のように配置しました。
ExternalLibs/ncnn_prebuilt/x64_vs2017/include
ExternalLibs/ncnn_prebuilt/x64_vs2017/ncnn.lib
Linuxの場合
mkdir build && cd build
cmake ..
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j2
make install
Linux(Ubuntu)で上記コマンド実行で、ncnnライブラリと必要なヘッダファイルが build_ncnn\install\lib
とbuild_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でプロジェクトを作る
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フォルダを実行バイナリが出力される場所にコピーしています。
ソースコード
#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.param
とncnn_mobilenet.bin
を使用しました。(https://github.com/take-iwiw/NcnnMultiPlatformProject/tree/master/resource )
ncnn_mobilenet.param
にモデル構造、ncnn_mobilenet.bin
にweightパラメータが入っているようです。
ex.input
とex.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に対応したサンプルコードも増やしていきたいと思っています。