Spresenseで人工知能 -入門-

この記事は[リンク情報システム](https://www.lis.co.jp/)の2018年アドベントカレンダーのリレー記事です。
engineer.hanzomon のグループメンバによってリレーされます。
（リンク情報システムのFacebookは[こちら](https://www.facebook.com/lis.co.jp/)から）


#はじめに
最後の記事を担当します、mk-takahashiです。
今日はクリスマスですが、残念ながらこの記事は一切クリスマスに関係のない内容となります。

今回は表題の通り、SONYのマイコンボード[Spresense](https://developer.sony.com/ja/develop/spresense/#overview-content)のDNNRTライブラリを用いて画像認識処理を行いたいと思います。

#使うもの
・Spresense（メインボードと拡張ボード）
・microSDカード
・[7segLED](http://akizukidenshi.com/catalog/g/gI-03673/)
・タクトスイッチ
<img src="https://qiita-image-store.s3.amazonaws.com/0/286312/6e309966-7b13-dac3-0565-fb61e43b00f7.jpeg" width=70%>

#準備
今回作成するコードはサンプルスケッチ"number_recognition.ino"を元にして作成します。
このスケッチはSonyの[Neural Network Console](https://dl.sony.com/ja/)で学習したモデルを使用して、マイコン側0~9までの数字の認識処理を行うというものです。

サンプルプログラム用の学習モデルの作成方法は[Spresense Arduino Library 開発ガイド](https://developer.sony.com/ja/develop/spresense/developer-tools/get-started-using-arduino-ide/developer-guide#_dnnrtライブラリ)に書いてあるので、やり方にしたがって準備してください。

※ちなみに学習モデルを作成するのにはちょっぴり時間がかかります。
私は作成に12分程度かかりました。

#処理の流れ
全体的な処理の流れは以下の通りになります。
①マイコン：USB MSC機能を有効化（PC上からSDカードの中身が見られるようにする）
②ユーザー：認識させたい画像（今回はサンプルで提供されている画像）をSDに入れる
③ユーザー：タクトスイッチを押す(押されるまで待機)
④マイコン：USB MSC機能を無効化
⑤マイコン：認識処理を実施する
⑥マイコン：7segLEDに認識した数字を表示する
以降①からの繰り返し

※②で用意する認識させたい画像はPGM(Portable Gray Map)で、サイズが28×28のものを用意してください

#コード
```C:
#include <SDHCI.h>
#include <NetPBM.h>
#include <DNNRT.h>

SDClass SD;
DNNRT dnnrt;

boolean Number[10][7]={
  {1,1,1,1,1,1,0},
  {0,1,1,0,0,0,0},
  {1,1,0,1,1,0,1},
  {1,1,1,1,0,0,1},
  {0,1,1,0,0,1,1},
  {1,0,1,1,0,1,1},
  {0,0,1,1,1,1,1},
  {1,1,1,0,0,0,0},
  {1,1,1,1,1,1,1},
  {1,1,1,0,0,1,1}
};

void Ledoff(){
  digitalWrite(0, LOW);
  digitalWrite(1, LOW);
  digitalWrite(2, LOW);
  digitalWrite(3, LOW);
  digitalWrite(4, LOW);
  digitalWrite(5, LOW);
  digitalWrite(6, LOW);
  digitalWrite(7, LOW);
}

void setup() {
  pinMode(0, OUTPUT);
  pinMode(1, OUTPUT);
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(8, INPUT_PULLUP);
  Ledoff();
  
  if (!SD.begin()) {
        for (int i=0;i<7;i++){
      digitalWrite(0, Number[0][i]);
    }
    digitalWrite(7,1);
  }
}

void loop() {
  if (SD.beginUsbMsc()) {
    for (int i=0;i<7;i++){
      digitalWrite(1, Number[1][i]);
    }
    digitalWrite(7,1);
  }

  while (true) {
    if(!digitalRead(8)){
      break;
    }
  }

  if (SD.endUsbMsc()) {
    for (int i=0;i<7;i++){
      digitalWrite(2, Number[2][i]);
    }
    digitalWrite(7,1);  
  }

  delay(1000);
  
  File nnbfile("network.nnb");
  if (!nnbfile) {
    for (int i=0;i<7;i++){
      digitalWrite(3, Number[3][i]);
    }
    digitalWrite(7,1);  
    return;
  }
  int ret = dnnrt.begin(nnbfile);
  if (ret < 0) {
    for (int i=0;i<7;i++){
      digitalWrite(4, Number[4][i]);
    }
    digitalWrite(7,1);  
    return;
  }

  Ledoff();
  delay(100);

  File pgmfile("number.pgm");
  NetPBM pgm(pgmfile);

  unsigned short width, height;
  pgm.size(&width, &height);

  DNNVariable input(width * height);
  float *buf = input.data();
  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);

  int index = output.maxIndex();
  for (int i=0;i<7;i++){
    digitalWrite(i, Number[index][i]);
  }

  dnnrt.end();
}
```

※SpresenseをPCにつなぐ際は、拡張ボード側のmicroUSB端子を使用してください。
メインボード側だとUSB MSC機能が働かないので実行中に画像を変更することができません。
※上記のプログラムを使う際は、画像ファイル名は「number.pgm」にしてください。

#結果＆感想
SDカードに保存した画像の数値を認識して、7SegLEDに表示するプログラムを作成できました。
下のGIFは処理の流れ②まで実施済みです。認識させているのは「4」の画像です。
<img src="https://qiita-image-store.s3.amazonaws.com/0/286312/1551d5f7-79ae-5525-3b0b-680bb4e4994c.gif" width=70%>


すごく簡単にできるので、皆さんにもとりあえずサクッとやってほしいですね。

今はユーザーが用意した画像でしか認識処理ができませんが、カメラモジュールなどをつけて、マイコン側だけで処理が完結できるようにしたいです。
数字以外の学習データを作成して、認識させるのもやりたいです。

#問題点
ここまでいいことばかり書き連ねてきましたが、現時点（2018年12月20日現在）で重大な問題があります。

3回以上連続で認識処理を実施することができないです。

```C:
  DNNVariable output = dnnrt.outputVariable(0);

  int index = output.maxIndex();
  for (int i=0;i<7;i++){
    digitalWrite(i, Number[index][i]);
  }
```
上記のコードで、outputに認識結果が入ります。
outputは要素数10の配列で要素番号0に画像の数字が0である確率、要素番号1に画像の数字が1である確率、・・・といった形で値が納められています。
indexにはoutputの中で一番大きな数字(一番確率の高いもの)の要素番号が入り、その値に応じたLEDを光らせます。

2回目まではそれが正しく動いているみたいなのですが、3回目からはoutputの全要素が0.1になり正常に動作しなくなります。
4回目以降は応答がなくなります。

2018年12月17日にDNNRTライブラリの[不具合修正](https://developer.sony.com/ja/develop/spresense/developer-tools/release-notes/spresense-v1-1-2-release-2018-12-17/)がでていて、それを使っているはずなんですがうまくいかないです。
自分のコードに問題があるのか、ライブラリの不具合修正が実は適応出来てないのか、はたまた別の原因があるのかは今後調べようと思います。