SSブログ

SPRESENSE で小型AIカメラを作ってみた! [SPRESENSE]

SPRESENSEにOLEDがついたので、今度はカメラをつけてAIカメラにしてみようと思います。久しぶりにNeural Network Console を使います。


sDSC01751.jpg


1. 画像を準備
まず、画像を用意します。ネットから適当に集めてきた”鳥”、”馬”、”うさぎ”、”人”、”壁”の画像を使いました。


2020-02-18.png


2. 画像を加工
これらを、ImageMagick を使って、28x28pixel 8bit (256階調)のグレースケールの画像に変換し、ニューラルネットワークのデータセットとして登録します。(詳細は、また別の機会に)


2020-02-18 (2).png


3. ニューラルネットワークをデザイン
Neural Network Console を開いてニューラルネットワークをデザインします。今回は LeNet ベースで作ってみました。(Spresenseに入るように少し改造しています)


2020-02-18 (3).png


4. ニューラルネットワークを学習・評価
登録したデータセットを使って、デザインしたニューロンを学習・評価します。


2020-02-18 (4).png


5. ニューロンモデルの出力
SPRESENSEで解釈できるニューロンモデル(model.nnb)を出力します。


2020-02-18 (5).png


6. Arduino スケッチの作成・焼き込み
ArduinoのAIスケッチを作成します。AIカメラを小さくしかったので、ニューロンモデルはSPRESENSEメインボードのフラッシュの中に格納してみました。

#include <Camera.h>
#include <Flash.h>
#include <DNNRT.h>
#include <File.h>

#include "Adafruit_GFX.h"
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

#define BAUDRATE    115200

#define OFFSET_X        48
#define OFFSET_Y         8
#define BOX_WIDTH      224
#define BOX_HEIGHT     224
#define DNN_IMG_WIDTH   28
#define DNN_IMG_HEIGHT  28


DNNRT dnnrt;
DNNVariable input(DNN_IMG_WIDTH*DNN_IMG_HEIGHT);

String gStrResult = "";


void CamCB(CamImage img) { 
  if (!img.isAvailable()) return;
  img.convertPixFormat(CAM_IMAGE_PIX_FMT_RGB565);  
  Serial.println("start clip and resize");
  gStrResult = "";
  int x;
  int index = 0;
  static uint8_t label[5] = {0, 1, 2, 3, 4};

  CamImage small;
  CamErr camErr = img.clipAndResizeImageByHW(small
              ,OFFSET_X ,OFFSET_Y 
              ,OFFSET_X+BOX_WIDTH-1 ,OFFSET_Y+BOX_HEIGHT-1 
              ,DNN_IMG_WIDTH ,DNN_IMG_HEIGHT);
  if (!small.isAvailable()) {
    Serial.println("Error Occured at making a target image");
    if (camErr) Serial.println("CamErr: " + String(camErr));
    return;
  }
  uint16_t* buf = (uint16_t*)small.getImgBuff();
  float* input_buffer = input.data();
  for (int i = 0; i < DNN_IMG_WIDTH * DNN_IMG_HEIGHT; ++i, ++buf) {
    input_buffer[i] = (float)(((*buf & 0x07E0) >> 5) << 2) ; // extract green
  }
    
  Serial.println("DNN forward");
  dnnrt.inputVariable(input, 0);
  dnnrt.forward();
  DNNVariable output = dnnrt.outputVariable(0);
  float max_value = 0.0;
  for (int i = 0; output.size() > i; ++i) {
    if (output[i] > max_value) {
      max_value = output[i];
      index = i;
    }
  }
  
  Serial.print("Result : ");
  Serial.println(label[index] + "  (" + String(output[index]) + ")");
  if (label[index] != 10) { 
    gStrResult += String(label[index]);
  } else {
    gStrResult += String(" ");
  }
  
  display.clearDisplay();
  display.setTextSize(3);      // Normal 1:1 pixel scale
  display.setCursor(0, 0);     // Start at top-left corner
  display.cp437(true);         // Use full 256 char 'Code Page 437' font

  switch (index) {
  case 0: display.println("BIRD"); break;
  case 1: display.println("HORSE"); break;
  case 2: display.println("HUMAN"); break;
  case 3: display.println("-----"); break;
  case 4: display.println("RABBIT"); break;
  }
  display.display(); 
  Serial.println("Recognition Result: " + gStrResult);
}

void setup() {
  Serial.begin(BAUDRATE);

  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }

  display.display();
  delay(2000); // Pause for 2 seconds

  // Clear the buffer
  display.clearDisplay();

  // Draw a single pixel in white
  display.drawPixel(10, 10, SSD1306_WHITE);  
  display.display();
  delay(2000);

  display.setTextSize(1);      // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE); // Draw white text
  display.setCursor(0, 0);     // Start at top-left corner
  display.cp437(true);         // Use full 256 char 'Code Page 437' font

  for(int16_t i=0; i<256; i++) {
    if(i == '\n') display.write(' ');
    else          display.write(i);
  }

  display.display();
  
  Serial.println("Loading network model");
  File nnbfile = Flash.open("model.nnb", FILE_READ);
  if (!nnbfile) {
    Serial.println("nnb not found");
    while(1);
  }
  Serial.println("Initialize DNNRT");
  int ret = dnnrt.begin(nnbfile);
  if (ret < 0) {
    Serial.println("DNNRT initialize error.");
    while(1);
  } 

  theCamera.begin();
  theCamera.startStreaming(true, CamCB);
}

void loop() {
  /* do nothing here */  
}




動作の様子は動画でどうぞ!!( ^ω^ )/~







関連記事
SPRESENSEでSony Neural Network Console を使ってみた! 
https://makers-with-myson.blog.ss-blog.jp/2019-07-14





ソニー開発のNeural Network Console入門【増補改訂・クラウド対応版】--数式なし、コーディングなしのディープラーニング

ソニー開発のNeural Network Console入門【増補改訂・クラウド対応版】--数式なし、コーディングなしのディープラーニング

  • 出版社/メーカー: リックテレコム
  • 発売日: 2018/11/14
  • メディア: 単行本(ソフトカバー)



SONY SPRESENSE メインボード CXD5602PWBMAIN1

SONY SPRESENSE メインボード CXD5602PWBMAIN1

  • 出版社/メーカー: スプレッセンス(Spresense)
  • メディア: Tools & Hardware



SONY SPRESENSE カメラモジュール CXD5602PWBCAM1

SONY SPRESENSE カメラモジュール CXD5602PWBCAM1

  • 出版社/メーカー: スプレッセンス(Spresense)
  • メディア: Tools & Hardware




nice!(23)  コメント(4) 

nice! 23

コメント 4

ぽちの輔

こんなに小さいのに賢いですねぇ^^
by ぽちの輔 (2020-02-19 06:12) 

CGM11

コメント

Hi Taro, I have two questions:

1)- What's the size of your model file?

2)- is The Flash singleton automatically mapped to an SD card on the OLED?
by CGM11 (2020-02-20 01:01) 

ys_taro

ぽちの輔さん、ありがとうございます!

Hi CGM11,
Here are the answers
1) 76kB
2) The Flash is not on the OLED. I'm using an equipped 8MB flash on Spresense Mainboard.
by ys_taro (2020-02-22 03:17) 

renpoco

すみません、質問させてください。
SpresenseのFlashエリアに予めnnbを書き込んでおく操作はどのように行われたのでしょうか。
Arduino IDEから行えるのでしょうか。
不躾な質問ですがどうぞ宜しくお願い申し上げます。
by renpoco (2021-04-13 12:23) 

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。