セマンティックセグメンテーション推論器のC++/nnabla実装について

267 views
Skip to first unread message

Nick

unread,
Aug 24, 2022, 2:49:51 AM8/24/22
to Neural Network Console Users (JP)
お世話になります。

表題の件に関して、Sony Neural Network Consoleで学習させたモデル(.nnpファイル)をC++/nnabla環境にインポートしてからの正しい実行方法のサンプルコードは存在するのでしょうか?

現状、Consoleで推論したときの出力画像とC++/nnabla環境で推論したときの出力画像が異なります。基本的なセグメンテーションはできているようには見えるのですが、Consoleで得た結果と比較するとC++/nnabla環境の方が荒く、ノイズが多いように見受けられます。Consoleの「評価」で出る結果は何かしら後処理がされているのでしょうか?

もしご存知の方がいらっしゃいましたらご教授いただければ幸いです。

Nick

unread,
Aug 30, 2022, 2:25:03 AM8/30/22
to Neural Network Console Users (JP)
本件に関して報告があります。
自分でいろいろ試行錯誤で探ったところ、NNCとnnablaの推論結果差異に対する要因を発見しました。
その結果をまとめて投稿します。

ネットワーク:DeepLabV3plusベース(加工あり)
用途:セマンティックセグメンテーション(領域分割)
入力画像:256x256 (24-bit, ※RGB)
推論出力:256x256 (8-bit, grayscale)

※元画像は8-bitだが、ネットワークの入力層は3-channel

0.0) OpenCVフォーマットの画像をそのまま用いる方法

// データの読み込み
cv::Mat img = cv::imread("data/test.png", cv::IMREAD_COLOR);

// Nnabla部分の初期化
nbla::Context cpu_ctx{{"cpu:float"}, "CpuCachedArray", "0"};
nbla::Context ctx = cpu_ctx;

nbla::utils::nnp::Nnp nnp(ctx);

const std::string nnp_filename = "model/export_model.nnp";
nnp.add(nnp_filename);

std::string executor_name("runtime");
auto executor = nnp.get_executor(executor_name);
executor->set_batch_size(1);

nbla::CgVariablePtr x = executor->get_data_variables().at(0).variable;

float *data = x->variable()->nbla::Variable::cast_data_and_get_pointer<float>(cpu_ctx);

// OpenCV -> nnabla展開
std::memcpy(data, img.data, 3 * 256 * 256 * sizeof(uint8_t));

0.1) 読み込んだ画像の正規化方法

// ピクセル毎の正規化
for (int i = 0; i < 256 * 256; i++)
{
     data[i] = (float)img.data[i] / 255.0;
}

1) OpenCVの(height, width, channel)形式からnnablaの(channel, height, width)形式に変換(上記正規化処理を含む)

uint32_t HEIGHT = 256;
uint32_t WIDTH = 256;
uint32_t CHANNELS = 3;

// 注意:本題では8-bit元画像を使用しているため、BGR/RGBの変換は行っていない
for (int hw = 0; hw < WIDTH * HEIGHT; hw++)
{
     for (int c = 0; c < CHANNELS; c++)
     {
         data[c * WIDTH * HEIGHT + hw] = (float)img.data[hw * CHANNELS + c] / 255.0;
     }
}

2) 本題では8-bitの出力画像を求めるが、出力はfloatタイプの推論結果となるため、変換が必要。
  ただし、切り上げではなく切り捨て処理を行う

// 推論を実行
std::cout << "Executing..." << std::endl;
executor->execute();

// 推論結果を取りに行く
nbla::CgVariablePtr y = executor->get_output_variables().at(0).variable;
const float *y_data = y->variable()->get_data_pointer<float>(cpu_ctx);

// 切り捨て処理を行う
uint8_t y_data_aux[256*256];

for (int i = 0; i < 256 * 256; i++)
{
    y_data_aux[i] = std::floor(y_data[i] * 255);
}

// OpenCV行列に展開
cv::Mat out(256, 256, CV_8U);
memcpy(out.data, y_data_aux, 256 * 256 * sizeof(uint8_t));

// データをディスクに書き込む
std::vector<int> compression_params;
compression_params.push_back(cv::ImwriteFlags::IMWRITE_PNG_STRATEGY);

cv::imwrite("data/out.png", out, compression_params);

3) NNCで学習が完了するにあたって、自動生成されるresults.nnpではなく
  明示的に「TRAINING」タブでモデルを
  右クリック→エクスポート→NNP (Neural Network Libraries file format)
  で生成されるmodel.nnpフアイルで推論を行う

(1) ~ (3) を合わせて、NNCとnnablaで同一結果を得られました(ImageJにて確認)。ご参考までに。

以上
Reply all
Reply to author
Forward
0 new messages