OpenVINO hackathon: prepoznavanje glasa i emocija na Raspberry Pi

30. studenog - 1. prosinca u Nizhny Novgorodu je održan OpenVINO hackathon. Sudionici su zamoljeni da izrade prototip proizvodnog rješenja pomoću alata Intel OpenVINO. Organizatori su predložili popis okvirnih tema kojima bi se mogli rukovoditi pri odabiru zadatka, no konačna odluka ostala je na timovima. Osim toga, poticalo se korištenje modela koji nisu uključeni u proizvod.

OpenVINO hackathon: prepoznavanje glasa i emocija na Raspberry Pi

U ovom članku ćemo vam ispričati kako smo napravili naš prototip proizvoda, s kojim smo na kraju osvojili prvo mjesto.

Na hackathonu je sudjelovalo više od 10 timova. Lijepo je što su neki došli iz drugih krajeva. Mjesto održavanja hackathona bio je kompleks "Kremlinsky on Pochain", gdje su unutra, u pratnji, bile obješene drevne fotografije Nižnjeg Novgoroda! (Podsjećam vas da se trenutno središnji ured Intela nalazi u Nižnjem Novgorodu). Sudionici su dobili 26 sati da napišu kod, a na kraju su morali prezentirati svoje rješenje. Zasebna prednost bila je prisutnost demo sesije kako bi se osiguralo da je sve planirano stvarno provedeno i da ne ostaju ideje u prezentaciji. Roba, grickalice, hrana, sve je bilo tu!

Osim toga, Intel je po izboru osigurao kamere, Raspberry PI, Neural Compute Stick 2.

Izbor zadatka

Jedan od najtežih dijelova priprema za slobodni hackathon je odabir izazova. Odmah smo odlučili osmisliti nešto što još nije bilo u proizvodu, budući da je u najavi stajalo da je to vrlo dobrodošlo.

Nakon što je analizirao model, koji su uključeni u proizvod u trenutnom izdanju, dolazimo do zaključka da većina njih rješava različite probleme računalnog vida. Štoviše, vrlo je teško smisliti problem u području računalnog 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 javnoj domeni. Odlučili smo kopati u drugom smjeru - 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 temelju njenog lica, ali:

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

Razvijmo ideju: uzmimo ideju za maloprodajni segment kao osnovu. Zadovoljstvo kupaca možete mjeriti na blagajni trgovine. Ako je netko od kupaca nezadovoljan uslugom i počne povisivati ​​ton, možete odmah pozvati administratora za pomoć.
U ovom slučaju, moramo dodati prepoznavanje ljudskog glasa, to će nam omogućiti razlikovanje zaposlenika trgovine od kupaca i pružanje analitike za svakog pojedinca. Pa, osim toga, bit će moguće analizirati ponašanje samih zaposlenika trgovine, procijeniti atmosferu u timu, zvuči dobro!

Formuliramo zahtjeve za naše rješenje:

  • Mala veličina ciljnog uređaja
  • Rad u stvarnom vremenu
  • Niska cijena
  • Jednostavna skalabilnost

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

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

Postoji samo jedna mala stvar koju trebate učiniti: trebate nabaviti mikrofon. Obični USB mikrofon će poslužiti, ali neće izgledati dobro zajedno s 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 Voice Kit, na kojem se nalazi žičani stereo mikrofon.

Preuzmite Raspbian s Repozitorij AIY projekata i prenesite ga na flash disk, provjerite radi li mikrofon pomoću sljedeće naredbe (snimit će zvuk u trajanju od 5 sekundi i spremiti ga u datoteku):

arecord -d 5 -r 16000 test.wav

Odmah moram napomenuti da je mikrofon vrlo osjetljiv i dobro hvata buku. Da biste to popravili, idemo na alsamixer, odaberite Capture devices i smanjite razinu ulaznog signala na 50-60%.

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

Dodavanje gumba indikatora

Dok rastavljamo AIY Voice Kit, sjetili smo se da postoji RGB tipka čijim se pozadinskim osvjetljenjem može upravljati softverom. Tražimo "Google AIY Led" i nalazimo dokumentaciju: https://aiyprojects.readthedocs.io/en/latest/aiy.leds.html
Zašto ne koristiti ovaj gumb za prikaz prepoznate emocije, imamo samo 7 klasa, a gumb ima 8 boja, sasvim dovoljno!

Povezujemo gumb preko GPIO-a na Voice Bonnet, učitavamo potrebne biblioteke (one su već instalirane u distribucijskom kompletu iz AIY projekata)

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

Kreirajmo dikt u kojem će svaka emocija imati odgovarajuću boju u obliku RGB Tuple i objekt klase aiy.leds.Leds, preko kojeg ć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 na kraju, 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 hackathon: prepoznavanje glasa i emocija na Raspberry Pi
Gumb, gori!

Rad s glasom

Koristit ćemo pyaudio za snimanje streama s mikrofona i webrtcvad za filtriranje buke i otkrivanje glasa. Osim toga, stvorit ćemo red čekanja u koji ćemo asinkrono dodavati i uklanjati glasovne izvatke.

Budući da webrtcvad ima ograničenje veličine isporučenog fragmenta - mora biti jednak 10/20/30ms, a obuka modela za prepoznavanje emocija (kako ćemo kasnije saznati) provedena je na skupu podataka od 48kHz, mi ćemo uhvatiti dijelove veličine 48000×20ms/1000×1(mono)=960 bajtova. Webrtcvad će vratiti True/False za svaki od ovih dijelova, što odgovara prisutnosti ili odsutnosti glasa u komadu.

Implementirajmo sljedeću logiku:

  • Dodat ćemo na popis one dijelove za koje postoji glasovanje; ako nema glasanja, tada ćemo povećati brojač praznih dijelova.
  • Ako je brojač praznih chunkova >=30 (600 ms), tada gledamo veličinu popisa akumuliranih chunkova; ako je >250, tada ga dodajemo u red čekanja; ako nije, smatramo da je duljina zapisa nije dovoljno da se unese u model za identifikaciju govornika.
  • Ako je brojač praznih dijelova još uvijek < 30, a veličina popisa akumuliranih dijelova prelazi 300, tada ćemo fragment dodati u red čekanja za točnije predviđanje. (jer se emocije s 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, a uz to ih pretvoriti u interni format OpenVINO-a - IR (Intermediate Representation). Isprobali smo oko 5-7 različitih rješenja s githuba, a ako je model za prepoznavanje emocija proradio odmah, onda smo s prepoznavanjem glasa morali čekati duže - koriste složenije arhitekture.

Usredotočeni smo na sljedeće:

Zatim ćemo govoriti o modelima pretvaranja, počevši od teorije. OpenVINO uključuje nekoliko modula:

  • Otvorite Model Zoo, modeli iz kojih se mogu koristiti i uključiti u vaš proizvod
  • Model Optimzer, zahvaljujući kojem možete pretvoriti model iz različitih okvirnih formata (Tensorflow, ONNX itd.) u Intermediate Representation format, s kojim ćemo dalje raditi
  • Inference Engine vam omogućuje pokretanje modela u IR formatu na Intelovim procesorima, Myriad čipovima i Neural Compute Stick akceleratorima
  • Najučinkovitija verzija OpenCV-a (s podrškom za Inference Engine)
    Svaki model u IR formatu opisan je s dvije datoteke: .xml i .bin.
    Modeli se pretvaraju u IR format putem alata za optimizaciju modela 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ćuje odabir formata podataka s kojim će model raditi. Podržani su FP32, FP16, INT8. Odabir optimalne vrste podataka može dati dobar poticaj 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 već konvertirani model u IR formatu preko DNN modula učitati u OpenCV i proslijediti mu ga.

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

    Posljednji redak u ovom slučaju omogućuje preusmjeravanje izračuna na Neural Compute Stick, osnovni izrač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š zvuk na prozore određene veličine (za nas je to 0.4 s), pretvaramo svaki od tih prozora u MFCC, koji zatim šaljemo u mrežu:

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

    Zatim, uzmimo najčešću klasu za sve prozore. Jednostavno rješenje, ali za hackathon ne morate smišljati nešto previše nejasno, samo ako imate vremena. Imamo još puno posla, pa idemo dalje – pozabavit ćemo se prepoznavanjem glasa. Potrebno je napraviti nekakvu bazu podataka u koju bi se spremali spektrogrami unaprijed snimljenih glasova. Budući da je ostalo malo vremena, riješit ćemo ovaj problem najbolje što možemo.

    Naime, kreiramo skriptu za snimanje glasovnog isječka (radi na isti način kao što je gore opisano, samo će kada se prekine s tipkovnice spremiti glas u datoteku).

    Pokušajmo:

    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 Fourierovu transformaciju, dobivamo spektrogram i spremamo ga kao numpy polje (.npy):

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

    Više detalja u datoteci create_base.py
    Kao rezultat toga, kada pokrenemo glavnu skriptu, dobit ćemo ugradnje 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 primimo ugradnju iz zvučnog segmenta, moći ćemo odrediti kome pripada uzimajući kosinusnu udaljenost od prolaza do svih glasova u bazi podataka (što je manji, to je vjerojatnije) - 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 bih želio 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 dodavati nove modele i fokusirali smo se na pisanje prototipa web aplikacije.

    Web aplikacija

    Važna točka: uzimamo ruter sa sobom od kuće i postavljamo svoju lokalnu mrežu, pomaže u povezivanju uređaja i prijenosnih računala preko mreže.

    Pozadina je end-to-end kanal za poruke između prednje strane i Raspberry Pi-ja, temeljen na websocket tehnologiji (http preko tcp protokola).

    Prva faza je primanje obrađenih informacija od raspberry-a, odnosno prediktora upakiranih u json koji se na pola puta spremaju u bazu kako bi se mogla generirati statistika o emocionalnoj pozadini korisnika za to razdoblje. Ovaj paket se zatim šalje sučelju, koje koristi pretplatu i prima pakete od krajnje točke websocketa. Cijeli pozadinski mehanizam izgrađen je u golang jeziku; odabran je jer je vrlo prikladan za asinkrone zadatke, s kojima se goroutine dobro nose.
    Prilikom pristupa krajnjoj točki korisnik se registrira i upisuje u strukturu, zatim se prima njegova poruka. I korisnik i poruka se unose u zajednički hub, iz kojeg se poruke već šalju dalje (na pretplaćeni front), a ako korisnik prekine vezu (malina ili front), tada mu se otkazuje pretplata i uklanja iz čvorište.

    OpenVINO hackathon: prepoznavanje glasa i emocija na Raspberry Pi
    Čekamo vezu s leđa

    Front-end je web aplikacija napisana u JavaScriptu koja koristi biblioteku React za ubrzavanje i pojednostavljenje procesa razvoja. Svrha ove aplikacije je vizualizacija podataka dobivenih korištenjem algoritama koji se izvode na stražnjoj strani i izravno na Raspberry Pi. Stranica ima sekcijsko usmjeravanje implementirano pomoću react-routera, ali glavna stranica od interesa je glavna stranica, gdje se kontinuirani tok podataka prima u stvarnom vremenu od poslužitelja pomoću WebSocket tehnologije. Raspberry Pi detektira glas, utvrđuje pripada li određenoj osobi iz registrirane baze podataka i klijentu šalje listu vjerojatnosti. Klijent prikazuje najnovije relevantne podatke, prikazuje avatar osobe koja je najvjerojatnije govorila u mikrofon, kao i emocije s kojima izgovara riječi.

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

    Zaključak

    Nije bilo moguće dovršiti sve kako je planirano, jednostavno nismo imali vremena, pa smo se najviše nadali u demo, da će sve uspjeti. U prezentaciji su pričali o tome kako sve funkcionira, koje su modele uzeli, s kakvim problemima su se susretali. Sljedeći je bio demo dio - stručnjaci su hodali prostorijom nasumičnim redoslijedom i prilazili svakom timu kako bi pogledali radni prototip. Pitali su i nas, svatko je odgovorio na svoj dio, web su ostavili na laptopu i sve je stvarno radilo kako se i očekivalo.

    Napominjem da je ukupna cijena našeg rješenja bila 150 USD:

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

    Kako poboljšati:

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

    Spremište: https://github.com/vladimirwest/OpenEMO

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

    Na kraju želim zahvaliti organizatorima i sudionicima. Među projektima drugih timova osobno nam se svidjelo rješenje za nadzor slobodnih parkirnih mjesta. Za nas je to bilo iznimno cool iskustvo uranjanja u proizvod i razvoj. Nadam se da će se u regijama održavati sve više zanimljivih događaja, uključujući i teme umjetne inteligencije.

Izvor: www.habr.com

Dodajte komentar