使用Arduino 33 BLE Sense和Edge Impulse构建的咳嗽检测系统

在本教程中,我们将使用 Arduino 33 BLE Sense 和 Edge Impulse Studio 构建咳嗽检测系统。它可以区分正常的背景噪音和实时音频中的咳嗽。我们使用 Edge Impulse Studio 训练咳嗽和背景噪声样本数据集,并构建高度优化的 TInyML 模型,该模型可以实时检测咳嗽声音。

所需组件

硬件

Arduino 33 BLE 感知

引领

跳线

软件

边缘脉冲工作室

Arduino IDE

电路原理图

下面给出了使用 Arduino 33 BLE Sense进行咳嗽检测的电路图。Arduino 33 BLE 的 Fritzing 部件不可用,所以我使用了 Arduino Nano,因为它们具有相同的引脚。

LED 的正极引线连接到 Arduino 33 BLE sense 的数字引脚 4,负极引线连接到 Arduino 的 GND 引脚。

为咳嗽检测机创建数据集

如前所述,我们正在使用 Edge Impulse Studio 来训练我们的咳嗽检测模型。为此,我们必须收集一个数据集,其中包含我们希望能够在 Arduino 上识别的数据样本。由于目标是检测咳嗽,因此您需要收集其中的一些样本和其他一些噪声样本,以便区分咳嗽和其他噪声。

我们将创建一个包含“咳嗽”和“噪音”两个类别的数据集。要创建数据集,请创建一个Edge Impulse帐户,验证您的帐户,然后开始一个新项目。您可以使用手机、Arduino 板加载样本,也可以将数据集导入边缘脉冲帐户。将样本加载到您的帐户中的最简单方法是使用您的手机。为此,您必须将您的手机与 Edge Impulse 连接。

要连接您的手机,请单击“设备”,然后单击“连接新设备”。

现在在下一个窗口中,单击“使用您的手机”,将出现一个二维码。使用 Google Lens 或其他 QR 码扫描仪应用程序使用您的手机扫描 QR 码。

这会将您的手机与 Edge Impulse studio 连接起来。

将手机与 Edge Impulse Studio 连接后,您现在可以加载样本。要加载样本,请单击“数据采集”。现在在数据采集页面上,输入标签名称,选择麦克风作为传感器,然后输入样本长度。单击“开始采样”,开始采样 40 秒样本。您可以使用不同长度的在线咳嗽样本,而不是强迫自己咳嗽。共记录 10 到 12 个不同长度的咳嗽样本。

上传咳嗽样本后,现在将标签设置为“噪声”并再收集 10 到 12 个噪声样本。

这些样本用于训练模块,在接下来的步骤中,我们将收集测试数据。测试数据至少应该是训练数据的 30%,所以收集 3 个样本的“噪音”和 4 到 5 个样本的“咳嗽”。

您可以使用 Edge Impulse CLI Uploader 将我们的数据集导入您的 Edge Impulse 帐户,而不是收集您的数据。

要安装 CLI Uploader,首先,在您的笔记本电脑上下载并安装Node.js。之后打开命令提示符并输入以下命令:

npm install -g edge-impulse-cli

现在下载数据集(数据集链接)并将文件解压缩到您的项目文件夹中。打开命令提示符并导航到数据集位置并运行以下命令:

edge-impulse-uploader –clean

edge-impulse-uploader –category training training/*.json

edge-impulse-uploader –category training training/*.cbor

edge-impulse-uploader –category testing testing/*.json

edge-impulse-uploader –category testing testing/*.cbor

训练模型并调整代码

随着数据集准备就绪,现在我们将为数据创建一个脉冲。为此,请转到“创建冲动”页面。

现在在“创建冲动”页面上,单击“添加处理块”。在下一个窗口中,选择音频 (MFCC) 块。之后单击“添加学习块”并选择神经网络(Keras)块。然后点击“保存冲动”。

在下一步中,转到 MFCC 页面,然后单击“生成功能”。它将为我们所有的音频窗口生成 MFCC 块。

之后进入“ NN Classifier”页面,点击“ Neural Network settings”右上角的三个点,选择“ Switch to Keras (expert) mode”。

将原始代码替换为以下代码,并将“最低置信度评级”更改为“0.70”。然后单击“开始培训”按钮。它将开始训练您的模型。

将张量流导入为 tf

从 tensorflow.keras.models 导入顺序

从 tensorflow.keras.layers 导入 Dense、InputLayer、Dropout、Flatten、Reshape、BatchNormalization、Conv2D、MaxPooling2D、AveragePooling2D

从 tensorflow.keras.optimizers 导入 Adam

从 tensorflow.keras.constraints 导入 MaxNorm

# 模型架构

模型=顺序()

model.add(InputLayer(input_shape=(X_train.shape[1], ), name=‘x_input’))

model.add(Reshape((int(X_train.shape[1] / 13), 13, 1), input_shape=(X_train.shape[1], )))

model.add(Conv2D(10, kernel_size=5, activation=‘relu’, padding=‘same’, kernel_constraint=MaxNorm(3)))

model.add(AveragePooling2D(pool_size=2, padding=‘same’))

model.add(Conv2D(5, kernel_size=5, activation=‘relu’, padding=‘same’, kernel_constraint=MaxNorm(3)))

model.add(AveragePooling2D(pool_size=2, padding=‘same’))

model.add(展平())

model.add(密集(类,activation=‘softmax’,name=‘y_pred’,kernel_constraint=MaxNorm(3)))

# 这控制了学习率

选择 = 亚当(lr=0.005,beta_1=0.9,beta_2=0.999)

# 训练神经网络

model.compile(loss=‘categorical_crossentropy’,优化器=opt,metrics=[‘accuracy’])

model.fit(X_train,Y_train,batch_size=32,epochs=9,validation_data=(X_test,Y_test),详细=2)

训练模型后,将显示训练性能。对我来说,准确率是 96.5%,损失是 0.10,这很好。

现在我们的咳嗽检测模型已经准备就绪,我们将把这个模型部署为 Arduino 库。在将模型下载为库之前,您可以通过转到“实时分类”页面来测试性能。

转到“部署”页面并选择“ Arduino Library”。现在向下滚动并单击“构建”以开始该过程。这将为您的项目构建一个 Arduino 库。

现在在您的 Arduino IDE 中添加库。为此,打开 Arduino IDE,然后单击Sketch 》 Include Library 》 Add.ZIP library。

然后,通过转到 文件 》 示例 》 您的项目名称 – Edge Impulse 》 nano_ble33_sense_microphone 来加载示例。

我们将对代码进行一些更改,以便在 Arduino 检测到咳嗽时发出警报声。为此,Arduino 连接了一个蜂鸣器,当它检测到咳嗽时,LED 会闪烁 3 次。

这些更改是在打印噪音和咳嗽值的void loop()函数中进行的。在原始代码中,它同时打印标签及其值。

对于 (size_t ix = 0; ix 《 EI_CLASSIFIER_LABEL_COUNT; ix++) {

ei_printf(“%s: %.5f
”, result.classification[ix].label, result.classification[ix].value);

}

我们将把噪声值和咳嗽值保存在不同的变量中,并比较噪声值。如果噪声值低于 0.50,则表示咳嗽值大于 0.50,它会发出声音。用这个替换原来的 for loop()代码:

对于(size_t ix = 1;ix 《 EI_CLASSIFIER_LABEL_COUNT;ix++){

Serial.print(result.classification[ix].value);

浮动数据=结果。分类[ix]。值;

如果(数据 《 0.50){

Serial.print(“检测到咳嗽”);

警报();

}

}

进行更改后,将代码上传到您的 Arduino。以 115200 波特率打开串行监视器。

所以这就是咳嗽检测机器的构建方式,它不是找到任何 COVID19 嫌疑人的非常有效的方法,但它可以在一些拥挤的区域很好地工作。

#define EIDSP_QUANTIZE_FILTERBANK 0

#include

#include

#define LED 5

/** 音频缓冲区、指针和选择器 */

类型定义结构{

int16_t *缓冲区;

uint8_t buf_ready;

uint32_t buf_count;

uint32_t n_samples;

} inference_t;

静态推理_t推理;

静态布尔记录就绪=假;

静态有符号短样本缓冲区[2048];

静态 bool debug_nn = false; // 将此设置为 true 以查看例如从原始信号生成的特征

无效设置()

{

// 把你的设置代码放在这里,运行一次:

序列号.开始(115200);

pinMode(LED,输出);

Serial.println(“边缘脉冲推理演示”);

// 推理设置摘要(来自 model_metadata.h)

ei_printf(“推理设置:
“);

ei_printf(” Interval: %.2f ms.
“, (float)EI_CLASSIFIER_INTERVAL_MS);

ei_printf(” 帧大小: %d
“, EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE);

ei_printf(” 样本长度: %d ms.
“, EI_CLASSIFIER_RAW_SAMPLE_COUNT / 16);

ei_printf(” No. of classes: %d
“, sizeof(ei_classifier_inferencing_categories) / sizeof(ei_classifier_inferencing_categories[0]));

if (microphone_inference_start(EI_CLASSIFIER_RAW_SAMPLE_COUNT) == false) {

ei_printf(“ERR: 设置音频采样失败
“);

返回;

}

}

无效循环()

{

ei_printf(“2秒后开始推理…
“);

延迟(2000);

ei_printf(“正在录制…
“);

bool m = 麦克风推理记录();

如果(!米){

ei_printf(“ERR: 录音失败…
“);

返回;

}

ei_printf(“录制完成
“);

signal_t 信号;

signal.total_length = EI_CLASSIFIER_RAW_SAMPLE_COUNT;

signal.get_data = µphone_audio_signal_get_data;

ei_impulse_result_t 结果 = { 0 };

EI_IMPULSE_ERROR r = run_classifier(&signal, &result, debug_nn);

如果(r!= EI_IMPULSE_OK){

ei_printf(“ERR: 分类器运行失败(%d)
“, r);

返回;

}

// 打印预测

ei_printf(“预测 (DSP: %d ms., 分类: %d ms., 异常: %d ms.):
“,

result.timing.dsp,result.timing.classification,result.timing.anomaly);

对于(size_t ix = 1;ix < EI_CLASSIFIER_LABEL_COUNT;ix++){

Serial.print(result.classification[ix].value);

浮动数据=结果.分类[ix].值;

如果(数据 < 0.50){

Serial.print(“检测到咳嗽”);

警报();

}

}

//for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {

// ei_printf(” %s: %.5f
“, result.classification[ix].label, result.classification[ix].value);

// }

#if EI_CLASSIFIER_HAS_ANOMALY == 1

ei_printf(“异常分数:%.3f
“, result.anomaly);

#万一

}

void ei_printf(const char *format, …) {

静态字符 print_buf[1024] = { 0 };

va_list 参数;

va_start(参数,格式);

int r = vsnprintf(print_buf, sizeof(print_buf), 格式, args);

va_end(args);

如果 (r > 0) {

Serial.write(print_buf);

}

}

静态无效 pdm_data_ready_inference_callback(void)

{

int bytesAvailable = PDM.available();

// 读入样本缓冲区

int bytesRead = PDM.read((char *)&sampleBuffer[0], bytesAvailable);

if (record_ready == true || inference.buf_ready == 1) {

for(int i = 0; i < bytesRead>>1; i++) {

inference.buffer[inference.buf_count++] = sampleBuffer[i];

if(inference.buf_count >= inference.n_samples) {

inference.buf_count = 0;

inference.buf_ready = 1;

}

}

}

}

静态布尔mic_inference_start(uint32_t n_samples)

{

inference.buffer = (int16_t *)malloc(n_samples * sizeof(int16_t));

如果(推理。缓冲区 == NULL){

返回假;

}

inference.buf_count = 0;

inference.n_samples = n_samples;

inference.buf_ready = 0;

// 配置数据接收回调

PDM.onReceive(&pdm_data_ready_inference_callback);

// 可选设置增益,默认为 20

PDM.setGain(80);

//ei_printf(“扇区大小: %d nblocks: %d
“, ei_nano_fs_get_block_size(), n_sample_blocks);

PDM.setBufferSize(4096);

// 使用以下命令初始化 PDM:

// – 一个通道(单声道模式)

// – 16 kHz 采样率

if (!PDM.begin(1, EI_CLASSIFIER_FREQUENCY)) {

ei_printf(“启动 PDM 失败!”);

}

记录就绪=真;

返回真;

}

静态布尔麦克风推理记录(无效)

{

inference.buf_ready = 0;

inference.buf_count = 0;

而(inference.buf_ready == 0){

延迟(10);

}

返回真;

}

静态 int 麦克风_音频_信号_get_data(size_t 偏移量,size_t 长度,浮点 *out_ptr)

{

arm_q15_to_float(&inference.buffer[offset], out_ptr, length);

返回0;

}

静态无效麦克风推理结束(无效)

{

PDM.end();

免费(推理。缓冲区);

}

#if !defined(EI_CLASSIFIER_SENSOR) || EI_CLASSIFIER_SENSOR != EI_CLASSIFIER_SENSOR_MICROPHONE

#error “电流传感器的型号无效。”

#万一

无效警报(){

for (size_t t = 0; t < 4; t++) {

数字写入(领导,高);

延迟(1000);

数字写入(领导,低);

延迟(1000);

// digitalWrite(led, HIGH);

// 延迟(1000);

// digitalWrite(LED, LOW);

}

}

发表评论