OpenVINO hackathon: rozpoznávanie hlasu a emócií na Raspberry Pi

30. novembra – 1. decembra v Nižnom Novgorode sa konalo OpenVINO hackathon. Účastníci boli požiadaní, aby vytvorili prototyp produktového riešenia pomocou sady nástrojov Intel OpenVINO. Organizátori navrhli zoznam približných tém, ktorými by sa mohli riadiť pri výbere úlohy, ale konečné rozhodnutie zostalo na tímoch. Okrem toho bolo podporované používanie modelov, ktoré nie sú súčasťou produktu.

OpenVINO hackathon: rozpoznávanie hlasu a emócií na Raspberry Pi

V tomto článku vám povieme o tom, ako sme vytvorili náš prototyp produktu, s ktorým sme nakoniec obsadili prvé miesto.

Na hackathone sa zúčastnilo viac ako 10 tímov. Je fajn, že niektorí prišli z iných regiónov. Miestom hackathonu bol komplex „Kremlinsky on Pochain“, v ktorom boli v sprievode zavesené staré fotografie Nižného Novgorodu! (Pripomínam, že v súčasnosti sa centrála spoločnosti Intel nachádza v Nižnom Novgorode). Účastníci dostali 26 hodín na napísanie kódu a na konci museli prezentovať svoje riešenie. Samostatnou výhodou bola prítomnosť demonštračnej relácie, aby sa zabezpečilo, že všetko plánované bolo skutočne implementované a nezostali nápady v prezentácii. Tovar, občerstvenie, jedlo, všetko tam tiež bolo!

Okrem toho Intel voliteľne dodal kamery, Raspberry PI, Neural Compute Stick 2.

Výber úlohy

Jednou z najťažších častí prípravy na voľný hackathon je výber výzvy. Okamžite sme sa rozhodli prísť s niečím, čo ešte nebolo v produkte, pretože oznámenie hovorilo, že je to veľmi vítané.

Po analýze model, ktoré sú súčasťou produktu v aktuálnom vydaní, prichádzame k záveru, že väčšina z nich rieši rôzne problémy počítačového videnia. Navyše je veľmi ťažké prísť s problémom v oblasti počítačového videnia, ktorý sa nedá vyriešiť pomocou OpenVINO, a aj keď sa nejaký dá vynájsť, je ťažké nájsť vo verejnej sfére vopred vyškolené modely. Rozhodneme sa ísť iným smerom – smerom k spracovaniu reči a analytike. Uvažujme o zaujímavej úlohe rozpoznávania emócií z reči. Treba povedať, že OpenVINO už má model, ktorý určuje emócie človeka na základe jeho tváre, ale:

  • Teoreticky je možné vytvoriť kombinovaný algoritmus, ktorý bude pracovať so zvukom aj obrazom, čo by malo zvýšiť presnosť.
  • Kamery majú väčšinou úzky pozorovací uhol, na pokrytie veľkej plochy je potrebných viac ako jedna kamera, zvuk nemá také obmedzenie.

Rozviňme myšlienku: vezmime si za základ myšlienku pre maloobchodný segment. Spokojnosť zákazníkov môžete merať pri pokladniach v obchode. Ak je niektorý zo zákazníkov so službou nespokojný a začne zvyšovať tón, môžete okamžite zavolať správcu o pomoc.
V tomto prípade musíme pridať rozpoznávanie ľudského hlasu, čo nám umožní rozlíšiť zamestnancov predajne od zákazníkov a poskytnúť analýzy pre každého jednotlivca. Okrem toho bude možné analyzovať správanie samotných zamestnancov obchodu, hodnotiť atmosféru v tíme, znie to dobre!

Formulujeme požiadavky na naše riešenie:

  • Malá veľkosť cieľového zariadenia
  • Prevádzka v reálnom čase
  • Nízka cena
  • Jednoduchá škálovateľnosť

V dôsledku toho sme ako cieľové zariadenie vybrali Raspberry Pi 3 c Intel NCS 2.

Tu je dôležité poznamenať jednu dôležitú vlastnosť NCS - funguje najlepšie so štandardnými architektúrami CNN, ale ak potrebujete spustiť model s vlastnými vrstvami, potom počítajte s optimalizáciou na nízkej úrovni.

Stačí jedna malá vec: musíte si zaobstarať mikrofón. Bežný USB mikrofón postačí, ale spolu s RPI to nebude vyzerať dobre. Ale aj tu je riešenie doslova „v blízkosti“. Na nahrávanie hlasu sme sa rozhodli použiť dosku Voice Bonnet zo stavebnice Google AIY Voice Kit, na ktorom je drôtový stereo mikrofón.

Stiahnite si Raspbian z Úložisko projektov AIY a nahrajte ho na flash disk, vyskúšajte, či mikrofón funguje pomocou nasledujúceho príkazu (nahrá zvuk v dĺžke 5 sekúnd a uloží ho do súboru):

arecord -d 5 -r 16000 test.wav

Hneď by som mal poznamenať, že mikrofón je veľmi citlivý a dobre zachytáva hluk. Ak to chcete vyriešiť, prejdite na alsamixer, vyberte položku Capture devices a znížte úroveň vstupného signálu na 50-60%.

OpenVINO hackathon: rozpoznávanie hlasu a emócií na Raspberry Pi
Korpus upravíme pilníkom a všetko sedí, dokonca ho môžete uzavrieť vrchnákom

Pridanie tlačidla indikátora

Pri rozoberaní AIY Voice Kit si pamätáme, že je tu tlačidlo RGB, ktorého podsvietenie je možné ovládať softvérovo. Hľadáme „Google AIY Led“ a nájdeme dokumentáciu: https://aiyprojects.readthedocs.io/en/latest/aiy.leds.html
Prečo nepoužívať toto tlačidlo na zobrazenie rozpoznanej emócie, máme len 7 tried a tlačidlo má 8 farieb, akurát dosť!

Tlačidlo pripojíme cez GPIO k Voice Bonnet, načítame potrebné knižnice (už sú nainštalované v distribučnej súprave z projektov AIY)

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

Vytvorme si diktát, v ktorom bude mať každá emócia zodpovedajúcu farbu v podobe RGB Tuple a objektu triedy aiy.leds.Leds, prostredníctvom ktorého budeme farbu aktualizovať:

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()

A nakoniec, po každej novej predpovedi emócie, aktualizujeme farbu tlačidla v súlade s ňou (podľa kľúča).

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

OpenVINO hackathon: rozpoznávanie hlasu a emócií na Raspberry Pi
Tlačidlo, horieť!

Práca s hlasom

Na zachytenie streamu z mikrofónu použijeme pyaudio a webrtcvad na filtrovanie šumu a detekciu hlasu. Okrem toho vytvoríme rad, do ktorého budeme asynchrónne pridávať a odoberať úryvky hlasu.

Keďže webrtcvad má obmedzenie veľkosti dodaného fragmentu – musí sa rovnať 10/20/30 ms a trénovanie modelu na rozpoznávanie emócií (ako sa dozvieme neskôr) sa uskutočnilo na 48kHz datasetu, budeme zachytávanie kúskov s veľkosťou 48000×20ms/1000×1(mono)=960 bajtov. Webrtcvad vráti hodnotu True/False pre každý z týchto častí, čo zodpovedá prítomnosti alebo neprítomnosti hlasu v časti.

Implementujme nasledujúcu logiku:

  • Do zoznamu pridáme tie časti, kde je hlasovanie, ak sa nehlasuje, zvýšime počítadlo prázdnych kúskov.
  • Ak je počítadlo prázdnych blokov >=30 (600 ms), potom sa pozrieme na veľkosť zoznamu akumulovaných blokov; ak je >250, pridáme ho do frontu; ak nie, považujeme za dĺžku záznamu nestačí na to, aby ho priviedol k modelu na identifikáciu hovoriaceho.
  • Ak je počítadlo prázdnych blokov stále < 30 a veľkosť zoznamu nahromadených blokov presahuje 300, potom fragment pridáme do frontu pre presnejšiu predpoveď. (pretože emócie majú tendenciu sa časom meniť)

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

Je čas hľadať vopred pripravené modely vo verejnej doméne, ísť na github, Google, ale pamätajte, že máme obmedzenie na použitú architektúru. Toto je pomerne náročná časť, pretože modely musíte otestovať na svojich vstupných dátach a navyše ich previesť do interného formátu OpenVINO – IR (Intermediate Representation). Vyskúšali sme asi 5-7 rôznych riešení od githubu a ak model na rozpoznávanie emócií fungoval okamžite, tak s rozpoznávaním hlasu sme museli čakať dlhšie – využívajú zložitejšie architektúry.

Zameriavame sa na nasledovné:

Ďalej budeme hovoriť o prevode modelov, počnúc teóriou. OpenVINO obsahuje niekoľko modulov:

  • Open Model Zoo, modely, ktoré môžete použiť a zahrnúť do vášho produktu
  • Model Optimzer, vďaka ktorému môžete previesť model z rôznych rámcových formátov (Tensorflow, ONNX atď.) do formátu Intermediate Representation, s ktorým budeme ďalej pracovať
  • Inference Engine vám umožňuje spúšťať modely vo formáte IR na procesoroch Intel, čipoch Myriad a akcelerátoroch Neural Compute Stick
  • Najúčinnejšia verzia OpenCV (s podporou Inference Engine)
    Každý model vo formáte IR je popísaný dvoma súbormi: .xml a .bin.
    Modely sa konvertujú do formátu IR pomocou nástroja Model Optimizer takto:

    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 umožňuje vybrať dátový formát, s ktorým bude model pracovať. Podporované sú FP32, FP16, INT8. Výber optimálneho typu údajov môže poskytnúť dobré zvýšenie výkonu.
    --input_shape označuje rozmer vstupných údajov. Zdá sa, že schopnosť dynamicky meniť to je prítomná v C++ API, ale tak ďaleko sme nekopali a jednoducho sme to opravili pre jeden z modelov.
    Ďalej skúsme načítať už skonvertovaný model v IR formáte cez modul DNN do OpenCV a preposlať mu ho.

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

    Posledný riadok v tomto prípade umožňuje presmerovať výpočty na Neural Compute Stick, základné výpočty sa vykonávajú na procesore, no v prípade Raspberry Pi to nepôjde, budete potrebovať stick.

    Ďalej je logika nasledovná: rozdelíme náš zvuk do okien určitej veľkosti (u nás je to 0.4 s), každé z týchto okien konvertujeme na MFCC, ktoré potom privádzame do mriežky:

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

    Ďalej si vezmime najbežnejšiu triedu pre všetky okná. Jednoduché riešenie, ale pre hackathon nemusíte vymýšľať niečo príliš hlúpe, iba ak máte čas. Máme pred sebou ešte veľa práce, takže poďme ďalej a zaoberme sa rozpoznávaním hlasu. Je potrebné urobiť nejakú databázu, v ktorej by boli uložené spektrogramy vopred nahratých hlasov. Keďže zostáva málo času, vyriešime tento problém čo najlepšie.

    Totiž, vytvoríme skript na nahrávanie hlasového úryvku (funguje rovnako ako vyššie, len pri prerušení z klávesnice hlas uloží do súboru).

    Vyskúšajme:

    python3 voice_db/record_voice.py test.wav

    Nahrávame hlasy niekoľkých ľudí (v našom prípade troch členov tímu)
    Ďalej pre každý zaznamenaný hlas vykonáme rýchlu Fourierovu transformáciu, získame spektrogram a uložíme ho ako numpy pole (.npy):

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

    Viac podrobností v súbore create_base.py
    Výsledkom je, že keď spustíme hlavný skript, hneď na začiatku získame vloženia z týchto spektrogramov:

    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)

    Po prijatí vloženia z ozvučeného segmentu budeme môcť určiť, komu patrí, a to tak, že vezmeme kosínusovú vzdialenosť od pasáže ku všetkým hlasom v databáze (čím menšie, tým pravdepodobnejšie) - pre demo nastavíme prahovú hodnotu do 0.3):

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

    Na záver by som rád poznamenal, že rýchlosť inferencie bola vysoká a umožnila pridať 1-2 ďalšie modely (pre vzorku s dĺžkou 7 sekúnd to trvalo 2.5). Už sme nemali čas pridávať nové modely a sústredili sme sa na písanie prototypu webovej aplikácie.

    Webová aplikácia

    Dôležitý bod: vezmeme si so sebou router z domu a nastavíme si lokálnu sieť, pomáha prepojiť zariadenie a notebooky cez sieť.

    Backend je end-to-end kanál správ medzi frontom a Raspberry Pi, založený na technológii websocket (http cez tcp protokol).

    Prvou fázou je príjem spracovaných informácií z maliny, teda prediktorov zabalených v json, ktoré sa v polovici svojej cesty uložia do databázy, aby bolo možné generovať štatistiky o emocionálnom pozadí používateľa za dané obdobie. Tento paket je potom odoslaný na frontend, ktorý používa predplatné a prijíma pakety z koncového bodu websocket. Celý backendový mechanizmus je postavený v jazyku golang, bol vybraný preto, že sa dobre hodí pre asynchrónne úlohy, ktoré goroutiny dobre zvládajú.
    Pri prístupe ku koncovému bodu sa užívateľ zaregistruje a zapíše do štruktúry, následne je prijatá jeho správa. Používateľ aj správa sa zadajú do spoločného hubu, z ktorého sa už správy odosielajú ďalej (na predplatenú frontu) a ak používateľ uzavrie spojenie (malina alebo front), jeho predplatné je zrušené a je odstránený z rozbočovač.

    OpenVINO hackathon: rozpoznávanie hlasu a emócií na Raspberry Pi
    Čakáme na spojenie zozadu

    Front-end je webová aplikácia napísaná v JavaScripte pomocou knižnice React na urýchlenie a zjednodušenie procesu vývoja. Účelom tejto aplikácie je vizualizovať dáta získané pomocou algoritmov bežiacich na strane back-endu a priamo na Raspberry Pi. Stránka má sekčné smerovanie implementované pomocou reakčného smerovača, ale hlavnou stránkou záujmu je hlavná stránka, kde sa zo servera získava nepretržitý tok údajov v reálnom čase pomocou technológie WebSocket. Raspberry Pi deteguje hlas, z registrovanej databázy určí, či patrí konkrétnej osobe a klientovi odošle zoznam pravdepodobnosti. Klient zobrazuje najnovšie relevantné údaje, zobrazuje avatara človeka, ktorý s najväčšou pravdepodobnosťou hovoril do mikrofónu, ako aj emóciu, s akou slová vyslovuje.

    OpenVINO hackathon: rozpoznávanie hlasu a emócií na Raspberry Pi
    Domovská stránka s aktualizovanými predpoveďami

    Záver

    Nebolo možné dokončiť všetko podľa plánu, jednoducho sme nemali čas, takže hlavná nádej bola v deme, že všetko bude fungovať. V prezentácii hovorili o tom, ako všetko funguje, aké modely si zobrali, s akými problémami sa stretli. Nasledovala demo časť – experti chodili po miestnosti v náhodnom poradí a pristupovali ku každému tímu, aby sa pozrel na fungujúci prototyp. Pýtali sa aj nás, každý odpovedal na svoju časť, web nechali na notebooku a všetko naozaj fungovalo podľa očakávania.

    Dovoľte mi poznamenať, že celkové náklady na naše riešenie boli 150 USD:

    • Raspberry Pi 3 ~ 35 dolárov
    • Google AIY Voice Bonnet (môžete si vziať poplatok za reproduktor) ~ 15 $
    • Intel NCS 2 ~ 100 $

    Ako sa zlepšiť:

    • Využite registráciu od klienta – požiadajte o prečítanie textu, ktorý sa náhodne vygeneruje
    • Pridajte niekoľko ďalších modelov: pohlavie a vek môžete určiť hlasom
    • Samostatné súčasne znejúce hlasy (diarizácia)

    Úložisko: https://github.com/vladimirwest/OpenEMO

    OpenVINO hackathon: rozpoznávanie hlasu a emócií na Raspberry Pi
    Sme unavení, ale šťastní

    Na záver by som sa chcel poďakovať organizátorom a účastníkom. Z projektov iných tímov sa nám osobne páčilo riešenie monitorovania voľných parkovacích miest. Pre nás to bol úžasne skvelý zážitok z ponorenia sa do produktu a vývoja. Dúfam, že v regiónoch sa bude konať stále viac zaujímavých podujatí vrátane tém AI.

Zdroj: hab.com

Pridať komentár