📑

SPRESENSEで画像認識2

2021/05/11に公開

前回のあらすじ

前回の時点でカメラで画像をキャプチャしuSDに保存できるようになりました。今回はSony NNCを使ってSpresenseで画像認識を行います。

作るもの(再掲)

息子3人の顔をSpresenseで画像認識することを目指します。

NNCとは

NNCとはSonyが開発したノーコードで深層/機械学習が行えるアプリケーションです(サイト)。
クラウド版とプリミティブ版があります。プリミティブ版は日本国内でのみダウンロード可能で無料で使えます。
NNCの使い方の説明はこちらYoutubeにも公式の詳しい説明があります(こちら)

全体の流れを俯瞰

NNCはアプリケーションです。Spresenseはカメラ付きの基板です。これがどう結びつくのか少し疑問ではないでしょうか。大まかな流れを俯瞰する前にそもそも機械学習の流れとNNCが何をするかを把握しておきましょう。

そもそもの機械学習(教師あり学習)の流れ

機械学習には色々な種類がありますが、今回行うのは教師あり学習です。教師あり学習の流れは以下の通りです。

  1. 答えがわかっている問題(教師データ)をコンピューターに大量に入力する
  2. コンピューターが正答率が上がるまで問題を何度も解く(学習)
  3. コンピューターが問題の解き方を覚える(学習済モデル)
    上記の学習済モデルを生成してくれる便利なツールがNNCです。

全体の流れ

教師あり学習の流れとNNCの役割について多少理解を深めた上で、全体の流れは以下の通りです。

  1. Spresenseで息子3人の顔写真を大量に撮影する(教師データとして用います)
  2. NNCに入力し学習済モデルを生成する
  3. 生成された学習済モデルをuSDカードに保存しSpresenseで読み込む
  4. Spresense+カメラが学習済モデルを元に被写体が誰かを認識する

なんだか簡単そうですね!

サンプルコードの説明

Spresenseのサンプルコードを読んでさらに理解を深めていきましょう。Arduino IDEのファイル > スケッチ例 > DNNRT > number_recognition からサンプルコードを見てみます。このサンプルコードはのpgmファイル(各画素が0-255のグレースケールで表示される画像)を読み取り、その画像が4か9かを判別するものです。Serialの設定やエラー対応などを取り除いたコードが以下です。

#include <SDHCI.h>
#include <NetPBM.h>
#include <DNNRT.h>

DNNRT dnnrt;
SDClass SD;

void setup() {
  File nnbfile = SD.open("network.nnb");
  int ret = dnnrt.begin(nnbfile);
  // Image size for this network model is 28 x 28.
  File pgmfile("number4.pgm");
  NetPBM pgm(pgmfile);
  unsigned short width, height;
  pgm.size(&width, &height);
  DNNVariable input(width * height);
  float *buf = input.data();
  
  int i = 0;
  /*
   * Normalize pixel data into between 0.0 and 1.0.
   * PGM file is gray scale pixel map, so divide by 255.
   * This normalization depends on the network model.
   */
  for (int x = 0; x < height; x++) {
    for (int y = 0; y < width; y++) {
      buf[i] = float(pgm.getpixel(x, y)) / 255.0;
      i++;
    }
  }

  dnnrt.inputVariable(input, 0);
  dnnrt.forward();
  DNNVariable output = dnnrt.outputVariable(0);

  /*
   * Get index for maximum value.
   * In this example network model, this index represents a number,
   * so you can determine recognized number from this index.
   */

  int index = output.maxIndex();
  Serial.print("Image is ");
  Serial.print(index);
  Serial.println();
  Serial.print("value ");
  Serial.print(output[index]);
  Serial.println();

  dnnrt.end();
}

void loop() {
  // put your main code here, to run repeatedly:

}

サンプルコードの理解を深める

各部分についてわかる範囲で簡単に説明します。
以下で、NNCで生成した学習モデルを読み込み使えるようにしています。

File nnbfile = SD.open("network.nnb");
int ret = dnnrt.begin(nnbfile);

画像データを読み込みソースコード内で使えるようにしています。

// Image size for this network model is 28 x 28.
File pgmfile("number4.pgm");
NetPBM pgm(pgmfile);

以下で、入力データのサイズを取得しバッファを定義しています。

unsigned short width, height;
pgm.size(&width, &height);
DNNVariable input(width * height);
float *buf = input.data();

次に入力データの各画素を0~1の範囲で正規化し上記で生成したバッファに値を順次入力しています。

int i = 0;
for (int x = 0; x < height; x++) {
  for (int y = 0; y < width; y++) {
    buf[i] = float(pgm.getpixel(x, y)) / 255.0;
    i++;
  }
}

学習済モデルを用いて入力データから出力値を求めます。

dnnrt.inputVariable(input, 0);
dnnrt.forward();
DNNVariable output = dnnrt.outputVariable(0);

結果を出力して終了します。出力結果が0に近ければ入力データは"4"で、出力結果が"1"に近ければ入力データは"9"と認識していることを示します。

  int index = output.maxIndex();
  Serial.print("Image is ");
  Serial.print(index);
  Serial.println();
  Serial.print("value ");
  Serial.print(output[index]);
  Serial.println();

  dnnrt.end();

今初めてサンプルコードに目を通しましたが、もっと簡単にAPI一発で画像認識できると思っていたのですが、そんな単純にはいかないかもしれません。顔画像認識できるのかちょっと不安になってきましたが、頑張ります。

Discussion