編集履歴一覧に戻る
zoneのアイコン画像

zone が 2022年09月20日15時25分18秒 に編集

初版

タイトルの変更

+

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

タグの変更

+

spresense

メイン画像の変更

メイン画像が設定されました

記事種類の変更

+

セットアップや使用方法

本文の変更

+

2022年 SPRESENSE™ 活用コンテスト 応募作品 ==== 開発に至った経緯 ==== - パネルを操作するには、パネルを直に触る必要があるが。昨今、新型コロナにより接触感染等が問題になっている。 - 最近、パネルにカメラを配置し非接触によるパネルを操作する方法が発表されたが、それだとタッチした感覚が無い。 - そこで、指を叩く事でパネル操作できたら面白いんじゃないか? (指を叩く事でタッチした感覚=選択した感覚を得られるんじゃないか?) - どーせなら、ついでに被写体をパネルに写して選択させてはどうか? 経緯は以上です。 全体図 ==== ![キャプションを入力できます](https://camo.elchika.com/08fdfa1b85f7038850aad69f485744e4216c2b80/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f37356561636532632d663739382d343366612d613337662d3034343532376630636434372f61633862313163622d336636322d346537612d613261352d356132326431346566343764/)  (各パーツを写したかったので、ジャンパ線にて接続) 使用例 ==== 1. 機器の電源を入れます。 ![キャプションを入力できます](https://camo.elchika.com/913e6923fe72a952f2a9b7e7658d97eb7c97f332/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f37356561636532632d663739382d343366612d613337662d3034343532376630636434372f61643130396130622d353139332d343536352d613634662d376637616366643565353961/) 1. 被写体をLCDで確認しながら、ボタン1を押して、写真を撮ります。 ![キャプションを入力できます](https://camo.elchika.com/39cc1d37ed16f578ea7b84070a6a33c67dea8605/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f37356561636532632d663739382d343366612d613337662d3034343532376630636434372f36363166633035332d623731372d343039622d383166352d643332313166626366313965/) 1. 繰り返しで4枚の写真を撮ります。 ![キャプションを入力できます](https://camo.elchika.com/9965caf1b9e57504bb81e79fac78369c5a2c9346/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f37356561636532632d663739382d343366612d613337662d3034343532376630636434372f37393833616461382d646233372d343033312d396565372d663862303333343736343431/) (スクショでは隠れていますが。SPRESENSEのLEDを順に点灯させ、撮った事が分かるようにしております) 1. 4枚の写真がLCDに表示されます。 ![キャプションを入力できます](https://camo.elchika.com/9e042c639b28903b996b65d424dde4268ee373f7/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f37356561636532632d663739382d343366612d613337662d3034343532376630636434372f32363961636664362d623562312d346431372d613937322d643964343933613536346665/) 1. 指を叩いて、写真を選択します。 -  指を叩くとは ![手を開いて(パーの状態)→親指と中指を閉じます](https://camo.elchika.com/a863ad64ac844862f8d9e6c064660c499fe36567/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f37356561636532632d663739382d343366612d613337662d3034343532376630636434372f39393362353662312d313261612d343438612d386537662d323863343438366238633164/) 軽くトンっと音が鳴ります。 ![左下のマイクの前で指を叩いています](https://camo.elchika.com/5d1a757f47b8531e6a46dccffb15eeffeb3c88b8/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f37356561636532632d663739382d343366612d613337662d3034343532376630636434372f39646666623163652d663436322d346635322d383234392d653839316538636565656265/) 6. 選択された写真(この例では左下のケーキ)が表示されます。 ![キャプションを入力できます](https://camo.elchika.com/beddbc95a98552db8b4e7d94bccae818ab9fa58b/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f37356561636532632d663739382d343366612d613337662d3034343532376630636434372f30643937376435322d323331612d343631632d613333392d336639613632653030396636/) 使用したハード機器 ==== | * Spresense 本体 | |:---:| | * Spresense 拡張ボード | | * カメラ | | Mic&LCD Kit for Spresense(AUTOLAB製) | | SDカード | (* 主催様によるモニター提供、ありがとうございます。この場を借りて御礼申し上げます) Mic&LCD Kit for Spresense(AUTOLAB製)について ==== 完成されているキットなので、接続例等を参考にして接続してください。 なお、端子の接続ミスetc、十分にご注意ください。 (SPRESENSE拡張ボードの「JP1:IO Voltage Jumper」は **3.3V** です。こちらもご注意ください) ![キャプションを入力できます](https://camo.elchika.com/c9ccb22f751bb1af2e8086e17c3846bb45aabe68/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f37356561636532632d663739382d343366612d613337662d3034343532376630636434372f32636666376661662d666533392d343139362d393534612d376461666564303935356563/) 使用したライブラリ ==== | [らびやん様が制作されたライブラリ(LovyanGFX)](https://github.com/lovyan03/LovyanGFX) | |:---:| (この場を借りて、ライブラリを公開しているらびやん様へ感謝申し上げます) 回路図(ブロック図) ==== AUTOLAB製キットの使用により、ブロック図にて図示。 ※ SPRESENSE拡張ボードは **3.3V** に設定。 ![回路ブロック図](https://camo.elchika.com/c9b12563052651729453f4d7db3ea168d4fb5412/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f37356561636532632d663739382d343366612d613337662d3034343532376630636434372f32633532626435362d613738332d346565352d616236382d616638626661373932383438/) 処理イメージ図(ブロック図) ==== ![キャプションを入力できます](https://camo.elchika.com/0df1802c2b6b039eb4f53805ec0ca3b3df953d9f/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f37356561636532632d663739382d343366612d613337662d3034343532376630636434372f35633030353463362d353539322d343231632d623531342d353365333736363033366136/) ![キャプションを入力できます](https://camo.elchika.com/1a5d22a9e31bc415dc53fbe069c9a60999284a42/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f37356561636532632d663739382d343366612d613337662d3034343532376630636434372f32383531393661322d373130662d343066652d613436352d653563653336623634356537/) ソースコード ==== - MainCore Memory - **1024 KB** にセット。 ※ 予め、ライブラリマネージャにて「LovyanGFX 」をインストールしておく。  LovyanGFX - SPRESENSE用 hpp ファイルがあるので、"LGFX_SPRESENSE.hpp"として利用。 ![キャプションを入力できます](https://camo.elchika.com/a1d88fa1ba996b796429e7d74ab1f3baa40774c8/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f37356561636532632d663739382d343366612d613337662d3034343532376630636434372f33633737656532312d633738652d343163632d626332392d323461623433623631656235/) ```arduino:撮影した写真の非接触選択(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 ```arduino:撮影した写真の非接触選択(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も拾ってしまう。