OpenVINO hackathon: genkender stemme og følelser på Raspberry Pi

30. november - 1. december i Nizhny Novgorod blev afholdt OpenVINO hackathon. Deltagerne blev bedt om at skabe en prototype af en produktløsning ved hjælp af Intel OpenVINO-værktøjssættet. Arrangørerne foreslog en liste over omtrentlige emner, der kunne styres efter, når de skulle vælge en opgave, men den endelige beslutning forblev hos holdene. Derudover blev der opfordret til brug af modeller, der ikke er inkluderet i produktet.

OpenVINO hackathon: genkender stemme og følelser på Raspberry Pi

I denne artikel vil vi fortælle dig om, hvordan vi skabte vores prototype af produktet, som vi til sidst tog førstepladsen med.

Mere end 10 hold deltog i hackathonet. Det er rart, at nogle af dem kom fra andre egne. Stedet for hackathonet var "Kremlinsky on Pochain"-komplekset, hvor gamle fotografier af Nizhny Novgorod blev hængt inde i et følge! (Jeg minder dig om, at Intels hovedkontor i øjeblikket ligger i Nizhny Novgorod). Deltagerne fik 26 timer til at skrive kode, og til sidst skulle de præsentere deres løsning. En separat fordel var tilstedeværelsen af ​​en demo-session for at sikre, at alt det planlagte faktisk blev implementeret og ikke forblev ideer i præsentationen. Merch, snacks, mad, alt var der også!

Derudover leverede Intel valgfrit kameraer, Raspberry PI, Neural Compute Stick 2.

Valg af opgave

En af de sværeste dele af forberedelsen til et hackathon i fri form er at vælge en udfordring. Vi besluttede os straks for at komme med noget, der endnu ikke var i produktet, da meddelelsen sagde, at dette var meget velkomment.

Efter at have analyseret model, som er inkluderet i produktet i den aktuelle udgivelse, kommer vi til den konklusion, at de fleste af dem løser forskellige computersynsproblemer. Desuden er det meget svært at komme med et problem inden for computersyn, som ikke kan løses ved hjælp af OpenVINO, og selvom en kan opfindes, er det svært at finde præ-trænede modeller i det offentlige domæne. Vi beslutter os for at grave i en anden retning - mod talebehandling og analyse. Lad os overveje en interessant opgave med at genkende følelser fra tale. Det skal siges, at OpenVINO allerede har en model, der bestemmer en persons følelser baseret på deres ansigt, men:

  • I teorien er det muligt at lave en kombineret algoritme, der vil virke på både lyd og billede, hvilket skulle give en øget nøjagtighed.
  • Kameraer har normalt en snæver betragtningsvinkel; mere end ét kamera er påkrævet for at dække et stort område; lyd har ikke en sådan begrænsning.

Lad os udvikle ideen: lad os tage ideen til detailsegmentet som grundlag. Du kan måle kundetilfredsheden ved butikkens kasser. Hvis en af ​​kunderne er utilfreds med tjenesten og begynder at hæve deres tone, kan du straks ringe til administratoren for at få hjælp.
I dette tilfælde skal vi tilføje menneskelig stemmegenkendelse, dette vil give os mulighed for at skelne butiksansatte fra kunder og levere analyser til hver enkelt. Nå, derudover vil det være muligt at analysere butiksansattes adfærd selv, evaluere atmosfæren i teamet, lyder godt!

Vi formulerer kravene til vores løsning:

  • Målenhedens lille størrelse
  • Drift i realtid
  • Lav pris
  • Nem skalerbarhed

Som et resultat vælger vi Raspberry Pi 3 c som målenheden Intel NCS 2.

Her er det vigtigt at bemærke en vigtig egenskab ved NCS - den fungerer bedst med standard CNN-arkitekturer, men hvis du skal køre en model med brugerdefinerede lag på, så forvent optimering på lavt niveau.

Der er kun en lille ting at gøre: du skal have en mikrofon. En almindelig USB-mikrofon duer, men den vil ikke se godt ud sammen med RPI. Men selv her ligger løsningen bogstaveligt talt i nærheden. For at optage stemme beslutter vi at bruge Voice Bonnet-tavlen fra sættet Google AIY Voice Kit, hvorpå der er en kablet stereomikrofon.

Download Raspbian fra AIY projekter repository og upload det til et flashdrev, test at mikrofonen virker ved hjælp af følgende kommando (den optager lyd i 5 sekunder og gemmer den i en fil):

arecord -d 5 -r 16000 test.wav

Jeg skal straks bemærke, at mikrofonen er meget følsom og opfanger støj godt. For at løse dette, lad os gå til alsamixer, vælge Capture devices og reducere inputsignalniveauet til 50-60%.

OpenVINO hackathon: genkender stemme og følelser på Raspberry Pi
Vi modificerer kroppen med en fil og alt passer, du kan endda lukke den med et låg

Tilføjelse af en indikatorknap

Mens vi skiller AIY Voice Kit fra hinanden, husker vi, at der er en RGB-knap, hvis baggrundsbelysning kan styres af software. Vi søger på "Google AIY Led" og finder dokumentation: https://aiyprojects.readthedocs.io/en/latest/aiy.leds.html
Hvorfor ikke bruge denne knap til at vise de genkendte følelser, vi har kun 7 klasser, og knappen har 8 farver, lige nok!

Vi forbinder knappen via GPIO til Voice Bonnet, indlæser de nødvendige biblioteker (de er allerede installeret i distributionssættet fra AIY-projekter)

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

Lad os skabe en diktat, hvor hver følelse vil have en tilsvarende farve i form af en RGB Tuple og et objekt af klassen aiy.leds.Leds, hvorigennem vi opdaterer farven:

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

Og endelig, efter hver ny forudsigelse af en følelse, vil vi opdatere farven på knappen i overensstemmelse med den (med nøgle).

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

OpenVINO hackathon: genkender stemme og følelser på Raspberry Pi
Knap, brænd!

Arbejde med stemme

Vi vil bruge pyaudio til at fange streamen fra mikrofonen og webrtcvad for at filtrere støj og registrere stemme. Derudover vil vi oprette en kø, hvortil vi asynkront tilføjer og fjerner stemmeuddrag.

Da webrtcvad har en begrænsning på størrelsen af ​​det leverede fragment - det skal være lig med 10/20/30ms, og træningen af ​​modellen til genkendelse af følelser (som vi vil lære senere) blev udført på et 48kHz datasæt, vil vi fange bidder af størrelsen 48000×20ms/1000×1(mono)=960 bytes. Webrtcvad returnerer True/False for hver af disse bidder, hvilket svarer til tilstedeværelsen eller fraværet af en stemme i chunken.

Lad os implementere følgende logik:

  • Vi tilføjer til listen de bidder, hvor der er en stemme; hvis der ikke er nogen afstemning, vil vi øge tælleren af ​​tomme bidder.
  • Hvis tælleren for tomme bidder er >=30 (600 ms), så ser vi på størrelsen af ​​listen over akkumulerede bidder; hvis den er >250, så føjer vi den til køen; hvis ikke, vurderer vi, at længden af rekorden er ikke nok til at føre den til modellen for at identificere højttaleren.
  • Hvis tælleren for tomme bidder stadig er < 30, og størrelsen af ​​listen over akkumulerede bidder overstiger 300, tilføjer vi fragmentet til køen for en mere præcis forudsigelse. (fordi følelser har en tendens til at ændre sig over tid)

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

Det er tid til at lede efter præ-trænede modeller i det offentlige domæne, gå til github, Google, men husk, at vi har en begrænsning på den anvendte arkitektur. Dette er en ret svær del, fordi du skal teste modellerne på dine inputdata og derudover konvertere dem til OpenVINOs interne format - IR (Intermediate Representation). Vi prøvede omkring 5-7 forskellige løsninger fra github, og hvis modellen til genkendelse af følelser virkede med det samme, så måtte vi med stemmegenkendelse vente længere – de bruger mere komplekse arkitekturer.

Vi fokuserer på følgende:

Dernæst vil vi tale om konvertering af modeller, begyndende med teori. OpenVINO indeholder flere moduler:

  • Open Model Zoo, modeller som kan bruges og inkluderes i dit produkt
  • Model Optimzer, takket være hvilken du kan konvertere en model fra forskellige rammeformater (Tensorflow, ONNX osv.) til det mellemliggende repræsentationsformat, som vi vil arbejde videre med
  • Inference Engine giver dig mulighed for at køre modeller i IR-format på Intel-processorer, Myriad-chips og Neural Compute Stick-acceleratorer
  • Den mest effektive version af OpenCV (med Inference Engine-understøttelse)
    Hver model i IR-format er beskrevet af to filer: .xml og .bin.
    Modeller konverteres til IR-format via Model Optimizer som følger:

    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 giver dig mulighed for at vælge det dataformat, som modellen skal arbejde med. FP32, FP16, INT8 er understøttet. Valg af den optimale datatype kan give et godt ydelsesboost.
    --input_shape angiver dimensionen af ​​inputdata. Muligheden for dynamisk at ændre det ser ud til at være til stede i C++ API, men vi gravede ikke så langt og rettede det simpelthen for en af ​​modellerne.
    Lad os derefter prøve at indlæse den allerede konverterede model i IR-format via DNN-modulet til OpenCV og videresende den til den.

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

    Den sidste linje i dette tilfælde giver dig mulighed for at omdirigere beregninger til Neural Compute Stick, grundlæggende beregninger udføres på processoren, men i tilfælde af Raspberry Pi vil dette ikke fungere, du skal bruge en stick.

    Dernæst er logikken som følger: vi opdeler vores lyd i vinduer af en vis størrelse (for os er det 0.4 s), vi konverterer hvert af disse vinduer til MFCC, som vi derefter sender til nettet:

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

    Lad os derefter tage den mest almindelige klasse for alle vinduer. En simpel løsning, men til et hackathon behøver du ikke finde på noget for abstrut, kun hvis du har tid. Vi har stadig meget arbejde at gøre, så lad os komme videre – vi skal beskæftige os med stemmegenkendelse. Det er nødvendigt at lave en slags database, hvori spektrogrammer af forudindspillede stemmer vil blive lagret. Da der er kort tid tilbage, vil vi løse dette problem så godt vi kan.

    Vi laver nemlig et script til at optage et stemmeuddrag (det fungerer på samme måde som beskrevet ovenfor, kun når det afbrydes fra tastaturet, gemmer det stemmen til en fil).

    Lad os prøve:

    python3 voice_db/record_voice.py test.wav

    Vi optager stemmer fra flere personer (i vores tilfælde tre teammedlemmer)
    Dernæst udfører vi for hver optaget stemme en hurtig fourier-transformation, henter et spektrogram og gemmer det som et numpy-array (.npy):

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

    Flere detaljer i filen create_base.py
    Som et resultat, når vi kører hovedscriptet, vil vi få indlejringer fra disse spektrogrammer helt i begyndelsen:

    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)

    Efter at have modtaget indlejringen fra det lydede segment, vil vi være i stand til at bestemme, hvem det tilhører ved at tage cosinusafstanden fra passagen til alle stemmerne i databasen (jo mindre, jo mere sandsynligt) - for demoen sætter vi tærsklen til 0.3):

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

    Til sidst vil jeg gerne bemærke, at inferenshastigheden var hurtig og gjorde det muligt at tilføje 1-2 flere modeller (for en prøve på 7 sekunder tog det 2.5 for inferens). Vi havde ikke længere tid til at tilføje nye modeller og fokuserede på at skrive en prototype af webapplikationen.

    Webapplikation

    En vigtig pointe: vi tager en router med hjemmefra og sætter vores lokale netværk op, det hjælper at forbinde enheden og bærbare computere over netværket.

    Backend er en ende-til-ende meddelelseskanal mellem fronten og Raspberry Pi, baseret på websocket-teknologi (http over tcp-protokol).

    Første trin er at modtage bearbejdet information fra raspberry, det vil sige prædiktorer pakket i json, som gemmes i databasen halvvejs gennem deres rejse, så der kan genereres statistik om brugerens følelsesmæssige baggrund for perioden. Denne pakke sendes derefter til frontenden, som bruger abonnement og modtager pakker fra websockets slutpunkt. Hele backend-mekanismen er bygget i golang-sproget; den blev valgt, fordi den er velegnet til asynkrone opgaver, som goroutiner håndterer godt.
    Ved adgang til slutpunktet registreres brugeren og indtastes i strukturen, hvorefter hans besked modtages. Både brugeren og beskeden indtastes i en fælles hub, hvorfra beskeder allerede sendes videre (til den abonnerede front), og hvis brugeren lukker forbindelsen (hindbær eller front), så opsiges hans abonnement og han fjernes fra navet.

    OpenVINO hackathon: genkender stemme og følelser på Raspberry Pi
    Vi venter på en forbindelse bagfra

    Front-end er en webapplikation skrevet i JavaScript ved hjælp af React-biblioteket til at fremskynde og forenkle udviklingsprocessen. Formålet med denne applikation er at visualisere data opnået ved hjælp af algoritmer, der kører på back-end-siden og direkte på Raspberry Pi. Siden har sektionsrouting implementeret ved hjælp af react-router, men hovedsiden af ​​interesse er hovedsiden, hvor en kontinuerlig strøm af data modtages i realtid fra serveren ved hjælp af WebSocket teknologi. Raspberry Pi registrerer en stemme, afgør, om den tilhører en bestemt person fra den registrerede database, og sender en sandsynlighedsliste til klienten. Klienten viser de seneste relevante data, viser avataren for den person, der højst sandsynligt talte i mikrofonen, samt den følelse, han udtaler ordene med.

    OpenVINO hackathon: genkender stemme og følelser på Raspberry Pi
    Hjemmeside med opdaterede forudsigelser

    Konklusion

    Det var ikke muligt at gennemføre alt som planlagt, vi havde simpelthen ikke tid, så det primære håb var i demoen, at alt ville fungere. I præsentationen talte de om, hvordan alting fungerer, hvilke modeller de tog, hvilke problemer de stødte på. Dernæst var demodelen - eksperter gik rundt i lokalet i tilfældig rækkefølge og henvendte sig til hvert hold for at se på den fungerende prototype. De stillede os også spørgsmål, alle svarede på deres del, de forlod internettet på den bærbare computer, og alt fungerede virkelig som forventet.

    Lad mig bemærke, at den samlede pris for vores løsning var $150:

    • Raspberry Pi 3 ~ $35
    • Google AIY Voice Bonnet (du kan tage et højttalergebyr) ~ 15$
    • Intel NCS 2 ~ 100$

    Sådan forbedres:

    • Brug registrering fra klienten - bed om at læse teksten, der genereres tilfældigt
    • Tilføj et par flere modeller: du kan bestemme køn og alder med stemmen
    • Adskil samtidig lydende stemmer (diarisering)

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

    OpenVINO hackathon: genkender stemme og følelser på Raspberry Pi
    Vi er trætte, men glade

    Afslutningsvis vil jeg gerne sige tak til arrangørerne og deltagerne. Blandt andre teams projekter kunne vi personligt lide løsningen til overvågning af gratis parkeringspladser. For os var det en vildt fed oplevelse af fordybelse i produktet og udviklingen. Jeg håber, at der vil blive afholdt flere og flere interessante arrangementer i regionerne, også om AI-emner.

Kilde: www.habr.com

Tilføj en kommentar