OpenVINO hakatonas: balso ir emocijų atpažinimas naudojant Raspberry Pi

Lapkričio 30 – gruodžio 1 dienomis Nižnij Novgorode vyko OpenVINO hakatonas. Dalyvių buvo paprašyta sukurti produkto sprendimo prototipą naudojant „Intel OpenVINO“ įrankių rinkinį. Organizatoriai pasiūlė sąrašą apytikslių temų, kuriomis būtų galima vadovautis renkantis užduotį, tačiau galutinis sprendimas liko komandoms. Be to, buvo skatinama naudoti modelius, kurių nėra gaminyje.

OpenVINO hakatonas: balso ir emocijų atpažinimas naudojant Raspberry Pi

Šiame straipsnyje papasakosime apie tai, kaip sukūrėme savo gaminio prototipą, su kuriuo galiausiai užėmėme pirmąją vietą.

Hakatone dalyvavo daugiau nei 10 komandų. Smagu, kad dalis jų atkeliavo iš kitų regionų. Hakatono vieta buvo kompleksas „Kremlinsky on Pochain“, kurio viduje buvo pakabintos senovinės Nižnij Novgorodo nuotraukos! (Primenu, kad šiuo metu centrinis „Intel“ biuras yra Nižnij Novgorode). Dalyviai turėjo 26 valandas parašyti kodą, o pabaigoje jie turėjo pristatyti savo sprendimą. Atskiras privalumas buvo demonstracinės sesijos buvimas, siekiant įsitikinti, kad viskas, kas buvo suplanuota, realiai įgyvendinta ir pristatyme neliko idėjų. Prekės, užkandžiai, maistas, visko taip pat buvo!

Be to, „Intel“ pasirinktinai pateikė kameras, „Raspberry PI“, „Neural Compute Stick 2“.

Užduočių pasirinkimas

Viena iš sunkiausių pasiruošimo laisvos formos hakatonui dalių yra iššūkio pasirinkimas. Nedelsdami nusprendėme sugalvoti tai, ko dar nebuvo gaminyje, nes pranešime buvo teigiama, kad tai labai sveikintina.

Išanalizavęs modeliai, kurie yra įtraukti į gaminį dabartinėje laidoje, darome išvadą, kad dauguma jų išsprendžia įvairias kompiuterinio regėjimo problemas. Be to, kompiuterinio matymo srityje labai sunku sugalvoti problemą, kurios nepavyktų išspręsti naudojant OpenVINO, ir net jei ją galima sugalvoti, sunku rasti iš anksto paruoštus modelius viešoje erdvėje. Nusprendžiame kasti kita kryptimi – kalbos apdorojimo ir analitikos link. Panagrinėkime įdomią užduotį atpažinti emocijas iš kalbos. Reikia pasakyti, kad OpenVINO jau turi modelį, kuris nustato žmogaus emocijas pagal veidą, tačiau:

  • Teoriškai galima sukurti kombinuotą algoritmą, kuris veiktų ir garsu, ir vaizdu, o tai turėtų padidinti tikslumą.
  • Kameros paprastai turi siaurą žiūrėjimo kampą, reikia daugiau nei vienos kameros, kad apimtų didelį plotą, garsui tokių apribojimų nėra.

Plėtokime idėją: remkime idėją mažmeninės prekybos segmentui. Galite išmatuoti klientų pasitenkinimą parduotuvės kasose. Jei kuris nors iš klientų yra nepatenkintas paslauga ir pradeda kelti toną, galite nedelsiant kreiptis pagalbos į administratorių.
Šiuo atveju reikia pridėti žmogaus balso atpažinimą, tai leis atskirti parduotuvės darbuotojus nuo klientų ir pateikti analizę kiekvienam individualiai. Na, o be to, bus galima analizuoti pačių parduotuvės darbuotojų elgesį, įvertinti atmosferą kolektyve, skamba gerai!

Mes suformuluojame reikalavimus mūsų sprendimui:

  • Mažas tikslinio įrenginio dydis
  • Veikimas realiu laiku
  • Maža kaina
  • Lengvas mastelio keitimas

Dėl to kaip tikslinį įrenginį pasirenkame Raspberry Pi 3 c Intel NCS 2.

Čia svarbu atkreipti dėmesį į vieną svarbią NCS savybę – ji geriausiai veikia su standartinėmis CNN architektūromis, tačiau jei reikia paleisti modelį su pasirinktiniais sluoksniais, tikėkitės žemo lygio optimizavimo.

Reikia padaryti tik vieną mažą dalyką: reikia turėti mikrofoną. Tiks ir įprastas USB mikrofonas, bet jis neatrodys gerai kartu su RPI. Bet net ir čia sprendimas tiesiogine prasme „slypi šalia“. Norėdami įrašyti balsą, nusprendžiame naudoti „Voice Bonnet“ plokštę iš komplekto Google AIY balso rinkinys, ant kurio yra laidinis stereo mikrofonas.

Atsisiųskite Raspbian iš AIY projektų saugykla ir įkelkite jį į „flash“ diską, patikrinkite, ar mikrofonas veikia, naudodami šią komandą (jis įrašys garsą 5 sekundes ir išsaugos faile):

arecord -d 5 -r 16000 test.wav

Iš karto turėčiau pastebėti, kad mikrofonas yra labai jautrus ir gerai sugeria triukšmą. Norėdami tai išspręsti, eikime į alsamixer, pasirinkite Capture devices ir sumažinkite įvesties signalo lygį iki 50-60%.

OpenVINO hakatonas: balso ir emocijų atpažinimas naudojant Raspberry Pi
Korpusą modifikuojame dilde ir viskas tinka, galima net uždaryti dangteliu

Indikatoriaus mygtuko pridėjimas

Išardydami AIY Voice Kit prisimename, kad yra RGB mygtukas, kurio apšvietimą galima valdyti programine įranga. Ieškome „Google AIY Led“ ir randame dokumentus: https://aiyprojects.readthedocs.io/en/latest/aiy.leds.html
Kodėl gi nepanaudojus šio mygtuko atpažintai emocijai parodyti, turime tik 7 klases, o mygtukas turi 8 spalvas, tik tiek!

Mygtuką per GPIO prijungiame prie Voice Bonnet, įkeliame reikiamas bibliotekas (jos jau įdiegtos platinimo rinkinyje iš AIY projektų)

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

Sukurkime diktą, kuriame kiekviena emocija turės atitinkamą spalvą RGB Tuple pavidalu ir aiy.leds.Leds klasės objektą, per kurį atnaujinsime spalvą:

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

Ir galiausiai po kiekvieno naujo emocijos numatymo mygtuko spalvą atnaujinsime pagal ją (pagal raktą).

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

OpenVINO hakatonas: balso ir emocijų atpažinimas naudojant Raspberry Pi
Mygtuku, sudegink!

Darbas su balsu

Naudosime pyaudio srautui iš mikrofono užfiksuoti, o webrtcvad – triukšmui filtruoti ir balsui aptikti. Be to, sukursime eilę, į kurią asinchroniškai pridėsime ir pašalinsime balso ištraukas.

Kadangi webrtcvad turi pateikiamo fragmento dydžio apribojimą – jis turi būti lygus 10/20/30 ms, o emocijų atpažinimo modelio mokymas (kaip sužinosime vėliau) buvo atliktas naudojant 48 kHz duomenų rinkinį, užfiksuoti 48000×20ms/1000×1(mono)=960 baitų dydžio gabalus. Webrtcvad grąžins True/False kiekvienam iš šių dalių, o tai atitinka balso buvimą ar nebuvimą gabale.

Įdiegkime tokią logiką:

  • Į sąrašą įtrauksime tuos gabalus, kuriuose yra balsavimas, jei nėra balsavimo, tada padidinsime tuščių gabalų skaitiklį.
  • Jei tuščių gabalų skaitiklis yra >=30 (600 ms), tada žiūrime į sukauptų gabalų sąrašo dydį; jei jis yra >250, tada įtraukiame į eilę; jei ne, laikome, kad ilgis įrašo neužtenka, kad jį paduotų modeliui, kad būtų galima nustatyti garsiakalbį.
  • Jei tuščių gabalų skaitiklis vis dar yra < 30, o sukauptų gabalų sąrašo dydis viršija 300, tada fragmentą įtrauksime į eilę, kad būtų galima tiksliau prognozuoti. (nes emocijos laikui bėgant keičiasi)

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

Atėjo laikas ieškoti iš anksto parengtų modelių viešajame domene, eikite į „github“, „Google“, tačiau atminkite, kad taikome naudojamos architektūros apribojimus. Tai gana sudėtinga dalis, nes jūs turite išbandyti modelius pagal savo įvesties duomenis, be to, konvertuoti juos į OpenVINO vidinį formatą - IR (tarpinį atstovavimą). Iš „github“ išbandėme apie 5-7 skirtingus sprendimus, o jei emocijų atpažinimo modelis suveikė iš karto, tai su balso atpažinimu teko laukti ilgiau – jie naudoja sudėtingesnę architektūrą.

Mes sutelkiame dėmesį į šiuos dalykus:

Toliau kalbėsime apie modelių konvertavimą, pradedant nuo teorijos. OpenVINO apima kelis modulius:

  • Atidarykite modelių zoologijos sodą, kurio modelius galite naudoti ir įtraukti į jūsų gaminį
  • Modelio optimizavimo priemonė, kurios dėka galite konvertuoti modelį iš įvairių karkaso formatų (Tensorflow, ONNX ir kt.) į tarpinį atstovavimo formatą, su kuriuo dirbsime toliau
  • „Inference Engine“ leidžia paleisti modelius IR formatu „Intel“ procesoriuose, „Myriad“ lustuose ir „Neural Compute Stick“ greitintuvuose
  • Veiksmingiausia OpenCV versija (su Inference Engine palaikymu)
    Kiekvienas modelis IR formatu apibūdinamas dviem failais: .xml ir .bin.
    Modeliai konvertuojami į IR formatą naudojant modelio optimizavimo priemonę taip:

    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 leidžia pasirinkti duomenų formatą, su kuriuo modelis veiks. Palaikomi FP32, FP16, INT8. Pasirinkus optimalų duomenų tipą, gali padidėti našumas.
    --input_shape nurodo įvesties duomenų matmenis. Galimybė jį dinamiškai keisti, atrodo, yra C++ API, bet mes taip toli nesigilinome ir paprasčiausiai tai pataisėme vienam iš modelių.
    Toliau pabandykime jau konvertuotą modelį IR formatu per DNN modulį įkelti į OpenCV ir persiųsti jam.

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

    Paskutinė eilutė šiuo atveju leidžia nukreipti skaičiavimus į Neural Compute Stick, pagrindiniai skaičiavimai atliekami procesoriuje, tačiau Raspberry Pi atveju tai neveiks, reikės lazdos.

    Be to, logika yra tokia: mes padalijame savo garsą į tam tikro dydžio langus (mums tai yra 0.4 s), kiekvieną iš šių langų konvertuojame į MFCC, kurį tada perduodame į tinklelį:

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

    Toliau paimkime labiausiai paplitusią visų langų klasę. Paprastas sprendimas, bet hakatonui nereikia sugalvoti kažko pernelyg įmantraus, tik jei turite laiko. Dar turime daug darbo, tad judėkime toliau – susitvarkysime su balso atpažinimu. Reikia padaryti kažkokią duomenų bazę, kurioje būtų saugomos iš anksto įrašytų balsų spektrogramos. Kadangi laiko liko mažai, šią problemą išspręsime kaip galime.

    Būtent, sukuriame balso ištraukos įrašymo scenarijų (veikia taip pat, kaip aprašyta aukščiau, tik pertraukus nuo klaviatūros balsą išsaugos į failą).

    Pabandykime:

    python3 voice_db/record_voice.py test.wav

    Įrašome kelių žmonių (mūsų atveju trijų komandos narių) balsus
    Tada kiekvienam įrašytam balsui atliekame greitą Furjė transformaciją, gauname spektrogramą ir išsaugome ją kaip numpy masyvą (.npy):

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

    Daugiau informacijos faile create_base.py
    Dėl to, kai paleisime pagrindinį scenarijų, pačioje pradžioje gausime įterpimus iš šių 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)

    Gavę įdėjimą iš įgarsinto segmento, galėsime nustatyti, kam jis priklauso, paėmę kosinuso atstumą nuo ištraukos iki visų duomenų bazės balsų (kuo mažesnis, tuo labiau tikėtina) - demonstracinei versijai nustatome slenkstį iki 0.3):

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

    Pabaigoje norėčiau pažymėti, kad išvados greitis buvo greitas ir leido pridėti dar 1-2 modelius (7 sekundžių trukmės pavyzdžiui prireikė 2.5 išvados). Nebeturėjome laiko pridėti naujų modelių ir susitelkėme ties žiniatinklio programos prototipo kūrimu.

    Žiniatinklio programa

    Svarbus dalykas: iš namų pasiimame maršrutizatorių ir nustatome vietinį tinklą, jis padeda prijungti įrenginį ir nešiojamus kompiuterius per tinklą.

    Užpakalinė programa yra tiesioginis pranešimų kanalas tarp priekinės dalies ir Raspberry Pi, pagrįstas žiniatinklio lizdo technologija (http per tcp protokolą).

    Pirmasis etapas yra apdorotos informacijos gavimas iš raspberry, tai yra json supakuotų prognozių, kurios išsaugomos duomenų bazėje įpusėjus kelionei, kad būtų galima generuoti statistiką apie vartotojo emocinį foną per laikotarpį. Tada šis paketas siunčiamas į sąsają, kuri naudoja prenumeratą ir gauna paketus iš žiniatinklio lizdo galinio taško. Visas backend mechanizmas sukurtas golango kalba; jis buvo pasirinktas todėl, kad jis puikiai tinka asinchroninėms užduotims, kurias gorutinai atlieka gerai.
    Prisijungus prie galinio taško, vartotojas registruojamas ir įvedamas į struktūrą, tada gaunamas jo pranešimas. Tiek vartotojas, tiek žinutė įvedami į bendrą šakotuvą, iš kurio žinutės jau siunčiamos toliau (į prenumeruojamą frontą), o jei vartotojas nutraukia ryšį (avietinis ar priekinis), tada jo prenumerata atšaukiama ir jis pašalinamas iš mazgas.

    OpenVINO hakatonas: balso ir emocijų atpažinimas naudojant Raspberry Pi
    Laukiame ryšio iš galo

    Front-end yra žiniatinklio programa, parašyta „JavaScript“, naudojant „React“ biblioteką, siekiant pagreitinti ir supaprastinti kūrimo procesą. Šios programos tikslas yra vizualizuoti duomenis, gautus naudojant algoritmus, veikiančius galinėje pusėje ir tiesiogiai Raspberry Pi. Puslapyje yra sekcinis maršrutas, įdiegtas naudojant react-router, tačiau pagrindinis dominantis puslapis yra pagrindinis puslapis, kuriame iš serverio, naudojant WebSocket technologiją, realiu laiku gaunamas nenutrūkstamas duomenų srautas. Raspberry Pi aptinka balsą, iš registruotos duomenų bazės nustato, ar jis priklauso konkrečiam asmeniui, ir išsiunčia klientui tikimybių sąrašą. Klientas parodo naujausius susijusius duomenis, parodo žmogaus, kuris greičiausiai kalbėjo į mikrofoną, avatarą, taip pat emociją, su kuria jis taria žodžius.

    OpenVINO hakatonas: balso ir emocijų atpažinimas naudojant Raspberry Pi
    Pagrindinis puslapis su atnaujintomis prognozėmis

    išvada

    Nebuvo įmanoma visko atlikti kaip planuota, tiesiog neturėjome laiko, todėl pagrindinė viltis buvo demonstracijoje, kad viskas pavyks. Pristatyme jie kalbėjo apie tai, kaip viskas veikia, kokius modelius ėmėsi, su kokiomis problemomis susidūrė. Toliau buvo demonstracinė dalis – ekspertai atsitiktine tvarka vaikščiojo po kambarį ir priėjo prie kiekvienos komandos pažiūrėti veikiančio prototipo. Jie taip pat uždavė mums klausimų, kiekvienas atsakė į savo dalį, jie paliko internetą nešiojamajame kompiuteryje ir viskas iš tikrųjų veikė taip, kaip tikėtasi.

    Norėčiau pažymėti, kad bendra mūsų sprendimo kaina buvo 150 USD:

    • Raspberry Pi 3 ~ 35 USD
    • Google AIY Voice Bonnet (galite imti perkalbėtojo mokestį) ~ 15 USD
    • Intel NCS 2 ~ 100 USD

    Kaip pagerinti:

    • Naudokite kliento registraciją – paprašykite perskaityti atsitiktinai sugeneruotą tekstą
    • Pridėkite dar kelis modelius: lytį ir amžių galite nustatyti balsu
    • Atskirkite vienu metu skambančius balsus (diarizacija)

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

    OpenVINO hakatonas: balso ir emocijų atpažinimas naudojant Raspberry Pi
    Esame pavargę, bet laimingi

    Baigdamas noriu padėkoti organizatoriams ir dalyviams. Iš kitų komandų projektų mums asmeniškai patiko nemokamų parkavimo vietų stebėjimo sprendimas. Mums tai buvo nepaprastai šauni pasinėrimo į produktą ir kūrimą patirtis. Tikiuosi, kad regionuose vyks vis daugiau įdomių renginių, taip pat ir AI temomis.

Šaltinis: www.habr.com

Добавить комментарий