- BrainTools - https://www.braintools.ru -
В этой статье я хочу поделиться своим опытом [1] портирования проекта распознавания музыкальных жанров аудиозаписей на ESP32-C3. Исходный проект взят из репозитория книги TinyML-Cookbook_2E [2].
При анализе речи или других звуков важно выделить такие характеристики, которые отражают строение сигнала, но при этом не зависят от конкретных слов, громкости и других мешающих факторов. Для этого используют cepstrum, mel-cepstrum и MFCC – это шаги преобразования, которые переводят звук в удобную для анализа форму.
Мы сначала превращаем звуковой сигнал в спектр, потом берём логарифм спектра, а затем применяем ещё одно преобразование Фурье. Получившийся результат и называется cepstrum.
На практике:
Берём короткий фрагмент звука (например, 20–40 мс).
Строим его спектр (FFT).
Берём логарифм амплитуды спектра — логарифм усиливает слабые составляющие и делает спектр более «ровным».
Применяем снова FFT → результат — это cepstrum.
Логарифм превращает умножение (а спектр речи можно представить как произведение голоса и вокального тракта) в сложение. А затем второе FFT помогает разложить это сложение и извлечь структуру.
Когда человек говорит, звук речи можно условно представить как две части:
Источник звука (excitation) — вибрация голосовых связок. Это периодический сигнал с основной частотой (например, 100 Гц).
Формирующий тракт (vocal tract) — рот, язык, губы и т.п., которые формируют звук, усиливая/подавляя определённые частоты.
Когда мы применяем IFFT, в обратном временном представлении (quefrency) эти части попадают в разные диапазоны:
Низкие quefrency (0–10 мс) → форма голосового тракта (огибающая).
Высокие quefrency (10–20 мс) → возбуждение [3], периодичность, F0.
Quefrency — это шуточное название (перестановка букв от frequency, частота), которое обозначает ось в cepstrum. Но физически это время, выраженное в миллисекундах. То есть:
Низкие значения quefrency (в начале графика) отражают гладкую форму спектра — это так называемая огибающая.
Высокие значения могут содержать периодические пики, связанные с основным тоном (F0).
Форманты — это устойчивые пики в спектре речи. Они возникают из-за резонанса в голосовом тракте (ротовой полости, гортани и т.д.).
Например, когда мы произносим разные гласные, положение языка и губ изменяет резонансные частоты — именно они и называются формантами.
Каждый гласный звук имеет свой «узор» формант, и именно по ним мы отличаем одни гласные от других.
Если в cepstrum оставить только низкие значения quefrency (обрезать всё остальное), а затем применить обратное преобразование, мы получим гладкую огибающую спектра. Это похоже на сглаживание: мы убираем детали и оставляем основную форму.
Человеческое ухо слышит не линейно: мы лучше различаем низкие частоты, чем высокие.

Mel-шкала — это шкала, которая приближает частотное восприятие [4] к человеческому.
Например:
Разница между 500 и 1000 Гц кажется нам большой.
А разница между 5000 и 5500 Гц — почти незаметна.
Mel-фильтры — это набор треугольных фильтров, равномерно размещённых по mel-шкале. Они позволяют выделить энергию в частотных диапазонах, важных для слуха [5].


MFCC (Mel-Frequency Cepstral Coefficients) — это компактное числовое представление звука, которое используется в машинном обучении [6].
Шаги получения MFCC:
Делим сигнал на окна.
Для каждого окна:
Строим спектр.
Пропускаем его через mel-фильтры.
Берём логарифм результата.
Применяем DCT (дискретное косинус-преобразование) — это как ещё одно сглаживание и декорреляция.
На выходе получаем небольшой набор коэффициентов, которые:
описывают форму спектра;
устойчивы к шуму и изменениям громкости;
хорошо подходят для классификации и распознавания речи.
В конце статьи я приведу ссылки на ресурсы, где можно более подробно ознакомится с цифровой обработкой аудио.
Для получения исходного кода модели распознавания музыкальных жанров необходимо преобразовать обученную нейросеть в tflite, а затем в model.h. Для этого воспользуемся листингом Jupyter Notebook prepare_model.ipynb [7]. Файл можно запустить локально, например в VScode с расширением Jupyter, или с помощью Google Colab.
В исходном файле автор выбрал три типа музыкальных жанра для распознавания: ‘disco’, ‘jazz’, ‘metal’ датасета GTZAN Dataset [8] из соображений экономии ресурсов на Raspberry Pico. В нашем примере обучим модель на всех десяти жанрах датасета. На практике это занимает не намного больше ресурсов, и ESP32 с этим вполне хорошо справляется.
Для предсказания временных рядов используется Lstm (рекуррентный) слой ML-модели.
x = norm_layer(input)
x = layers.LSTM(32, return_sequences=True)(x)
x = layers.Dropout(0.5)(x)
x = layers.Flatten()(x)
x = layers.Dense(32, activation='relu')(x)
x = layers.Dense(len(LIST_GENRES), activation='softmax')(x)
По умолчанию в Python устанавливается Tensorflow последней версии, который содержит Keras 3 версии. И это влечет несколько проблем. В файле prepare_model.ipynb используется Keras 2 синтаксис конвертации модели в формат .tflite. Рефакторинг на keras 3 решается достаточно легко если погуглить, или обратиться к “всемогущему” Чату. Но со второй проблемой вы столкнетесь только когда запустите микроконтроллер. В консоли последовательного порта будет сообщение, что в модели отсутствуют необходимые OPS слои. Возможно это частный случай компонента espressif/esp-tflite-micro.
Установим необходимые зависимости. Расширим первую ячейку prepare_model.ipynb, добавив к существующим зависимостям tensorflow с выбранной версией 2.12.0.
!pip install numpy==1.23.5
!pip install cmsisdsp==1.9.6
!pip install tensorflow==2.12.0
Далее необходимо скачать и распаковать GTZAN Dataset [8]. В Jupyter указан каталог для образцов mgr_dataset. Для работы в Colab нужно создать каталог с таким же именем в корне Google drive и туда расположить образцы мелодий из genres_original, или в самом Jupyter-файле изменить имя каталога.
Подключаем Google drive к Colab.
from google.colab import drive
drive.mount('/content/drive')
После выполнения блока кода всплывет диалог авторизации Google, а затем диалог выбора разрешений. Если выбрать не все разрешения, то скорее всего диск не подключиться, и не будет доступа к каталогам с образцами мелодий.
Для того, чтобы обучить модель для всех 10 типов жанра из датасета, необходимо расширить список LIST_GENRES
LIST_GENRES = ['blues', 'classical', 'country', 'disco', 'hiphop', 'jazz', 'metal', 'pop', 'reggae', 'rock']
После этого можно запустить выполнение prepare_model.ipynb и идти заваривать чай.
Если верить метрикам времени выполнения каждого блока кода в Colab, то весь процесс занял 18 минут.
В результате получается набор заголовочных файлов на языке C для микроконтроллера, которые содержат MFCC-коэффициенты.
Эти заголовки (кроме test_dst.h, test_src.h) нужно расположить в каталоге основного компонента.
Для установки зависимости [9] Tensorflow lite в ESP-IDF проекте выполняем команду в ESP-IDF терминале.
idf.py add-dependency "esp-tflite-micro"
После чего манифест idf_component.yml будет иметь следующее содержимое
## IDF Component Manager Manifest File
dependencies:
espressif/esp-tflite-micro: "*"
## Required IDF version
idf:
version: ">=4.1.0"
В espressif/esp-tflite-micro нужно явно подключать слои модели. Для этого загрузим файл model.tflite на ресурс https://netron.app [10]. Данная модель имеет следующую структуру.
В статье Machine learning на ESP32 [11] я описывал процесс обучения модели, и реализацию исходного кода для ESP32. Здесь приведу уже готовый листинг для данного проекта модели.
void tflu_initialization() {
if (tensor_arena == NULL) {
tensor_arena = (uint8_t *) heap_caps_malloc(tensor_arena_size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
}
if (tensor_arena == NULL) {
ESP_LOGE(TAG, "Couldn't allocate memory of %d bytesn", tensor_arena_size);
return;
}
tflu_model = tflite::GetModel(model_tflite);
if (tflu_model->version() != TFLITE_SCHEMA_VERSION) {
ESP_LOGE(TAG, "Model schema version mismatch!");
while (1);
}
static tflite::MicroMutableOpResolver<9>; resolver;
resolver.AddQuantize();
resolver.AddDequantize();
resolver.AddSub();
resolver.AddMul();
resolver.AddUnidirectionalSequenceLSTM();
resolver.AddStridedSlice();
resolver.AddFullyConnected();
resolver.AddSoftmax();
static tflite::MicroInterpreter static_interpreter(
tflu_model, resolver, tensor_arena, tensor_arena_size);
tflu_interpreter = &static_interpreter;
tflu_interpreter->AllocateTensors();
tflu_i_tensor = tflu_interpreter->input(0);
tflu_o_tensor = tflu_interpreter->output(0);
ESP_LOGI(TAG, "TensorFlow Lite initialization completed");
}
Для проекта был выбран модуль ESP32-C3 0.42 OLED. Он скромно ждал своего момента с тех времен, когда мне захотелось познакомится с ассемблером RISK-V. Модуль имеет следующие особенности [12]:
RISC-V SoC @ 160MHz with 4MB flash and 400kB RAM
WS2812B RGB serial LED
0.42-inch OLED over I2C
Qwiic I2C connector
One pushbutton
Onboard ceramic chip antenna
On-chip USB-UART converter

Главной фишкой данного модуля является 0.42-дюймовый OLED-дисплей, в нашем случае будем на нем показывать распознанный жанр.

Характеристики:
Напряжение питания: 2,7 В – 5,5 В при токе 3 мА
Выход: 2Vpp при смещении 1,25В
Частотная характеристика: 20 Гц – 20 кГц
Программируемый коэффициент атаки и спада
Автоматическое усиление, выбирается максимум из 40дБ, 50дБ или 60дБ
Низкая входная плотность шума 30 нВ/Гц
Низкий THD: 0,04% (тип)
Размеры: 25х14мм , диаметр отверстия 2мм, расстояние от центра до центра отверстия 10мм
Чип в основе этого усилителя – MAX9814 [13]
Электретный микрофон [14]
Вывод OUT микрофона подключается к аналоговому входу A0 ESP32-С3. Вывод Gain нужно подключить к V+, тогда усиление микрофона будет максимальным.
Проверить работу микрофона можно с помощью осциллографа. Для этого нужно подать питание на микрофон, щуп подключить к OUT, и воспроизвести аудиосигнал.

Для программирования монохромных LCD-дисплеев существует библиотека u8g2 [15]. Пример исходного кода для 0.42 OLED на Arduino можно найти здесь [16]. Из этого примера мы можем найти строчку конфигурации для дисплея 0.42.
U8G2_SSD1306_72X40_ER_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); // EastRising 0.42" OLED
Клонируем u8g2 [17] репозиторий в каталог компонентов нашего проекта ./components. В этом проекте CMakeLists.txt уже содержит команды для ESP-IDF
if(COMMAND idf_component_register)
idf_component_register(SRCS "${COMPONENT_SRCS}" INCLUDE_DIRS csrc)
return()
endif()
Для упрощения работы с u8g2 в ESP-IDF можно воспользоваться hal-модулем из nkolban/esp32-snippets [18] репозитория. Нам нужны два файла: u8g2_esp32_hal.h и u8g2_esp32_hal.c, которые копируем в каталог с основным модулем main.c.
Пример работы с hal-модулем находится в этом же репозитории test_SSD1306_i2c.c [19]
Чтобы компилировать hal-модуль совместно с C++ классами (Tensorflow, CMCIS) нужно немного доработать u8g2_esp32_hal.h, а именно – добавить директивы extern "C".
#ifndef U8G2_ESP32_HAL_H_
#define U8G2_ESP32_HAL_H_
#ifdef __cplusplus
extern "C" {
#endif
//исходный код
#ifdef __cplusplus
}
#endif
#endif /* U8G2_ESP32_HAL_H_ */
В основном модуле main.c подключаем заголовок #include "u8g2_esp32_hal.h"
В main-функции инициализируем hal-модуль и OLED, как в примере из test_SSD1306_i2c.c. Так как дисплей подключается к ESP32 I2C-шине, нам нужно указать выводы PIN_SDA и PIN_SDA. В случае ESP32-C3 – это пины 5 и 6 соответственно. I2C адрес дисплея – 0x78.
#include "u8g2_esp32_hal.h"
#define PIN_SDA GPIO_NUM_5
#define PIN_SCL GPIO_NUM_6
u8g2_t u8g2;
// initialize the u8g2 hal
u8g2_esp32_hal_t u8g2_esp32_hal = U8G2_ESP32_HAL_DEFAULT;
u8g2_esp32_hal.sda = PIN_SDA;
u8g2_esp32_hal.scl = PIN_SCL;
u8g2_esp32_hal_init(u8g2_esp32_hal);
// initialize the u8g2 library
u8g2_Setup_ssd1306_i2c_72x40_er_f(
&u8g2,
U8G2_R0,
u8g2_esp32_i2c_byte_cb,
u8g2_esp32_gpio_and_delay_cb);
// set the display address
u8x8_SetI2CAddress(&u8g2.u8x8, 0x78);
// initialize the display
u8g2_InitDisplay(&u8g2);
// wake up the display
u8g2_SetPowerSave(&u8g2, 0);
Чтобы вывести текст на дисплей необходимо вызывать функцию u8g2_DrawStr. Перед этим очищаем буфер u8g2_ClearBuffer, устанавливаем шрифт u8g2_SetFont, а после отсылаем буфер u8g2_SendBuffer.
u8g2_ClearBuffer(&u8g2);
u8g2_SetFont(&u8g2, u8g2_font_ncenB08_tr);
u8g2_DrawStr(&u8g2, 0,15, "Hello world");
u8g2_SendBuffer(&u8g2);
где,
&u8g2 – указатель на структуру u8g2_t u8g2,
0,15 – координаты x, y в пикселях с верхнего левого края.

В исходном проекте для вычисления MFCC-коэффициентов в микроконтроллере используется CMSIS-библиотека. Автор уже скомпилировал этот проект, и предоставил ссылку на zip-архив. Этот архив затем подключается, как библиотека к Arduino IDE.
Для ESP-IDF проекта нужно подключить CMSIS-библиотеку как компонент. Для этого нужно склонировать CMSIS-DSP [20] репозиторий в каталог /components. А также нам нужны CMSIS Core заголовки, которые берем отсюда https://github.com/ARM-software/CMSIS_5/tree/develop/CMSIS/Core.
[21] После этого нужно составить make-файлы для компонентов CMSIS.
CMakefile.txt для CMSIS Core.
idf_component_register(
INCLUDE_DIRS
"Include" # Path to the CMSIS-Core include directory
)
CMakefile.txt CMSIS-DSP
# Define the path to the CMSIS-DSP library
set(CMSISDSP "${CMAKE_CURRENT_LIST_DIR}")
# Recursively gather all source files from the CMSIS-DSP Source directory
file(GLOB_RECURSE CMSIS_DSP_SRCS "${CMSISDSP}/Source/*.c")
# Exclude NEON-specific files if NEON is not supported
list(FILTER CMSIS_DSP_SRCS EXCLUDE REGEX "_arm_mat_mult_neon.c")
idf_component_register(
SRCS ${CMSIS_DSP_SRCS}
INCLUDE_DIRS
"${CMSISDSP}/Include"
"${CMSISDSP}/PrivateInclude"
"${CMAKE_CURRENT_LIST_DIR}/../CMSIS-Core/Include" # Add CMSIS-Core include path
"."
)
В CMSIS-DSP я исключил модуль armmat_mult_neon.c, т.к. он требовал дополнительных зависимостей, а в проекте он не используется.
Вся логика [22] обработки MFCC вынесена в отдельный модуль MFCC_Q15.h, MFCC_Q15.c. Подробное описание исходного кода MFCC можно посмотреть в книге TinyML-Cookbook_2E. Для нас важно то, что вызывается метод void MFCC_Q15::run(const q15_t* src, float* dst). В метод передаем указатель на буфер с аудиосемплами и указатель на входные данные тензора. Затем вычисляются MFCC-коэффициенты, которые передаются в тензор.
mfccs.run((const q15_t*)audio_buffer, tflu_i_tensor->data.f);
Далее производится инференс модели.
tflu_interpreter->Invoke();
Согласно руководству ESP-IDF v4.4 – Analog to Digital Converter [23] ESP32-C3 содержит два ADC с поддержкой 6 каналов измерения (аналоговых входов). В ESP-IDF v5.4 ADC API будет отличаться, но архитектура и конфигурация имеет тот же смысл.
Поддерживаемые каналы:
ADC1:
5 каналов: GPIO0 – GPIO4
ADC2:
1 канал: GPIO5
Аттенюация ADC
Vref — это опорное напряжение, используемое внутри ESP32-C3 для измерения входного сигнала. ADC способен измерять напряжения от 0 В до Vref. Среднее значение Vref составляет 1.1 В, но оно может варьироваться между чипами. Для измерения напряжений выше Vref применяется аттенюация (ослабление сигнала). Доступны четыре уровня аттенюации:
|
Аттенюация |
Диапазон измеряемого напряжения |
|---|---|
|
ADC_ATTEN_DB_0 |
0 мВ – 750 мВ |
|
ADC_ATTEN_DB_2_5 |
0 мВ – 1050 мВ |
|
ADC_ATTEN_DB_6 |
0 мВ – 1300 мВ |
|
ADC_ATTEN_DB_11 |
0 мВ – 2500 мВ |
Преобразование ADC
Преобразование аналогового сигнала в цифровой представляет собой получение необработанного значения. Разрешение ADC в режиме одиночного чтения — 12 бит. Основные функции:
adc1_get_raw()
adc2_get_raw()
adc_digi_read_bytes()
Для расчета напряжения на основе необработанных данных используется формула:
Vout = Dout * Vmax / Dmax
где:
Vout — измеренное напряжение
Dout — цифровое значение (raw)
Vmax — максимальное измеряемое напряжение (см. таблицу аттенюации)
Dmax — максимальное значение цифрового выхода, 4095 для 12 бит
Если на плате поддерживается калибровка ADC через eFuse, можно использовать функцию esp_adc_cal_raw_to_voltage(), которая возвращает откалиброванное значение напряжения (в мВ). В этом случае формула выше не требуется. Если калибровка используется на платах без eFuse, будут выводиться предупреждения.
Ограничения ADC
Модуль ADC2 также используется Wi-Fi, поэтому чтение через adc2_get_raw() может завершиться неудачей между вызовами esp_wifi_start() и esp_wifi_stop().
Один модуль ADC может работать только в одном режиме: либо непрерывном, либо одиночном.
ADC1 и ADC2 не могут одновременно работать в режиме одиночного чтения. Один будет заблокирован до завершения работы другого.
В непрерывном режиме частота дискретизации должна находиться в пределах SOC_ADC_SAMPLE_FREQ_THRES_LOW и SOC_ADC_SAMPLE_FREQ_THRES_HIGH.
Использование драйвера ADC
Доступны два режима работы:
одиночное чтение (single read) — подходит для низкочастотных измерений
непрерывное чтение (DMA) — для высокочастотной выборки
Если пин не подключён к источнику сигнала, результаты ADC будут случайными.
Режим непрерывного чтения (DMA)
Порядок использования:
Инициализация драйвера: adc_digi_initialize()
Настройка контроллера: adc_digi_controller_config()
Запуск чтения: adc_digi_start()
Чтение данных: adc_digi_read_bytes()
Остановка чтения: adc_digi_stop()
Деинициализация драйвера: adc_digi_deinitialize()
Пример кода находится в examples/peripherals/adc/dma_read в ESP-IDF.
Режим одиночного чтения
Перед чтением необходимо настроить параметры:
Для ADC1: adc1_config_width() и adc1_config_channel_atten()
Для ADC2: adc2_config_channel_atten(); ширина чтения задаётся при вызове adc2_get_raw()
Аттенюация настраивается для каждого канала отдельно. Функции чтения:
adc1_get_raw()
adc2_get_raw()
Пример — examples/peripherals/adc/single_read.
Минимизация шума
ADC чувствителен к шуму. Рекомендуется подключать байпасный керамический конденсатор (например, 100 нФ) к активному пину, а также использовать многократную выборку (multisampling) для уменьшения влияния шумов.
Калибровка ADC
Состоит из аппаратной и программной части.
Аппаратная калибровка
Производится автоматически драйвером и включает:
Настройку опорного напряжения (bandgap)
Коррекцию смещения в характеристике Vin–Dout (обычно: f(x) = A * x + B, где B — смещение)
Результаты — это всё ещё “сырые” данные, требующие дальнейшего преобразования по формуле или через программную калибровку.
Программная калибровка
Проверка поддержки калибровки через eFuse: esp_adc_cal_check_efuse()
Расчёт калибровочных характеристик: esp_adc_cal_characterize(). Они зависят от модуля и уровня аттенюации. Например, ADC1 канал 0 и канал 2 при 11 дБ будут иметь одинаковые характеристики, а ADC1 канал 0 при 6 дБ уже будет отличаться.
Получение напряжения: esp_adc_cal_raw_to_voltage()
Результаты — откалиброванные значения напряжения. Пример кода — в examples/peripherals/adc/single_read.
Для использования ADC необходимо подключить заголовки
#include "driver/adc.h"
#include "esp_adc_cal.h"
Далее необходимо инициализировать ADC.
esp_adc_cal_characteristics_t* adc_chars;
// Initialize ADC
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_11);
adc_chars = (esp_adc_cal_characteristics_t*) calloc(1, sizeof(esp_adc_cal_characteristics_t));
if (adc_chars == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory for ADC characteristics");
return;
}
esp_adc_cal_value_t val_type = esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, DEFAULT_VREF, adc_chars);
ADC_WIDTH_BIT_12 – используем 12-битный ADC;ADC_ATTEN_DB_11 – диапазон напряжения 0 мВ – 2500 мВ, т. к. опорное напряжение на микрофоне с подтянутым усиление = 1.25 В;ADC1_CHANNEL_0 – используем вывод GPIO0 на ESP32-C3.
Согласно документации с примерами имеет смысл сделать калибровку ADC с помощью вызова функции esp_adc_cal_characterize, перед этим не забываем [24] выделить память [25] для adc_chars.
В исходном примере 11_music_genre_classification.ino [26] после считывания ADC производится вычитание смещения согласно формуле
int BIAS_MIC = 1552; // (1.25V * 4095) / 3.3
На практике более оптимальным оказалось динамическое вычисление среднего смещения во время запуска ESP32. Т. е., считываем значение ADC, суммируем в каждом цикле, а затем вычисляем среднее за 1000 итераций.
int measure_bias_offset()
{
int sum = 0;
const int samples = 1000;
for (int i = 0; i < samples; i++) {
sum += adc1_get_raw(ADC1_CHANNEL_0);
ets_delay_us(1000);
}
return sum / samples;
}
Как и в исходном примере обработка аудиосемпла выполняется с помощью вызова коллбэка таймера. Можно попробовать выполнить обработку и в Freertos-задаче, но в моем случае определение жанра работало плохо, и я решил оставить таймер.
void timer_callback(void* arg) {
if (buffer_index < AUDIO_LENGTH_SAMPLES) {
int adc_value = adc1_get_raw(ADC1_CHANNEL_0); // Read ADC
//printf("Raw ADC Value: %dn", adc_value);
audio_buffer[buffer_index++] = adc_value - bias_offset; // Adjust for bias
} else {
buffer_ready = true;
}
}
const esp_timer_create_args_t timer_args = {
.callback = &timer_callback,
.name = "audio_timer"
};
esp_timer_handle_t timer;
esp_timer_create(&timer_args, &timer);
esp_timer_start_periodic(timer, 1000000 / SAMPLE_RATE); // Sampling rate
Настраиваем периодичность таймера на 1/SAMPLE_RATE сек, где SAMPLE_RATE=22050. С этим значением частоты выборки ранее производилось вычисление MFCC-коэффициентов, а затем обучение модели, но можно также выбрать и стандартное для CD – 44100, и заново выполнить Jupyter-файл.
В коллбэке считываем значение ADC, отнимаем смещение опорного напряжение и устанавливаем значение буфера.
Аудио-жанр определяется на основании выходных данных тензора.
static const char *label[] = {"blues", "classical", "country", "disco", "hiphop", "jazz", "metal", "pop", "reggae", "rock"};
size_t max_index = 0;
float max_value = 0;
for (size_t i = 0; i < 10; i++) {
if (tflu_o_tensor->data.f[i] > max_value) {
max_index = i;
max_value = tflu_o_tensor->data.f[i];
}
}
ESP_LOGI(TAG, "Predicted genre: %s", label[max_index]);
В итоге структура проекта для ESP32-C3
├── main
│ ├── main.cc # Main application logic
│ ├── u8g2_esp32_hal.h # HAL for U8G2 library
│ ├── u8g2_esp32_hal.c # Implementation of U8G2 HAL
│ ├── MFCC_Q15.cc # MFCC computation logic
│ ├── model.h # TensorFlow Lite model header
│ ├── dct_wei_mtx_q15_T.h # DCT weight matrix for MFCC computation
│ ├── hann_lut_q15.h # Hanning window lookup table
│ ├── log_lut_q13_3.h # Logarithm lookup table
│ ├── mel_wei_mtx_q15_T.h # Mel filter bank weights
│ ├── mfccs_consts.h # Constants for MFCC computation
├── components
│ ├── esp-tflite-micro # TensorFlow Lite Micro component
│ ├── esp-mfcc # MFCC computation library
│ ├── u8g2 # U8G2 library for OLED display
├── CMakeLists.txt # Build configuration
├── README.md # Project documentation
Исходный код проекта выложен в репозитории genre-classification-esp32 [27].
Определение reggae-жанра
Определение metall-жанра
Этот проект служит практическим примером развертывания моделей машинного обучения на встраиваемых системах с ограниченными ресурсами, открывая возможности для приложений в обработке звука и распознавании речи. Удалось успешно портировать исходный код 11_music_genre_classification.ino [26] для ESP-IDF проекта ESP32-C3. Для реализации логики системы были подключены библиотеки цифровой обработки CMSIS, а также библиотека для работы с OLED-дисплеем u8g2.
Модель обработки аудиофайлов расширена для определения 10 музыкальных жанров.
Из недостатков данного проекта следует отметить то, что логика на ESP32 не всегда хорошо справляется с определением жанров. Если сделать тестовый проект в Jupyter, а в качестве входных данных предоставлять аудиофайл из тренировочного датасета, то в большинстве случаев модель справляется с определением жанра. Но если предоставить рандомный музыкальный файл, то результат может быть не предсказуемый. Опытным путем определено, что модель хорошо определяет жанры с выраженными музыкальными композициями, например где присутствуют гитарные солло-партии в металл-группах. Практическая ценность конкретного проекта скорее всего не велика. Проект хорошо подходит в качестве знакомства с технологиями обработки и распознаванием звука.
Автор: vladipirogov
Источник [33]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/14889
URLs in this post:
[1] опытом: http://www.braintools.ru/article/6952
[2] TinyML-Cookbook_2E: https://github.com/PacktPublishing/TinyML-Cookbook_2E
[3] возбуждение: http://www.braintools.ru/article/9158
[4] восприятие: http://www.braintools.ru/article/7534
[5] слуха: http://www.braintools.ru/article/6251
[6] обучении: http://www.braintools.ru/article/5125
[7] prepare_model.ipynb: https://github.com/PacktPublishing/TinyML-Cookbook_2E/blob/main/Chapter05_06/ColabNotebooks/prepare_model.ipynb
[8] GTZAN Dataset: https://www.kaggle.com/datasets/andradaolteanu/gtzan-dataset-music-genre-classification
[9] зависимости: https://components.espressif.com/components/espressif/esp-tflite-micro/versions/1.3.1?language=en
[10] https://netron.app: https://netron.app
[11] Machine learning на ESP32: https://habr.com/ru/articles/891314/
[12] особенности: https://docs.nordicsemi.com/bundle/ncs-latest/page/zephyr/boards/01space/esp32c3_042_oled/doc/index.html#hardware
[13] MAX9814: https://www.analog.com/en/products/max9814.html
[14] микрофон: https://cdn-shop.adafruit.com/datasheets/CMA-4544PF-W.pdf
[15] u8g2: https://github.com/olikraus/u8g2/wiki
[16] здесь: https://github.com/01Space/ESP32-C3-0.42LCD/blob/main/ESP32-C3-GraphicsTest/ESP32-C3-GraphicsTest.ino
[17] u8g2: https://github.com/olikraus/u8g2
[18] nkolban/esp32-snippets: https://github.com/nkolban/esp32-snippets/blob/master/hardware/displays/U8G2/u8g2_esp32_hal.c
[19] test_SSD1306_i2c.c: https://github.com/nkolban/esp32-snippets/blob/master/hardware/displays/U8G2/test_SSD1306_i2c.c
[20] CMSIS-DSP: https://github.com/ARM-software/CMSIS-DSP
[21] https://github.com/ARM-software/CMSIS_5/tree/develop/CMSIS/Core.
: https://github.com/ARM-software/CMSIS_5/tree/develop/CMSIS/Core.%EF%BF%BC
[22] логика: http://www.braintools.ru/article/7640
[23] Analog to Digital Converter: https://docs.espressif.com/projects/esp-idf/en/v4.4/esp32c3/api-reference/peripherals/adc.html
[24] забываем: http://www.braintools.ru/article/333
[25] память: http://www.braintools.ru/article/4140
[26] 11_music_genre_classification.ino: https://github.com/PacktPublishing/TinyML-Cookbook_2E/blob/main/Chapter05_06/ArduinoSketches/11_music_genre_classification.ino
[27] genre-classification-esp32: https://github.com/vladipirogov/genre-classification-esp32
[28] TinyML Cookbook – Second Edition: https://www.oreilly.com/library/view/tinyml-cookbook/9781837637362/
[29] The cepstrum, mel-cepstrum and mel-frequency cepstral coefficients: https://speechprocessingbook.aalto.fi/Representations/Melcepstrum.html
[30] Mel Frequency Cepstral Coefficient (MFCC) tutorial: http://practicalcryptography.com/miscellaneous/machine-learning/guide-mel-frequency-cepstral-coefficients-mfccs/
[31] ESP32C3 0.42 OLED: https://github.com/01Space/ESP32-C3-0.42LCD
[32] Audio Signal Processing for Machine Learning: https://www.youtube.com/playlist?list=PL-wATfeyAMNqIee7cH3q1bh4QJFAaeNv0
[33] Источник: https://habr.com/ru/articles/906658/?utm_campaign=906658&utm_source=habrahabr&utm_medium=rss
Нажмите здесь для печати.