Технология распознавания речи чрезвычайно полезна в различных системах автоматизации – она не только позволяет управлять устройствами не используя для этого руки (hands free), но также повышает безопасность системы. Также эта технология применяется во многих современных электронных гаджетах и оказывает неоценимую помощью людям с некоторыми тяжелыми заболеваниями.
Ранее на нашем сайте мы уже рассматривали преобразование текста в речь на основе Arduino, в этом же проекте мы будем использовать машинное обучение для тренировки (настройки) модели распознавания речи в Edge Impulse Studio. Мы будем тренировать нашу систему на три команды: ‘LIGHT ON’ (включить свет), ‘LIGHT OFF’ (выключить свет) и ‘NOISE’ (шум). Edge Impulse – это онлайн платформа машинного обучения, которая позволяет разработчикам создавать (проектировать) следующее поколение высоко интеллектуальных устройств со встроенным в них машинным обучением (Machine Learning).
Необходимые компоненты
Аппаратное обеспечение
- Плата Arduino Nano 33 BLE Sense.
- Светодиод (купить на AliExpress).
- Соединительные провода.
Программное обеспечение
- Edge Impulse Studio.
- Arduino IDE.
Схема проекта
Схема проекта распознавания речи на основе платы Arduino Nano 33 BLE Sense представлена на следующем рисунке. К сожалению, в программе Fritzing, которую мы использовали для рисования схемы, нет платы Arduino 33 BLE Sense, поэтому вместо нее на схеме мы использовали плату Arduino Nano поскольку она имеет точно такую же распиновку.
Положительный контакт светодиода подключен к цифровому контакту 5 платы Arduino Nano 33 BLE sense, а отрицательный контакт светодиода подключен к контакту GND платы Arduino.
Внешний вид собранной конструкции проекта показан на следующем рисунке.
Создание набора данных для системы распознавания речи
В нашем проекте мы будем использовать Edge Impulse Studio для тренировки (настройки) нашей модели распознавания речи. Тренировка (настройка) модели в Edge Impulse Studio очень похожа на тренировку моделей машинного обучения в других подобных системах. Для тренировки (настройки) модели машинного обучения первым шагом нам необходимо собрать набор данных (dataset), который будет содержать отсчеты/образцы (сэмплы) данных, которые мы хотим распознавать.
Поскольку цель нашего проекта будет состоять в том, чтобы управлять состоянием светодиода с помощью голосовых команд, нам необходимо собрать образцы звуков для всех наших команд и шума чтобы наше устройство могло отличать наши голосовые команды от шумов.
Мы создадим набор данных (dataset) с тремя классами - “LED ON”, “LED OFF” и “noise”. Для создания этого набора данных необходимо создать себе аккаунт в Edge Impulse, верифицировать его и затем создать в нем новый проект. Вы можете загрузить в него образцы (samples) звуков, используя ваш смартфон, плату Arduino или другое устройство, с помощью которого можно импортировать образцы звуков в ваш аккаунт на Edge Impulse. Самый простой способ загрузить образцы звуков в аккаунт Edge Impulse – это использовать мобильный телефон (смартфон). Для этого соедините ваш смартфон с Edge Impulse. Чтобы сделать это кликните в аккаунте Edge Impulse на ‘Devices’, а затем на ‘Connect a New Device’ (подключить новое устройство).
В окне, которое появится после этого, нажмите на ‘Use your Mobile Phone’ (использовать ваш мобильный телефон), после чего появится QR код. Отсканируйте этот QR код вашим мобильным телефоном или введите URL, показанный на этом QR коде.
После этого ваш телефон будет подключен к Edge Impulse studio.
Когда ваш телефон подключен к Edge Impulse Studio вы можете загружать в него ваши образцы звуков. Чтобы сделать это кликните на ‘Data acquisition’. После этого на странице получения данных (Data acquisition page) введите номер метки, выберите микрофон в качестве датчика и ведите длину образца звука (sample length). Затем нажмите на ‘Start sampling’ и ваше устройство запишет образец звука длительностью 2 секунды. Запишите от 10 до 12 образцов звука в различных условиях.
После загрузки образцов звука (samples) для первого класса измените метку и соберите также образцы звуков для классов ‘light off’ и ‘noise’
Собранные нами образцы звуков нужны для обучения (тренировки) модуля, далее нам необходимо еще собрать тестовые данные (Test Data). Эти данные должны составлять, по меньшей мере, 30% от тренировочных данных, поэтому нам нужно еще собрать 4 образца шума (‘noise’) и 4 или 5 образцов для звуков ‘light on’ и ‘light off’.
Тренировка (обучение) модели
Когда наш набор данных будет готов, мы можем создать импульс (impulse) для данных. Для этого вам нужно перейти на страницу ‘Create impulse’. Измените на ней настройки по умолчанию Window size (размер окна) с 1000 ms на 1200ms, а параметр Window increase измените с 500 ms до 58 ms. Эти настройки будут означать, что наши данные будут обрабатываться по времени 1,2 секунды, стартуя каждые 58ms.
После этого на странице ‘Create impulse’ нажмите на ‘Add a processing block’ (добавить блок обработки). В следующем окне выберите Audio (MFCC) block. После этого нажмите на ‘Add a learning block’ (добавить обучающий блок) и выберите в новом окне Neural Network (Keras) block (блок нейронной сети). Затем нажмите на ‘Save Impulse’ (сохранить импульс).
После этого перейдите на страницу MFCC и нажмите на ‘Generate Features’. В результате этого будут сгенерированы MFCC блоки для всех наших окон с аудио.
После этого перейдите на страницу ‘NN Classifier’ и нажмите там на три точки в правом верхнем углу напротив надписи ‘Neural Network settings’ (настройки нейронной сети), после чего выберите пункт ‘Switch to Keras (expert) mode’ как показано на следующем рисунке.
Замените оригинал следующим фрагментом кода и измените ‘Minimum confidence rating’ (минимальный доверительный интервал) на ‘0.70’. Затем нажмите на кнопку ‘Start training’ (начать обучение). После этого начнется обучение (тренировка) вашей модели.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense, InputLayer, Dropout, Flatten, Reshape, BatchNormalization, Conv2D, MaxPooling2D, AveragePooling2D from tensorflow.keras.optimizers import Adam from tensorflow.keras.constraints import MaxNorm # model architecture model = Sequential() 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(Flatten()) model.add(Dense(classes, activation='softmax', name='y_pred', kernel_constraint=MaxNorm(3))) # this controls the learning rate opt = Adam(lr=0.005, beta_1=0.9, beta_2=0.999) # train the neural network model.compile(loss='categorical_crossentropy', optimizer=opt, metrics=['accuracy']) model.fit(X_train, Y_train, batch_size=32, epochs=9, validation_data=(X_test, Y_test), verbose=2) |
После обучения модели будет показан результат обучения. В нашем случае точность составила 81.1%, а коэффициент потерь - 0.45. Это не является идеальным результатом обучения, но мы можем продолжать работать в этом направлении. Вы можете улучшить производительность модели при помощи создания более обширного набора данных.
Теперь, когда наша модель распознавания речи (Speech Recognition model) готова, мы можем разместить эту модель в библиотеке Arduino. Перед загрузкой модели в библиотеку вы можете протестировать ее производительность (функциональность) на странице ‘Live Classification’. На этой странице можно протестировать модель как с помощью существующего набора тестовых данных, так и в режиме реального времени, транслируя аудио данные с вашего мобильного телефона.
Чтобы протестировать данные с помощью мобильного телефона, выберите пункт ‘Switch to Classification Mode’ (переключиться в режим классификации) на вашем телефоне.
Теперь, чтобы загрузить модель в библиотеку Arduino, перейдите на страницу ‘Deployment’ и выберите на ней ‘Arduino Library’. Пролистайте страницу вниз и нажмите на ‘Build’ чтобы начать процесс. При завершении этого процесса будет создана библиотека Arduino для вашего проекта.
После этого добавьте данную библиотеку в вашу Arduino IDE выбрав в ней пункт меню Sketch > Include Library > Add.ZIP library.
Затем загрузите пример по адресу File > Examples > Your project name - Edge Impulse > nano_ble33_sense_microphone.
Объяснение кода программы распознавания речи для Arduino
Полный код программы приведен в конце статьи, здесь же мы кратко рассмотрим его основные фрагменты.
Мы в программе будет управлять состояние светодиода (включен/выключен) в зависимости от поступающих голосовых команд.
Мы внесем некоторые изменения в функцию void loop() открытого нами примера в месте, где происходит вывод вероятностей команд на экран. В оригинальном коде выводятся все метки и их значения вместе.
1 2 3 |
for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) { ei_printf(" %s: %.5f\n", result.classification[ix].label, result.classification[ix].value); } |
Чтобы управлять светодиодом нам необходимо сохранить все вероятности команды в трех различных переменных таким образом, чтобы мы могли записать дополнительные условия для них. Таким образом, в соответствии с новым кодом, если вероятность команды ‘light on’ (включить светодиод) будет больше чем 0.50, то мы будем включать светодиод, а если вероятность команды ‘light off’ будет больше чем 0.50, мы будем выключать светодиод.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
for (size_t ix = 2; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) { noise = result.classification[ix].value; Serial.println("Noise: "); Serial.println(noise); } for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix--) { lightoff = result.classification[ix].value; Serial.println("Light Off: "); Serial.print(lightoff); } lighton = 1- (noise +lightoff); Serial.println("Light ON: "); Serial.print(lighton); if (lighton > 0.50){ digitalWrite(led, HIGH); } if (lightoff > 0.50){ digitalWrite(led, LOW); } |
После внесения описанных изменений загрузите код программы в плату Arduino. Откройте монитор последовательного порта (serial monitor), скорость – 115200 бод.
Произнося команды вы сможете наблюдать как меняется информация в окне монитора последовательной связи. Более подробно все описанные в данной статье процессы вы можете посмотреть на видео, приведенном в конце статьи.
Исходный код программы (скетча)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
#define EIDSP_QUANTIZE_FILTERBANK 0 /* Includes ---------------------------------------------------------------- */ #include <PDM.h> #include <arduino_ac3121_inference.h> /** Audio buffers, pointers and selectors */ typedef struct { int16_t *buffer; uint8_t buf_ready; uint32_t buf_count; uint32_t n_samples; } inference_t; static inference_t inference; static bool record_ready = false; static signed short sampleBuffer[2048]; static bool debug_nn = false; // Set this to true to see e.g. features generated from the raw signal float lighton, lightoff, noise; #define led 5 void setup() { // put your setup code here, to run once: Serial.begin(115200); Serial.println("Edge Impulse Inferencing Demo"); pinMode( led, OUTPUT ); // summary of inferencing settings (from model_metadata.h) ei_printf("Inferencing settings:\n"); ei_printf("\tInterval: %.2f ms.\n", (float)EI_CLASSIFIER_INTERVAL_MS); ei_printf("\tFrame size: %d\n", EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE); ei_printf("\tSample length: %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: Failed to setup audio sampling\r\n"); return; } } /** * @brief Arduino main function. Runs the inferencing loop. */ void loop() { ei_printf("Starting inferencing in 2 seconds...\n"); delay(2000); ei_printf("Recording...\n"); bool m = microphone_inference_record(); if (!m) { ei_printf("ERR: Failed to record audio...\n"); return; } ei_printf("Recording done\n"); signal_t signal; signal.total_length = EI_CLASSIFIER_RAW_SAMPLE_COUNT; signal.get_data = µphone_audio_signal_get_data; ei_impulse_result_t result = { 0 }; EI_IMPULSE_ERROR r = run_classifier(&signal, &result, debug_nn); if (r != EI_IMPULSE_OK) { ei_printf("ERR: Failed to run classifier (%d)\n", r); return; } // print the predictions ei_printf("Predictions (DSP: %d ms., Classification: %d ms., Anomaly: %d ms.): \n", result.timing.dsp, result.timing.classification, result.timing.anomaly); // for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) { // ei_printf(" %s: %.5f\n", result.classification[ix].label, result.classification[ix].value); // } for (size_t ix = 2; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) { noise = result.classification[ix].value; Serial.println("Noise: "); Serial.println(noise); } for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix--) { lightoff = result.classification[ix].value; Serial.println("Light Off: "); Serial.print(lightoff); } lighton = 1- (noise +lightoff); Serial.println("Light ON: "); Serial.print(lighton); if (lighton > 0.60){ digitalWrite(led, HIGH); } if (lightoff > 0.29){ digitalWrite(led, LOW); } #if EI_CLASSIFIER_HAS_ANOMALY == 1 ei_printf(" anomaly score: %.3f\n", result.anomaly); #endif } /** * @brief Printf function uses vsnprintf and output using Arduino Serial * * @param[in] format Variable argument list */ void ei_printf(const char *format, ...) { static char print_buf[1024] = { 0 }; va_list args; va_start(args, format); int r = vsnprintf(print_buf, sizeof(print_buf), format, args); va_end(args); if (r > 0) { Serial.write(print_buf); } } /** * @brief PDM buffer full callback * Get data and call audio thread callback */ static void pdm_data_ready_inference_callback(void) { int bytesAvailable = PDM.available(); // read into the sample buffer 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; } } } } /** * @brief Init inferencing struct and setup/start PDM * * @param[in] n_samples The n samples * * @return { description_of_the_return_value } */ static bool microphone_inference_start(uint32_t n_samples) { inference.buffer = (int16_t *)malloc(n_samples * sizeof(int16_t)); if(inference.buffer == NULL) { return false; } inference.buf_count = 0; inference.n_samples = n_samples; inference.buf_ready = 0; // configure the data receive callback PDM.onReceive(&pdm_data_ready_inference_callback); // optionally set the gain, defaults to 20 PDM.setGain(80); //ei_printf("Sector size: %d nblocks: %d\r\n", ei_nano_fs_get_block_size(), n_sample_blocks); PDM.setBufferSize(4096); // initialize PDM with: // - one channel (mono mode) // - a 16 kHz sample rate if (!PDM.begin(1, EI_CLASSIFIER_FREQUENCY)) { ei_printf("Failed to start PDM!"); } record_ready = true; return true; } /** * @brief Wait on new data * * @return True when finished */ static bool microphone_inference_record(void) { inference.buf_ready = 0; inference.buf_count = 0; while(inference.buf_ready == 0) { delay(10); } return true; } /** * Get raw audio signal data */ static int microphone_audio_signal_get_data(size_t offset, size_t length, float *out_ptr) { arm_q15_to_float(&inference.buffer[offset], out_ptr, length); return 0; } /** * @brief Stop PDM and release buffers */ static void microphone_inference_end(void) { PDM.end(); free(inference.buffer); } #if !defined(EI_CLASSIFIER_SENSOR) || EI_CLASSIFIER_SENSOR != EI_CLASSIFIER_SENSOR_MICROPHONE #error "Invalid model for current sensor." #endif |
а как сделать так же но только чтобы через камеру
Чтобы речь распознавать с использованием камеры, а не микрофона?
всё понял кроме одного..... после того как всё сделал, загрузил в ардуино - с помощью какого микрофона он распознает речь? ни на схеме, ни на видео не показан где микрофон. куда говорить то? га плату ардуино?????????
Посмотрите описание платы Arduino Nano 33 BLE Sense (например, здесь - wiki.amperka.ru/products:arduino-nano-33-ble-sense) и вы увидите что в ее составе есть микрофон MP34DT05