zoneのアイコン画像
zone 2022年09月20日作成 (2022年09月20日更新)
セットアップや使用方法 セットアップや使用方法 閲覧数 497
zone 2022年09月20日作成 (2022年09月20日更新) セットアップや使用方法 セットアップや使用方法 閲覧数 497

撮影した写真の非接触選択

撮影した写真の非接触選択

2022年 SPRESENSE™ 活用コンテスト 応募作品

開発に至った経緯

  • パネルを操作するには、パネルを直に触る必要があります。しかし、昨今の新型コロナによる接触感染等が問題になっております。
  • 最近、パネルにカメラを設置し非接触によるパネルを操作する方法が発表されましたが、それだとタッチした感覚がありません。

そこで。

  • 指を叩く事でパネルを操作できたら面白いんじゃないだろうか?
    (指を叩く事でタッチした感覚=選択した感覚を得られるんじゃないだろうか?)
  • 一例として被写体(複数)をパネルに写して、そこから選択させてはどうだろうか?

経緯としては以上になります。

全体図

キャプションを入力できます
 (各パーツを写したかったので、ジャンパ線にて接続)

使用例

  1. 機器の電源を入れます。
    キャプションを入力できます

  2. 被写体をLCDで確認しながら、ボタン1を押して、写真を撮ります。
    キャプションを入力できます

  3. 繰り返しで4枚の写真を撮ります。
    キャプションを入力できます
    (スクショでは隠れていますが。SPRESENSEのLEDを順に点灯させ、撮った事が分かるようにしております)

  4. 4枚の写真がLCDに表示されます。
    キャプションを入力できます

  5. 指を叩いて、写真を選択します。

  • 指を叩くとは
    手を開いて(パーの状態)→親指と中指を閉じます
    軽くトンっと音が鳴ります(GIF動画となっております)。
    この例では左下のマイクの前で指を叩いています
  1. 選択された写真(この例では左下のケーキ)が表示されます。
    キャプションを入力できます

使用したハード機器

* Spresense 本体
* Spresense 拡張ボード
* カメラ
Mic&LCD Kit for Spresense(AUTOLAB社製)
SDカード

(* 主催様によるモニター提供、ありがとうございます。この場を借りて御礼申し上げます)

Mic&LCD Kit for Spresense(AUTOLAB社製)について

AUTOLAB株式会社様制作の完成されているキットです。接続例等を参考にして接続してください。
なお、端子の接続ミスetc、十分にご注意ください。
(SPRESENSE拡張ボードの「JP1:IO Voltage Jumper」は 3.3V です。こちらもご注意ください)
キャプションを入力できます

使用したライブラリ

(この場を借りて、ライブラリを公開しているらびやん様へ感謝申し上げます)

回路図(ブロック図)

AUTOLAB社製キットの使用により、ブロック図にて図示。
※ SPRESENSE拡張ボードは 3.3V に設定。
回路ブロック図

処理イメージ図(ブロック図)

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

ソースコード

  • MainCore
    Memory - 1024 KB にセット。
    ※ 予め、ライブラリマネージャにて「LovyanGFX 」をインストールしておきます。
     今回は、LovyanGFX - SPRESENSE用 hpp ファイルを"LGFX_SPRESENSE.hpp"として利用しております。
    キャプションを入力できます

撮影した写真の非接触選択(MainCore)

参考元:スケッチ例 - Signal Processing - Sound Detector - MainAudio #ifdef SUBCORE #error "Core selection is wrong!!" #endif #include <MP.h> #include <Audio.h> #include <Camera.h> #include <SPI.h> #include <SDHCI.h> #include "LGFX_SPRESENSE.hpp" const int PIN_SW1 = 4; const int subcore = 1; AudioClass *theAudio; /* Select mic channel number */ const int mic_channel_num = 4; struct Request { void *buffer; int sample; int channel; }; struct Result { bool found[mic_channel_num]; int channel; }; SDClass theSD; static LGFX lcd; bool _btn1push = false; int _jpegCnt = 0; void changeState () { _btn1push = true; } void CamCB(CamImage img) { if (img.isAvailable()) { img.convertPixFormat(CAM_IMAGE_PIX_FMT_RGB565); lcd.pushImage(0, 0, 320, 240, (uint16_t *)img.getImgBuff()); } } void setup() { // pinMode(LED0, OUTPUT); pinMode(LED1, OUTPUT); pinMode(LED2, OUTPUT); pinMode(LED3, OUTPUT); // シャッタースイッチ 設定 pinMode(PIN_SW1, INPUT_PULLUP); // theSD.begin(); Serial.begin(115200); // Audio 初期化 theAudio = AudioClass::getInstance(); // パネル 初期化 lcd.init(); lcd.setSwapBytes(true); lcd.setRotation(1); lcd.clear(TFT_BLUE); // カメラ スタート theCamera.begin(); theCamera.startStreaming(true, CamCB); theCamera.setStillPictureImageFormat( CAM_IMGSIZE_QUADVGA_H , CAM_IMGSIZE_QUADVGA_V , CAM_IMAGE_PIX_FMT_JPG); attachInterrupt(digitalPinToInterrupt(PIN_SW1) , changeState , FALLING); /* Launch SubCore */ int ret = MP.begin(subcore); if (ret < 0) { printf("MP.begin error = %d\n", ret); } /* receive with non-blocking */ MP.RecvTimeout(1); } /** 指定したLED On/Off */ void lightOnLED(int n, bool on) { // switch (n) { case 0: if (on) ledOn(LED0); else ledOff(LED0); break; case 1: if (on) ledOn(LED1); else ledOff(LED1); break; case 2: if (on) ledOn(LED2); else ledOff(LED2); break; case 3: if (on) ledOn(LED3); else ledOff(LED3); break; } } void loop() { // bool lpNext = false; // Step.1 カメラ→写真4枚撮る MPLog("Step.1\n"); // Debug while (lpNext == false) { // シャッターボタンが押されたら、 if (_btn1push) { // SDカードに写像を保存(Jpeg) theCamera.startStreaming(false, CamCB); CamImage img = theCamera.takePicture(); if (img.isAvailable()) { // lightOnLED(_jpegCnt, true); // char filename[16] = {0}; sprintf(filename, "PICT%03d.JPG", _jpegCnt); if (theSD.exists(filename)) { theSD.remove(filename); } // MPLog("%s -> Save!\n", filename); // Debug File myFile = theSD.open(filename, FILE_WRITE); myFile.write(img.getImgBuff(), img.getImgSize()); myFile.close(); _jpegCnt++; } // 4枚保存したら、次へ if (_jpegCnt >= 4) { lpNext = true; } else { // _btn1push = false; theCamera.startStreaming(true, CamCB); } } delay(1); } theCamera.end(); // Step.2 写真4枚をパネルに表示 MPLog("Step.2\n"); // Debug for (int i = 0; i < 4; i++) { lightOnLED(i, false); } lcd.clear(TFT_BLUE); lcd.drawJpgFile(theSD, "/PICT000.jpg", 0, 0, 160, 120, 0, 0, 0.125f); lcd.drawJpgFile(theSD, "/PICT001.jpg", 160, 0, 160, 120, 0, 0, 0.125f); lcd.drawJpgFile(theSD, "/PICT002.jpg", 0, 120, 160, 120, 0, 0, 0.125f); lcd.drawJpgFile(theSD, "/PICT003.jpg", 160, 120, 160, 120, 0, 0, 0.125f); // Step.3 指を叩いて(非接触)、写真を選択 MPLog("Step.3\n"); // Debug // Audio 設定→Start theAudio->begin(); /* Select input device as AMIC */ theAudio->setRecorderMode(AS_SETRECDR_STS_INPUTDEVICE_MIC, 210); /* Set PCM capture */ uint8_t channel; switch (mic_channel_num) { case 1: channel = AS_CHANNEL_MONO; break; case 2: channel = AS_CHANNEL_STEREO; break; case 4: channel = AS_CHANNEL_4CH; break; } theAudio->initRecorder(AS_CODECTYPE_PCM, "/mnt/sd0/BIN", AS_SAMPLINGRATE_48000, channel); // MPLog("Step.3-1\n"); // Debug theAudio->startRecorder(); int jpgSelect = -1; lpNext = false; while (lpNext == false) { // int8_t sndid = 100; /* user-defined msgid */ int8_t rcvid = 0; Request request; Result* result; static const int32_t buffer_sample = 768 * mic_channel_num; static const int32_t buffer_size = buffer_sample * sizeof(int16_t); static char buffer[buffer_size]; uint32_t read_size; /* Read frames to record in buffer */ int err = theAudio->readFrames(buffer, buffer_size, &read_size); if (err != AUDIOLIB_ECODE_OK && err != AUDIOLIB_ECODE_INSUFFICIENT_BUFFER_AREA) { printf("Error err = %d\n", err); sleep(1); theAudio->stopRecorder(); exit(1); } if ((read_size != 0) && (read_size == buffer_size)) { request.buffer = buffer; request.sample = buffer_sample / mic_channel_num; request.channel = mic_channel_num; MP.Send(sndid, &request, subcore); } else { /* Receive detector results from SubCore */ int ret = MP.Recv(&rcvid, &result, subcore); //MPLog(" <- SubCore1 %d\n", ret); // Debug if (ret >= 0) { // int foundCnt = 0; for (int i = 0; i < mic_channel_num; i++) { if (result->found[i]) { jpgSelect = i; foundCnt++; } } if (foundCnt == 1) { lpNext = true; } } } delay(1); } // Audio Stop theAudio->stopRecorder(); theAudio->end(); // Step.4 選択した写真のみを表示 MPLog("Step.4\n"); // Debug lightOnLED(jpgSelect, true); char filename[16] = {0}; sprintf(filename, "/PICT%03d.JPG", jpgSelect); lcd.drawJpgFile(theSD, filename, 0, 0, 320, 240, 0, 0, 0.25f); // MPLog("- End -"); // Debug while (true) { delay(1000); } }
  • SubCore-1
    Memory - 256 KB にセット。

撮影した写真の非接触選択(SubCore-1)

参考元:スケッチ例 - Signal Processing - Sound Detector - SubFFT #if (SUBCORE != 1) #error "Core selection is wrong!!" #endif #include <MP.h> #include "FFT.h" /* FFT parameters */ /* Select FFT length */ #define FFT_LEN 1024 /* Number of channels*/ #define MAX_CHANNEL_NUM 4 #define SAMPLING_RATE 48000 // ex.) 48000, 16000 //#define FFT_LEN 1024 // ex.) 128, 256, 1024 #define OVERLAP (FFT_LEN/2) // ex.) 0, 128, 256 FFTClass<MAX_CHANNEL_NUM, FFT_LEN> FFT; /* Detector parameters */ #define POWER_THRESHOLD 6 // Power #define LENGTH_THRESHOLD 20 #define INTERVAL_THRESHOLD 100 // 100ms #define BOTTOM_SAMPLING_RATE 80 // Hz #define TOP_SAMPLING_RATE 120 // Hz #define FS2BAND(x) ((x)*FFT_LEN/SAMPLING_RATE) #define BOTTOM_BAND (FS2BAND(BOTTOM_SAMPLING_RATE)) #define TOP_BAND (FS2BAND(TOP_SAMPLING_RATE)) #define MS2FRAME(x) (((x)*SAMPLING_RATE/1000/(FFT_LEN-OVERLAP))+1) #define LENGTH_FRAME MS2FRAME(LENGTH_THRESHOLD) #define INTERVAL_FRAME MS2FRAME(INTERVAL_THRESHOLD) /* Allocate the larger heap size than default */ USER_HEAP_SIZE(64 * 1024); /* MultiCore definitions */ struct Request { void *buffer; int sample; int chnum; }; struct Result { Result() { clear(); } bool found[MAX_CHANNEL_NUM]; int channel; void clear() { for (int i = 0; i < MAX_CHANNEL_NUM; i++) { found[i] = false; } } }; void setup() { /* Initialize MP library */ int ret = MP.begin(); if (ret < 0) { MPLog("SubCore-1 begin Error!\n"); errorLoop(2); } /* receive with non-blocking */ MP.RecvTimeout(MP_RECV_POLLING); FFT.begin(); } #define RESULT_SIZE 4 void loop() { int ret; int8_t sndid = 10; /* user-defined msgid */ int8_t rcvid; Request *request; static Result result[RESULT_SIZE]; static int pos = 0; result[pos].clear(); static float pDst[FFT_LEN / 2]; /* Receive PCM captured buffer from MainCore */ ret = MP.Recv(&rcvid, &request); if (ret >= 0) { FFT.put((q15_t*)request->buffer, request->sample); } while (!FFT.empty(0)) { result[pos].channel = MAX_CHANNEL_NUM; for (int i = 0; i < MAX_CHANNEL_NUM; i++) { FFT.get(pDst, i); result[pos].found[i] = detect_sound(BOTTOM_BAND, TOP_BAND, pDst, i); // if(result[pos].found[i]){ printf("Sub channel %d\n",i); } } ret = MP.Send(sndid, &result[pos], 0); pos = (pos + 1) % RESULT_SIZE; if (ret < 0) { errorLoop(1); } } } /*-----------------------------------------------------------------*/ /* Detector functions */ struct Sounds { Sounds() { clear(); } int continuity[MAX_CHANNEL_NUM]; int interval[MAX_CHANNEL_NUM]; void clear() { for (int i = 0; i < MAX_CHANNEL_NUM; i++) { continuity[i] = 0; interval[i] = 0; } } }; bool detect_sound(int bottom, int top, float* pdata, int channel ) { static Sounds sounds; if (bottom > top) return false; if (sounds.interval[channel] > 0) { /* Do not detect in interval time.*/ sounds.interval[channel]--; sounds.continuity[channel] = 0; return false; } for (int i = bottom; i <= top; i++) { // printf("!!%2.8f\n",*(pdata+i)); if (*(pdata + i) > POWER_THRESHOLD) { // find sound. // printf("!!%2.8f\n",*(pdata+i)); sounds.continuity[channel]++; // printf("con=%d\n",continuity); if (sounds.continuity[channel] > LENGTH_FRAME) { // length is enough. sounds.interval[channel] = INTERVAL_FRAME; return true; } else { // puts("continue sound"); return false; } } } sounds.continuity[channel] = 0; return false; } void errorLoop(int num) { int i; while (1) { for (i = 0; i < num; i++) { ledOn(LED0); delay(300); ledOff(LED0); delay(300); } delay(1000); } }

苦労した点

  • マイク4つに「指を叩いた音」を入力し、どのマイクから入力されたか?の判定。
    (その調整をトライ&エラーするしか無かった)
  • 保存したjpegファイルをLCDに表示する方法。
    (良いライブラリを見つけるまで時間が掛かった)
  • 各コアに設定するMemoryの設定値。
    (正直、今の設定も適正値かが分からない)

TODO(改善したかった事)

  • 完全非接触を実現すべく、写真を撮る際も指を叩く方法を採りたかったが。
    次のステップへ移行する良い案が浮かばなかった。
  • 指を叩いた音を判定させているが、実際は扇風機の音etcも拾ってしまう。
  • zone さんが 2022/09/20 に 編集 をしました。 (メッセージ: 初版)
  • zone さんが 2022/09/20 に 編集 をしました。 (メッセージ: 誤字脱字の修正。)
ログインしてコメントを投稿する