OpenVINO hackathon: rozpoznávání hlasu a emocí na Raspberry Pi

30. listopadu - 1. prosince v Nižním Novgorodu se konalo OpenVINO hackathon. Účastníci byli požádáni, aby vytvořili prototyp produktového řešení pomocí sady nástrojů Intel OpenVINO. Organizátoři navrhli seznam přibližných témat, kterými by se mohli řídit při výběru úkolu, ale konečné rozhodnutí zůstalo na týmech. Kromě toho bylo podporováno použití modelů, které nejsou součástí produktu.

OpenVINO hackathon: rozpoznávání hlasu a emocí na Raspberry Pi

V tomto článku vám povíme o tom, jak jsme vytvořili náš prototyp produktu, se kterým jsme nakonec obsadili první místo.

Hackathonu se zúčastnilo více než 10 týmů. Je fajn, že někteří přijeli z jiných krajů. Místem konání hackathonu byl komplex „Kremlinsky on Pochain“, kde byly uvnitř v doprovodu zavěšeny staré fotografie Nižního Novgorodu! (Připomínám, že v současné době se centrála Intelu nachází v Nižném Novgorodu). Účastníci měli 26 hodin na napsání kódu a na závěr museli prezentovat své řešení. Samostatnou výhodou byla přítomnost demo session, aby bylo zajištěno, že vše, co bylo naplánováno, bylo skutečně realizováno a nezůstaly nápady v prezentaci. Merch, občerstvení, jídlo, všechno tam bylo taky!

Intel navíc volitelně dodal kamery, Raspberry PI, Neural Compute Stick 2.

Výběr úkolu

Jednou z nejtěžších částí přípravy na hackathon ve volné formě je výběr výzvy. Okamžitě jsme se rozhodli přijít s něčím, co v produktu ještě nebylo, protože oznámení říkalo, že je to velmi vítané.

Po analýze modely, které jsou součástí produktu v aktuální verzi, dojdeme k závěru, že většina z nich řeší různé problémy s počítačovým viděním. Navíc je velmi obtížné přijít s problémem v oblasti počítačového vidění, který nelze vyřešit pomocí OpenVINO, a i když se nějaký dá vymyslet, je těžké najít předtrénované modely ve veřejné doméně. Rozhodli jsme se jít jiným směrem – ke zpracování řeči a analýze. Uvažujme o zajímavém úkolu rozpoznávání emocí z řeči. Je třeba říci, že OpenVINO již má model, který určuje emoce člověka na základě jeho tváře, ale:

  • Teoreticky je možné vytvořit kombinovaný algoritmus, který bude pracovat se zvukem i obrazem, což by mělo přinést zvýšení přesnosti.
  • Kamery mají většinou úzký zorný úhel, k pokrytí velké plochy je potřeba více kamer, zvuk takové omezení nemá.

Pojďme myšlenku rozvinout: jako základ vezměme myšlenku pro maloobchodní segment. Spokojenost zákazníků můžete měřit na pokladnách obchodů. Pokud je některý ze zákazníků se službou nespokojený a začne zvyšovat tón, můžete okamžitě zavolat administrátora o pomoc.
V tomto případě musíme přidat rozpoznávání lidského hlasu, což nám umožní odlišit zaměstnance prodejny od zákazníků a poskytnout analýzu pro každého jednotlivce. No, navíc bude možné analyzovat chování samotných zaměstnanců prodejny, hodnotit atmosféru v týmu, to zní dobře!

Formulujeme požadavky na naše řešení:

  • Malá velikost cílového zařízení
  • Provoz v reálném čase
  • Nízká cena
  • Snadná škálovatelnost

V důsledku toho jsme jako cílové zařízení vybrali Raspberry Pi 3 c Intel NCS 2.

Zde je důležité poznamenat jednu důležitou vlastnost NCS – nejlépe funguje se standardními architekturami CNN, ale pokud potřebujete spustit model s vlastními vrstvami, pak počítejte s optimalizací na nízké úrovni.

Stačí jedna malá věc: musíte si pořídit mikrofon. Běžný USB mikrofon postačí, ale spolu s RPI to nebude vypadat dobře. Ale i zde řešení doslova „leží poblíž“. Pro záznam hlasu jsme se rozhodli použít desku Voice Bonnet ze stavebnice Google AIY Voice Kit, na kterém je drátový stereo mikrofon.

Stáhněte si Raspbian z Úložiště projektů AIY a nahrajte jej na flash disk, otestujte, že mikrofon funguje pomocí následujícího příkazu (nahraje zvuk o délce 5 sekund a uloží jej do souboru):

arecord -d 5 -r 16000 test.wav

Hned bych měl poznamenat, že mikrofon je velmi citlivý a dobře snímá hluk. Chcete-li to opravit, přejděte do alsamixeru, vyberte Capture devices a snižte úroveň vstupního signálu na 50-60%.

OpenVINO hackathon: rozpoznávání hlasu a emocí na Raspberry Pi
Korpus upravíme pilníkem a vše sedí, můžete i zavřít víkem

Přidání tlačítka indikátoru

Při rozebírání AIY Voice Kitu si pamatujeme, že je zde RGB tlačítko, jehož podsvícení lze ovládat softwarově. Hledáme „Google AIY Led“ a najdeme dokumentaci: https://aiyprojects.readthedocs.io/en/latest/aiy.leds.html
Proč nepoužít toto tlačítko k zobrazení rozpoznané emoce, máme jen 7 tříd a tlačítko má 8 barev, tak akorát!

Tlačítko připojíme přes GPIO k Voice Bonnet, načteme potřebné knihovny (jsou již nainstalovány v distribuční sadě z AIY projektů)

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

Vytvořme si diktát, ve kterém bude mít každá emoce odpovídající barvu v podobě RGB Tuple a objektu třídy aiy.leds.Leds, jehož prostřednictvím budeme barvu aktualizovat:

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 nakonec po každé nové predikci emoce aktualizujeme barvu tlačítka v souladu s ní (podle klíče).

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

OpenVINO hackathon: rozpoznávání hlasu a emocí na Raspberry Pi
Tlačítko, hoří!

Práce s hlasem

K zachycení streamu z mikrofonu použijeme pyaudio a webrtcvad k filtrování šumu a detekci hlasu. Navíc si vytvoříme frontu, do které budeme asynchronně přidávat a odebírat úryvky hlasu.

Protože webrtcvad má omezení na velikost dodaného fragmentu - musí se rovnat 10/20/30 ms a trénování modelu pro rozpoznávání emocí (jak se dozvíme později) bylo provedeno na datové sadě 48 kHz, zachytit bloky o velikosti 48000×20ms/1000×1(mono)=960 bajtů. Webrtcvad vrátí True/False pro každý z těchto bloků, což odpovídá přítomnosti nebo nepřítomnosti hlasování v bloku.

Pojďme implementovat následující logiku:

  • Přidáme do seznamu ty bloky, kde se hlasuje, pokud nebude hlasovat, zvýšíme počítadlo prázdných bloků.
  • Pokud je počítadlo prázdných bloků >=30 (600 ms), podíváme se na velikost seznamu nashromážděných bloků; pokud je >250, přidáme jej do fronty; pokud ne, považujeme délku záznamu nestačí k tomu, aby jej předal modelu k identifikaci mluvčího.
  • Pokud je počítadlo prázdných bloků stále < 30 a velikost seznamu nashromážděných bloků přesahuje 300, přidáme fragment do fronty pro přesnější předpověď. (protože emoce mají tendenci se časem měnit)

 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 hledat předtrénované modely ve veřejné doméně, jít na github, Google, ale pamatujte, že máme omezení na použitou architekturu. To je poměrně obtížná část, protože musíte modely otestovat na vašich vstupních datech a navíc je převést do interního formátu OpenVINO – IR (Intermediate Representation). Vyzkoušeli jsme asi 5-7 různých řešení od githubu a pokud model pro rozpoznávání emocí fungoval okamžitě, tak s rozpoznáváním hlasu jsme museli čekat déle – používají složitější architektury.

Zaměřujeme se na následující:

Dále budeme hovořit o převodu modelů, začneme teorií. OpenVINO obsahuje několik modulů:

  • Open Model Zoo, modely, které lze použít a zahrnout do vašeho produktu
  • Model Optimzer, díky kterému můžete převést model z různých rámcových formátů (Tensorflow, ONNX atd.) do formátu Intermediate Representation, se kterým budeme dále pracovat
  • Inference Engine vám umožňuje spouštět modely ve formátu IR na procesorech Intel, čipech Myriad a akcelerátorech Neural Compute Stick
  • Nejúčinnější verze OpenCV (s podporou Inference Engine)
    Každý model ve formátu IR je popsán dvěma soubory: .xml a .bin.
    Modely jsou převedeny do IR formátu pomocí 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 vybrat datový formát, se kterým bude model pracovat. Podporovány jsou FP32, FP16, INT8. Výběr optimálního datového typu může poskytnout dobré zvýšení výkonu.
    --input_shape označuje rozměr vstupních dat. Schopnost dynamicky měnit to vypadá, že je přítomná v C++ API, ale nekopali jsme tak daleko a jednoduše jsme to opravili pro jeden z modelů.
    Dále zkusme načíst již převedený model v IR formátu přes modul DNN do OpenCV a přeposlat mu jej.

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

    Poslední řádek v tomto případě umožňuje přesměrovat výpočty na Neural Compute Stick, základní výpočty se provádějí na procesoru, ale v případě Raspberry Pi to nepůjde, budete potřebovat stick.

    Dále je logika následující: rozdělíme naše audio do oken určité velikosti (u nás je to 0.4 s), každé z těchto oken převedeme na MFCC, které pak dodáváme do mřížky:

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

    Dále si vezměme nejběžnější třídu pro všechna okna. Jednoduché řešení, ale pro hackathon nemusíte vymýšlet něco příliš složitého, pouze pokud máte čas. Čeká nás ještě spousta práce, takže pojďme dál – budeme se zabývat rozpoznáváním hlasu. Je potřeba vytvořit jakousi databázi, do které by se ukládaly spektrogramy předem nahraných hlasů. Protože zbývá málo času, vyřešíme tento problém co nejlépe.

    Jmenovitě vytvoříme skript pro záznam hlasového úryvku (funguje stejně jako výše, pouze při přerušení z klávesnice hlas uloží do souboru).

    Zkusme to:

    python3 voice_db/record_voice.py test.wav

    Nahráváme hlasy několika lidí (v našem případě tří členů týmu)
    Dále pro každý nahraný hlas provedeme rychlou Fourierovu transformaci, získáme spektrogram a uložíme jej jako numpy pole (.npy):

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

    Více podrobností v souboru create_base.py
    Výsledkem je, že když spustíme hlavní skript, hned na začátku získáme vložení z těchto spektrogramů:

    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 obdržení vložení z ozvučeného segmentu budeme schopni určit, komu patří tak, že vezmeme kosinusovou vzdálenost od pasáže ke všem hlasům v databázi (čím menší, tím pravděpodobnější) - pro demo nastavíme práh až 0.3):

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

    Na závěr bych rád poznamenal, že rychlost inference byla vysoká a umožnila přidat 1-2 další modely (u vzorku dlouhého 7 sekund to trvalo 2.5 pro inferenci). Už jsme neměli čas přidávat nové modely a soustředili jsme se na psaní prototypu webové aplikace.

    Webová aplikace

    Důležitý bod: vezmeme si s sebou router z domova a nastavíme naši lokální síť, pomáhá propojit zařízení a notebooky přes síť.

    Backend je end-to-end kanál zpráv mezi frontou a Raspberry Pi, založený na technologii websocket (protokol http přes tcp).

    První fází je příjem zpracovaných informací z maliny, tedy prediktorů zabalených v json, které se v polovině cesty uloží do databáze, aby bylo možné generovat statistiky o emočním pozadí uživatele za dané období. Tento paket je poté odeslán do frontendu, který používá předplatné a přijímá pakety z koncového bodu webového soketu. Celý backendový mechanismus je postaven v jazyce golang, byl vybrán proto, že se dobře hodí pro asynchronní úlohy, které goroutiny dobře zvládají.
    Při přístupu ke koncovému bodu je uživatel zaregistrován a vložen do struktury, poté je přijata jeho zpráva. Uživatel i zpráva jsou zapsány do společného hubu, ze kterého jsou již zprávy odesílány dále (na předplacenou frontu), a pokud uživatel uzavře spojení (malina nebo front), tak je jeho předplatné zrušeno a je odebrán z rozbočovač.

    OpenVINO hackathon: rozpoznávání hlasu a emocí na Raspberry Pi
    Čekáme na spojení zezadu

    Front-end je webová aplikace napsaná v JavaScriptu využívající knihovnu React pro urychlení a zjednodušení procesu vývoje. Účelem této aplikace je vizualizovat data získaná pomocí algoritmů běžících na back-endové straně a přímo na Raspberry Pi. Stránka má sekční směrování implementováno pomocí Reag-router, ale hlavní stránkou zájmu je hlavní stránka, kde je ze serveru přijímán nepřetržitý proud dat v reálném čase pomocí technologie WebSocket. Raspberry Pi detekuje hlas, určí, zda patří konkrétní osobě z registrované databáze, a odešle klientovi pravděpodobnostní seznam. Klient zobrazí nejnovější relevantní údaje, zobrazí avatara osoby, která s největší pravděpodobností mluvila do mikrofonu, a také emoci, se kterou slova vyslovuje.

    OpenVINO hackathon: rozpoznávání hlasu a emocí na Raspberry Pi
    Domovská stránka s aktualizovanými předpověďmi

    Závěr

    Nedalo se vše dokončit podle plánu, prostě jsme neměli čas, takže hlavní naděje byla v demu, že vše bude fungovat. V prezentaci hovořili o tom, jak vše funguje, jaké modely si vzali, s jakými problémy se setkali. Další byla ukázková část – experti chodili po místnosti v náhodném pořadí a přistupovali ke každému týmu, aby se podíval na fungující prototyp. Také se nás ptali, každý odpověděl na svou část, web nechali na notebooku a vše skutečně fungovalo podle očekávání.

    Dovolte mi poznamenat, že celkové náklady na naše řešení byly 150 $:

    • Raspberry Pi 3 ~ 35 $
    • Google AIY Voice Bonnet (můžete si vzít poplatek za reproduktor) ~ 15 $
    • Intel NCS 2 ~ 100 $

    Jak vylepšit:

    • Použijte registraci od klienta - požádejte o přečtení textu, který je náhodně vygenerován
    • Přidejte několik dalších modelů: pohlaví a věk můžete určit hlasem
    • Oddělte současně znějící hlasy (diarizace)

    úložiště: https://github.com/vladimirwest/OpenEMO

    OpenVINO hackathon: rozpoznávání hlasu a emocí na Raspberry Pi
    Jsme unavení, ale šťastní

    Na závěr bych chtěl poděkovat organizátorům a účastníkům. Z projektů ostatních týmů se nám osobně líbilo řešení monitoringu volných parkovacích míst. Pro nás to byla neuvěřitelně skvělá zkušenost ponoření se do produktu a vývoje. Doufám, že se v regionech bude konat stále více zajímavých akcí, včetně AI témat.

Zdroj: www.habr.com

Přidat komentář