Hackathon OpenVINO: reconèixer la veu i les emocions a Raspberry Pi

Del 30 de novembre a l'1 de desembre es va celebrar a Nizhny Novgorod Hackathon d'OpenVINO. Es va demanar als participants que creessin un prototip de solució de producte mitjançant el conjunt d'eines Intel OpenVINO. Els organitzadors van proposar una llista de temes aproximats pels quals es podria orientar a l'hora d'escollir una tasca, però la decisió final va quedar en mans dels equips. A més, es va fomentar l'ús de models que no estan inclosos en el producte.

Hackathon OpenVINO: reconèixer la veu i les emocions a Raspberry Pi

En aquest article us explicarem com vam crear el nostre prototip de producte, amb el qual finalment vam aconseguir el primer lloc.

Més de 10 equips van participar al hackató. És bo que alguns d'ells vinguin d'altres regions. El lloc de la hackathon va ser el complex "Kremlinsky on Pochain", on es van penjar fotografies antigues de Nizhny Novgorod a l'interior, en un seguici! (Us recordo que actualment l'oficina central d'Intel es troba a Nizhny Novgorod). Els participants tenien 26 hores per escriure codi i, al final, havien de presentar la seva solució. Un avantatge a part va ser la presència d'una sessió de demostració per assegurar-se que tot el que s'havia previst s'havia implementat realment i que no quedaven idees a la presentació. Mercaderia, aperitius, menjar, tot hi havia també!

A més, Intel proporcionava opcionalment càmeres, Raspberry PI, Neural Compute Stick 2.

Selecció de tasques

Una de les parts més difícils de preparar-se per a un hackathon de forma lliure és triar un repte. Immediatament vam decidir inventar alguna cosa que encara no estava al producte, ja que l'anunci va dir que era molt benvingut.

Havent analitzat models, que s'inclouen al producte a la versió actual, arribem a la conclusió que la majoria resolen diversos problemes de visió per ordinador. A més, és molt difícil plantejar un problema en el camp de la visió per ordinador que no es pugui resoldre amb OpenVINO, i encara que es pugui inventar, és difícil trobar models pre-entrenats al domini públic. Decidim cavar en una altra direcció: cap al processament i l'anàlisi de la parla. Considerem una tasca interessant de reconèixer les emocions a partir de la parla. Cal dir que OpenVINO ja té un model que determina les emocions d'una persona en funció del seu rostre, però:

  • En teoria, és possible crear un algorisme combinat que funcioni tant en so com en imatge, la qual cosa hauria de donar un augment de precisió.
  • Les càmeres solen tenir un angle de visió estret; es requereix més d'una càmera per cobrir una àrea gran; el so no té aquesta limitació.

Desenvolupem la idea: prenem com a base la idea del segment minorista. Podeu mesurar la satisfacció del client a les caixes de la botiga. Si un dels clients no està satisfet amb el servei i comença a augmentar el to, podeu trucar immediatament a l'administrador per demanar ajuda.
En aquest cas, hem d'afegir el reconeixement de veu humana, això ens permetrà distingir els empleats de la botiga dels clients i oferir anàlisis per a cada individu. Bé, a més, es podrà analitzar el comportament dels mateixos empleats de la botiga, avaluar l'ambient de l'equip, sona bé!

Formulem els requisits per a la nostra solució:

  • Petita mida del dispositiu objectiu
  • Funcionament en temps real
  • Preu baix
  • Fàcil escalabilitat

Com a resultat, seleccionem Raspberry Pi 3 c com a dispositiu objectiu Intel NCS 2.

Aquí és important tenir en compte una característica important de NCS: funciona millor amb arquitectures CNN estàndard, però si necessiteu executar un model amb capes personalitzades, espereu una optimització de baix nivell.

Només hi ha una petita cosa a fer: necessiteu un micròfon. Un micròfon USB normal servirà, però no es veurà bé juntament amb el RPI. Però fins i tot aquí la solució literalment "és a prop". Per gravar veu, decidim utilitzar la placa Voice Bonnet del kit Kit de veu Google AIY, on hi ha un micròfon estèreo per cable.

Baixeu Raspbian des de Repositori de projectes AIY i pengeu-lo a una unitat flaix, comproveu que el micròfon funciona amb l'ordre següent (gravarà l'àudio durant 5 segons i el desarà en un fitxer):

arecord -d 5 -r 16000 test.wav

De seguida he de tenir en compte que el micròfon és molt sensible i capta bé el soroll. Per solucionar-ho, anem a alsamixer, seleccioneu Dispositius de captura i reduïu el nivell del senyal d'entrada al 50-60%.

Hackathon OpenVINO: reconèixer la veu i les emocions a Raspberry Pi
Modifiquem el cos amb una llima i tot encaixa, fins i tot pots tancar-lo amb una tapa

Afegeix un botó indicador

Mentre desmuntem el kit de veu AIY, recordem que hi ha un botó RGB, la llum de fons del qual es pot controlar mitjançant programari. Busquem "Google AIY Led" i trobem documentació: https://aiyprojects.readthedocs.io/en/latest/aiy.leds.html
Per què no utilitzar aquest botó per mostrar l'emoció reconeguda, només tenim 7 classes, i el botó té 8 colors, prou!

Connectem el botó via GPIO a Voice Bonnet, carreguem les biblioteques necessàries (ja estan instal·lades al kit de distribució dels projectes AIY)

from aiy.leds import Leds, Color
from aiy.leds import RgbLeds

Anem a crear un dictat en el qual cada emoció tindrà un color corresponent en forma de tupla RGB i un objecte de la classe aiy.leds.Leds, a través del qual actualitzarem el color:

led_dict = {'neutral': (255, 255, 255), 'happy': (0, 255, 0), 'sad': (0, 255, 255), 'angry': (255, 0, 0), 'fearful': (0, 0, 0), 'disgusted':  (255, 0, 255), 'surprised':  (255, 255, 0)} 
leds = Leds()

I finalment, després de cada nova predicció d'una emoció, actualitzarem el color del botó d'acord amb aquest (per clau).

leds.update(Leds.rgb_on(led_dict.get(classes[prediction])))

Hackathon OpenVINO: reconèixer la veu i les emocions a Raspberry Pi
Botó, crema!

Treballant amb la veu

Utilitzarem pyaudio per capturar el flux des del micròfon i webrtcvad per filtrar el soroll i detectar la veu. A més, crearem una cua a la qual afegirem i eliminarem fragments de veu de manera asíncrona.

Com que webrtcvad té una limitació en la mida del fragment subministrat: ha de ser igual a 10/20/30 ms, i l'entrenament del model per al reconeixement d'emocions (com aprendrem més endavant) es va dur a terme en un conjunt de dades de 48 kHz, captura trossos de mida 48000×20ms/1000×1(mono)=960 bytes. Webrtcvad retornarà True/False per a cadascun d'aquests fragments, que correspon a la presència o absència d'un vot al fragment.

Implementem la següent lògica:

  • Afegirem a la llista aquells trossos on hi ha vot; si no hi ha vot, augmentarem el comptador de trossos buits.
  • Si el comptador de trossos buits és >=30 (600 ms), observem la mida de la llista de blocs acumulats; si és >250, l'afegim a la cua; si no, considerem que la longitud del registre no és suficient per alimentar-lo al model per identificar el parlant.
  • Si el comptador de blocs buits encara és <30 i la mida de la llista de blocs acumulats supera els 300, afegirem el fragment a la cua per a una predicció més precisa. (perquè les emocions tendeixen a canviar amb el temps)

 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 = []

És el moment de buscar models prèviament entrenats al domini públic, aneu a github, Google, però recordeu que tenim una limitació en l'arquitectura utilitzada. Aquesta és una part força difícil, perquè heu de provar els models a les vostres dades d'entrada i, a més, convertir-los al format intern d'OpenVINO: IR (Representació intermèdia). Vam provar entre 5 i 7 solucions diferents de github, i si el model per reconèixer les emocions va funcionar immediatament, amb el reconeixement de veu vam haver d'esperar més: utilitzen arquitectures més complexes.

Ens centrem en el següent:

A continuació parlarem de la conversió de models, començant per la teoria. OpenVINO inclou diversos mòduls:

  • Open Model Zoo, models dels quals es poden utilitzar i incloure al vostre producte
  • Model Optimzer, gràcies al qual podeu convertir un model de diversos formats de framework (Tensorflow, ONNX, etc.) al format de representació intermèdia, amb el qual treballarem més.
  • Inference Engine us permet executar models en format IR en processadors Intel, xips Myriad i acceleradors Neural Compute Stick
  • La versió més eficient d'OpenCV (amb suport de motor d'inferència)
    Cada model en format IR es descriu mitjançant dos fitxers: .xml i .bin.
    Els models es converteixen al format IR mitjançant Model Optimizer de la següent manera:

    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_type permet seleccionar el format de dades amb què funcionarà el model. S'admeten FP32, FP16, INT8. Escollir el tipus de dades òptim pot augmentar el rendiment.
    --input_shape indica la dimensió de les dades d'entrada. La capacitat de canviar-lo dinàmicament sembla estar present a l'API de C++, però no vam cavar tan lluny i simplement ho vam arreglar per a un dels models.
    A continuació, intentem carregar el model ja convertit en format IR mitjançant el mòdul DNN a OpenCV i reenviar-lo.

    import cv2 as cv
    emotionsNet = cv.dnn.readNet('emotions_model.bin',
                              'emotions_model.xml')
    emotionsNet.setPreferableTarget(cv.dnn.DNN_TARGET_MYRIAD)

    L'última línia en aquest cas permet redirigir els càlculs al Neural Compute Stick, els càlculs bàsics es fan al processador, però en el cas del Raspberry Pi això no funcionarà, necessitareu un stick.

    A continuació, la lògica és la següent: dividim el nostre àudio en finestres d'una mida determinada (per a nosaltres és de 0.4 s), convertim cadascuna d'aquestes finestres en MFCC, que després alimentem a la graella:

    emotionsNet.setInput(MFCC_from_window)
    result = emotionsNet.forward()

    A continuació, anem a prendre la classe més comuna per a totes les finestres. Una solució senzilla, però per a un hackathon no cal que trobis alguna cosa massa abstrusa, només si tens temps. Encara ens queda molta feina per fer, així que seguim, ens ocuparem del reconeixement de veu. Cal fer algun tipus de base de dades en la qual s'emmagatzemaran espectrogrames de veus pregravades. Com que queda poc temps, resoldrem aquest problema com puguem.

    És a dir, creem un script per gravar un fragment de veu (funciona de la mateixa manera que es descriu anteriorment, només quan s'interromp des del teclat, desarà la veu en un fitxer).

    Anem a provar:

    python3 voice_db/record_voice.py test.wav

    Gravem les veus de diverses persones (en el nostre cas, tres membres de l'equip)
    A continuació, per a cada veu gravada realitzem una transformació de Fourier ràpida, obtenim un espectrograma i el desem com a matriu numpy (.npy):

    for file in glob.glob("voice_db/*.wav"):
            spec = get_fft_spectrum(file)
            np.save(file[:-4] + '.npy', spec)

    Més detalls a l'arxiu create_base.py
    Com a resultat, quan executem l'script principal, obtindrem incrustacions d'aquests espectrogrames al principi:

    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)

    Després de rebre la incrustació del segment sonat, podrem determinar a qui pertany prenent la distància del cosinus des del passatge fins a totes les veus de la base de dades (com més petites, més probables) - per a la demostració establim el llindar fins a 0.3):

            dist_list = cdist(emb, enroll_embs, metric="cosine")
            distances = pd.DataFrame(dist_list, columns = df.speaker)

    Al final, m'agradaria assenyalar que la velocitat d'inferència va ser ràpida i va permetre afegir 1-2 models més (per a una mostra de 7 segons de durada es va necessitar 2.5 per a la inferència). Ja no vam tenir temps d'afegir nous models i ens vam centrar a escriure un prototip de l'aplicació web.

    Aplicació web

    Un punt important: portem un encaminador amb nosaltres des de casa i configurem la nostra xarxa local, ajuda a connectar el dispositiu i els ordinadors portàtils a través de la xarxa.

    El backend és un canal de missatges d'extrem a extrem entre la part frontal i Raspberry Pi, basat en la tecnologia websocket (protocol http sobre tcp).

    La primera etapa és rebre informació processada de raspberry, és a dir, predictors empaquetats en json, que es guarden a la base de dades a mig camí per tal que es puguin generar estadístiques sobre els antecedents emocionals de l'usuari durant el període. Aquest paquet s'envia a la interfície, que utilitza la subscripció i rep paquets des del punt final del websocket. Tot el mecanisme de backend està construït en el llenguatge golang; es va escollir perquè és adequat per a tasques asíncrones, que les goroutines gestionen bé.
    En accedir al punt final, l'usuari es registra i entra a l'estructura, després es rep el seu missatge. Tant l'usuari com el missatge s'introdueixen en un concentrador comú, des del qual els missatges ja s'envien més endavant (al front subscrit) i si l'usuari tanca la connexió (gerd o frontal), la seva subscripció es cancel·la i s'elimina de el hub.

    Hackathon OpenVINO: reconèixer la veu i les emocions a Raspberry Pi
    Estem esperant una connexió des del darrere

    Front-end és una aplicació web escrita en JavaScript que utilitza la biblioteca React per accelerar i simplificar el procés de desenvolupament. El propòsit d'aquesta aplicació és visualitzar les dades obtingudes mitjançant algorismes que s'executen a la part posterior i directament al Raspberry Pi. La pàgina té un encaminament seccional implementat mitjançant react-router, però la pàgina principal d'interès és la pàgina principal, on es rep un flux continu de dades en temps real des del servidor mitjançant la tecnologia WebSocket. Raspberry Pi detecta una veu, determina si pertany a una persona específica de la base de dades registrada i envia una llista de probabilitats al client. El client mostra les últimes dades rellevants, mostra l'avatar de la persona que probablement ha parlat pel micròfon, així com l'emoció amb què pronuncia les paraules.

    Hackathon OpenVINO: reconèixer la veu i les emocions a Raspberry Pi
    Pàgina d'inici amb prediccions actualitzades

    Conclusió

    No va ser possible completar-ho tot com estava previst, simplement no teníem temps, així que l'esperança principal estava en la demostració, que tot funcionés. A la presentació van parlar de com funciona tot, quins models van agafar, quins problemes van trobar. A continuació va ser la part de demostració: els experts van caminar per la sala en ordre aleatori i es van acostar a cada equip per mirar el prototip de treball. També ens van fer preguntes, cadascú va respondre a la seva part, van deixar la web a l'ordinador portàtil i tot va funcionar com s'esperava.

    Permeteu-me tenir en compte que el cost total de la nostra solució va ser de 150 dòlars:

    • Raspberry Pi 3 ~ 35 dòlars
    • Google AIY Voice Bonnet (podeu cobrar una tarifa de realtaveu) ~ 15 $
    • Intel NCS 2 ~ 100 $

    Com millorar:

    • Utilitzeu el registre del client: demaneu llegir el text que es genera aleatòriament
    • Afegeix uns quants models més: pots determinar el gènere i l'edat per veu
    • Separar veus que sonen simultàniament (diarització)

    Repositori: https://github.com/vladimirwest/OpenEMO

    Hackathon OpenVINO: reconèixer la veu i les emocions a Raspberry Pi
    Estem cansats però contents

    Per acabar, m'agradaria donar les gràcies als organitzadors i participants. Entre els projectes d'altres equips, personalment ens ha agradat la solució de seguiment de places d'aparcament gratuïtes. Per a nosaltres, va ser una experiència fantàstica d'immersió en el producte i desenvolupament. Espero que es facin cada cop més esdeveniments interessants a les regions, fins i tot sobre temes d'IA.

Font: www.habr.com

Afegeix comentari