OpenVINO hakaton: prepoznavanje glasa i emocija na Raspberry Pi

30. novembar - 1. decembar održan je u Nižnjem Novgorodu OpenVINO hackathon. Od učesnika je zatraženo da kreiraju prototip rješenja proizvoda koristeći Intel OpenVINO alat. Organizatori su predložili listu okvirnih tema kojima bi se mogli voditi pri odabiru zadatka, ali je konačna odluka ostala na timovima. Osim toga, ohrabruje se korištenje modela koji nisu uključeni u proizvod.

OpenVINO hakaton: prepoznavanje glasa i emocija na Raspberry Pi

U ovom članku ćemo vam reći kako smo kreirali naš prototip proizvoda, s kojim smo na kraju zauzeli prvo mjesto.

Na hakatonu je učestvovalo više od 10 timova. Lijepo je što su neki od njih došli iz drugih krajeva. Mesto održavanja hakatona bio je kompleks „Kremljinski na Počejnu“, gde su u pratnji bile okačene drevne fotografije Nižnjeg Novgoroda! (Podsjećam da se trenutno centralna kancelarija Intela nalazi u Nižnjem Novgorodu). Učesnicima je dato 26 sati da napišu kod, a na kraju su morali predstaviti svoje rješenje. Posebna prednost je prisustvo demo sesije kako bi se uverilo da je sve planirano zaista sprovedeno i da nisu ostale ideje u prezentaciji. Roba, grickalice, hrana, sve je bilo tu!

Pored toga, Intel je opciono obezbedio kamere, Raspberry PI, Neural Compute Stick 2.

Izbor zadataka

Jedan od najtežih dijelova pripreme za hakaton slobodne forme je odabir izazova. Odmah smo odlučili da smislimo nešto što još nije bilo u proizvodu, jer je u najavi pisalo da je to vrlo dobrodošlo.

Nakon analize modeli, koji su uključeni u proizvod u trenutnom izdanju, dolazimo do zaključka da većina njih rješava različite probleme s računalnim vidom. Štaviše, vrlo je teško doći do problema u polju kompjuterskog vida koji se ne može riješiti korištenjem OpenVINO-a, a čak i ako se može izmisliti, teško je pronaći unaprijed obučene modele u javnom domenu. Odlučujemo da kopamo u drugom pravcu – prema obradi govora i analitici. Razmotrimo zanimljiv zadatak prepoznavanja emocija iz govora. Mora se reći da OpenVINO već ima model koji određuje emocije osobe na osnovu njenog lica, ali:

  • U teoriji, moguće je kreirati kombinovani algoritam koji će raditi i na zvuku i na slici, što bi trebalo dati povećanje tačnosti.
  • Kamere obično imaju uzak ugao gledanja; potrebno je više od jedne kamere za pokrivanje velikog područja; zvuk nema takvo ograničenje.

Hajde da razvijemo ideju: uzmimo ideju za maloprodajni segment kao osnovu. Zadovoljstvo kupaca možete mjeriti na blagajni u trgovinama. Ako je neko od korisnika nezadovoljan uslugom i počne da diže ton, možete odmah pozvati administratora u pomoć.
U ovom slučaju moramo dodati prepoznavanje ljudskog glasa, to će nam omogućiti da razlikujemo zaposlenike u trgovini od kupaca i pružimo analitiku za svakog pojedinca. Pa, osim toga, moći će se analizirati ponašanje samih zaposlenika trgovine, ocijeniti atmosferu u timu, zvuči dobro!

Formuliramo zahtjeve za naše rješenje:

  • Mala veličina ciljnog uređaja
  • Rad u realnom vremenu
  • Niska cijena
  • Laka skalabilnost

Kao rezultat, biramo Raspberry Pi 3 c kao ciljni uređaj Intel NCS 2.

Ovdje je važno napomenuti jednu važnu karakteristiku NCS-a - najbolje radi sa standardnim CNN arhitekturama, ali ako trebate pokrenuti model sa prilagođenim slojevima na njemu, očekujte optimizaciju niskog nivoa.

Postoji samo jedna mala stvar: morate nabaviti mikrofon. Običan USB mikrofon će poslužiti, ali neće izgledati dobro zajedno sa RPI. Ali čak i ovdje rješenje doslovno „leži u blizini“. Za snimanje glasa, odlučili smo koristiti ploču Voice Bonnet iz kompleta Google AIY glasovni komplet, na kojem se nalazi žičani stereo mikrofon.

Preuzmite Raspbian sa Repozitorijum AIY projekata i otpremite ga na fleš disk, testirajte da mikrofon radi koristeći sljedeću naredbu (snimiće zvuk u trajanju od 5 sekundi i sačuvati ga u datoteku):

arecord -d 5 -r 16000 test.wav

Odmah treba da primetim da je mikrofon veoma osetljiv i dobro hvata buku. Da to popravimo, idemo na alsamixer, odaberite Capture devices i smanjite nivo ulaznog signala na 50-60%.

OpenVINO hakaton: prepoznavanje glasa i emocija na Raspberry Pi
Tijelo modificiramo turpijom i sve stane, čak možete zatvoriti i poklopcem

Dodavanje dugmeta indikatora

Dok rastavljamo AIY Voice Kit, setimo se da postoji RGB dugme, čije pozadinsko osvetljenje se može kontrolisati softverom. Tražimo "Google AIY Led" i pronalazimo dokumentaciju: https://aiyprojects.readthedocs.io/en/latest/aiy.leds.html
Zašto ne koristite ovo dugme za prikaz prepoznate emocije, imamo samo 7 časova, a dugme ima 8 boja, sasvim dovoljno!

Povezujemo dugme preko GPIO na Voice Bonnet, učitavamo potrebne biblioteke (one su već instalirane u distributivnom kompletu iz AIY projekata)

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

Kreirajmo dict u kojem će svaka emocija imati odgovarajuću boju u obliku RGB Tuple i objekta klase aiy.leds.Leds, kroz koji ćemo ažurirati boju:

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 konačno, nakon svakog novog predviđanja emocije, ažurirat ćemo boju gumba u skladu s njom (po ključu).

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

OpenVINO hakaton: prepoznavanje glasa i emocija na Raspberry Pi
Dugme, spali!

Rad sa glasom

Koristit ćemo pyaudio da uhvatimo stream iz mikrofona i webrtcvad za filtriranje buke i detekciju glasa. Osim toga, kreirat ćemo red u koji ćemo asinhrono dodavati i uklanjati glasovne izvode.

Budući da webrtcvad ima ograničenje na veličinu dostavljenog fragmenta - ono mora biti jednako 10/20/30ms, a obuka modela za prepoznavanje emocija (kako ćemo kasnije saznati) izvedena je na skupu podataka od 48kHz, mi ćemo uhvatiti komade veličine 48000×20ms/1000×1( mono)=960 bajtova. Webrtcvad će vratiti Tačno/Netačno za svaki od ovih dijelova, što odgovara prisutnosti ili odsustvu glasa u dijelu.

Hajde da implementiramo sledeću logiku:

  • Na listu ćemo dodati one dijelove za koje postoji glasanje; ako nema glasanja, onda ćemo povećati brojač praznih dijelova.
  • Ako je brojač praznih komada >=30 (600 ms), onda gledamo veličinu liste akumuliranih komada; ako je >250, onda ga dodajemo u red; ako nije, smatramo da je dužina zapisa nije dovoljno da ga prenese modelu da identifikuje govornika.
  • Ako je brojač praznih komada još uvijek < 30, a veličina liste akumuliranih dijelova prelazi 300, tada ćemo fragment dodati u red za preciznije predviđanje. (jer se emocije vremenom mijenjaju)

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

Vrijeme je da potražite unaprijed obučene modele u javnoj domeni, idite na github, Google, ali imajte na umu da imamo ograničenja u korištenoj arhitekturi. Ovo je prilično težak dio, jer morate testirati modele na svojim ulaznim podacima, i pored toga, konvertirati ih u OpenVINO interni format - IR (Intermediate Representation). Isprobali smo oko 5-7 različitih rješenja sa githuba, i ako je model za prepoznavanje emocija proradio odmah, onda smo s prepoznavanjem glasa morali čekati duže - koriste složenije arhitekture.

Fokusiramo se na sljedeće:

Sljedeće ćemo govoriti o pretvaranju modela, počevši od teorije. OpenVINO uključuje nekoliko modula:

  • Otvorite model Zoo, modeli iz kojih bi se mogli koristiti i uključiti u vaš proizvod
  • Model Optimzer, zahvaljujući kojem možete pretvoriti model iz različitih formata okvira (Tensorflow, ONNX itd.) u format Intermediate Representation, s kojim ćemo dalje raditi
  • Inference Engine vam omogućava da pokrenete modele u IR formatu na Intel procesorima, Myriad čipovima i Neural Compute Stick akceleratorima
  • Najefikasnija verzija OpenCV-a (sa podrškom za Inference Engine)
    Svaki model u IR formatu je opisan sa dvije datoteke: .xml i .bin.
    Modeli se konvertuju u IR format preko Model Optimizer-a na sljedeći način:

    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 omogućava vam da odaberete format podataka s kojim će model raditi. Podržani su FP32, FP16, INT8. Odabir optimalnog tipa podataka može dati dobar napredak u performansama.
    --input_shape označava dimenziju ulaznih podataka. Čini se da je mogućnost dinamičke promjene prisutna u C++ API-ju, ali nismo kopali toliko daleko i jednostavno smo to popravili za jedan od modela.
    Zatim, pokušajmo učitati već konvertirani model u IR formatu preko DNN modula u OpenCV i proslijediti ga na njega.

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

    Posljednji red u ovom slučaju vam omogućava da preusmjerite kalkulacije na Neural Compute Stick, osnovni proračuni se izvode na procesoru, ali u slučaju Raspberry Pi to neće raditi, trebat će vam stick.

    Dalje, logika je sljedeća: dijelimo naš audio na prozore određene veličine (za nas je to 0.4 s), svaki od ovih prozora pretvaramo u MFCC, koji zatim šaljemo u mrežu:

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

    Zatim, uzmimo najobičniju klasu za sve prozore. Jednostavno rješenje, ali za hakaton ne morate smišljati nešto previše zamršeno, samo ako imate vremena. Imamo još dosta posla, pa idemo dalje – bavićemo se prepoznavanjem glasa. Potrebno je napraviti neku vrstu baze podataka u koju bi se pohranjivali spektrogrami unaprijed snimljenih glasova. Pošto je ostalo malo vremena, riješit ćemo ovaj problem najbolje što možemo.

    Naime, kreiramo skriptu za snimanje glasovnog izvoda (radi na isti način kao što je gore opisano, samo kada se prekine sa tastature snimiće glas u fajl).

    Pokusajmo:

    python3 voice_db/record_voice.py test.wav

    Snimamo glasove nekoliko ljudi (u našem slučaju tri člana tima)
    Zatim, za svaki snimljeni glas izvodimo brzu Furierovu transformaciju, dobijamo spektrogram i spremamo ga kao numpy niz (.npy):

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

    Više detalja u fajlu create_base.py
    Kao rezultat toga, kada pokrenemo glavnu skriptu, dobićemo ugrađivanje iz ovih spektrograma na samom početku:

    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)

    Nakon što dobijemo embeddovanje iz ozvučenog segmenta, moći ćemo odrediti kome pripada uzimajući kosinusnu udaljenost od prolaza do svih glasova u bazi (što je manji, to je vjerovatniji) - za demo postavljamo prag do 0.3):

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

    Na kraju, želio bih napomenuti da je brzina zaključivanja bila velika i omogućila je dodavanje još 1-2 modela (za uzorak od 7 sekundi bilo je potrebno 2.5 za zaključivanje). Više nismo imali vremena za dodavanje novih modela i fokusirali smo se na pisanje prototipa web aplikacije.

    Web aplikacija

    Važna stvar: uzimamo ruter sa sobom od kuće i postavljamo našu lokalnu mrežu, pomaže u povezivanju uređaja i laptopa preko mreže.

    Backend je end-to-end kanal za poruke između fronta i Raspberry Pi, zasnovan na websocket tehnologiji (http preko tcp protokola).

    Prva faza je primanje obrađenih informacija od raspberry-a, odnosno prediktora upakovanih u json, koji se na pola puta pohranjuju u bazi podataka kako bi se mogla generirati statistika o emocionalnoj pozadini korisnika za određeni period. Ovaj paket se zatim šalje na frontend, koji koristi pretplatu i prima pakete sa krajnje tačke websocketa. Cijeli pozadinski mehanizam je izgrađen na jeziku golang; odabran je zato što je dobro prilagođen za asinhrone zadatke, s kojima se goroutine dobro snalaze.
    Prilikom pristupa krajnjoj tački, korisnik se registruje i unosi u strukturu, zatim se prima njegova poruka. I korisnik i poruka se unose u zajedničko čvorište iz kojeg se poruke već šalju dalje (na pretplaćeni front), a ako korisnik zatvori vezu (malina ili front), onda mu se pretplata otkazuje i uklanja se sa čvorište.

    OpenVINO hakaton: prepoznavanje glasa i emocija na Raspberry Pi
    Čekamo vezu sa zadnje strane

    Front-end je web aplikacija napisana u JavaScript-u koja koristi React biblioteku kako bi se ubrzao i pojednostavio proces razvoja. Svrha ove aplikacije je vizualizacija podataka dobivenih korištenjem algoritama koji rade na pozadinskoj strani i direktno na Raspberry Pi. Stranica ima sekcijsko rutiranje implementirano pomoću react-routera, ali glavna stranica od interesa je glavna stranica, gdje se kontinuirani tok podataka prima u realnom vremenu sa servera koristeći WebSocket tehnologiju. Raspberry Pi detektuje glas, utvrđuje da li pripada određenoj osobi iz registrovane baze podataka i šalje listu verovatnoće klijentu. Klijent prikazuje najnovije relevantne podatke, prikazuje avatar osobe koja je najvjerovatnije progovorila u mikrofon, kao i emociju s kojom izgovara riječi.

    OpenVINO hakaton: prepoznavanje glasa i emocija na Raspberry Pi
    Početna stranica s ažuriranim predviđanjima

    zaključak

    Nije bilo moguće sve završiti kako je planirano, jednostavno nismo imali vremena, tako da je glavna nada bila u demo-u, da će sve funkcionirati. U prezentaciji su govorili o tome kako sve funkcionira, koje su modele uzeli, na koje probleme su naišli. Sljedeći je bio demo dio – stručnjaci su šetali prostorijom nasumično i prišli svakom timu da pogledaju prototip koji radi. I nama su postavljali pitanja, svako je odgovarao na svoj dio, ostavili su web na laptopu i sve je zaista funkcioniralo kako se očekivalo.

    Dozvolite mi da napomenem da je ukupna cijena našeg rješenja bila 150 USD:

    • Raspberry Pi 3 ~ 35 USD
    • Google AIY Voice Bonnet (možete uzeti naknadu za zvučnik) ~ 15$
    • Intel NCS 2 ~ 100$

    Kako poboljšati:

    • Koristite registraciju od klijenta - zatražite da pročitate tekst koji se generira nasumično
    • Dodajte još nekoliko modela: spol i godine možete odrediti glasom
    • Odvojite glasove koji istovremeno zvuče (dijarizacija)

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

    OpenVINO hakaton: prepoznavanje glasa i emocija na Raspberry Pi
    Umorni ali sretni smo

    Na kraju želim da se zahvalim organizatorima i učesnicima. Od projekata drugih timova, nama se lično dopalo rješenje za praćenje besplatnih parking mjesta. Za nas je to bilo divno kul iskustvo uranjanja u proizvod i razvoj. Nadam se da će se u regionima održavati sve više zanimljivih događaja, uključujući i teme AI.

izvor: www.habr.com

Dodajte komentar