Parte 2 · Prática

Reconhecimento de
Palavras-Chave (KWS)

Capture áudio com o microfone PDM do Nano 33 BLE Sense, extraia coeficientes MFCC e classifique palavras faladas em tempo real com uma CNN — tudo na borda.

🎙️ Microfone PDM MP34DT05 🎼 MFCC · Espectrograma Mel 🧠 CNN 1D · TFLite int8 🐍 Python + Keras + librosa 🔵 Arduino Nano 33 BLE Sense
Começar tutorial ↓ Documentação do microfone ↗

O que é KWS (Keyword Spotting)?

Keyword Spotting é a tarefa de detectar palavras específicas em um fluxo contínuo de áudio. É a tecnologia por trás dos assistentes de voz ("Hey Siri", "OK Google"), e uma das aplicações mais estudadas em TinyML porque o modelo precisa rodar 24/7 consumindo pouquíssima energia.

Nesta prática classificaremos 4 palavras em português: "sim", "não", "liga" e "desliga", além de uma classe "silêncio" para rejeitar áudio sem comando.

← Parte 1 — Gestos (IMU)

  • Sensor: acelerômetro + giroscópio
  • Input: série temporal 119×6 floats
  • Pré-proc: normalização Z-score
  • Modelo: rede densa (MLP)
  • Dado bruto → rede diretamente

Parte 2 — Voz (Microfone) →

  • Sensor: microfone PDM 16 kHz
  • Input: imagem MFCC 49×40 int8
  • Pré-proc: FFT → Mel filterbank → MFCC
  • Modelo: CNN 1D
  • Áudio → espectrograma → rede
🎙️
Áudio PDM 16 kHz · 1s
📊
FFT Janelas 25ms
🎼
Mel MFCC 40 coef · 49 frames
🧠
CNN int8 TFLite Micro
💬
Palavra + confiança
1

Instalar dependências

Biblioteca PDM no Arduino IDE e librosa + sounddevice no Python.

2

Coletar amostras de áudio

Gravar clipes WAV de 1s para cada palavra com o Arduino ou microfone do PC.

3

Extrair MFCCs

Converter WAV em imagens de espectrograma Mel com librosa.

4

Treinar CNN

Treinar uma CNN 1D com Keras usando as imagens MFCC como entrada.

5

Converter para TFLite int8

Quantizar e exportar o modelo como array C para o Arduino.

6

Flash e inferência

Compilar o sketch de inferência e classificar palavras em tempo real.

Por que MFCC?

O microfone captura uma onda de pressão no tempo. Redes neurais aprendem melhor com representações que destacam as características perceptuais do som — os Mel-Frequency Cepstral Coefficients (MFCCs) fazem exatamente isso.

🎙️
Áudio bruto PCM 16 kHz
🪟
Janelamento Hamming 25ms
overlap 10ms
📈
FFT 512 pontos
→ espectro
🎼
Mel filterbank 40 filtros
escala perceptual
📊
Log + DCT 40 coeficientes
por frame
🖼️
Imagem MFCC 49 frames × 40
= input da CNN
💡 Escala Mel

O ouvido humano percebe diferenças de frequência de forma logarítmica — distingue melhor entre 100 Hz e 200 Hz do que entre 8000 Hz e 8100 Hz. A escala Mel imita esse comportamento, tornando os coeficientes muito mais discriminativos para sons da voz do que uma FFT linear.

Estrutura de arquivos

tinyml/
  ├── arduino/
  │   ├── coleta_audio/
  │   │   └── coleta_audio.ino  ← captura PCM → Serial
  │   └── inferencia_kws/
  │       ├── inferencia_kws.ino  ← sketch principal
  │       ├── kws_model_data.h  ← gerado pelo notebook
  │       └── kws_params.h  ← labels, thresholds
  ├── dados_audio/
  │   ├── sim/  ← .wav de 1s
  │   ├── nao/
  │   ├── liga/
  │   ├── desliga/
  │   └── silencio/
  ├── notebooks/
  │   ├── 03_treinamento_kws.ipynb
  │   └── 04_conversao_kws_tflite.ipynb
  └── outputs/

Bibliotecas necessárias

Arduino (Library Manager)

PDM built-in

Biblioteca nativa do Arduino Mbed OS para captura de áudio PDM do microfone MP34DT05. Já inclusa no pacote de placas "Arduino Mbed OS Nano Boards".

Harvard_TinyMLx v1.2.2+

Mesma biblioteca da Parte 1 — TFLite Micro empacotado para Arduino. Buscar por "TinyMLibrary" na Library Manager.

Python (venv)

# Ativar o ambiente virtual
source env/bin/activate

# Instalar dependências novas (além das da Parte 1)
pip install librosa sounddevice soundfile audiomentations
PacoteFinalidadeVersão testada
librosaExtração de MFCC e pré-processamento de áudio0.10+
sounddeviceGravação de áudio pelo microfone do PC0.4+
soundfileLeitura e escrita de arquivos WAV0.12+
audiomentationsData augmentation de áudio (ruído, pitch shift…)0.36+
tensorflowTreinamento CNN + conversão TFLite2.x / 2.16
numpyManipulação de arrays1.26+

Passo a passo completo

Siga as etapas abaixo na ordem indicada.

1

Instalar a biblioteca PDM e verificar o microfone

A biblioteca PDM já vem com o pacote de placas do Nano 33 BLE Sense. Para verificar se o microfone funciona, carregue o exemplo de teste:

// No Arduino IDE:
// File → Examples → PDM → PDMSerialPlotter

Abra o Serial Plotter (115200 baud) e fale próximo ao Arduino — você deve ver a forma de onda do áudio em tempo real.

💡 Localização do microfone

O MP34DT05 fica na face inferior da placa (lado oposto ao USB), próximo ao canto. Mantenha essa face voltada para a fonte sonora durante a coleta.

2

Coletar amostras de áudio

Há duas abordagens para coletar as amostras WAV de 1 segundo:

Opção A — Microfone do PC (mais simples)

Grave diretamente pelo Python com sounddevice:

import sounddevice as sd
import soundfile as sf
import os, time

palavras = ['sim', 'nao', 'liga', 'desliga', 'silencio']
AMOSTRAS = 80   # por classe
SR       = 16000  # Hz

for palavra in palavras:
    os.makedirs(f'dados_audio/{palavra}', exist_ok=True)
    for i in range(AMOSTRAS):
        input(f"\nPressione Enter e diga '{palavra}' ({i+1}/{AMOSTRAS})...")
        audio = sd.rec(SR, samplerate=SR, channels=1, dtype='int16')
        sd.wait()
        sf.write(f'dados_audio/{palavra}/{palavra}_{i:03d}.wav', audio, SR)
        print('Gravado!')

Opção B — Microfone do Arduino

Carregue o sketch coleta_audio.ino, que captura 16.000 amostras (1 segundo a 16 kHz) e transmite via Serial em formato binário. Um script Python lê a porta serial e salva cada gravação como WAV.

ℹ️ Quantidade recomendada

Colete no mínimo 80 amostras por classe. Varie a distância, intensidade de voz e ambiente (com e sem ruído de fundo) para aumentar a robustez do modelo.

3

Extrair features MFCC com librosa

Antes de treinar, cada arquivo WAV é convertido em uma "imagem" de coeficientes MFCC. Execute o notebook de treinamento que inclui a extração:

import librosa
import numpy as np

def extrair_mfcc(caminho_wav, n_mfcc=40, max_frames=49):
    """Retorna array (max_frames, n_mfcc) normalizado."""
    y, sr = librosa.load(caminho_wav, sr=16000, duration=1.0)
    mfcc  = librosa.feature.mfcc(y=y, sr=sr,
                                   n_mfcc=n_mfcc,
                                   n_fft=512,
                                   hop_length=160,   # 10 ms
                                   win_length=400)  # 25 ms
    # Padeia ou trunca para max_frames
    if mfcc.shape[1] < max_frames:
        mfcc = np.pad(mfcc, ((0,0),(0, max_frames - mfcc.shape[1])))
    else:
        mfcc = mfcc[:, :max_frames]
    # Normaliza por instância (zero mean, unit variance)
    mfcc = (mfcc - mfcc.mean()) / (mfcc.std() + 1e-8)
    return mfcc.T  # shape: (49, 40)
🎼 Parâmetros do MFCC

Janela de 25 ms (400 amostras a 16 kHz) com salto de 10 ms (160 amostras) gera ≈ 99 frames por segundo. Usando 1s de áudio e truncando em 49 frames obtemos input de forma 49 × 40 para a CNN.

4

Treinar a CNN com Keras

Execute o notebook de treinamento:

jupyter notebook notebooks/03_treinamento_kws.ipynb

Arquitetura da CNN usada (leve o suficiente para MCU):

from tensorflow import keras

model = keras.Sequential([
    keras.layers.Input(shape=(49, 40)),                  # frames × coef

    keras.layers.Conv1D(32, kernel_size=3, activation='relu'),
    keras.layers.BatchNormalization(),
    keras.layers.MaxPooling1D(pool_size=2),

    keras.layers.Conv1D(64, kernel_size=3, activation='relu'),
    keras.layers.BatchNormalization(),
    keras.layers.GlobalAveragePooling1D(),

    keras.layers.Dense(64, activation='relu'),
    keras.layers.Dropout(0.3),
    keras.layers.Dense(5, activation='softmax')  # 5 classes
])

model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])
💡 Data Augmentation

Use audiomentations para adicionar ruído de fundo, mudança de pitch e variação de velocidade antes de extrair os MFCCs. Isso aumenta muito a robustez em ambientes reais.

5

Converter para TFLite int8 e gerar os .h

Execute o notebook de conversão:

jupyter notebook notebooks/04_conversao_kws_tflite.ipynb

O processo é análogo à Parte 1, mas o dataset representativo usa amostras MFCC:

import tensorflow as tf

def representative_dataset():
    for x in X_val[:200]:
        yield [x[np.newaxis].astype(np.float32)]  # (1, 49, 40)

converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations            = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset   = representative_dataset
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter.inference_input_type     = tf.int8
converter.inference_output_type    = tf.int8

tflite_model = converter.convert()

# Salvar e exportar como array C
with open('outputs/kws_int8.tflite', 'wb') as f:
    f.write(tflite_model)

Execute xxd -i outputs/kws_int8.tflite > arduino/inferencia_kws/kws_model_data.h para gerar o array C.

6

Implementar o sketch de inferência no Arduino

O sketch inferencia_kws.ino executa o seguinte loop continuamente:

  • Captura 16.000 amostras PCM via biblioteca PDM (1 segundo)
  • Calcula os coeficientes MFCC on-device (FFT → Mel → DCT)
  • Quantiza o input para int8 usando scale e zero_point
  • Invoca o MicroInterpreter
  • Dequantiza a saída e imprime a classe com maior confiança
// Trecho principal — inferência_kws.ino
if (novo_audio_disponivel) {
  calcular_mfcc(buffer_audio, mfcc_input);  // 49×40 → input tensor

  for (int i = 0; i < 49 * 40; i++) {
    int32_t v = (int32_t)(mfcc_input[i] / input_scale) + input_zp;
    input->data.int8[i] = (int8_t)constrain(v, -128, 127);
  }

  interpreter->Invoke();

  int melhor = argmax_int8(output, kNumClasses);
  float confianca = (output->data.int8[melhor] - output_zp) * output_scale;

  if (confianca > 0.75f) {
    Serial.print("→ "); Serial.println(kLabels[melhor]);
  }
}
⚠️ Calcular MFCC no MCU

A FFT e os filtros Mel precisam ser reimplementados em C++ sem uso de float complexo. Utilize a biblioteca CMSIS-DSP (já inclusa no core do Nano 33 BLE Sense) ou a implementação de referência do TFLite Micro (micro_features/ na Harvard_TinyMLx).

7

Testar a classificação em tempo real

Abra o Serial Monitor a 115200 baud, fale cada palavra a ~20 cm do microfone e observe a saída:

=====================================
  TinyML Parte 2 — KWS em Português
  Arduino Nano 33 BLE Sense
=====================================
  Modelo:   18432 bytes
  Arena:    40 KB
  Classes:  sim | nao | liga | desliga | silencio
=====================================
Aguardando comando de voz...

→ sim      (91.2%)  |  inferência: 22.1 ms
→ desliga  (87.5%)  |  inferência: 21.8 ms
→ silencio (98.0%)  |  inferência: 21.3 ms

LED RGB: verde = sim, vermelho = não, azul = liga, amarelo = desliga, apagado = silêncio.

Erros comuns

ProblemaCausa provávelSolução
PDM não inicializa Pacote de placa desatualizado Atualizar "Arduino Mbed OS Nano Boards" para v4.1+
Áudio com muito ruído Taxa de amostragem errada Fixar em 16.000 Hz — o MP34DT05 opera melhor nessa frequência
AllocateTensors() failed Arena insuficiente para a CNN Aumentar kTensorArenaSize para 48 * 1024
Sempre classifica "silêncio" MFCC calculado diferente no MCU vs Python Verificar parâmetros: n_fft=512, hop=160, win=400, n_mfcc=40
Baixa acurácia em produção Pouco áudio de treino ou sem augmentation Aumentar para 150+ amostras/classe e aplicar ruído de fundo
librosa não encontrado Ambiente virtual não ativado Executar source env/bin/activate antes do Jupyter