Hackathon OpenVINO: njohja e zërit dhe emocioneve në Raspberry Pi

30 nëntor - 1 dhjetor në Nizhny Novgorod u mbajt Hackathon OpenVINO. Pjesëmarrësve iu kërkua të krijonin një prototip të një zgjidhje produkti duke përdorur paketën e veglave Intel OpenVINO. Organizatorët propozuan një listë me tema të përafërta nga të cilat mund të udhëhiqeni kur zgjidhni një detyrë, por vendimi përfundimtar mbeti me ekipet. Përveç kësaj, u inkurajua përdorimi i modeleve që nuk përfshihen në produkt.

Hackathon OpenVINO: njohja e zërit dhe emocioneve në Raspberry Pi

Në këtë artikull do t'ju tregojmë se si krijuam prototipin tonë të produktit, me të cilin përfundimisht zumë vendin e parë.

Më shumë se 10 ekipe morën pjesë në hackathon. Është mirë që disa prej tyre kanë ardhur nga rajone të tjera. Vendi i hackathon ishte kompleksi "Kremlinsky on Pochain", ku fotografitë e lashta të Nizhny Novgorod ishin varur brenda, në një shoqërues! (Ju kujtoj se për momentin zyra qendrore e Intel ndodhet në Nizhny Novgorod). Pjesëmarrësve iu dhanë 26 orë kohë për të shkruar kodin dhe në fund ata duhej të paraqisnin zgjidhjen e tyre. Një avantazh më vete ishte prania e një sesioni demo për t'u siguruar që gjithçka e planifikuar ishte zbatuar në të vërtetë dhe nuk mbeti ide në prezantim. Mallra, ushqime, ushqime, gjithçka ishte gjithashtu atje!

Përveç kësaj, Intel ofroi opsionalisht kamera, Raspberry PI, Neural Compute Stick 2.

Zgjedhja e detyrës

Një nga pjesët më të vështira të përgatitjes për një hackathon në formë të lirë është zgjedhja e një sfide. Ne menjëherë vendosëm të dilnim me diçka që nuk ishte ende në produkt, pasi njoftimi thoshte se kjo ishte shumë e mirëpritur.

Duke analizuar модели, të cilat janë përfshirë në produkt në versionin aktual, arrijmë në përfundimin se shumica e tyre zgjidhin probleme të ndryshme të shikimit kompjuterik. Për më tepër, është shumë e vështirë të dalësh me një problem në fushën e vizionit kompjuterik që nuk mund të zgjidhet duke përdorur OpenVINO, dhe edhe nëse mund të shpikesh, është e vështirë të gjesh modele të para-trajnuara në domenin publik. Ne vendosim të gërmojmë në një drejtim tjetër - drejt përpunimit të të folurit dhe analitikës. Le të shqyrtojmë një detyrë interesante të njohjes së emocioneve nga fjalimi. Duhet thënë se OpenVINO tashmë ka një model që përcakton emocionet e një personi bazuar në fytyrën e tij, por:

  • Në teori, është e mundur të krijohet një algoritëm i kombinuar që do të funksionojë si në zë ashtu edhe në imazh, i cili duhet të japë një rritje të saktësisë.
  • Kamerat zakonisht kanë një kënd të ngushtë shikimi; kërkohet më shumë se një kamera për të mbuluar një zonë të madhe; zëri nuk ka një kufizim të tillë.

Le të zhvillojmë idenë: le të marrim si bazë idenë për segmentin e shitjes me pakicë. Ju mund të matni kënaqësinë e klientit në arkat e dyqaneve. Nëse një nga klientët është i pakënaqur me shërbimin dhe fillon të ngrejë tonin e tij, mund të telefononi menjëherë administratorin për ndihmë.
Në këtë rast, ne duhet të shtojmë njohjen e zërit njerëzor, kjo do të na lejojë të dallojmë punonjësit e dyqaneve nga klientët dhe të ofrojmë analitikë për çdo individ. Epo, përveç kësaj, do të jetë e mundur të analizohet sjellja e vetë punonjësve të dyqanit, të vlerësohet atmosfera në ekip, tingëllon mirë!

Ne formulojmë kërkesat për zgjidhjen tonë:

  • Madhësia e vogël e pajisjes së synuar
  • Operacioni në kohë reale
  • Çmimi i ulët
  • Shkallueshmëri e lehtë

Si rezultat, ne zgjedhim Raspberry Pi 3 c si pajisjen e synuar Intel NCS 2.

Këtu është e rëndësishme të theksohet një veçori e rëndësishme e NCS - funksionon më së miri me arkitekturat standarde të CNN, por nëse duhet të ekzekutoni një model me shtresa të personalizuara mbi të, atëherë prisni optimizim të nivelit të ulët.

Ka vetëm një gjë të vogël për të bërë: duhet të marrësh një mikrofon. Një mikrofon i rregullt USB do të funksionojë, por nuk do të duket mirë së bashku me RPI. Por edhe këtu zgjidhja fjalë për fjalë "shtrihet afër". Për të regjistruar zërin, vendosim të përdorim tabelën Voice Bonnet nga kompleti Google AIY Voice Kit, në të cilën ka një mikrofon stereo me tela.

Shkarkoni Raspbian nga Depoja e projekteve AIY dhe ngarkoni atë në një flash drive, provoni që mikrofoni të funksionojë duke përdorur komandën e mëposhtme (ai do të regjistrojë audio për 5 sekonda dhe do ta ruajë në një skedar):

arecord -d 5 -r 16000 test.wav

Duhet të theksoj menjëherë se mikrofoni është shumë i ndjeshëm dhe kap mirë zhurmën. Për ta rregulluar këtë, le të shkojmë te alsamixer, të zgjedhim Capture devices dhe të zvogëlojmë nivelin e sinjalit të hyrjes në 50-60%.

Hackathon OpenVINO: njohja e zërit dhe emocioneve në Raspberry Pi
Trupin e modifikojmë me skedar dhe çdo gjë përshtatet, madje mund ta mbyllni me kapak

Shtimi i një butoni tregues

Ndërsa ndajmë paketën AIY Voice, kujtojmë se ekziston një buton RGB, drita e prapme e të cilit mund të kontrollohet nga softueri. Ne kërkojmë për "Google AIY Led" dhe gjejmë dokumentacionin: https://aiyprojects.readthedocs.io/en/latest/aiy.leds.html
Pse të mos e përdorni këtë buton për të shfaqur emocionin e njohur, ne kemi vetëm 7 klasa, dhe butoni ka 8 ngjyra, mjafton!

Ne e lidhim butonin nëpërmjet GPIO me Voice Bonnet, ngarkojmë bibliotekat e nevojshme (ato janë instaluar tashmë në kompletin e shpërndarjes nga projektet AIY)

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

Le të krijojmë një diktim në të cilin çdo emocion do të ketë një ngjyrë përkatëse në formën e një tupleje RGB dhe një objekt të klasës aiy.leds.Led, përmes së cilës do të përditësojmë ngjyrën:

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

Dhe së fundi, pas çdo parashikimi të ri të një emocioni, ne do të përditësojmë ngjyrën e butonit në përputhje me të (me çelës).

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

Hackathon OpenVINO: njohja e zërit dhe emocioneve në Raspberry Pi
Butoni, djeg!

Puna me zë

Ne do të përdorim pyaudio për të kapur transmetimin nga mikrofoni dhe webrtcvad për të filtruar zhurmën dhe për të zbuluar zërin. Përveç kësaj, ne do të krijojmë një radhë në të cilën do të shtojmë dhe heqim në mënyrë asinkrone fragmente zanore.

Meqenëse webrtcvad ka një kufizim në madhësinë e fragmentit të dhënë - ai duhet të jetë i barabartë me 10/20/30ms, dhe trajnimi i modelit për njohjen e emocioneve (siç do të mësojmë më vonë) është kryer në një grup të dhënash 48 kHz, ne do kapni copa me madhësi 48000×20ms/1000×1(mono)=960 bajt. Webrtcvad do të kthejë E vërtetë/E gabuar për secilën prej këtyre pjesëve, që korrespondon me praninë ose mungesën e një votimi në atë pjesë.

Le të zbatojmë logjikën e mëposhtme:

  • Ne do t'i shtojmë listës ato pjesë ku ka votim; nëse nuk ka votë, atëherë do të rrisim numëruesin e pjesëve boshe.
  • Nëse numëruesi i pjesëve boshe është >=30 (600 ms), atëherë shikojmë madhësinë e listës së pjesëve të grumbulluara; nëse është >250, atëherë e shtojmë në radhë; nëse jo, konsiderojmë se gjatësia e rekordit nuk mjafton për t'i dhënë modelit për të identifikuar folësin.
  • Nëse numëruesi i pjesëve boshe është ende < 30, dhe madhësia e listës së pjesëve të grumbulluara i kalon 300, atëherë ne do ta shtojmë fragmentin në radhë për një parashikim më të saktë. (sepse emocionet kanë tendencë të ndryshojnë me kalimin e kohës)

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

Është koha për të kërkuar modele të trajnuara paraprakisht në domenin publik, shkoni te github, Google, por mbani mend se ne kemi një kufizim në arkitekturën e përdorur. Kjo është një pjesë mjaft e vështirë, sepse ju duhet të testoni modelet në të dhënat tuaja hyrëse, dhe përveç kësaj, t'i konvertoni ato në formatin e brendshëm të OpenVINO - IR (Përfaqësimi i ndërmjetëm). Provuam rreth 5-7 zgjidhje të ndryshme nga github, dhe nëse modeli për njohjen e emocioneve funksionoi menjëherë, atëherë me njohjen e zërit duhej të prisnim më gjatë - ata përdorin arkitektura më komplekse.

Ne fokusohemi në sa vijon:

Më tej do të flasim për konvertimin e modeleve, duke filluar nga teoria. OpenVINO përfshin disa module:

  • Open Model Zoo, modele nga të cilat mund të përdoren dhe të përfshihen në produktin tuaj
  • Model Optimzer, falë të cilit ju mund të konvertoni një model nga formate të ndryshme kornizë (Tensorflow, ONNX etj) në formatin e Përfaqësimit të Ndërmjetëm, me të cilin do të punojmë më tej
  • Inference Engine ju lejon të ekzekutoni modele në formatin IR në procesorë Intel, çipa të shumtë dhe përshpejtues Neural Compute Stick
  • Versioni më efikas i OpenCV (me mbështetjen e Inference Engine)
    Çdo model në formatin IR përshkruhet nga dy skedarë: .xml dhe .bin.
    Modelet konvertohen në formatin IR nëpërmjet Model Optimizer si më poshtë:

    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 ju lejon të zgjidhni formatin e të dhënave me të cilin do të funksionojë modeli. FP32, FP16, INT8 mbështeten. Zgjedhja e llojit optimal të të dhënave mund të japë një rritje të mirë të performancës.
    --input_shape tregon dimensionin e të dhënave hyrëse. Mundësia për ta ndryshuar në mënyrë dinamike duket se është e pranishme në API-në C++, por ne nuk e gërmuam aq larg dhe thjesht e rregulluam atë për një nga modelet.
    Më pas, le të përpiqemi të ngarkojmë modelin tashmë të konvertuar në formatin IR nëpërmjet modulit DNN në OpenCV dhe ta përcjellim atë tek ai.

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

    Rreshti i fundit në këtë rast ju lejon të ridrejtoni llogaritjet në Neural Compute Stick, llogaritjet bazë kryhen në procesor, por në rastin e Raspberry Pi kjo nuk do të funksionojë, do t'ju duhet një shkop.

    Tjetra, logjika është si më poshtë: ne ndajmë audion tonë në dritare të një madhësie të caktuar (për ne është 0.4 s), ne konvertojmë secilën prej këtyre dritareve në MFCC, të cilat më pas i ushqejmë në rrjet:

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

    Më pas, le të marrim klasën më të zakonshme për të gjitha dritaret. Një zgjidhje e thjeshtë, por për një hackathon nuk keni nevojë të dilni me diçka shumë të pakëndshme, vetëm nëse keni kohë. Kemi ende shumë punë për të bërë, kështu që le të vazhdojmë - do të merremi me njohjen e zërit. Është e nevojshme të krijohet një lloj databaze në të cilën do të ruhen spektrogramet e zërave të regjistruar paraprakisht. Duke qenë se ka mbetur pak kohë, këtë çështje do ta zgjidhim sa më mirë.

    Gjegjësisht, ne krijojmë një skript për regjistrimin e një fragmenti zanor (funksionon në të njëjtën mënyrë siç përshkruhet më sipër, vetëm kur ndërpritet nga tastiera do ta ruajë zërin në një skedar).

    Le te perpiqemi:

    python3 voice_db/record_voice.py test.wav

    Ne regjistrojmë zërat e disa njerëzve (në rastin tonë, tre anëtarëve të ekipit)
    Më pas, për secilin zë të regjistruar ne kryejmë një transformim të shpejtë të katërfishtë, marrim një spektrogram dhe e ruajmë atë si një grup numpy (.npy):

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

    Më shumë detaje në dosje create_base.py
    Si rezultat, kur të ekzekutojmë skenarin kryesor, do të marrim përfshirje nga këto spektrograme që në fillim:

    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)

    Pas marrjes së ngulitjes nga segmenti i tingullit, ne do të jemi në gjendje të përcaktojmë se kujt i përket duke marrë distancën kosinus nga kalimi tek të gjithë zërat në bazën e të dhënave (sa më i vogël, aq më i mundshëm) - për demonstrimin vendosim pragun në 0.3):

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

    Në fund, dëshiroj të vërej se shpejtësia e konkluzionit ishte e shpejtë dhe bëri të mundur shtimin e 1-2 modeleve të tjera (për një mostër 7 sekonda të gjatë u deshën 2.5 për përfundimin). Nuk kishim më kohë për të shtuar modele të reja dhe u fokusuam në shkrimin e një prototipi të aplikacionit në internet.

    Aplikacioni Ueb

    Një pikë e rëndësishme: marrim një ruter me vete nga shtëpia dhe konfigurojmë rrjetin tonë lokal, ndihmon për të lidhur pajisjen dhe laptopët përmes rrjetit.

    Backend është një kanal mesazhesh nga fundi në fund midis pjesës së përparme dhe Raspberry Pi, i bazuar në teknologjinë websocket (http mbi protokollin tcp).

    Faza e parë është marrja e informacionit të përpunuar nga raspberry, domethënë parashikuesit e paketuar në json, të cilët ruhen në bazën e të dhënave në gjysmë të rrugës së tyre, në mënyrë që të gjenerohen statistika rreth sfondit emocional të përdoruesit për periudhën. Kjo paketë dërgohet më pas në frontend, i cili përdor abonimin dhe merr pako nga pika fundore e uebsocket-it. I gjithë mekanizmi i backend-it është ndërtuar në gjuhën golang; ai u zgjodh sepse është i përshtatshëm për detyra asinkrone, të cilat gorutinat i trajtojnë mirë.
    Kur hyni në pikën përfundimtare, përdoruesi regjistrohet dhe futet në strukturë, pastaj merret mesazhi i tij. Si përdoruesi ashtu edhe mesazhi futen në një qendër të përbashkët, nga e cila mesazhet tashmë dërgohen më tej (në frontin e abonuar), dhe nëse përdoruesi mbyll lidhjen (mjedër ose përpara), atëherë abonimi i tij anulohet dhe ai hiqet nga qendër.

    Hackathon OpenVINO: njohja e zërit dhe emocioneve në Raspberry Pi
    Presim një lidhje nga mbrapa

    Front-end është një aplikacion ueb i shkruar në JavaScript duke përdorur bibliotekën React për të shpejtuar dhe thjeshtuar procesin e zhvillimit. Qëllimi i këtij aplikacioni është të vizualizojë të dhënat e marra duke përdorur algoritme që funksionojnë në anën e pasme dhe drejtpërdrejt në Raspberry Pi. Faqja ka drejtim seksional të zbatuar duke përdorur react-router, por faqja kryesore me interes është faqja kryesore, ku një rrjedhë e vazhdueshme e të dhënave merret në kohë reale nga serveri duke përdorur teknologjinë WebSocket. Raspberry Pi zbulon një zë, përcakton nëse i përket një personi specifik nga baza e të dhënave të regjistruar dhe i dërgon klientit një listë probabiliteti. Klienti shfaq të dhënat më të fundit përkatëse, shfaq avatarin e personit që ka shumë të ngjarë të ketë folur në mikrofon, si dhe emocionin me të cilin shqipton fjalët.

    Hackathon OpenVINO: njohja e zërit dhe emocioneve në Raspberry Pi
    Faqja kryesore me parashikime të përditësuara

    Përfundim

    Nuk ishte e mundur të përfundonim gjithçka siç ishte planifikuar, thjesht nuk kishim kohë, kështu që shpresa kryesore ishte në demo, se gjithçka do të funksiononte. Në prezantim ata folën se si funksionon çdo gjë, çfarë modele kanë marrë, çfarë problemesh kanë hasur. Tjetra ishte pjesa demo - ekspertët shëtisnin nëpër dhomë në mënyrë të rastësishme dhe iu afruan secilit ekip për të parë prototipin e punës. Ata na bënë pyetje gjithashtu, të gjithë iu përgjigjën pjesës së tyre, ata lanë ueb-in në laptop dhe gjithçka funksionoi vërtet siç pritej.

    Më lejoni të vërej se kostoja totale e zgjidhjes sonë ishte 150 dollarë:

    • Raspberry Pi 3 ~ 35 dollarë
    • Google AIY Voice Bonnet (mund të marrësh një tarifë për folës) ~ 15$
    • Intel NCS 2 ~ 100$

    Si të përmirësohet:

    • Përdorni regjistrimin nga klienti - kërkoni të lexoni tekstin që krijohet rastësisht
    • Shtoni disa modele të tjera: ju mund të përcaktoni gjininë dhe moshën me zë
    • Ndani zëra që tingëllojnë njëkohësisht (diarizimi)

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

    Hackathon OpenVINO: njohja e zërit dhe emocioneve në Raspberry Pi
    Të lodhur por të lumtur jemi

    Si përfundim, dëshiroj të falënderoj organizatorët dhe pjesëmarrësit. Ndër projektet e ekipeve të tjera, personalisht na pëlqeu zgjidhja për monitorimin e hapësirave të parkimit falas. Për ne, ishte një përvojë jashtëzakonisht e lezetshme e zhytjes në produkt dhe zhvillim. Shpresoj që gjithnjë e më shumë ngjarje interesante do të mbahen në rajone, përfshirë temat e AI.

Burimi: www.habr.com

Shto një koment