CDH29のアイコン画像
CDH29 2022年09月21日作成 (2022年09月24日更新) © GPL-3.0+
製作品 製作品 閲覧数 1604
CDH29 2022年09月21日作成 (2022年09月24日更新) © GPL-3.0+ 製作品 製作品 閲覧数 1604

【Spresense】でNNCを使ったカメラ撮影に挑戦した

【Spresense】でNNCを使ったカメラ撮影に挑戦した

概要

自宅庭に野良猫がしばしば訪れます。
その生態を捉えるため、猫を認識したら撮影するシステムを構築しようと思い立ちました。

Spresenseで監視

AIで猫を認識し

その画像を記録し

SDcardに保存する

私はお気軽に趣味でarduinoを用いた電子工作をする程度の知識しか持ち合わせておらず、
AIに興味を持ちながらもpythonの敷居を高く感じておりました。
NNCであればGUIで初心者でも何とかなるのではないかと思い、今回のコンテストを機会に挑戦してみることにしました。

部品

  • spresens メインボード
  • spresens 拡張ボード
  • spresens カメラボード
  • Transend 1GB micro SD
  • モバイルUSBバッテリー
  • タッパー

キャプションを入力できます

NNCをはじめる

「sony NNC」で検索すると公式チュートリアルなど出てくるので概要をつかむことができましたが、
AIについての基礎を何も持ち合わせていないので、それだけでは不十分でした。

参考図書 工学社 はじめての「SonyNNC」改訂版 柴田良一 著

これは、丁寧に練習問題を行いながらNNCの理解を深めることができ 大変参考になりました。

NNCで猫を認識したい

参考図書の実例では
NNCのサンプルプロジェクトにある、
2層Neural Networkで構成された「01_logistic_regression」で画像認識を行うよりも、多層NNのうち、畳み込みNN(CNN)で構成された「02_binary_cnn」の方が認識率が高くなることが分かりました。

今回は後者のサンプルプロジェクトを基にNNCを構成してみることにしました。

  1. 画像検索で「猫」の写真を370枚集めました。対象として「犬」の画像も370枚集めた
  2. それをもとに、白黒・28x28サイズにしてデータセットを作成
  3. 「02_binary_cnn」から新規project作成
    キャプションを入力できます
  4. しかし accuracy0.48 に留まったため対象画像を「犬」から「人間」に変更。同じく370枚とした。
  5. 新たにデータセットを作成し直しやり直したところ、accuracy 0.9047 と好成績だった(と思う)
    キャプションを入力できます
    キャプションを入力できます
  6. 試しにCNNではなく2層NNで同様のデータセットを用いて検討したが accuracy0.7333 だったため却下
  7. さらに上記④のプロジェクト(猫と人間で学習)で「構造自動探索」を行って成績向上をめざした
    1.10件ほど検討したが成績は向上せず時間もかかるのでそれ以上は行わなかった
  8. プロジェクトを右クリック→エクスポート→NNBを選択→[model.nnb]をFAT32でフォーマットしておいたmicroSDの直下に保存した

windows版CNNでは画像認識はうまく構成されたと思います。

スケッチ

参考図書 オライリー・ジャパン SPRESENSEではじめるローパワーエッジAI 太田 義則  著

NCCで認識し撮影する

#include <Camera.h> #include <DNNRT.h> #include <SDHCI.h> #include <stdio.h> #define BAUDRATE (115200) #define TOTAL_PICTURE_COUNT (20) SDClass theSD; DNNRT dnnrt; const int DNN_width = 28; const int DNN_height = 28; const float threshold = 0.65; boolean save_flag = false; int take_picture_count = 0; DNNVariable input(DNN_width * DNN_height); void printError(enum CamErr err)  // カメラ エラーコード { Serial.print("Error: "); switch (err) { case CAM_ERR_NO_MEMORY: Serial.println("No memory"); break; default: break; } } void CamCB(CamImage img) { if (!img.isAvailable()) return; // カメラ画像の切り抜きと縮小 CamImage small_image; CamErr err = img.clipAndResizeImageByHW(small_image, 48, 8, 271, 231, DNN_width, DNN_height); // 縮小に失敗したらリターン if (!small_image.isAvailable()) return; // 認識用モノクロ画像を設定 uint16_t* Imgbuf = (uint16_t*)small_image.getImgBuff(); float *dnnbuf = input.data(); for (int n = 0; n < DNN_height*DNN_width; ++n) { dnnbuf[n] = (float)(((Imgbuf[n] & 0xf000) >> 8) | ((Imgbuf[n] & 0x00f0) >> 4))/255.0; } // 推論結果 dnnrt.inputVariable(input, 0); dnnrt.forward(); DNNVariable output = dnnrt.outputVariable(0); if((output[0] > threshold)) { save_flag = true; Serial.println("DNNRT recognized the picture as a cat."); } else { Serial.println("dnnrt couldn't recognize."); } } void setup() { CamErr err; Serial.begin(BAUDRATE); while (!Serial) { ; } // SDカードにある学習済モデルの読み込み File nnbfile = theSD.open("model.nnb"); if (!nnbfile) { Serial.print("nnb not found"); return; } // 学習済モデルでDNNRTを開始 int ret = dnnrt.begin(nnbfile); if (ret < 0) { Serial.print("Runtime initialization failure. "); Serial.print(ret); Serial.println(); return; } Serial.println("Prepare camera"); err = theCamera.begin(); if (err != CAM_ERR_SUCCESS) { printError(err); } Serial.println("Prepare camera"); theCamera.begin(); Serial.println("Start streaming"); theCamera.startStreaming(true, CamCB); Serial.println("Set Auto white balance parameter"); theCamera.setAutoWhiteBalanceMode(CAM_WHITE_BALANCE_DAYLIGHT); Serial.println("Set still picture format"); err = theCamera.setStillPictureImageFormat( CAM_IMGSIZE_QUADVGA_H, CAM_IMGSIZE_QUADVGA_V, CAM_IMAGE_PIX_FMT_JPG); if (err != CAM_ERR_SUCCESS) { printError(err); } pinMode(LED0, OUTPUT); // LED準備 } void loop() { sleep(1); if (take_picture_count < TOTAL_PICTURE_COUNT) { Serial.println("call takePicture()"); CamImage img = theCamera.takePicture(); if(save_flag) { if (img.isAvailable()) { char filename[16] = {0}; sprintf(filename, "CAT%03d.JPG", take_picture_count); Serial.print("Save taken picture as "); Serial.print(filename); Serial.println(""); theSD.remove(filename); File myFile = theSD.open(filename, FILE_WRITE); myFile.write(img.getImgBuff(), img.getImgSize()); myFile.close(); // 撮影をLEDでも 視認できるようにする digitalWrite(LED0, HIGH); delay(1000); digitalWrite(LED0, LOW); delay(10); } else { Serial.println("Failed to take picture"); } take_picture_count++; } save_flag = false; } else { Serial.println("END."); dnnrt.end(); theCamera.end(); } }

しかし猫の画像をカメラ前においても撮影することができずうまくいきませんでした。

これが結構 ハマってしまいました。
色々変数を変えたりnnbファイルを作り直したりして原因を探りました。

はじめはCamErrを記述していませんでしたので上記スケッチのように加えたところ
”No Memory"を返しているために撮影できないことがわかりました

サンプルスケッチ[camera]の記述を利用していましたが、
setStillPictureImageFormatを

 CAM_IMGSIZE_QVGA_H,
 CAM_IMGSIZE_QVGA_V,
 CAM_IMAGE_PIX_FMT_JPG);

に修正したところ無事撮影することができました。
PCモニターに猫の写真を表示するとそれを認識して撮影します。
シリアルモニタでも確認できますが、LED点灯でも確認できるようにしました。

撮影サンプル

モニターに映し出した猫を撮影しました

画面の猫を撮影

画面の猫を撮影

犬 の写真や、猫のぬいぐるみでは撮影してくれなかったので うまく認識しているようです。

ケース

100均で購入したタッパーにカメラを出す穴を開けて
モバイルバッテリーと接続したSpresenseを入れることにしました

100均タッパー

カメラの穴を開けた

入れて蓋をして完了

今後の展望

in vitroでは撮影できていましたが、コンテスト期限直前になってしまい
実際に屋外で猫の撮影を試すことまでは間に合いませんでした。

今後は屋外放置し野良猫をうまく撮影できるかの検証と、
バッテリーの持ちなどについて検証してみたいと思います。

考察

初めてAIを導入し、neural network console を用いたカメラ撮影を行いました。

全く知識がないところから始めましたが、NNCは予想していたより導入は易しく初心者においても適切な機構だと感じました。
サンプルスケッチからスケッチを組めるものだと安易に思っていたところが調整に手間取りましたが、
参考書をにらめっこしながら、目標としていたエッジAIをどうにか構築することができました。

spresenseはGPS機能も有しているので
今後はその機能を使った作品にも挑戦していきたいと思います。

spresenseは初心者にも導入しやすく、様々な用途に応用できるデバイスだと体感することができました。

3
1
ログインしてコメントを投稿する