在本教程中,我們將使用 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 連接后,您現在可以加載樣本。要加載樣本,請單擊“數據采集”?,F在在數據采集頁面上,輸入標簽名稱,選擇麥克風作為傳感器,然后輸入樣本長度。單擊“開始采樣”,開始采樣 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”?,F在向下滾動并單擊“構建”以開始該過程。這將為您的項目構建一個 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\n”, 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("推理設置:\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樣本長度: %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: 設置音頻采樣失敗\r\n");
返回;
}
}
無效循環()
{
ei_printf("2秒后開始推理...\n");
延遲(2000);
ei_printf("正在錄制...\n");
bool m = 麥克風推理記錄();
如果(!米){
ei_printf("ERR: 錄音失敗...\n");
返回;
}
ei_printf("錄制完成\n");
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)\n", r);
返回;
}
// 打印預測
ei_printf("預測 (DSP: %d ms., 分類: %d ms., 異常: %d ms.): \n",
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\n", result.classification[ix].label, result.classification[ix].value);
// }
#if EI_CLASSIFIER_HAS_ANOMALY == 1
ei_printf("異常分數:%.3f\n", 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\r\n", 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 “電流傳感器的型號無效?!?br />
#萬一
無效警報(){
for (size_t t = 0; t < 4; t++) {
數字寫入(領導,高);
延遲(1000);
數字寫入(領導,低);
延遲(1000);
// digitalWrite(led, HIGH);
// 延遲(1000);
// digitalWrite(LED, LOW);
}
}
評論
查看更多