在本教程中,我們將使用 Arduino 33 BLE Sense 和 Edge Impulse Studio 構(gòu)建咳嗽檢測(cè)系統(tǒng)。它可以區(qū)分正常的背景噪音和實(shí)時(shí)音頻中的咳嗽。我們使用 Edge Impulse Studio 訓(xùn)練咳嗽和背景噪聲樣本數(shù)據(jù)集,并構(gòu)建高度優(yōu)化的 TInyML 模型,該模型可以實(shí)時(shí)檢測(cè)咳嗽聲音。
所需組件
Arduino 33 BLE 感知
引領(lǐng)
跳線
軟件
邊緣脈沖工作室
Arduino IDE
電路原理圖
下面給出了使用 Arduino 33 BLE Sense進(jìn)行咳嗽檢測(cè)的電路圖。Arduino 33 BLE 的 Fritzing 部件不可用,所以我使用了 Arduino Nano,因?yàn)樗鼈兙哂邢嗤囊_。
LED 的正極引線連接到 Arduino 33 BLE sense 的數(shù)字引腳 4,負(fù)極引線連接到 Arduino 的 GND 引腳。
為咳嗽檢測(cè)機(jī)創(chuàng)建數(shù)據(jù)集
如前所述,我們正在使用 Edge Impulse Studio 來(lái)訓(xùn)練我們的咳嗽檢測(cè)模型。為此,我們必須收集一個(gè)數(shù)據(jù)集,其中包含我們希望能夠在 Arduino 上識(shí)別的數(shù)據(jù)樣本。由于目標(biāo)是檢測(cè)咳嗽,因此您需要收集其中的一些樣本和其他一些噪聲樣本,以便區(qū)分咳嗽和其他噪聲。
我們將創(chuàng)建一個(gè)包含“咳嗽”和“噪音”兩個(gè)類別的數(shù)據(jù)集。要?jiǎng)?chuàng)建數(shù)據(jù)集,請(qǐng)創(chuàng)建一個(gè)Edge Impulse帳戶,驗(yàn)證您的帳戶,然后開(kāi)始一個(gè)新項(xiàng)目。您可以使用手機(jī)、Arduino 板加載樣本,也可以將數(shù)據(jù)集導(dǎo)入邊緣脈沖帳戶。將樣本加載到您的帳戶中的最簡(jiǎn)單方法是使用您的手機(jī)。為此,您必須將您的手機(jī)與 Edge Impulse 連接。
要連接您的手機(jī),請(qǐng)單擊“設(shè)備”,然后單擊“連接新設(shè)備”。
現(xiàn)在在下一個(gè)窗口中,單擊“使用您的手機(jī)”,將出現(xiàn)一個(gè)二維碼。使用 Google Lens 或其他 QR 碼掃描儀應(yīng)用程序使用您的手機(jī)掃描 QR 碼。
這會(huì)將您的手機(jī)與 Edge Impulse studio 連接起來(lái)。
將手機(jī)與 Edge Impulse Studio 連接后,您現(xiàn)在可以加載樣本。要加載樣本,請(qǐng)單擊“數(shù)據(jù)采集”。現(xiàn)在在數(shù)據(jù)采集頁(yè)面上,輸入標(biāo)簽名稱,選擇麥克風(fēng)作為傳感器,然后輸入樣本長(zhǎng)度。單擊“開(kāi)始采樣”,開(kāi)始采樣 40 秒樣本。您可以使用不同長(zhǎng)度的在線咳嗽樣本,而不是強(qiáng)迫自己咳嗽。共記錄 10 到 12 個(gè)不同長(zhǎng)度的咳嗽樣本。
上傳咳嗽樣本后,現(xiàn)在將標(biāo)簽設(shè)置為“噪聲”并再收集 10 到 12 個(gè)噪聲樣本。
這些樣本用于訓(xùn)練模塊,在接下來(lái)的步驟中,我們將收集測(cè)試數(shù)據(jù)。測(cè)試數(shù)據(jù)至少應(yīng)該是訓(xùn)練數(shù)據(jù)的 30%,所以收集 3 個(gè)樣本的“噪音”和 4 到 5 個(gè)樣本的“咳嗽”。
您可以使用 Edge Impulse CLI Uploader 將我們的數(shù)據(jù)集導(dǎo)入您的 Edge Impulse 帳戶,而不是收集您的數(shù)據(jù)。
要安裝 CLI Uploader,首先,在您的筆記本電腦上下載并安裝Node.js。之后打開(kāi)命令提示符并輸入以下命令:
npm install -g edge-impulse-cli
現(xiàn)在下載數(shù)據(jù)集(數(shù)據(jù)集鏈接)并將文件解壓縮到您的項(xiàng)目文件夾中。打開(kāi)命令提示符并導(dǎo)航到數(shù)據(jù)集位置并運(yùn)行以下命令:
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
訓(xùn)練模型并調(diào)整代碼
隨著數(shù)據(jù)集準(zhǔn)備就緒,現(xiàn)在我們將為數(shù)據(jù)創(chuàng)建一個(gè)脈沖。為此,請(qǐng)轉(zhuǎn)到“創(chuàng)建沖動(dòng)”頁(yè)面。
現(xiàn)在在“創(chuàng)建沖動(dòng)”頁(yè)面上,單擊“添加處理塊”。在下一個(gè)窗口中,選擇音頻 (MFCC) 塊。之后單擊“添加學(xué)習(xí)塊”并選擇神經(jīng)網(wǎng)絡(luò)(Keras)塊。然后點(diǎn)擊“保存沖動(dòng)”。
在下一步中,轉(zhuǎn)到 MFCC 頁(yè)面,然后單擊“生成功能”。它將為我們所有的音頻窗口生成 MFCC 塊。
之后進(jìn)入“ NN Classifier”頁(yè)面,點(diǎn)擊“ Neural Network settings”右上角的三個(gè)點(diǎn),選擇“ Switch to Keras (expert) mode”。
將原始代碼替換為以下代碼,并將“最低置信度評(píng)級(jí)”更改為“0.70”。然后單擊“開(kāi)始培訓(xùn)”按鈕。它將開(kāi)始訓(xùn)練您的模型。
將張量流導(dǎo)入為 tf
從 tensorflow.keras.models 導(dǎo)入順序
從 tensorflow.keras.layers 導(dǎo)入 Dense、InputLayer、Dropout、Flatten、Reshape、BatchNormalization、Conv2D、MaxPooling2D、AveragePooling2D
從 tensorflow.keras.optimizers 導(dǎo)入 Adam
從 tensorflow.keras.constraints 導(dǎo)入 MaxNorm
# 模型架構(gòu)
模型=順序()
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)))
# 這控制了學(xué)習(xí)率
選擇 = 亞當(dāng)(lr=0.005,beta_1=0.9,beta_2=0.999)
# 訓(xùn)練神經(jīng)網(wǎng)絡(luò)
model.compile(loss=‘categorical_crossentropy’,優(yōu)化器=opt,metrics=[‘a(chǎn)ccuracy’])
model.fit(X_train,Y_train,batch_size=32,epochs=9,validation_data=(X_test,Y_test),詳細(xì)=2)
訓(xùn)練模型后,將顯示訓(xùn)練性能。對(duì)我來(lái)說(shuō),準(zhǔn)確率是 96.5%,損失是 0.10,這很好。
現(xiàn)在我們的咳嗽檢測(cè)模型已經(jīng)準(zhǔn)備就緒,我們將把這個(gè)模型部署為 Arduino 庫(kù)。在將模型下載為庫(kù)之前,您可以通過(guò)轉(zhuǎn)到“實(shí)時(shí)分類”頁(yè)面來(lái)測(cè)試性能。
轉(zhuǎn)到“部署”頁(yè)面并選擇“ Arduino Library”。現(xiàn)在向下滾動(dòng)并單擊“構(gòu)建”以開(kāi)始該過(guò)程。這將為您的項(xiàng)目構(gòu)建一個(gè) Arduino 庫(kù)。
現(xiàn)在在您的 Arduino IDE 中添加庫(kù)。為此,打開(kāi) Arduino IDE,然后單擊Sketch 》 Include Library 》 Add.ZIP library。
然后,通過(guò)轉(zhuǎn)到 文件 》 示例 》 您的項(xiàng)目名稱 - Edge Impulse 》 nano_ble33_sense_microphone 來(lái)加載示例。
我們將對(duì)代碼進(jìn)行一些更改,以便在 Arduino 檢測(cè)到咳嗽時(shí)發(fā)出警報(bào)聲。為此,Arduino 連接了一個(gè)蜂鳴器,當(dāng)它檢測(cè)到咳嗽時(shí),LED 會(huì)閃爍 3 次。
這些更改是在打印噪音和咳嗽值的void loop()函數(shù)中進(jìn)行的。在原始代碼中,它同時(shí)打印標(biāo)簽及其值。
對(duì)于 (size_t ix = 0; ix 《 EI_CLASSIFIER_LABEL_COUNT; ix++) {
ei_printf(“%s: %.5f\n”, result.classification[ix].label, result.classification[ix].value);
}
我們將把噪聲值和咳嗽值保存在不同的變量中,并比較噪聲值。如果噪聲值低于 0.50,則表示咳嗽值大于 0.50,它會(huì)發(fā)出聲音。用這個(gè)替換原來(lái)的 for loop()代碼:
對(duì)于(size_t ix = 1;ix 《 EI_CLASSIFIER_LABEL_COUNT;ix++){
Serial.print(result.classification[ix].value);
浮動(dòng)數(shù)據(jù)=結(jié)果。分類[ix]。值;
如果(數(shù)據(jù) 《 0.50){
Serial.print(“檢測(cè)到咳嗽”);
警報(bào)();
}
}
進(jìn)行更改后,將代碼上傳到您的 Arduino。以 115200 波特率打開(kāi)串行監(jiān)視器。
所以這就是咳嗽檢測(cè)機(jī)器的構(gòu)建方式,它不是找到任何 COVID19 嫌疑人的非常有效的方法,但它可以在一些擁擠的區(qū)域很好地工作。
#define EIDSP_QUANTIZE_FILTERBANK 0
#include
#include
#define LED 5
/** 音頻緩沖區(qū)、指針和選擇器 */
類型定義結(jié)構(gòu){
int16_t *緩沖區(qū);
uint8_t buf_ready;
uint32_t buf_count;
uint32_t n_samples;
} inference_t;
靜態(tài)推理_t推理;
靜態(tài)布爾記錄就緒=假;
靜態(tài)有符號(hào)短樣本緩沖區(qū)[2048];
靜態(tài) bool debug_nn = false; // 將此設(shè)置為 true 以查看例如從原始信號(hào)生成的特征
無(wú)效設(shè)置()
{
// 把你的設(shè)置代碼放在這里,運(yùn)行一次:
序列號(hào).開(kāi)始(115200);
pinMode(LED,輸出);
Serial.println("邊緣脈沖推理演示");
// 推理設(shè)置摘要(來(lái)自 model_metadata.h)
ei_printf("推理設(shè)置:\n");
ei_printf("\tInterval: %.2f ms.\n", (float)EI_CLASSIFIER_INTERVAL_MS);
ei_printf("\t幀大小: %d\n", EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE);
ei_printf("\t樣本長(zhǎng)度: %d ms.\n", EI_CLASSIFIER_RAW_SAMPLE_COUNT / 16);
ei_printf("\tNo. of classes: %d\n", sizeof(ei_classifier_inferencing_categories) / sizeof(ei_classifier_inferencing_categories[0]));
if (microphone_inference_start(EI_CLASSIFIER_RAW_SAMPLE_COUNT) == false) {
ei_printf("ERR: 設(shè)置音頻采樣失敗\r\n");
返回;
}
}
無(wú)效循環(huán)()
{
ei_printf("2秒后開(kāi)始推理...\n");
延遲(2000);
ei_printf("正在錄制...\n");
bool m = 麥克風(fēng)推理記錄();
如果(!米){
ei_printf("ERR: 錄音失敗...\n");
返回;
}
ei_printf("錄制完成\n");
signal_t 信號(hào);
signal.total_length = EI_CLASSIFIER_RAW_SAMPLE_COUNT;
signal.get_data = μphone_audio_signal_get_data;
ei_impulse_result_t 結(jié)果 = { 0 };
EI_IMPULSE_ERROR r = run_classifier(&signal, &result, debug_nn);
如果(r!= EI_IMPULSE_OK){
ei_printf("ERR: 分類器運(yùn)行失敗(%d)\n", r);
返回;
}
// 打印預(yù)測(cè)
ei_printf("預(yù)測(cè) (DSP: %d ms., 分類: %d ms., 異常: %d ms.): \n",
result.timing.dsp,result.timing.classification,result.timing.anomaly);
對(duì)于(size_t ix = 1;ix < EI_CLASSIFIER_LABEL_COUNT;ix++){
Serial.print(result.classification[ix].value);
浮動(dòng)數(shù)據(jù)=結(jié)果.分類[ix].值;
如果(數(shù)據(jù) < 0.50){
Serial.print("檢測(cè)到咳嗽");
警報(bào)();
}
}
//for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
// ei_printf(" %s: %.5f\n", result.classification[ix].label, result.classification[ix].value);
// }
#if EI_CLASSIFIER_HAS_ANOMALY == 1
ei_printf("異常分?jǐn)?shù):%.3f\n", result.anomaly);
#萬(wàn)一
}
void ei_printf(const char *format, ...) {
靜態(tài)字符 print_buf[1024] = { 0 };
va_list 參數(shù);
va_start(參數(shù),格式);
int r = vsnprintf(print_buf, sizeof(print_buf), 格式, args);
va_end(args);
如果 (r > 0) {
Serial.write(print_buf);
}
}
靜態(tài)無(wú)效 pdm_data_ready_inference_callback(void)
{
int bytesAvailable = PDM.available();
// 讀入樣本緩沖區(qū)
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;
}
}
}
}
靜態(tài)布爾mic_inference_start(uint32_t n_samples)
{
inference.buffer = (int16_t *)malloc(n_samples * sizeof(int16_t));
如果(推理。緩沖區(qū) == NULL){
返回假;
}
inference.buf_count = 0;
inference.n_samples = n_samples;
inference.buf_ready = 0;
// 配置數(shù)據(jù)接收回調(diào)
PDM.onReceive(&pdm_data_ready_inference_callback);
// 可選設(shè)置增益,默認(rèn)為 20
PDM.setGain(80);
//ei_printf("扇區(qū)大小: %d nblocks: %d\r\n", ei_nano_fs_get_block_size(), n_sample_blocks);
PDM.setBufferSize(4096);
// 使用以下命令初始化 PDM:
// - 一個(gè)通道(單聲道模式)
// - 16 kHz 采樣率
if (!PDM.begin(1, EI_CLASSIFIER_FREQUENCY)) {
ei_printf("啟動(dòng) PDM 失敗!");
}
記錄就緒=真;
返回真;
}
靜態(tài)布爾麥克風(fēng)推理記錄(無(wú)效)
{
inference.buf_ready = 0;
inference.buf_count = 0;
而(inference.buf_ready == 0){
延遲(10);
}
返回真;
}
靜態(tài) int 麥克風(fēng)_音頻_信號(hào)_get_data(size_t 偏移量,size_t 長(zhǎng)度,浮點(diǎn) *out_ptr)
{
arm_q15_to_float(&inference.buffer[offset], out_ptr, length);
返回0;
}
靜態(tài)無(wú)效麥克風(fēng)推理結(jié)束(無(wú)效)
{
PDM.end();
免費(fèi)(推理。緩沖區(qū));
}
#if !defined(EI_CLASSIFIER_SENSOR) || EI_CLASSIFIER_SENSOR != EI_CLASSIFIER_SENSOR_MICROPHONE
#error “電流傳感器的型號(hào)無(wú)效。”
#萬(wàn)一
無(wú)效警報(bào)(){
for (size_t t = 0; t < 4; t++) {
數(shù)字寫(xiě)入(領(lǐng)導(dǎo),高);
延遲(1000);
數(shù)字寫(xiě)入(領(lǐng)導(dǎo),低);
延遲(1000);
// digitalWrite(led, HIGH);
// 延遲(1000);
// digitalWrite(LED, LOW);
}
}
評(píng)論
查看更多