2022年 SPRESENSE™ 活用コンテスト 応募作品
開発に至った経緯
- パネルを操作するには、パネルを直に触る必要があります。しかし、昨今の新型コロナによる接触感染等が問題になっております。
- 最近、パネルにカメラを設置し非接触によるパネルを操作する方法が発表されましたが、それだとタッチした感覚がありません。
そこで。
- 指を叩く事でパネルを操作できたら面白いんじゃないだろうか?
(指を叩く事でタッチした感覚=選択した感覚を得られるんじゃないだろうか?) - 一例として被写体(複数)をパネルに写して、そこから選択させてはどうだろうか?
経緯としては以上になります。
全体図
使用例
使用したハード機器
* 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
に
編集
をしました。
(メッセージ: 誤字脱字の修正。)
ログインしてコメントを投稿する