ncnnでよくある問題とその対処

## 概要
ncnnのWikiに記載されている、よくある問題とその対処を翻訳して紹介します。


## ncnnはなぜ開発されたできたのか？
ディープラーニングアルゴリズムを携帯電話に実装する必要があり、caffeは依存関係が多すぎるし、携帯電話にはcudaがないので、高速で小さなフォワードネットワークの実装が必要でした。

## ncnnの名前の由来は？
 cnnはconvolutional neural network（畳み込みニューラルネットワーク）の略称で、冒頭のnはワンライナーのようなものです。 例えば、new/next(新しい実装)、naive(ncnnは素朴な実装)、neon(ncnnはもともと携帯電話向けに最適化された)。

## 対応プラットフォームは？
クロスプラットフォーム、android/ios/linux/windows/macosに対応、ベアメタルにも対応している。

A 計算精度はどうですか？
armv7 neon floatはieee754標準に準拠していないため、高速な実装（exp sinなど）を使用しているものもあり、高速ですが精度は十分高いです。


## ncnn の作者は？
nihui氏です。

## ソースコードのダウンロード方法は＿
```
git clone --recursive https://github.com/Tencent/ncnn/
```

## クロスコンパイルの方法と、cmakeツールチェインの設定方法は？
 https://github.com/Tencent/ncnn/wiki/how-to-build

## The submodules were not downloaded! Please update submodules with "git submodule update --init" and try again　とでた場合は？
上記のように、完全なソースコードをGitからrecursiveでダウンロードします。 または、プロンプトに従ってください

```
git submodule update --init
```

## Could NOT find CUDA (missing: CUDA_TOOLKIT_ROOT_DIR CUDA_INCLUDE_DIRS CUDA_CUDART_LIBRARY)と表示される

https://github.com/Tencent/ncnn/issues/1873

## Could not find a package configuration file provided by "OpenCV" with any of the following names: OpenCVConfig.cmake opencv-config.cmakeと表示される
```
sudo apt-get install libopencv-dev

```
または自分でコンパイルしてインストールする。

```CMakeList.txt
set(OpenCV_DIR {OpenCVConfig.cmakeがあるディレクトリ})
```
## Could not find a package configuration file provided by "ncnn" with any of the following names: ncnnConfig.cmake ncnn-config.cmakeと表示される
```

set(ncnn_DIR {ncnnConfig.cmakeがあるディレクトリ})
```
## Vulkanが見つからない。
cmakeのバージョンは3.10以上です。そうでない場合はFindVulkan.cmakeが付属しません。
android-api >= 24
macosの場合、最初にインストールスクリプトを実行する必要があります。

## undefined reference to typeinfo for ncnn::Layerと表示される
opencv rtti -> opencv-mobileへ変更する

## undefined reference to __cpu_modelと表示される
コンパイラ( libgcc_s libgcc)のアップグレードが必要

## unrecognized command line option "-mavx2"と表示される
gccのアップグレードが必要

## 私がコンパイルしたncnnアンドロイド・ライブラリが特に大きいのはなぜですか？
https://github.com/Tencent/ncnn/wiki/build-for-android.zh、より小さなncnnライブラリをカットする方法を参照してください。

## ncnnoptimizeとカスタムレイヤー
ncnnoptimizeがカスタムレイヤーの保存を処理できなくなるのを防ぐため、カスタムレイヤーを追加する前にncnnoptimizeを実行してください。

## rtti/EXCEPTIONの設定
ncnnはデフォルトでONになっているので、ncnnを再コンパイルする際に以下の2つのパラメーターを追加する

```
OFF:-DNCNN_DISABLE_RTTI=OFF -DNCNN_DISABLE_EXCEPTION=OFF
ON:-DNCNN_DISABLE_RTTI=ON -DNCNN_DISABLE_EXCEPTION=ON
```

## error: undefined symbol: ncnn::Extractor::extract(char const*, ncnn::Mat&)と表示される
Android StudioのNDKバージョンをアップグレードしてみる

## CMake 3.14.0 or higher is required. You are running version 2.8.12.2と表示される
```
wget https://github.com/Kitware/CMake/releases/download/v3.18.2/cmake-3.18.2-Linux-x86_64.tar.gz
tar zxvf cmake-3.18.2-Linux-x86_64.tar.gz
mv cmake-3.18.2-Linux-x86_64 /opt/cmake-3.18.2
ln -sf /opt/cmake-3.18.2/bin/* /usr/bin/
```

## プロジェクトにncnnライブラリを追加するには？
linux/windowsでは，make installを実行。
cmakeのncnn_DIR の設定で、インストールディレクトリの ncnnConfig.cmake を含むディレクトリを設定します。

## 学習モデリング変換の問題

 - caffe
```
./caffe2ncnn caffe.prototxt caffe.caffemodel ncnn.param ncnn.bin
```

-  mxnet
```
 ./mxnet2ncnn mxnet-symbol.json mxnet.params ncnn.param ncnn.bin
```
 - darknet
```
https://github.com/xiangweizeng/darknet2ncnn
```
-  pytorch - onnx

https://github.com/Tencent/ncnn/wiki/use-ncnn-with-pytorch-or-onnx

- tensorflow 1.x/2.x - keras
```
https://github.com/MarsTechHAN/keras2ncnn @MarsTechHAN
```
- tensorflow 2.x - mlir
- MLIR経由でtensorflow2のモデルをncnnに変換する 
https://zhuanlan.zhihu.com/p/152535430

- Shape not supported yet! Gather not supported yet! Cast not supported yet!
onnx-simplifier でshapeを整える

- convertmodel

https://convertmodel.com/ 
- netron
https://github.com/lutzroeder/netron


## Shape情報を固定してモデルを生成するには？

Input 0=w 1=h 2=c



## Could NOT find Protobuf (missing: Protobuf_INCLUDE_DIR)と表示される

```
sudo apt-get install libprotobuf-dev protobuf-compiler
```

### モデルを暗号化する方法は？

https://zhuanlan.zhihu.com/p/268327784

### 後処理を削除してonnxでエクスポートするには？

https://zhuanlan.zhihu.com/p/128974102 の記事の中でステップ3は、後処理を削除し、onnxをエクスポートすることができます。プロジェクト内でテストする際に、後続のステップを削除することになります。

### pytorchはいくつかのレイヤーからonnxを導出することができません。
まずエクスポートできる、ONNX_ATEN_FALLBACK 完全にカスタマイズされたopで、（例：スライスの連結）に変更し、ncnnに移動した後にparamを変更する。
もしくは、PNNXで出力する。PNNXの大まかな説明は以下の記事を参照されたい。
https://zhuanlan.zhihu.com/p/431833958
https://zhuanlan.zhihu.com/p/427512763




## Matlab が出力するcaffemodel が row-major になっていない

caffe2ncnnツールは、caffemodelがrow-major（pycaffe/c++のcaffe trainコマンドで生成）であることを前提としています。

例えば、カーネル3x3の重みは以下のように、格納されます。

```Matlab
a b c
d e f
g h i
```
しかし、matlab が出力するcaffeはcol-major caffemodelを生成します。

```Matlab
a d g
b e h
c f i
```

全てのカーネル重みを自分で転置するか、c++ caffe trainコマンドを使って再トレーニングする必要があります。また、 matcaffe model (column major)からpycaffe / c++ caffe (row major) modelへ変換するツールがあります。

https://github.com/conanhujinming/matcaffe2caffe

##  input画像が RGBか BGRかを確認する

もしあなたのモデルがcaffemodelがc++ caffeとopencvを使ってトレーニングされたものであれば、入力画像はBGRオーダーになるはずです。もしあなたのモデルがmatlab caffeやpytorch、mxnet、tensorflowを使って学習されたものであれば、入力画像はおそらくRGB順になります。

チャンネル順序は、ncnnの処理によって、以下のように適切なタイプに変更できます。

```C++
// construct RGB blob from rgb image
ncnn::Mat in_rgb = ncnn::Mat::from_pixels(rgb_data, ncnn::Mat::PIXEL_RGB, w, h);

// construct BGR blob from bgr image
ncnn::Mat in_bgr = ncnn::Mat::from_pixels(bgr_data, ncnn::Mat::PIXEL_BGR, w, h);

// construct BGR blob from rgb image
ncnn::Mat in_bgr = ncnn::Mat::from_pixels(rgb_data, ncnn::Mat::PIXEL_RGB2BGR, w, h);

// construct RGB blob from bgr image
ncnn::Mat in_rgb = ncnn::Mat::from_pixels(bgr_data, ncnn::Mat::PIXEL_BGR2RGB, w, h);
```

## imageデコード
JPEG(.jpg、.jpeg)は圧縮率が低いため、同じ位置の同じ画像でも画素値が異なる場合があります。代わりに.bmp画像をお勧めします。

## 補間／リサイズ
画像のリサイズにはいくつかの方法があり、同じ入力画像でも異なる結果を生成することがあります。
同じ補間方法を指定しても、フレームワーク/ライブラリやそのバージョンが異なれば、違いが生じる可能性があります。例えば、入力レイヤーが224x224のサイズを必要とする場合、224x244のbmp画像を読み込むようにします。

## Mat::from_pixels/from_pixels_resizeは、連続したピクセルバッファを仮定

連続したピクセルバッファを from_pixels ファミリに渡す必要があります。
OpenCVで，画像 がroiから得られた切り取られた画像である場合は， clone() を呼び出して，連続したピクセルバッファ画像を取得します。

```C++
cv::Mat image;// the image
cv::Rect facerect;// the face rectangle

cv::Mat faceimage = image(facerect).clone();// get a continuous sub image

ncnn::Mat in = ncnn::Mat::from_pixels(faceimage.data, ncnn::Mat::PIXEL_BGR, faceimage.cols, faceimage.rows);
```

## 事前処理

トレーニングの設定に従って前処理を適用します。
モデルによってプリプロセスの設定は異なりますが、データレイヤーセクションに以下のような変換のパラメータ設定があります。

```C++
transform_param {
    mean_value: 103.94
    mean_value: 116.78
    mean_value: 123.68
    scale: 0.017
}
```
すると、ncnnの前処理に対応するコードは次のようになります。
```
const float mean_vals[3] = { 103.94f, 116.78f, 123.68f };
const float norm_vals[3] = { 0.017f, 0.017f, 0.017f };
in.substract_mean_normalize(mean_vals, norm_vals);
```

pytorchまたはmxnet-gluonの場合は以下のように、パラメータを設定します。

```C++
transforms.ToTensor(),
transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)),
```
このときには、ncnnの前処理に対応するコードは、0~1の範囲のデータを0~255の範囲のデータになるように変換を行う必要があるため、次のようになります。

```
const float mean_vals[3] = {0.485f*255.f, 0.456f*255.f, 0.406f*255.f};
const float norm_vals[3] = {1/0.229f/255.f, 1/0.224f/255.f, 1/0.225f/255.f};
in.substract_mean_normalize(mean_vals, norm_vals);

// R' = (R / 255 - 0.485) / 0.229 = (R - 0.485 * 255) / 0.229 / 255
// G' = (G / 255 - 0.456) / 0.224 = (G - 0.456 * 255) / 0.224 / 255
// B' = (B / 255 - 0.406) / 0.225 = (B - 0.406 * 255) / 0.225 / 255
```

## ncnn::Extractorのbolb名をモデルのbolb名とあわせる

入力と出力のblob名はモデルによって異なります。
例えば、squeezenet v1.1では入力ブロブとして "data"、出力ブロブとして "prob "を使用しますが、mobilenet-ssdでは入力ブロブとして "data"、出力ブロブとして "detection_out "を使用します。
モデルによっては、複数の入力が必要な場合や、複数の出力を生成する場合があります。

```C++
ncnn::Extractor ex = net.create_extractor();

ex.input("data", in);// change "data" to yours
ex.input("mask", mask);// change "mask" to yours

ex.extract("output1", out1);// change "output1" to yours
ex.extract("output2", out2);// change "output2" to yours
```

## blob が channel gapを持つ場合

各チャンネルのポインタは，ncnn Mat 構造体内で 128bit ずつずらして保持しています。
(width x height) が 4 で正確に割り切れない場合，チャンネル間にギャップが生じる可能性があります。

画像データから入力 blob を作成するには， ncnn::Mat::from_pixels または、ncnn::Mat::from_pixels_resize を利用することを推奨します．

連続した blob バッファが必要な場合は，出力の形状を変更します

```C++
// out is the output blob extracted
ncnn::Mat flattened_out = out.reshape(out.w * out.h * out.c);

// plain array, C-H-W
const float* outptr = flattened_out;
```

## 画像ごとに新しい Extractor を作成する
ncnn::Extractor オブジェクトは処理状態を保持しています。異なる入力データに対して再利用すると、常に内部にキャッシュされた全く同じ結果が得られます。
Extractor がどのように動作するかを把握している場合を除き、画像をループで処理するには常に新しい Extractor を作成します。

```C++
for (int i=0; i<count; i++){
    // always create Extractor
    // it's cheap and almost instantly !
    ncnn::Extractor ex = net.create_extractor();

    // use
    ex.input(your_data[i]);
}
```

## 適切なLoad 関数を使う
メモリ上に配置したパラメータファイルバッファをロードしたい場合は、Net::load_param の代わりに Net::load_param_mem を使用しなければなりません。ncnn モデルのロード API については ncnn-load-model を参照してください。

[ncnn-load-model](https://github.com/Tencent/ncnn/wiki/ncnn-load-model)
```
ncnn::Net net;

// param_buffer is the content buffe of XYZ.param file
net.load_param_mem(param_buffer);
```

## fp16を無効にする
モデルによってはfp16がオーバーフローし、nanの結果になることがあります。
そこで、fp16の低精度最適化をオフにしてみると、精度がfp32に改善され、これに起因するオーバーフロー問題を調査・解決することができます。


```
ncnn::Net net；

net.opt.use_fp16_packed = false；
net.opt.use_fp16_storage = false；
net.opt.use_fp16_arithmetic = false；
```

## 参考
この記事は以下のページを参考に作成しました。

[FAQ-ncnn-produce-wrong-result](https://github.com/Tencent/ncnn/wiki/FAQ-ncnn-produce-wrong-result)
[FAQ]https://github.com/Tencent/ncnn/wiki/faq)