OpenVINO hackathon: prepoznavanje glasu in čustev na Raspberry Pi

30. november - 1. december v Nižnem Novgorodu je potekal OpenVINO hackathon. Udeleženci so bili naprošeni, da ustvarijo prototip rešitve izdelka z uporabo orodja Intel OpenVINO. Organizatorji so predlagali seznam približnih tem, ki bi jih lahko vodili pri izbiri naloge, vendar je končna odločitev ostala pri ekipah. Poleg tega se je spodbujala uporaba modelov, ki niso vključeni v izdelek.

OpenVINO hackathon: prepoznavanje glasu in čustev na Raspberry Pi

V tem članku vam bomo povedali, kako smo ustvarili naš prototip izdelka, s katerim smo na koncu zasedli prvo mesto.

Na hackathonu je sodelovalo več kot 10 ekip. Lepo je, da jih je nekaj prišlo iz drugih regij. Prizorišče hackathona je bil kompleks "Kremlinsky on Pochain", v katerem so bile v spremstvu obešene starodavne fotografije Nižnega Novgoroda! (Spomnim vas, da je trenutno centralna pisarna Intel v Nižnem Novgorodu). Udeleženci so imeli 26 ur časa za pisanje kode, na koncu pa so morali predstaviti svojo rešitev. Posebna prednost je bila prisotnost predstavitvene seje, da bi se prepričali, ali je bilo vse načrtovano dejansko izvedeno in niso ostale ideje v predstavitvi. Blago, prigrizki, hrana, vse je bilo tudi tam!

Poleg tega je Intel opcijsko zagotovil kamere, Raspberry PI, Neural Compute Stick 2.

Izbor nalog

Eden najtežjih delov priprave na hackathon v prosti obliki je izbira izziva. Takoj smo se odločili, da pripravimo nekaj, kar še ni bilo v izdelku, saj je bilo v objavi pisalo, da je to zelo dobrodošlo.

Po analizi modeli, ki so vključeni v izdelek v trenutni izdaji, ugotavljamo, da jih večina rešuje različne težave z računalniškim vidom. Poleg tega je zelo težko izmisliti problem na področju računalniškega vida, ki ga ni mogoče rešiti z uporabo OpenVINO, in tudi če ga je mogoče izumiti, je težko najti vnaprej pripravljene modele v javni domeni. Odločimo se, da bomo kopali v drugo smer – proti obdelavi govora in analitiki. Oglejmo si zanimivo nalogo prepoznavanja čustev iz govora. Povedati je treba, da OpenVINO že ima model, ki določa človekova čustva glede na njegov obraz, vendar:

  • Teoretično je možno ustvariti kombiniran algoritem, ki bo deloval tako na zvoku kot na sliki, kar naj bi povečalo natančnost.
  • Kamere imajo običajno ozek vidni kot, za pokrivanje velikega območja je potrebna več kot ena kamera, zvok pa nima te omejitve.

Razvijajmo idejo: za osnovo vzemimo idejo za maloprodajni segment. Zadovoljstvo strank lahko merite na blagajnah trgovin. Če katera od strank ni zadovoljna s storitvijo in začne zviševati ton, lahko takoj pokličete skrbnika za pomoč.
V tem primeru moramo dodati prepoznavanje človeškega glasu, to nam bo omogočilo razlikovanje zaposlenih v trgovini od strank in zagotavljanje analitike za vsakega posameznika. No, poleg tega bo mogoče analizirati vedenje samih zaposlenih v trgovini, oceniti vzdušje v ekipi, sliši se dobro!

Oblikujemo zahteve za našo rešitev:

  • Majhna velikost ciljne naprave
  • Delovanje v realnem času
  • Nizka cena
  • Enostavna razširljivost

Posledično kot ciljno napravo izberemo Raspberry Pi 3 c Intel NCS 2.

Tukaj je pomembno opozoriti na eno pomembno značilnost NCS - najbolje deluje s standardnimi arhitekturami CNN, če pa morate na njem zagnati model s plastmi po meri, potem pričakujte optimizacijo na nizki ravni.

Narediti morate samo eno malenkost: nabaviti morate mikrofon. Običajni mikrofon USB bo zadostoval, vendar skupaj z RPI ne bo videti dobro. Toda tudi tukaj je rešitev dobesedno »v bližini«. Za snemanje glasu se odločimo za uporabo plošče Voice Bonnet iz kompleta Google AIY glasovni komplet, na katerem je žični stereo mikrofon.

Prenesite Raspbian iz Repozitorij projektov AIY in ga naložite na bliskovni pogon, preizkusite, ali mikrofon deluje z naslednjim ukazom (posnel bo 5 sekund dolg zvok in ga shranil v datoteko):

arecord -d 5 -r 16000 test.wav

Takoj moram opozoriti, da je mikrofon zelo občutljiv in dobro zajema hrup. Če želite to popraviti, pojdimo na alsamixer, izberite Capture devices in zmanjšajte raven vhodnega signala na 50-60%.

OpenVINO hackathon: prepoznavanje glasu in čustev na Raspberry Pi
Telo predelamo s pilo in vse se prilega, lahko celo zaprete s pokrovom

Dodajanje indikatorskega gumba

Ko razstavljamo AIY Voice Kit, se spomnimo, da obstaja gumb RGB, katerega osvetlitev ozadja je mogoče nadzorovati s programsko opremo. Iščemo »Google AIY Led« in najdemo dokumentacijo: https://aiyprojects.readthedocs.io/en/latest/aiy.leds.html
Zakaj ne bi s tem gumbom prikazali prepoznano čustvo, imamo samo 7 razredov, gumb pa ima 8 barv, ravno dovolj!

Gumb povežemo prek GPIO z Voice Bonnetom, naložimo potrebne knjižnice (so že nameščene v distribucijskem kompletu iz projektov AIY)

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

Ustvarimo dikt, v katerem bo vsako čustvo imelo ustrezno barvo v obliki RGB Tuple in objekt razreda aiy.leds.Leds, preko katerega bomo posodobili barvo:

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

In končno, po vsaki novi napovedi čustva, bomo barvo gumba posodobili v skladu z njo (po ključu).

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

OpenVINO hackathon: prepoznavanje glasu in čustev na Raspberry Pi
Gumb, gori!

Delo z glasom

Uporabili bomo pyaudio za zajem toka iz mikrofona in webrtcvad za filtriranje šuma in zaznavanje glasu. Poleg tega bomo ustvarili čakalno vrsto, v katero bomo asinhrono dodajali in odstranjevali glasovne izseke.

Ker ima webrtcvad omejitev glede velikosti dobavljenega fragmenta - ta mora biti enaka 10/20/30 ms in ker je bilo usposabljanje modela za prepoznavanje čustev (kot bomo izvedeli kasneje) izvedeno na naboru podatkov 48 kHz, bomo zajeli kose velikosti 48000×20ms/1000×1(mono)=960 bajtov. Webrtcvad bo vrnil True/False za vsakega od teh kosov, kar ustreza prisotnosti ali odsotnosti glasovanja v kosu.

Uveljavimo naslednjo logiko:

  • Na seznam bomo dodali tiste kose, kjer je glasovanje, če glasovanja ni, bomo povečali števec praznih kosov.
  • Če je števec praznih kosov >=30 (600 ms), potem pogledamo velikost seznama nabranih kosov, če je >250, ga dodamo v čakalno vrsto, če ne, upoštevamo, da je dolžina zapisa ni dovolj, da bi ga posredovali modelu za identifikacijo govorca.
  • Če je števec praznih kosov še vedno < 30, velikost seznama zbranih kosov pa presega 300, bomo fragment dodali v čakalno vrsto za natančnejšo napoved. (ker se čustva sčasoma spreminjajo)

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

Čas je, da poiščete vnaprej usposobljene modele v javni domeni, pojdite na github, Google, vendar ne pozabite, da imamo omejitev glede uporabljene arhitekture. To je precej težaven del, saj morate preizkusiti modele na svojih vhodnih podatkih in jih poleg tega pretvoriti v notranji format OpenVINO - IR (Intermediate Representation). Preizkusili smo približno 5-7 različnih rešitev iz githuba in če je model za prepoznavanje čustev deloval takoj, potem smo morali s prepoznavanjem glasu čakati dlje - uporabljajo bolj zapletene arhitekture.

Osredotočeni smo na naslednje:

Nato bomo govorili o pretvorbi modelov, začenši s teorijo. OpenVINO vključuje več modulov:

  • Odprite Model Zoo, modele iz katerega bi lahko uporabili in vključili v svoj izdelek
  • Model Optimzer, zahvaljujoč kateremu lahko pretvorite model iz različnih okvirnih formatov (Tensorflow, ONNX itd.) v format Intermediate Representation, s katerim bomo delali naprej
  • Inference Engine vam omogoča izvajanje modelov v formatu IR na procesorjih Intel, čipih Myriad in pospeševalnikih Neural Compute Stick
  • Najučinkovitejša različica OpenCV (s podporo za Inference Engine)
    Vsak model v formatu IR je opisan z dvema datotekama: .xml in .bin.
    Modeli se pretvorijo v format IR prek Optimizatorja modelov, kot sledi:

    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 vam omogoča, da izberete format podatkov, s katerim bo model deloval. Podprti so FP32, FP16, INT8. Izbira optimalne podatkovne vrste lahko dobro poveča zmogljivost.
    --input_shape označuje dimenzijo vhodnih podatkov. Zdi se, da je možnost dinamičnega spreminjanja prisotna v API-ju C++, vendar nismo kopali tako daleč in smo to preprosto popravili za enega od modelov.
    Nato poskusimo naložiti že pretvorjeni model v IR formatu preko DNN modula v OpenCV in mu ga posredovati.

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

    Zadnja vrstica v tem primeru omogoča preusmeritev izračunov na Neural Compute Stick, osnovni izračuni se izvajajo na procesorju, vendar v primeru Raspberry Pi to ne bo delovalo, potrebovali boste palico.

    Nato je logika naslednja: svoj zvok razdelimo na okna določene velikosti (za nas je to 0.4 s), vsako od teh oken pretvorimo v MFCC, ki ga nato podamo v mrežo:

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

    Nato vzemimo najpogostejši razred za vsa okna. Preprosta rešitev, a za hackathon vam ni treba izmisliti nečesa preveč neumnega, le če imate čas. Čaka nas še veliko dela, zato gremo naprej – ukvarjali se bomo s prepoznavo glasu. Treba je narediti nekakšno bazo podatkov, v kateri bi bili shranjeni spektrogrami vnaprej posnetih glasov. Ker je časa še malo, bomo to težavo rešili po najboljših močeh.

    Izdelamo namreč skripto za snemanje glasovnega izseka (deluje na enak način kot je opisano zgoraj, le ob prekinitvi s tipkovnice shrani glas v datoteko).

    Poskusimo:

    python3 voice_db/record_voice.py test.wav

    Posnamemo glasove več oseb (v našem primeru treh članov ekipe)
    Nato za vsak posneti glas izvedemo hitro Fourierjevo transformacijo, pridobimo spektrogram in ga shranimo kot polje numpy (.npy):

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

    Več podrobnosti v datoteki create_base.py
    Kot rezultat, ko zaženemo glavni skript, bomo na samem začetku dobili vdelave iz teh 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)

    Ko prejmemo vdelavo iz ozvočenega segmenta, bomo lahko ugotovili, komu pripada, tako da vzamemo kosinusno razdaljo od prehoda do vseh glasov v bazi podatkov (manjši kot je, večja je verjetnost) - za demo nastavimo prag na 0.3):

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

    Na koncu bi rad omenil, da je bila hitrost sklepanja visoka in je omogočila dodajanje še 1-2 modelov (za vzorec dolžine 7 sekund je bilo za sklepanje potrebnih 2.5). Nismo imeli več časa za dodajanje novih modelov in smo se osredotočili na pisanje prototipa spletne aplikacije.

    Spletna aplikacija

    Pomembna točka: usmerjevalnik vzamemo s seboj od doma in vzpostavimo svoje lokalno omrežje, pomaga pri povezovanju naprave in prenosnikov prek omrežja.

    Zaledje je kanal za sporočila od konca do konca med sprednjo stranjo in Raspberry Pi, ki temelji na tehnologiji websocket (http prek protokola tcp).

    Prva stopnja je prejemanje obdelanih informacij iz maline, torej napovednikov, zapakiranih v json, ki se na polovici njihovega potovanja shranijo v bazo, tako da se lahko ustvari statistika o čustvenem ozadju uporabnika za obdobje. Ta paket se nato pošlje v sprednji del, ki uporablja naročnino in sprejema pakete iz končne točke spletne vtičnice. Celoten zaledni mehanizem je zgrajen v jeziku golang; izbran je bil, ker je zelo primeren za asinhrone naloge, ki jih goroutine dobro obvladajo.
    Pri dostopu do končne točke se uporabnik registrira in vnese v strukturo, nato se sprejme njegovo sporočilo. Tako uporabnik kot sporočilo se vneseta v skupno vozlišče, iz katerega se sporočila že pošiljajo naprej (na naročeno fronto), in če uporabnik prekine povezavo (malina ali fronta), se njegova naročnina prekine in odstrani iz vozlišče.

    OpenVINO hackathon: prepoznavanje glasu in čustev na Raspberry Pi
    Čakamo na povezavo od zadaj

    Front-end je spletna aplikacija, napisana v JavaScriptu, ki uporablja knjižnico React za pospešitev in poenostavitev razvojnega procesa. Namen te aplikacije je vizualizacija podatkov, pridobljenih z uporabo algoritmov, ki se izvajajo na zadnji strani in neposredno na Raspberry Pi. Stran ima sekcijsko usmerjanje, ki je implementirano z uporabo react-routerja, vendar je glavna zanimiva stran glavna stran, kjer neprekinjen tok podatkov prejema v realnem času s strežnika s tehnologijo WebSocket. Raspberry Pi zazna glas, ugotovi, ali pripada določeni osebi iz registrirane baze podatkov, in odjemalcu pošlje seznam verjetnosti. Stranka prikaže najnovejše relevantne podatke, prikaže avatar osebe, ki je najverjetneje govorila v mikrofon, pa tudi čustva, s katerimi izgovarja besede.

    OpenVINO hackathon: prepoznavanje glasu in čustev na Raspberry Pi
    Domača stran s posodobljenimi napovedmi

    Zaključek

    Ni bilo mogoče dokončati vsega, kot je bilo načrtovano, preprosto nismo imeli časa, zato je bilo glavno upanje v demo, da bo vse delovalo. V predstavitvi so govorili o tem, kako vse poteka, katere modele so vzeli, na kakšne težave so naleteli. Sledil je demo del - strokovnjaki so hodili po sobi v naključnem vrstnem redu in pristopili k vsaki ekipi, da bi si ogledali delujoči prototip. Tudi oni so nas spraševali, vsak je odgovarjal po svoje, splet so pustili na prenosniku in res je vse delovalo po pričakovanjih.

    Naj opozorim, da so bili skupni stroški naše rešitve 150 USD:

    • Raspberry Pi 3 ~ 35 USD
    • Google AIY Voice Bonnet (plačate lahko pristojbino za govornika) ~ 15 $
    • Intel NCS 2 ~ 100 $

    Kako izboljšati:

    • Uporabite registracijo pri stranki - prosite za branje besedila, ki se ustvari naključno
    • Dodajte še nekaj modelov: spol in starost lahko določite z glasom
    • Ločite sočasno zveneče glasove (diarizacija)

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

    OpenVINO hackathon: prepoznavanje glasu in čustev na Raspberry Pi
    Utrujeni, a veseli smo

    Za zaključek bi se rad zahvalil organizatorjem in udeležencem. Med projekti drugih ekip nam je bila osebno všeč rešitev za spremljanje prostih parkirnih mest. Za nas je bila to noro kul izkušnja potopitve v izdelek in razvoj. Upam, da bo v regijah vedno več zanimivih dogodkov, tudi na temo AI.

Vir: www.habr.com

Dodaj komentar