Dal 30 novembre al 1° dicembre a Nizhny Novgorod si è svolto. I partecipanti sono stati invitati a creare un prototipo di soluzione di prodotto utilizzando l'Intel OpenVINO toolkit. Gli organizzatori hanno fornito un elenco di temi indicativi che si potevano seguire per scegliere un compito, ma la decisione finale rimaneva alle squadre. Inoltre, era incoraggiato l'utilizzo di modelli non inclusi nel prodotto.

Nell'articolo parleremo di come abbiamo creato il nostro prototipo di prodotto, con il quale abbiamo alla fine vinto il primo posto.
All'hackathon hanno partecipato più di 10 squadre. È stato bello vedere che alcune di esse sono arrivate da altre regioni. La sede dell'hackathon è stata scelta nel complesso "Kremlevsky na Pochayne", dove all'interno erano appese foto storiche di Nizhny Novgorod, un'atmosfera unica! (ricordo che attualmente la sede centrale della Intel si trova proprio a Nizhny Novgorod). Ai partecipanti erano stati dati 26 ore per scrivere il codice, e alla fine dovevano presentare la loro soluzione. Un ulteriore vantaggio era la presenza di una demo session per assicurarsi che tutto ciò che era stato pianificato fosse veramente realizzato e non rimanesse solo un'idea nella presentazione. C'era anche merchandising, snack e cibo!
Inoltre, su richiesta, l'Intel forniva telecamere, Raspberry PI, Neural Compute Stick 2.
Scelta del compito
Uno degli aspetti più complessi della preparazione a un hackathon con tema libero è la scelta del compito. Si è deciso subito di pensare a qualcosa che non fosse già presente nel prodotto, poiché nell'annuncio era stato detto che questo era molto gradito.
Analizzando , che sono inclusi nel prodotto nella versione attuale, si giunge alla conclusione che la maggior parte di essi risolve vari compiti di visione artificiale. Ed è davvero difficile pensare a un compito nell'ambito della visione artificiale che non possa essere risolto utilizzando OpenVINO, e sebbene si possa pensare a qualcosa del genere, è difficile trovare modelli pre-addestrati di pubblico dominio. Decidiamo di esplorare anche un'altra direzione: quella dell'elaborazione e analisi del parlato. Consideriamo un compito interessante per il riconoscimento delle emozioni attraverso la voce. Bisogna dire che in OpenVINO c'è già un modello che determina le emozioni umane dal viso, ma:
- In teoria, si potrebbe creare un algoritmo combinato, che funzioni sia tramite suono che tramite immagine, il che dovrebbe aumentare la precisione.
- Le telecamere di solito hanno un angolo di visione stretto; per coprire un'ampia area, servono più telecamere, il suono non ha questa limitazione.
Sviluppiamo l'idea: prendiamo come base un concetto per il segmento retail. Possiamo determinare la soddisfazione del cliente alle casse dei negozi. Se un cliente è insoddisfatto del servizio e inizia ad alzare la voce, possiamo immediatamente chiamare un amministratore per aiuto.
In questo caso, è necessario aggiungere il riconoscimento vocale, il che ci permetterà di distinguere i dipendenti del negozio dai clienti, fornendo analisi su ogni singolo individuo. Inoltre, sarà possibile analizzare il comportamento stesso dei dipendenti del negozio e valutare l'atmosfera nel team, non suona male!
Formuliamo i requisiti per la nostra soluzione:
- Dimensioni compatte del dispositivo target
- Funzionamento in tempo reale
- Prezzo contenuto
- Facile scalabilità
In definitiva, come dispositivo target scegliamo Raspberry Pi 3 con .
È importante sottolineare una caratteristica importante dell'NCS: funziona meglio con architetture CNN standard; se è necessario eseguire un modello con strati personalizzati, aspettatevi qualche problema con l'ottimizzazione a basso livello.
Ora abbiamo solo bisogno di procurarsi un microfono. Un normale microfono USB andrà bene, anche se non si abbina molto bene con il RPI. Ma anche qui la soluzione è a portata di mano. Per la registrazione della voce, decidiamo di usare la scheda Voice Bonnet dal kit , su cui è saldati un microfono stereo.
Scarichiamo Raspbian dal e lo carichiamo su una chiavetta USB, testiamo che il microfono funzioni con il seguente comando (registrerà audio della durata di 5 secondi e lo salverà in un file):
arecord -d 5 -r 16000 test.wavVoglio sottolineare che il microfono è molto sensibile e cattura bene i rumori. Per correggerlo, andiamo su alsamixer, selezioniamo i dispositivi di cattura e riduciamo il livello del segnale in ingresso al 50-60%.

Rifiniamo il case con una lima e tutto entra, possiamo persino chiudere con il coperchio.
Aggiungiamo un pulsante indicatore.
Durante lo smontaggio del Google AIY Voice Kit, ricordiamo che esiste un pulsante RGB cui possiamo controllare la retroilluminazione tramite software. Cerchiamo “Google AIY Led” e troviamo la documentazione:
Perché non usare questo pulsante per visualizzare l'emozione riconosciuta? Abbiamo solo 7 classi, e il pulsante ha 8 colori, il che è perfetto!
Colleghiamo il pulsante tramite GPIO al Voice Bonnet e carichiamo le librerie necessarie (già installate nella distribuzione di AIY projects)
from aiy.leds import Leds, Color
from aiy.leds import RgbLedsCreiamo un dizionario in cui a ogni emozione corrisponderà un colore in formato RGB Tuple e un oggetto della classe aiy.leds.Leds, attraverso il quale aggiorneremo il colore:
led_dict = {'neutro': (255, 255, 255), 'felice': (0, 255, 0), 'triste': (0, 255, 255), 'arrabbiato': (255, 0, 0), 'pauroso': (0, 0, 0), 'disgustoso': (255, 0, 255), 'sorpreso': (255, 255, 0)}
leds = Leds()
E, infine, dopo ogni nuova previsione dell'emozione, aggiorneremo il colore del pulsante in base a essa (secondo la chiave).
leds.update(Leds.rgb_on(led_dict.get(classes[prediction])))
Pulsante, accendi!
Lavoriamo con la voce
Useremo pyaudio per catturare il flusso dal microfono e webrtcvad per filtrare il rumore e rilevare la voce. Inoltre, creeremo una coda in cui aggiungeremo e rimuoveremo asyncronamente segmenti di voce.
Poiché webrtcvad ha un limite sulle dimensioni del frammento in ingresso — deve essere pari a 10/20/30 ms, e l'allenamento del modello per il riconoscimento delle emozioni (come scopriremo in seguito) è stato effettuato su un dataset a 48 kHz, cattureremo chunk di dimensione 48000×20 ms/1000×1(mono)=960 byte. Webrtcvad restituirà True/False per ciascuno di questi chunk, corrispondento alla presenza o assenza di voce nel chunk.
Implementiamo la seguente logica:
- Aggiungeremo alla lista quei chunk in cui è presente la voce; se non c'è voce, incrementiamo il contatore dei chunk vuoti.
- Se il contatore dei chunk vuoti >=30 (600 ms), controlliamo la dimensione della lista dei chunk accumulati; se è >250, aggiungiamo alla coda, altrimenti consideriamo che la lunghezza della registrazione non è sufficiente per essere presentata al modello per identificare il parlante.
- Se il contatore dei chunk vuoti è ancora < 30 e la dimensione della lista dei chunk accumulati supera 300, aggiungeremo il frammento alla coda per una previsione più accurata. (poiché nel tempo le emozioni tendono a cambiare)
def to_queue(frames):
d = np.frombuffer(b''.join(frames), dtype=np.int16)
return d
framesQueue = queue.Queue()
def framesThreadBody():
CHUNK = 960
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 48000
p = pyaudio.PyAudio()
vad = webrtcvad.Vad()
vad.set_mode(2)
stream = p.open(format=FORMAT,
channels=CHANNELS,
rate=RATE,
input=True,
frames_per_buffer=CHUNK)
false_counter = 0
audio_frame = []
while process:
data = stream.read(CHUNK)
if not vad.is_speech(data, RATE):
false_counter += 1
if false_counter >= 30:
if len(audio_frame) > 250:
framesQueue.put(to_queue(audio_frame,timestamp_start))
audio_frame = []
false_counter = 0
if vad.is_speech(data, RATE):
false_counter = 0
audio_frame.append(data)
if len(audio_frame) > 300:
framesQueue.put(to_queue(audio_frame,timestamp_start))
audio_frame = []È tempo di cercare modelli pre-addestrati disponibili pubblicamente, andiamo su github, cerchiamo su Google, ma ricordiamo che abbiamo delle limitazioni sull'architettura utilizzabile. Questo è un passo piuttosto complesso, perché dobbiamo testare i modelli con i nostri dati d'ingresso e, inoltre, convertire nel formato interno di OpenVINO — IR (Rappresentazione Intermedia). Abbiamo provato circa 5-7 soluzioni diverse da github e, sebbene il modello per il riconoscimento delle emozioni abbia funzionato subito, con il riconoscimento vocale ci è voluto più tempo — lì vengono utilizzate architetture più complesse.
Ci fermiamo sui seguenti:
- Emozioni dalla voce —
Funziona secondo il seguente principio: l'audio viene suddiviso in frammenti di dimensioni specifiche, per ciascuno di questi frammenti estraiamo e poi li forniamo in input a una CNN - Riconoscimento vocale —
Qui, invece di MFCC, lavoriamo con uno spettrogramma; dopo FFT, forniamo il segnale a una CNN, dove in output otteniamo una rappresentazione vettoriale della voce.
Ora parleremo della conversione dei modelli, partendo dalla teoria. OpenVINO include diversi moduli:
- Open Model Zoo, i cui modelli possono essere utilizzati e integrati nel proprio prodotto
- Model Optimizer, grazie al quale è possibile riconvertire il modello da diversi formati di framework (Tensorflow, ONNX, ecc.) nel formato di Rappresentazione Intermedia, con cui lavoreremo in seguito
- Inference Engine consente di eseguire modelli in formato IR su processori Intel, chip Myriad e acceleratori Neural Compute Stick
- La versione più efficace di OpenCV (con supporto per Inference Engine)
Ogni modello in formato IR è descritto da due file: .xml e .bin.
I modelli vengono convertiti nel formato IR tramite il Model Optimizer nel seguente modo:python /opt/intel/openvino/deployment_tools/model_optimizer/mo_tf.py --input_model speaker.hdf5.pb --data_type=FP16 --input_shape [1,512,1000,1]--data_typepermette di scegliere il formato dei dati con cui lavorerà il modello. Sono supportati FP32, FP16, INT8. La scelta del tipo di dato ottimale può portare a un buon aumento delle prestazioni.
--input_shapeindica la dimensione dei dati in ingresso. Sembra che sia possibile cambiarla dinamicamente nell'API C++, ma non ci siamo addentrati così a fondo e per uno dei modelli l'abbiamo semplicemente fissata.
Successivamente proveremo a caricare il modello già convertito in formato IR tramite il modulo DNN in OpenCV e faremo un forward su di esso.import cv2 as cv emotionsNet = cv.dnn.readNet('emotions_model.bin', 'emotions_model.xml') emotionsNet.setPreferableTarget(cv.dnn.DNN_TARGET_MYRIAD)L'ultima riga in questo caso permette di reindirizzare i calcoli su Neural Compute Stick; di base, i calcoli vengono eseguiti sulla CPU, ma nel caso del Raspberry Pi non va bene, ci vorrà il stick.
Dopo, la logica è la seguente: divideremo il nostro audio in finestre di dimensioni specifiche (in questo caso 0.4s), ognuna di queste finestre verrà trasformata in MFCC, che poi passeremo alla rete:
emotionsNet.setInput(MFCC_from_window) result = emotionsNet.forward()Poi prenderemo la classe più frequentemente riscontrata per tutte le finestre. Una soluzione semplice, ma per un hackathon non è necessario inventare qualcosa di troppo complesso, a meno che non ci sia tempo. Abbiamo ancora molto lavoro da fare, quindi andiamo avanti e ci occupiamo del riconoscimento vocale. Dobbiamo fare una sorta di database per memorizzare le spettrogrammi delle voci registrate in precedenza. Poiché il tempo è poco, affrontiamo la questione come possiamo.
Cioè, creiamo uno script per registrare un frammento di voce (funziona come descritto sopra, solo che interrompendo la registrazione con la tastiera salverà la voce in un file).
Proviamo.
python3 voice_db/record_voice.py test.wavRegistriamo le voci di più persone (nel nostro caso tre membri del team)
Poi, per ogni voce registrata, eseguiamo la trasformata di Fourier veloce, otteniamo lo spettrogramma e lo salviamo come array numpy (.npy):for file in glob.glob("voice_db/*.wav"): spec = get_fft_spectrum(file) np.save(file[:-4] + '.npy', spec)Ulteriori dettagli nel file
create_base.py
Alla fine, quando eseguiamo lo script principale, otterremo all'inizio gli embedding da questi spettrogrammi:for file in glob.glob("voice_db/*.npy"): spec = np.load(file) spec = spec.astype('float32') spec_reshaped = spec.reshape(1, 1, spec.shape[0], spec.shape[1]) srNet.setInput(spec_reshaped) pred = srNet.forward() emb = np.squeeze(pred)Dopo aver ottenuto l'embedding dal segmento audio, possiamo determinare a chi appartiene calcolando la distanza coseno del segmento rispetto a tutte le voci nel database (più è bassa, più probabile è) — per la demo abbiamo impostato una soglia di 0.3):
dist_list = cdist(emb, enroll_embs, metric="cosine") distances = pd.DataFrame(dist_list, columns = df.speaker)Alla fine, voglio sottolineare che la velocità di inferenza è stata rapida e ha permesso di aggiungere altre 1-2 modelli (ci volevano 2.5 secondi per un campione lungo 7 secondi). Non siamo riusciti ad aggiungere nuovi modelli e ci siamo concentrati sulla scrittura del prototipo dell'app web.
Applicazione web
Un punto importante: portiamo con noi un router da casa e configuriamo il nostro locale, il che aiuta a connettere il dispositivo e i laptop in rete.
Il backend è costituito da un canale di messaggi tra il frontend e il Raspberry Pi, basato sulla tecnologia websocket (http su protocollo tcp).
La prima fase consiste nell'ottenere le informazioni elaborate dal Raspberry, ovvero le previsioni impacchettate in json, che a metà strada vengono salvate nel database, in modo da poter formare statistiche sullo stato d'animo dell'utente nel tempo. Successivamente, questo pacchetto viene inviato al frontend, che utilizza la sottoscrizione e la ricezione di pacchetti dall'endpoint websocket. L'intero meccanismo del backend è costruito in linguaggio golang, scelto perché si adatta bene ai compiti asincroni, nei quali le goroutine si rivelano efficaci.
Accedendo all'endpoint, l'utente si registra e viene inserito nella struttura, quindi riceve il suo messaggio. Sia l'utente che il messaggio vengono inseriti in un hub comune, da cui i messaggi vengono inviati ulteriormente (al frontend sottoscritto), e se l'utente chiude la connessione (il Raspberry o il frontend), la sua sottoscrizione viene annullata e viene rimosso dall'hub.
Aspettiamo la connessione con il backendIl front-end è un'applicazione web scritta in JavaScript utilizzando la libreria React per accelerare e semplificare il processo di sviluppo. L'obiettivo di questa applicazione è la visualizzazione dei dati ottenuti tramite algoritmi eseguiti sul lato back-end e direttamente su Raspberry Pi. La pagina include un routing per le sezioni, implementato tramite react-router, ma l'attenzione principale è rivolta alla homepage, dove un flusso continuo di dati dal server arriva in tempo reale tramite la tecnologia WebSocket. Raspberry Pi rileva la voce, determina l'appartenenza a una persona specifica nel database registrato e invia al cliente un elenco di probability. Il cliente visualizza i dati più recenti, mostra l'avatar della persona che ha più probabilità di aver parlato al microfono e l'emozione con cui pronuncia le parole.

Homepage con previsioni aggiornateConclusione
Non siamo riusciti a completare tutto come previsto, semplicemente non avevamo tempo, quindi la nostra principale speranza era la demo, che tutto funzionasse. Durante la presentazione, abbiamo parlato di come era tutto strutturato, quali modelli abbiamo utilizzato e quali problemi abbiamo affrontato. Successivamente c'è stata la parte demo: gli esperti giravano per la sala in modo casuale e si avvicinavano a ciascun team per vedere il prototipo funzionante. Facevano domande anche a noi, ognuno rispondeva per la propria parte, abbiamo lasciato il web sul laptop e tutto funzionava davvero come previsto.
Vorrei sottolineare che il costo totale della nostra soluzione è stato di 150$:
- Raspberry Pi 3 ~ 35$
- Google AIY Voice Bonnet (puoi usare la scheda respeaker) ~ 15$
- Intel NCS 2 ~ 100$
Come migliorare:
- Utilizzare la registrazione dal cliente - chiedere di leggere un testo che generiamo casualmente
- Aggiungere alcuni modelli in più: dalla voce è possibile determinare il genere e l'età
- Separare le voci che suonano contemporaneamente (diarizzazione)
Repository:

Stanchi, ma felici noiIn conclusione, vogliamo ringraziare gli organizzatori e i partecipanti. Tra i progetti delle altre squadre, ci è piaciuta molto la soluzione per monitorare i posti auto disponibili. Per noi è stata un'esperienza incredibilmente stimolante immergersi nel prodotto e nello sviluppo. Spero che nelle regioni ci siano sempre più eventi interessanti, compresi quelli a tema AI.
Fonte: habr.com



