OpenVINO hackathon: rekoni voĉon kaj emociojn sur Raspberry Pi

Novembro 30 - decembro 1 en Niĵnij Novgorod okazis OpenVINO-hakatono. Partoprenantoj estis petitaj krei prototipon de produkta solvo uzante la ilaron de Intel OpenVINO. La organizantoj proponis liston de proksimumaj temoj, kiuj povus esti gvidataj dum elekto de tasko, sed la fina decido restis ĉe la teamoj. Krome, la uzo de modeloj ne inkluzivitaj en la produkto estis kuraĝigita.

OpenVINO hackathon: rekoni voĉon kaj emociojn sur Raspberry Pi

En ĉi tiu artikolo ni rakontos al vi pri kiel ni kreis nian prototipon de la produkto, kun kiu ni fine prenis la unuan lokon.

Pli ol 10 teamoj partoprenis la hakatonon. Estas agrable, ke kelkaj el ili venis el aliaj regionoj. La ejo por la hakatono estis la komplekso "Kremlinsky sur Pochain", kie antikvaj fotoj de Niĵnij Novgorod estis penditaj interne, en akompanantaro! (Mi memorigas al vi, ke nuntempe la centra oficejo de Intel situas en Niĵnij Novgorod). Partoprenantoj ricevis 26 horojn por skribi kodon, kaj fine ili devis prezenti sian solvon. Aparta avantaĝo estis la ĉeesto de demo-sesio por certigi, ke ĉio planita estas efektive efektivigita kaj ne restis ideoj en la prezento. Merch, manĝetoj, manĝaĵoj, ĉio estis ankaŭ tie!

Krome, Intel laŭvole disponigis fotilojn, Raspberry PI, Neural Compute Stick 2.

Elekto de taskoj

Unu el la plej malfacilaj partoj de preparado por liberforma hakatono estas elekti defion. Ni tuj decidis elpensi ion, kio ankoraŭ ne estis en la produkto, ĉar la anonco diris, ke tio estas tre bonvena.

Analizinte modeloj, kiuj estas inkluzivitaj en la produkto en la nuna eldono, ni venas al la konkludo, ke la plej multaj el ili solvas diversajn komputilvidajn problemojn. Cetere, estas tre malfacile elpensi problemon en la kampo de komputila vidado, kiu ne povas esti solvita per OpenVINO, kaj eĉ se oni povas esti inventita, estas malfacile trovi antaŭtrejnitajn modelojn en la publika havaĵo. Ni decidas fosi alidirekten - al paroltraktado kaj analizo. Ni konsideru interesan taskon rekoni emociojn el parolo. Oni devas diri, ke OpenVINO jam havas modelon, kiu determinas la emociojn de homo surbaze de sia vizaĝo, sed:

  • En teorio, eblas krei kombinitan algoritmon, kiu funkcios kaj pri sono kaj bildo, kio devus doni pliigon de precizeco.
  • Fotiloj kutime havas mallarĝan rigardan angulon; pli ol unu fotilo estas postulata por kovri grandan areon; sono ne havas tian limigon.

Ni evoluigu la ideon: ni prenu la ideon por la podetala segmento kiel bazon. Vi povas mezuri klientan kontenton ĉe vendejaj kasoj. Se unu el la klientoj estas malkontenta pri la servo kaj komencas levi sian tonon, vi povas tuj voki la administranton por helpo.
En ĉi tiu kazo, ni devas aldoni homan voĉrekonon, ĉi tio permesos al ni distingi vendejajn dungitojn de klientoj kaj provizi analizojn por ĉiu individuo. Nu, krome, eblos analizi la konduton de la vendej-dungitoj mem, taksi la etoson en la teamo, sonas bone!

Ni formulas la postulojn por nia solvo:

  • Malgranda grandeco de la cela aparato
  • Operacio en reala tempo
  • Malalta prezo
  • Facila skaleblo

Kiel rezulto, ni elektas Raspberry Pi 3 c kiel la cela aparato Intel NCS 2.

Ĉi tie gravas noti unu gravan funkcion de NCS - ĝi funkcias plej bone kun normaj CNN-arkitekturoj, sed se vi bezonas ruli modelon kun kutimaj tavoloj sur ĝi, tiam atendu malaltnivelan optimumigon.

Estas nur unu eta afero farenda: vi devas akiri mikrofonon. Regula USB-mikrofono utilos, sed ĝi ne aspektos bone kune kun la RPI. Sed eĉ ĉi tie la solvo laŭvorte "kuŝas proksime". Por registri voĉon, ni decidas uzi la tabulon Voice Bonnet de la ilaro Guglo AIY Voĉa Ilaro, sur kiu estas kablita stereomikrofono.

Elŝutu Raspbian de Deponejo de AIY-projektoj kaj alŝutu ĝin al poŝmemoro, provu, ke la mikrofono funkcias per la jena komando (ĝi registros audio dum 5 sekundoj kaj konservos ĝin en dosiero):

arecord -d 5 -r 16000 test.wav

Mi tuj rimarku, ke la mikrofono estas tre sentema kaj bone kaptas bruon. Por ripari ĉi tion, ni iru al alsamixer, elektu Kapti aparatojn kaj reduktu la enigan signalnivelon al 50-60%.

OpenVINO hackathon: rekoni voĉon kaj emociojn sur Raspberry Pi
Ni modifas la korpon per dosiero kaj ĉio taŭgas, vi eĉ povas fermi ĝin per kovrilo

Aldonante indikilon butonon

Dum disigo de la AIY Voice Kit, ni memoras, ke ekzistas RGB-butono, kies retrolumo povas esti kontrolita per programaro. Ni serĉas "Google AIY Led" kaj trovas dokumentadon: https://aiyprojects.readthedocs.io/en/latest/aiy.leds.html
Kial ne uzi ĉi tiun butonon por montri la rekonitan emocion, ni havas nur 7 klasojn, kaj la butono havas 8 kolorojn, nur sufiĉe!

Ni konektas la butonon per GPIO al Voice Bonnet, ŝarĝas la necesajn bibliotekojn (ili jam estas instalitaj en la dissenda ilaro de AIY-projektoj)

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

Ni kreu dikton en kiu ĉiu emocio havos respondan koloron en formo de RGB-Opo kaj objekto de la klaso aiy.leds.Leds, per kiu ni ĝisdatigos la koloron:

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

Kaj fine, post ĉiu nova antaŭdiro de emocio, ni ĝisdatigos la koloron de la butono laŭ ĝi (per klavo).

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

OpenVINO hackathon: rekoni voĉon kaj emociojn sur Raspberry Pi
Butono, brulu!

Laborante kun voĉo

Ni uzos pyaudio por kapti la fluon de la mikrofono kaj webrtcvad por filtri bruon kaj detekti voĉon. Krome, ni kreos voston, al kiu ni nesinkrone aldonos kaj forigos voĉajn fragmentojn.

Ĉar webrtcvad havas limigon de la grandeco de la provizita fragmento - ĝi devas esti egala al 10/20/30ms, kaj la trejnado de la modelo por rekoni emociojn (kiel ni lernos poste) estis farita sur 48kHz-datumaro, ni faros. kaptu pecojn de grandeco 48000×20ms/1000×1(mono)=960 bajtoj. Webrtcvad redonos Veran/Malveran por ĉiu el ĉi tiuj pecoj, kio respondas al la ĉeesto aŭ foresto de voĉdono en la peco.

Ni efektivigu la sekvan logikon:

  • Ni aldonos al la listo tiujn partojn kie estas voĉdono; se ne estas voĉdono, tiam ni pliigos la nombrilon de malplenaj partoj.
  • Se la nombrilo de malplenaj pecoj estas >=30 (600 ms), tiam ni rigardas la grandecon de la listo de amasigitaj pecoj; se ĝi estas >250, tiam ni aldonas ĝin al la vosto; se ne, ni konsideras ke la longo de la disko ne sufiĉas por nutri ĝin al la modelo por identigi la parolanton.
  • Se la nombrilo de malplenaj pecoj estas ankoraŭ < 30, kaj la grandeco de la listo de amasigitaj pecoj superas 300, tiam ni aldonos la fragmenton al la vosto por pli preciza antaŭdiro. (ĉar emocioj emas ŝanĝiĝi kun la tempo)

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

Estas tempo serĉi antaŭtrejnitajn modelojn en la publika domajno, iru al github, Guglo, sed memoru, ke ni havas limigon pri la uzata arkitekturo. Ĉi tio estas sufiĉe malfacila parto, ĉar vi devas testi la modelojn sur viaj enirdatumoj, kaj krome konverti ilin al la interna formato de OpenVINO - IR (Meza Reprezento). Ni provis ĉirkaŭ 5-7 malsamajn solvojn de github, kaj se la modelo por rekoni emociojn funkciis tuj, tiam kun voĉa rekono ni devis atendi pli longe - ili uzas pli kompleksajn arkitekturojn.

Ni koncentriĝas pri la sekvaj:

Poste ni parolos pri konvertado de modeloj, komencante per teorio. OpenVINO inkluzivas plurajn modulojn:

  • Open Model Zoo, modeloj el kiuj povus esti uzataj kaj inkluzivitaj en via produkto
  • Model Optimzer, dank' al kiu vi povas konverti modelon el diversaj kadroformatoj (Tensorflow, ONNX ktp) al la Meza Reprezento-formato, kun kiu ni laboros plu
  • Inference Engine permesas ruli modelojn en IR-formato sur Intel-procesoroj, Myriad-fritoj kaj Neural Compute Stick-akceliloj
  • La plej efika versio de OpenCV (kun Inference Engine-subteno)
    Ĉiu modelo en IR-formato estas priskribita per du dosieroj: .xml kaj .bin.
    Modeloj estas konvertitaj al IR-formato per Model Optimizer jene:

    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 permesas vin elekti la datumformaton kun kiu la modelo funkcios. FP32, FP16, INT8 estas subtenataj. Elekti la optimuman datumtipo povas doni bonan rendimentan akcelon.
    --input_shape indikas la dimension de la eniga datumo. La kapablo dinamike ŝanĝi ĝin ŝajnas ĉeesti en la C++ API, sed ni ne fosis tiom longe kaj simple riparis ĝin por unu el la modeloj.
    Poste, ni provu ŝargi la jam konvertitan modelon en IR-formato per la DNN-modulo en OpenCV kaj plusendi ĝin al ĝi.

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

    La lasta linio en ĉi tiu kazo permesas vin redirekti kalkulojn al la Neŭrala Komputila Stick, bazaj kalkuloj estas faritaj sur la procesoro, sed en la kazo de la Raspberry Pi ĉi tio ne funkcios, vi bezonos bastonon.

    Poste, la logiko estas jena: ni dividas nian aŭdion en fenestrojn de certa grandeco (por ni ĝi estas 0.4 s), ni konvertas ĉiun el ĉi tiuj fenestroj en MFCC, kiun ni poste nutras al la krado:

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

    Poste, ni prenu la plej oftan klason por ĉiuj fenestroj. Simpla solvo, sed por hakatono vi ne bezonas elpensi ion tro abstruzan, nur se vi havas tempon. Ni ankoraŭ havas multan laboron por fari, do ni pluiru - ni traktos voĉrekonon. Necesas fari ian datumbazon, en kiu spektrogramoj de antaŭregistritaj voĉoj estus stokitaj. Ĉar restas malmulte da tempo, ni solvos ĉi tiun problemon kiel eble plej bone.

    Nome, ni kreas skripton por registri voĉĉerpaĵon (ĝi funkcias same kiel priskribite supre, nur kiam interrompite de la klavaro ĝi konservos la voĉon al dosiero).

    Ni provu:

    python3 voice_db/record_voice.py test.wav

    Ni registras la voĉojn de pluraj homoj (en nia kazo, tri teamanoj)
    Poste, por ĉiu registrita voĉo ni faras rapidan fourier-transformon, akiras spektrogramon kaj konservas ĝin kiel numpy tabelo (.npy):

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

    Pliaj detaloj en la dosiero create_base.py
    Kiel rezulto, kiam ni rulas la ĉefan skripton, ni ricevos enkonstruojn de ĉi tiuj spektrogramoj komence:

    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)

    Post ricevi la enkonstruadon de la sonita segmento, ni povos determini al kiu ĝi apartenas prenante la kosinusdistancon de la trairejo al ĉiuj voĉoj en la datumbazo (ju pli malgranda, des pli verŝajna) - por la demo ni fiksas la sojlon. al 0.3):

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

    Fine mi ŝatus rimarki, ke la inferrapideco estis rapida kaj ebligis aldoni 1-2 pliajn modelojn (por specimeno de 7 sekundoj necesas 2.5 por inferenco). Ni ne plu havis tempon aldoni novajn modelojn kaj koncentriĝis pri verkado de prototipo de la retejo-apliko.

    TTT-apliko

    Grava punkto: ni prenas enkursigilon kun ni de hejme kaj starigas nian lokan reton, ĝi helpas konekti la aparaton kaj tekkomputilojn tra la reto.

    La backend estas fin-al-fina mesaĝkanalo inter la fronto kaj Raspberry Pi, bazita sur websocket-teknologio (protokolo http super tcp).

    La unua etapo estas ricevi prilaboritajn informojn de frambo, tio estas, antaŭdiroj pakitaj en json, kiuj estas konservitaj en la datumbazo duonvoje tra sia vojaĝo por ke statistikoj estu generitaj pri la emocia fono de la uzanto por la periodo. Ĉi tiu pakaĵeto tiam estas sendita al la fasado, kiu uzas abonon kaj ricevas pakaĵetojn de la websocket finpunkto. La tuta malantaŭa mekanismo estas konstruita en la golang-lingvo; ĝi estis elektita ĉar ĝi bone taŭgas por nesinkronaj taskoj, kiujn gorutinoj bone traktas.
    Alirante la finpunkton, la uzanto estas registrita kaj enirita en la strukturon, tiam lia mesaĝo estas ricevita. Kaj la uzanto kaj la mesaĝo estas enmetitaj en komunan nabon, de kiu mesaĝoj estas jam senditaj plu (al la abonita fronto), kaj se la uzanto fermas la konekton (frambo aŭ fronto), tiam lia abono estas nuligita kaj li estas forigita de la nabo.

    OpenVINO hackathon: rekoni voĉon kaj emociojn sur Raspberry Pi
    Ni atendas konekton de malantaŭe

    Frontend estas TTT-apliko skribita en JavaScript uzanta la React-bibliotekon por akceli kaj simpligi la evoluprocezon. La celo de ĉi tiu aplikaĵo estas bildigi datumojn akiritajn per algoritmoj kurantaj sur la malantaŭa flanko kaj rekte sur la Raspberry Pi. La paĝo havas sekcan vojigon efektivigita per reakci-enkursigilo, sed la ĉefa paĝo de intereso estas la ĉefpaĝo, kie kontinua fluo de datumoj estas ricevita en reala tempo de la servilo uzante WebSocket-teknologion. Raspberry Pi detektas voĉon, determinas ĉu ĝi apartenas al specifa persono el la registrita datumbazo, kaj sendas probabloliston al la kliento. La kliento montras la lastajn koncernajn datumojn, montras la avataron de la persono, kiu plej verŝajne parolis en la mikrofonon, kaj ankaŭ la emocion, per kiu li prononcas la vortojn.

    OpenVINO hackathon: rekoni voĉon kaj emociojn sur Raspberry Pi
    Hejmpaĝo kun ĝisdatigitaj prognozoj

    konkludo

    Ne eblis kompletigi ĉion kiel planite, ni simple ne havis tempon, do la ĉefa espero estis en la demo, ke ĉio funkcios. En la prezento ili parolis pri kiel ĉio funkcias, kiajn modelojn ili prenis, kiajn problemojn ili renkontis. Poste estis la demo-parto - spertuloj ĉirkaŭiris la halon en hazarda ordo kaj alproksimiĝis al ĉiu teamo por rigardi la funkciantan prototipon. Ili faris al ni demandojn, ĉiu respondis sian parton, ili lasis la reton sur la tekkomputilo, kaj ĉio vere funkciis kiel atendite.

    Mi rimarku, ke la totala kosto de nia solvo estis $150:

    • Raspberry Pi 3 ~ $35
    • Google AIY Voice Bonnet (vi povas preni kotizon de reparolisto) ~ 15 $
    • Intel NCS 2 ~ 100 $

    Kiel plibonigi:

    • Uzu registradon de la kliento - petu legi la tekston, kiu estas generita hazarde
    • Aldonu kelkajn pliajn modelojn: vi povas determini sekson kaj aĝon per voĉo
    • Apartigi samtempe sonantajn voĉojn (diarigo)

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

    OpenVINO hackathon: rekoni voĉon kaj emociojn sur Raspberry Pi
    Lacaj sed feliĉaj ni estas

    Konklude, mi ŝatus danki vin al la organizantoj kaj partoprenantoj. Inter la projektoj de aliaj teamoj, ni persone ŝatis la solvon por monitorado de senpagaj parkumejoj. Por ni, ĝi estis tre mojosa sperto de mergo en la produkto kaj evoluo. Mi esperas, ke pli kaj pli da interesaj eventoj okazos en la regionoj, inkluzive pri AI-temoj.

fonto: www.habr.com

Aldoni komenton