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





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





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





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





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





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





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


#include &ltCamera.h>
#include &ltFlash.h>
#include &ltDNNRT.h>
#include &ltFile.h>

#include "Adafruit_GFX.h"
#include &ltAdafruit_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入門【増補改訂・クラウド対応版】--数式なし、コーディングなしのディープラーニング

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



SONY SPRESENSE メインボード CXD5602PWBMAIN1

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



SONY SPRESENSE カメラモジュール CXD5602PWBCAM1

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