デモ動画
部品
使用するハードウェアを次に示します。
部品名 | 個数 |
---|---|
Spresenseメインボード | 1 |
Spresense拡張ボード | 1 |
Spresenseカメラボード | 1 |
液晶モジュール | 1 |
使用するソフトウェアを次に示します。
ソフトウェア名 |
---|
Arduino IDE |
Edge Impulse |
設計図
「だれがとったの」は、誰かがテーブルに置いたお菓子を取ると、テキストにより取られたことを示す「Someone Take it!」とその時のキャプチャ画像を液晶モジュールに表示します。お菓子を元に戻すと、テキストにより戻されたことを示す「Found Snack!」とその時のキャプチャ画像を液晶モジュールに表示します。
「だれがとったの」は、次に示すハードウェアにより構成されます。
「だれがとったの」は、次のようにして開発しました。
- Spresenseメインボード上で動作するプログラムはArduino IDEを使って開発します。
- Spresenseカメラボードで取得した画像から、物体検出モデルを使って取られたお菓子を判断し、Spresense拡張ボードに接続された液晶モジュールに、取られたこととその時のキャプチャ画像を表示します。
- お菓子を検出する物体検出モデルはEdge Impulseを使って作成し、作成した物体検出モデルをArduino IDEで読み込める形式で出力し、作成したプログラムに埋め込みます。
接続図
Spresenseメインボード を実装したSpresense拡張ボードに、SPIインタフェースを持つ液晶モジュールを次のように接続します。
ハードウェア
液晶モジュールの背面からSpresense拡張ボードにSPIインタフェースにより接続します。Spresenseメインボードに接続されたSpresenseカメラボードは、お菓子を撮影します。
Edge ImpulseによるエッジAI
Edge Impulseのアカウントを作成してSpresense を接続し、学習データを取得します。物体検出モデルを作成して、作成した物体検出モデルをArduino用としてDeployします。
学習データ取得
Spresenseのカメラ画像から学習データを取得します。
特徴抽出
学習データの特徴を抽出して、その結果を「Feature explorer」に表示します。
物体検出モデル作成
物体検出モデルを作成します。終了すると次のように「Confusion matrix」により学習結果が表示されます。
ソースコード
Spresenseメインボード上で動作するプログラム「だれがとったの」はArduino IDEを使って開発します。プログラム「だれがとったの」は次の3つのコードに分割されます。
- Arduinoソースコード
- カメラソースコード
- 液晶ソースコード
Arduinoソースコード
Arduinoソースコードは、Spresenseカメラボードからの画像データをEdge Impulseにより作成した物体検出モデルに引き渡し、物体検出モデルからの物体検出結果を液晶モジュールへ引き渡します。
- インクルードファイル「ImpulseSpresense_inferencing.h」は、Edge Impulseによリ作成した物体検出モデルをArduino用としてDeployすることで作成されます。
- 関数「run_inference_to_make_predictions」で取得したSpresenseカメラボードからの画像データを、作成した物体検出モデルに入力します。物体検出モデルの物体検出結果を液晶モジュールに表示します。
- 関数「CAMERA_TakePicture_CamImage」でSpresenseカメラボードから画像データを取得します。
- 関数「run_classifier」で物体検出を行います。
- 関数「LCD_Display」でキャプチャした画像を液晶モジュールに表示します。
- 関数「LCD_Text」で、物体検出結果に従い、お菓子が取られたことを示す「Someone Take it!」と、お菓子が戻されたことを示す「Found Snack!」を表示します。
ImpulseSpresense.ino
/* Includes ---------------------------------------------------------------- */
// tomo #include <tomosoft-project-1_inferencing.h>
#include <ImpulseSpresense_inferencing.h>
#include "spresense.h" /* Include board defines */
#include "CameraSpresense.h"
#include "LCDSpresense.h"
/* Prototype ---------------------------------------------------------------- */
void run_inference_to_make_predictions();
/* DEFINITIONS ---------------------------------------------------------------- */
#define BAUDRATE (115200)
#define LED0_ON digitalWrite(LED0, HIGH) /* Macros for Switch on/off Board LEDs */
#define LED0_OFF digitalWrite(LED0, LOW)
#define LED1_ON digitalWrite(LED1, HIGH)
#define LED1_OFF digitalWrite(LED1, LOW)
#define PICTURE_SIGNALLING_ON LED0_ON /* Switch on LED when take a photo */
#define PICTURE_SIGNALLING_OFF LED0_OFF /* Switch off LED when taken photo */
/* VARIABLES ---------------------------------------------------------------- */
char msg[128] ;
static uint8_t *ei_camera_capture_out = NULL;
const char *classes[] = {"Found", "Loss"};
void setup() {
// put your setup code here, to run once:
pinMode(LED0, OUTPUT); /* LED0 as output. Used to signal taking a photo */
pinMode(LED1, OUTPUT);
/* LED0 as output. Used to signal taking a photo */
Serial.begin(BAUDRATE); /* Open serial communications and wait for port to open */
while (!Serial)
{
;
} /* Wait for serial port to connect. Needed for native USB port only */
LCD_Initialize();
CAMERA_Initialize();
}
void loop() {
// put your main code here, to run repeatedly:
//sleep(10);
run_inference_to_make_predictions();
}
void run_inference_to_make_predictions() {
// Summarize the Edge Impulse FOMO model inference settings (from model_metadata.h):
ei_printf("\nInference settings:\n");
ei_printf("\tImage resolution: %dx%d\n", EI_CLASSIFIER_INPUT_WIDTH, EI_CLASSIFIER_INPUT_HEIGHT);
ei_printf("\tFrame size: %d\n", EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE);
ei_printf("\tNo. of classes: %d\n",
sizeof(ei_classifier_inferencing_categories) / sizeof(ei_classifier_inferencing_categories[0]));
// Take a picture with the given still picture settings.
CamImage img = CAMERA_TakePicture_CamImage();
#ifdef SDCARD_SAVE
SDCARD_SaveFile("TakePicture.YUV422", (const char *)img.getImgBuff(), img.getImgSize());
#endif
if (CAMERA_IsImageAvailable(img)) {
CAMERA_Stop();
// Resize the currently captured image depending on the given FOMO model.
CamImage res_img;
img.resizeImageByHW(res_img, EI_CLASSIFIER_INPUT_WIDTH, EI_CLASSIFIER_INPUT_HEIGHT);
Serial.printf("Captured Image Resolution: %d / %d\nResized Image Resolution: %d / %d",
img.getWidth(), img.getHeight(), res_img.getWidth(), res_img.getHeight());
#ifdef SDCARD_SAVE
SDCARD_SaveFile("resizeImage.YUV422", (const char *)res_img.getImgBuff(), res_img.getImgSize());
#endif
// Convert the resized (sample) image data format to GRAYSCALE so as to run inferences with the model.
res_img.convertPixFormat(CAM_IMAGE_PIX_FMT_GRAY);
Serial.print("\nResized Image Format: ");
Serial.println((res_img.getPixFormat() == CAM_IMAGE_PIX_FMT_GRAY) ? "GRAYSCALE" : "ERROR");
#ifdef SDCARD_SAVE
SDCARD_SaveFile("convert.GRAY", (const char *)res_img.getImgBuff(), res_img.getImgSize());
#endif
// Run inference:
ei::signal_t signal;
ei_camera_capture_out = res_img.getImgBuff();
// Create a signal object from the resized and converted sample image.
signal.total_length = EI_CLASSIFIER_INPUT_WIDTH * EI_CLASSIFIER_INPUT_HEIGHT;
signal.get_data = &ei_camera_cutout_get_data;
// Run the classifier:
ei_impulse_result_t result = { 0 };
LED0_ON;
EI_IMPULSE_ERROR _err = run_classifier(&signal, &result, false);
LED0_OFF;
if (_err != EI_IMPULSE_OK) {
ei_printf("ERR: Failed to run classifier (%d)\n", _err);
CAMERA_Start();
return;
}
// Print the inference timings on the serial monitor.
ei_printf("Predictions (DSP: %d ms., Classification: %d ms., Anomaly: %d ms.): \n",
result.timing.dsp, result.timing.classification, result.timing.anomaly);
// Obtain the object detection results and bounding boxes for the detected labels (classes).
bool bb_found = result.bounding_boxes[0].value > 0;
for (size_t ix = 0; ix < EI_CLASSIFIER_OBJECT_DETECTION_COUNT; ix++) {
auto bb = result.bounding_boxes[ix];
if (bb.value == 0) continue;
LED1_ON;
// Print the detected bounding box measurements on the serial monitor.
ei_printf(" %s (", bb.label);
ei_printf_float(bb.value);
ei_printf(") [ x: %u, y: %u, width: %u, height: %u ]\n", bb.x, bb.y, bb.width, bb.height);
int ei_classifier_w = EI_CLASSIFIER_INPUT_WIDTH;
int ei_classifier_h = EI_CLASSIFIER_INPUT_HEIGHT;
//LCD_Box(bb.label, bb.x, bb.y, bb.width, bb.height, ei_classifier_w, ei_classifier_h);
LCD_Display( img);
msg[0] = 0;
if (bb.label == "nka")
{
sprintf(msg, "Someone Take it!");
LCD_Text( msg);
break;
}
if (bb.label == "ka")
{ sprintf(msg, "Found Snack!");
LCD_Text( msg);
break;
}
}
if (!bb_found) {
LED1_OFF;
ei_printf(" No objects found!\n");
}
// If the Edge Impulse FOMO model predicted a label (class) successfully:
CAMERA_Start();
} else {
Serial.println("Failed to take a picture!");
sleep(2);
}
}
int ei_camera_cutout_get_data(size_t offset, size_t length, float *out_ptr) {
// Convert the given image data (buffer) to the out_ptr format required by the Edge Impulse FOMO model.
size_t bytes_left = length;
size_t out_ptr_ix = 0;
// read byte for byte
while (bytes_left != 0) {
// grab the value and convert to r/g/b
uint8_t pixel = ei_camera_capture_out[offset];
uint8_t r, g, b;
mono_to_rgb(pixel, &r, &g, &b);
// then convert to out_ptr format
float pixel_f = (r << 16) + (g << 8) + b;
out_ptr[out_ptr_ix] = pixel_f;
// and go to the next pixel
out_ptr_ix++;
offset++;
bytes_left--;
}
return 0;
}
static inline void mono_to_rgb(uint8_t mono_data, uint8_t *r, uint8_t *g, uint8_t *b) {
uint8_t v = mono_data;
*r = *g = *b = v;
}
spresense.h
#ifndef _SPRESENSE_H_
#define _SPRESENSE_H_
// Define the required parameters to run an inference with the Edge Impulse model.
#define SERIAL_PRINT_DEBUG /* Used to check through the Serial Terminal what's happening */
#define CAMERA_PRESENT /* Used to activate camera functions to take photos */
//#define SDCARD_SAVE /* Used to save the taken photos to the SD Card */
/********************************************************************************
-----> CAMERA
********************************************************************************/
//Includes
#include <Camera.h> /* Include for Camera */
/********************************************************************************
-----> SDCARD
********************************************************************************/
#include <SDHCI.h> /* Include for SD card */
/********************************************************************************
-----> LCD ST7789
********************************************************************************/
#include <Adafruit_GFX.h> // Core graphics library
#include <Adafruit_ST7789.h> // Hardware-specific library for ST7789
#include <Fonts/FreeMonoBold12pt7b.h>
#include <SPI.h>
#define TFT_CS 10
#define TFT_RST 9 // Or set to -1 and connect to Arduino RESET pin
#define TFT_DC 8
#endif /* _SPRESENSE_H_ */
カメラソースコード
カメラソースコードはSpresenseカメラボードを制御して撮影などを行います。
CameraSpresense.cpp
/********************************************************************************
-----> INCLUDES
********************************************************************************/
#include "spresense.h" /* Include board defines */
#include "CameraSpresense.h"
#include "LCDSpresense.h"
/********************************************************************************
-----> DEFINITIONS
********************************************************************************/
#define CAMERA__WIDTH 320
#define CAMERA__HEIGHT 320
/********************************************************************************
-----> VARIABLES
********************************************************************************/
/********************************************************************************
-----> FUNCTIONS
********************************************************************************/
static void camera_CallBack(CamImage vF_Image);
bool CAMERA_Initialize(void)
{
bool bRet = true;
CamErr err = CAM_ERR_SUCCESS;
#ifdef SERIAL_PRINT_DEBUG
Serial.println("Prepare camera");
#endif /* SERIAL_PRINT_DEBUG */
err = theCamera.begin();
if (err != CAM_ERR_SUCCESS)
{
bRet = false;
}
// Start video stream and print errors, if any.
CAMERA_Start();
// Set the Auto white balance parameter and print errors, if any.
CAMERA_SetAutoWhiteBalanceMode();
// Set the still picture parameters and print errors, if any.
CAMERA_SetStillPictureImageFormat();
return (bRet);
}
bool CAMERA_Start(void)
{
bool bRet = true;
CamErr err = CAM_ERR_SUCCESS;
#ifdef SERIAL_PRINT_DEBUG
Serial.println("\nStart streaming");
#endif /* SERIAL_PRINT_DEBUG */
err = theCamera.startStreaming(true, camera_CallBack);
// sleep(10);
if (err != CAM_ERR_SUCCESS)
{
bRet = false;
}
return (bRet);
}
void CAMERA_Stop(void)
{
Serial.println("\nStop");
theCamera.startStreaming(false, camera_CallBack);
// sleep(10);
//theCamera.end();
}
bool CAMERA_SetStillPictureImageFormat(void)
{
bool bRet = true;
CamErr err = CAM_ERR_SUCCESS;
/* Set parameters about still picture.
In the following case, QUADVGA and JPEG.
*/
#ifdef SERIAL_PRINT_DEBUG
Serial.println("Set still picture format");
#endif /* SERIAL_PRINT_DEBUG */
//sleep(10);
err = theCamera.setStillPictureImageFormat(
CAMERA__WIDTH,
CAMERA__HEIGHT,
CAM_IMAGE_PIX_FMT_YUV422);
// 7);
if (err != CAM_ERR_SUCCESS)
{
bRet = false;
Serial.print("setStillPictureImageFormat: ");
Serial.println(err);
}
return (bRet);
}
CamImage CAMERA_TakePicture_CamImage(void)
{
CamImage Image;
#ifdef SERIAL_PRINT_DEBUG
Serial.println("Picture taken");
#endif /* SERIAL_PRINT_DEBUG */
Image = theCamera.takePicture();
return (Image);
}
bool CAMERA_IsImageAvailable(CamImage vF_Image)
{
return (vF_Image.isAvailable());
}
bool CAMERA_SetAutoWhiteBalanceMode(void)
{
bool bRet = true;
CamErr err = CAM_ERR_SUCCESS;
/* Auto white balance configuration */
#ifdef SERIAL_PRINT_DEBUG
Serial.println("Set Auto white balance parameter");
#endif /* SERIAL_PRINT_DEBUG */
err = theCamera.setAutoWhiteBalanceMode(CAM_WHITE_BALANCE_AUTO);
if (err != CAM_ERR_SUCCESS)
{
bRet = false;
}
return (bRet);
}
/****************************************************************************
Callback from Camera library when video frame is captured.
****************************************************************************/
void camera_CallBack(CamImage vF_Image)
{
/* Check the img instance is available or not. */
CamErr Error = CAM_ERR_SUCCESS;
if (vF_Image.isAvailable())
{
#ifdef SDCARD_SAVE
SDCARD_SaveFile("CallBack.YUV422", (const char *)vF_Image.getImgBuff(), vF_Image.getImgSize());
#endif
LCD_Display(vF_Image);
}
else
{
Serial.print("Failed to get video stream image\n");
}
}
CameraSpresense.h
#ifndef _CAMERA_SPRESENSE_H_
#define _CAMERA_SPRESENSE_H_
/********************************************************************************
-----> FUNCTIONS
********************************************************************************/
bool CAMERA_Initialize(void);
CamImage CAMERA_TakePicture_CamImage(void);
bool CAMERA_IsImageAvailable(CamImage vF_Image);
bool CAMERA_SetAutoWhiteBalanceMode(void);
bool CAMERA_SetStillPictureImageFormat(void);
void CAMERA_Stop(void);
bool CAMERA_Start(void);
#endif /* _CAMERA_SPRESENSE_H_ */
液晶ソースコード
液晶ソースコードは液晶モジュールへの表示を行います。
LCDSpresense.cpp
/********************************************************************************
-----> INCLUDES
********************************************************************************/
#include "spresense.h" /* Include board defines */
#include "LCDSpresense.h"
/***********************************
-----> DEFINITIONS
********************************************************************************/
#define TFT_WIDTH 320
#define TFT_HEIGHT 240
/********************************************************************************
-----> VARIABLES
********************************************************************************/
extern const char *classes[];
uint32_t color_codes[] = {0xffff - ST77XX_GREEN, 0xffff - ST77XX_MAGENTA};
SDClass theSD;
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);
int picnum = 0;
int predicted_class = -1;
/********************************************************************************
-----> FUNCTIONS
********************************************************************************/
bool SDCARD_Initialize(void);
void SDCARD_SaveFile(char *vF_Filename, const char *vF_Buffer, size_t vF_Size);
File SDCARD_Open(char *vF_Filename);
void invert(uint16_t *databuf, int count)
{
for (int i = 0; i < count; i ++) {
//Serial.print(*( databuf + i)); Serial.print(" ");
*( databuf + i) = 0xffff - *( databuf + i);
//Serial.println(*( databuf + i));
}
}
void LCD_Initialize(void)
{
tft.init(TFT_HEIGHT, TFT_WIDTH); // Init ST7789 240x320
tft.setRotation(3); // 1 or 3
SDCARD_Initialize();
}
void LCD_Display(CamImage img)
{
img.convertPixFormat(CAM_IMAGE_PIX_FMT_RGB565);
invert((uint16_t *)img.getImgBuff(), img.getImgSize() / 2);
tft.drawRGBBitmap(0, 0, (uint16_t *)img.getImgBuff(), TFT_WIDTH , TFT_HEIGHT);
Serial.print("D");
#ifdef SDCARD_SAVE
SDCARD_SaveFile("LCD_Disp.RGB565", (const char *)img.getImgBuff(), img.getImgSize());
#endif
}
void LCD_Box(const char *bb_label, int b_b_x, int b_b_y, int b_b_w, int b_b_h,
int ei_classifier_w, int ei_classifier_h)
{
// Get the predicted label (class).
if (bb_label == "ka") predicted_class = 0;
if (bb_label == "nka") predicted_class = 1;
Serial.print("Predicted Class: "); Serial.println(predicted_class);
int box_scale_x = tft.width() / ei_classifier_w;
b_b_x = b_b_x * box_scale_x;
b_b_w = b_b_w * box_scale_x * 16;
if ((b_b_w + b_b_x) > (tft.width() - 10)) b_b_w = tft.width() - b_b_x - 10;
int box_scale_y = tft.height() / ei_classifier_h;
b_b_y = b_b_y * box_scale_y;
b_b_h = b_b_h * box_scale_y * 16;
if ((b_b_h + b_b_y) > (tft.height() - 10)) b_b_h = tft.height() - b_b_y - 10;
Serial.print(b_b_x); Serial.print(" "); Serial.print(b_b_y); Serial.print(" "); Serial.print( b_b_w); Serial.print(" "); Serial.println(b_b_h);
// Display the predicted label (class) and the detected bounding box on the TFT screen.
for (int i = 0; i < 2; i++) {
tft.drawRect(b_b_x + i, b_b_y + i, b_b_w - (2 * i), b_b_h - (2 * i), color_codes[predicted_class]);
}
int c_x = 10, c_y = 10, r_x = 120, r_y = 40, r = 3, offset = 6;
//tft.fillRoundRect(c_x, c_y, r_x, r_y, r, 0xffff - ST77XX_WHITE );
tft.fillRoundRect(c_x + offset, c_y + offset, r_x - (2 * offset), r_y - (2 * offset), r,
color_codes[predicted_class]);
tft.setTextColor(0xffff - ST77XX_WHITE ); tft.setFont( &FreeMonoBold12pt7b );
tft.setCursor(c_x + (2 * offset), c_y + (4 * offset));
tft.printf(classes[predicted_class]);
}
void LCD_Text( char *text) {
tft.setTextColor(0xffff - ST77XX_WHITE ); tft.setFont( &FreeMonoBold12pt7b );
tft.setCursor(40, 210);
tft.printf(text);
}
bool SDCARD_Initialize(void)
{
bool bRet = false;
while (!theSD.begin())
{
Serial.println("Insert SD card."); /* wait until SD card is mounted. */
}
if (theSD.beginUsbMsc()) {
Serial.println("USB MSC Failure!");
}
bRet = true;
return;
}
void SDCARD_SaveFile(char *vF_Filename, const char *vF_Buffer, size_t vF_Size) {
if (picnum > 50) return;
else if (picnum == 50) {
picnum++;
if (theSD.endUsbMsc()) {
Serial.println("USB MSC Failure!");
}
if (theSD.beginUsbMsc()) {
Serial.println("USB MSC Failure!");
}
} else {
picnum++;
Serial.print("S" );
// Serial.println(vF_Filename);
theSD.remove(vF_Filename);
File myFile = theSD.open(vF_Filename, FILE_WRITE);
myFile.write(vF_Buffer, vF_Size);
myFile.close();
}
}
File SDCARD_Open(char *vF_Filename)
{
File rootCertsFile = theSD.open(vF_Filename, FILE_READ);
return (rootCertsFile);
}
LCDSpresense.h
#ifndef _LCD_SPRESENSE_H_
#define _LCD_SPRESENSE_H_
/********************************************************************************
-----> FUNCTIONS
********************************************************************************/
void LCD_Initialize(void);
void LCD_Display(CamImage img);
void LCD_Box(const char *bb_label, int b_b_x, int b_b_y, int b_b_w, int b_b_h,
int ei_classifier_w, int ei_classifier_h);
void LCD_Text( char *text);
void SDCARD_SaveFile(char *vF_Filename, const char *vF_Buffer, size_t vF_Size);
#endif /* _LCD_SPRESENSE_H_ */
投稿者の人気記事
-
kati
さんが
2022/09/11
に
編集
をしました。
(メッセージ: 初版)
ログインしてコメントを投稿する