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

30. november - 1. desember i Nizhny Novgorod ble holdt OpenVINO hackathon. Deltakerne ble bedt om å lage en prototype av en produktløsning ved å bruke Intel OpenVINO-verktøysettet. Arrangørene foreslo en liste over omtrentlige emner som kunne styres etter når de skulle velge en oppgave, men den endelige avgjørelsen forble hos lagene. I tillegg ble det oppfordret til bruk av modeller som ikke er inkludert i produktet.

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

I denne artikkelen vil vi fortelle deg om hvordan vi laget vår prototype av produktet, som vi til slutt tok førsteplassen med.

Mer enn 10 lag deltok i hackathonet. Det er hyggelig at noen av dem kom fra andre regioner. Stedet for hackathonet var "Kremlinsky on Pochain"-komplekset, hvor eldgamle fotografier av Nizhny Novgorod ble hengt opp inne, i et følge! (Jeg minner deg om at for øyeblikket ligger Intels sentralkontor i Nizhny Novgorod). Deltakerne fikk 26 timer til å skrive kode, og til slutt skulle de presentere sin løsning. En egen fordel var tilstedeværelsen av en demoøkt for å sikre at alt planlagt faktisk ble implementert og ikke forble ideer i presentasjonen. Merch, snacks, mat, alt var der også!

I tillegg leverte Intel valgfritt kameraer, Raspberry PI, Neural Compute Stick 2.

Oppgavevalg

En av de vanskeligste delene av å forberede seg til et hackathon i fri form er å velge en utfordring. Vi bestemte oss umiddelbart for å komme med noe som ennå ikke var i produktet, siden kunngjøringen sa at dette var svært velkomment.

Etter å ha analysert modell, som er inkludert i produktet i den nåværende utgivelsen, kommer vi til den konklusjon at de fleste løser ulike problemer med datasyn. Dessuten er det svært vanskelig å komme opp med et problem innen datasyn som ikke kan løses ved hjelp av OpenVINO, og selv om en kan bli oppfunnet, er det vanskelig å finne ferdigtrente modeller i det offentlige domene. Vi bestemmer oss for å grave i en annen retning - mot talebehandling og analyse. La oss vurdere en interessant oppgave med å gjenkjenne følelser fra tale. Det må sies at OpenVINO allerede har en modell som bestemmer en persons følelser basert på ansiktet deres, men:

  • I teorien er det mulig å lage en kombinert algoritme som skal fungere på både lyd og bilde, noe som skal gi økt nøyaktighet.
  • Kameraer har vanligvis en smal visningsvinkel; mer enn ett kamera er nødvendig for å dekke et stort område; lyd har ikke en slik begrensning.

La oss utvikle ideen: la oss ta ideen til detaljhandelssegmentet som grunnlag. Du kan måle kundetilfredsheten i butikkkassene. Hvis en av kundene er misfornøyd med tjenesten og begynner å heve tonen, kan du umiddelbart ringe administratoren for å få hjelp.
I dette tilfellet må vi legge til menneskelig stemmegjenkjenning, dette vil tillate oss å skille butikkmedarbeidere fra kunder og gi analyser for hver enkelt. Vel, i tillegg vil det være mulig å analysere oppførselen til de butikkansatte selv, evaluere atmosfæren i teamet, høres bra ut!

Vi formulerer kravene til vår løsning:

  • Liten størrelse på målenheten
  • Sanntidsdrift
  • Lav pris
  • Enkel skalerbarhet

Som et resultat velger vi Raspberry Pi 3 c som målenheten Intel NCS 2.

Her er det viktig å merke seg en viktig funksjon ved NCS - den fungerer best med standard CNN-arkitekturer, men hvis du trenger å kjøre en modell med tilpassede lag på, så forvent lavnivåoptimering.

Det er bare en liten ting å gjøre: du må skaffe deg en mikrofon. En vanlig USB-mikrofon vil gjøre det, men det vil ikke se bra ut sammen med RPI. Men selv her "ligger løsningen bokstavelig talt i nærheten." For å spille inn stemme, bestemmer vi oss for å bruke Voice Bonnet-tavlen fra settet Google AIY Voice Kit, som det er en kablet stereomikrofon på.

Last ned Raspbian fra AIY-prosjekter depot og last den opp til en flash-stasjon, test at mikrofonen fungerer ved å bruke følgende kommando (den vil ta opp lyd i 5 sekunder og lagre den i en fil):

arecord -d 5 -r 16000 test.wav

Jeg bør umiddelbart merke meg at mikrofonen er veldig følsom og fanger opp støy godt. For å fikse dette, la oss gå til alsamixer, velg Capture devices og redusere inngangssignalnivået til 50-60%.

OpenVINO hackathon: gjenkjenne stemme og følelser på Raspberry Pi
Vi modifiserer kroppen med en fil og alt passer, du kan til og med lukke den med et lokk

Legger til en indikatorknapp

Mens vi tar AIY Voice Kit fra hverandre, husker vi at det er en RGB-knapp, hvis bakgrunnsbelysning kan kontrolleres av programvare. Vi søker etter «Google AIY Led» og finner dokumentasjon: https://aiyprojects.readthedocs.io/en/latest/aiy.leds.html
Hvorfor ikke bruke denne knappen for å vise den gjenkjente følelsen, vi har bare 7 klasser, og knappen har 8 farger, akkurat nok!

Vi kobler knappen via GPIO til Voice Bonnet, laster de nødvendige bibliotekene (de er allerede installert i distribusjonssettet fra AIY-prosjekter)

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

La oss lage en diktat der hver følelse vil ha en tilsvarende farge i form av en RGB Tuple og et objekt av klassen aiy.leds.Leds, som vi vil oppdatere fargen gjennom:

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 til slutt, etter hver ny prediksjon av en følelse, vil vi oppdatere fargen på knappen i samsvar med den (med nøkkel).

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

OpenVINO hackathon: gjenkjenne stemme og følelser på Raspberry Pi
Knapp, brenn!

Arbeid med stemme

Vi vil bruke pyaudio til å fange strømmen fra mikrofonen og webrtcvad for å filtrere støy og oppdage stemme. I tillegg vil vi lage en kø som vi asynkront vil legge til og fjerne stemmeutdrag til.

Siden webrtcvad har en begrensning på størrelsen på det leverte fragmentet - det må være lik 10/20/30ms, og treningen av modellen for å gjenkjenne følelser (som vi skal lære senere) ble utført på et 48kHz datasett, vil vi fange opp biter i størrelsen 48000×20ms/1000×1(mono)=960 byte. Webrtcvad vil returnere True/False for hver av disse delene, som tilsvarer tilstedeværelsen eller fraværet av en stemme i delen.

La oss implementere følgende logikk:

  • Vi vil legge til de delene der det er en stemme på listen; hvis det ikke er noen stemme, vil vi øke telleren for tomme deler.
  • Hvis telleren for tomme biter er >=30 (600 ms), så ser vi på størrelsen på listen over akkumulerte biter; hvis den er >250, legger vi den til i køen; hvis ikke, anser vi at lengden av posten er ikke nok til å mate den til modellen for å identifisere høyttaleren.
  • Hvis telleren for tomme biter fortsatt er < 30, og størrelsen på listen over akkumulerte biter overstiger 300, vil vi legge til fragmentet i køen for en mer nøyaktig prediksjon. (fordi følelser har en tendens til å endre seg 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 på tide å se etter ferdigtrente modeller i det offentlige domene, gå til github, Google, men husk at vi har en begrensning på arkitekturen som brukes. Dette er en ganske vanskelig del, fordi du må teste modellene på inngangsdataene dine, og i tillegg konvertere dem til OpenVINOs interne format - IR (Intermediate Representation). Vi prøvde omtrent 5-7 forskjellige løsninger fra github, og hvis modellen for å gjenkjenne følelser fungerte umiddelbart, så med stemmegjenkjenning måtte vi vente lenger - de bruker mer komplekse arkitekturer.

Vi fokuserer på følgende:

Deretter vil vi snakke om konvertering av modeller, og starter med teori. OpenVINO inkluderer flere moduler:

  • Open Model Zoo, modeller som kan brukes og inkluderes i produktet ditt
  • Model Optimzer, takket være hvilken du kan konvertere en modell fra forskjellige rammeverksformater (Tensorflow, ONNX etc) til mellomrepresentasjonsformatet, som vi vil jobbe videre med
  • Inference Engine lar deg kjøre modeller i IR-format på Intel-prosessorer, Myriad-brikker og Neural Compute Stick-akseleratorer
  • Den mest effektive versjonen av OpenCV (med Inference Engine-støtte)
    Hver modell i IR-format er beskrevet av 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 lar deg velge dataformatet som modellen skal fungere med. FP32, FP16, INT8 støttes. Å velge den optimale datatypen kan gi et godt ytelsesløft.
    --input_shape angir dimensjonen til inngangsdataene. Muligheten til å dynamisk endre det ser ut til å være tilstede i C++ API, men vi gravde ikke så langt og fikset det ganske enkelt for en av modellene.
    Deretter, la oss prøve å laste den allerede konverterte modellen i IR-format via DNN-modulen inn i 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 siste linjen i dette tilfellet lar deg omdirigere beregninger til Neural Compute Stick, grunnleggende beregninger utføres på prosessoren, men i tilfelle av Raspberry Pi vil dette ikke fungere, du trenger en pinne.

    Deretter er logikken som følger: vi deler lyden vår inn i vinduer av en viss størrelse (for oss er det 0.4 s), vi konverterer hvert av disse vinduene til MFCC, som vi deretter mater til rutenettet:

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

    La oss deretter ta den vanligste klassen for alle vinduer. En enkel løsning, men for et hackathon trenger du ikke finne på noe for grovt, bare hvis du har tid. Vi har fortsatt mye arbeid å gjøre, så la oss gå videre – vi skal håndtere stemmegjenkjenning. Det er nødvendig å lage en slags database der spektrogrammer av forhåndsinnspilte stemmer vil bli lagret. Siden det er kort tid igjen, vil vi løse dette problemet så godt vi kan.

    Vi lager nemlig et skript for å ta opp et stemmeutdrag (det fungerer på samme måte som beskrevet ovenfor, bare når det avbrytes fra tastaturet vil det lagre stemmen til en fil).

    La oss prøve:

    python3 voice_db/record_voice.py test.wav

    Vi spiller inn stemmene til flere personer (i vårt tilfelle tre teammedlemmer)
    Deretter utfører vi en rask fourier-transformasjon for hver innspilt stemme, skaffer et spektrogram og lagrer det som en 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 kjører hovedskriptet, vil vi få innbygginger fra disse spektrogrammene helt i begynnelsen:

    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)

    Etter å ha mottatt innebyggingen fra det lydde segmentet, vil vi kunne bestemme hvem det tilhører ved å ta cosinusavstanden fra passasjen til alle stemmene i databasen (jo mindre, jo mer sannsynlig) - for demoen setter vi terskelen til 0.3):

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

    Til slutt vil jeg bemerke at inferenshastigheten var rask og gjorde det mulig å legge til 1-2 flere modeller (for en prøve på 7 sekunder tok det 2.5 for inferens). Vi hadde ikke lenger tid til å legge til nye modeller og fokuserte på å skrive en prototype av nettapplikasjonen.

    Webapplikasjon

    Et viktig poeng: vi tar med oss ​​en ruter hjemmefra og setter opp vårt lokale nettverk, det hjelper å koble enheten og bærbare datamaskiner over nettverket.

    Backend er en ende-til-ende meldingskanal mellom fronten og Raspberry Pi, basert på websocket-teknologi (http over tcp-protokoll).

    Det første trinnet er å motta behandlet informasjon fra bringebær, det vil si prediktorer pakket i json, som lagres i databasen halvveis i reisen slik at det kan genereres statistikk om brukerens følelsesmessige bakgrunn for perioden. Denne pakken sendes deretter til frontend, som bruker abonnement og mottar pakker fra websocket-endepunktet. Hele backend-mekanismen er bygget i golang-språket; den ble valgt fordi den er godt egnet for asynkrone oppgaver, som goroutiner håndterer godt.
    Ved tilgang til endepunktet blir brukeren registrert og lagt inn i strukturen, deretter mottas meldingen hans. Både brukeren og meldingen legges inn i en felles hub, hvorfra meldinger allerede sendes videre (til abonnentfronten), og hvis brukeren lukker forbindelsen (bringebær eller front), blir abonnementet kansellert og han fjernes fra navet.

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

    Front-end er en nettapplikasjon skrevet i JavaScript som bruker React-biblioteket for å fremskynde og forenkle utviklingsprosessen. Hensikten med denne applikasjonen er å visualisere data innhentet ved hjelp av algoritmer som kjører på baksiden og direkte på Raspberry Pi. Siden har seksjonsruting implementert ved hjelp av react-router, men hovedsiden av interesse er hovedsiden, hvor en kontinuerlig strøm av data mottas i sanntid fra serveren ved hjelp av WebSocket-teknologi. Raspberry Pi oppdager en stemme, avgjør om den tilhører en bestemt person fra den registrerte databasen, og sender en sannsynlighetsliste til klienten. Klienten viser de siste relevante dataene, viser avataren til personen som mest sannsynlig snakket inn i mikrofonen, samt følelsen han uttaler ordene med.

    OpenVINO hackathon: gjenkjenne stemme og følelser på Raspberry Pi
    Hjemmeside med oppdaterte spådommer

    Konklusjon

    Det var ikke mulig å fullføre alt som planlagt, vi hadde rett og slett ikke tid, så hovedhåpet var i demoen, at alt ville fungere. I presentasjonen snakket de om hvordan alt fungerer, hvilke modeller de tok, hvilke problemer de møtte. Neste var demodelen - eksperter gikk rundt i rommet i tilfeldig rekkefølge og henvendte seg til hvert team for å se på den fungerende prototypen. De stilte spørsmål til oss også, alle svarte på sin del, de la nettet på den bærbare datamaskinen, og alt fungerte egentlig som forventet.

    La meg merke seg at den totale kostnaden for løsningen vår var $150:

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

    Slik forbedrer du:

    • Bruk registrering fra klienten – be om å få lese teksten som genereres tilfeldig
    • Legg til noen flere modeller: du kan bestemme kjønn og alder med stemmen
    • Separer samtidig klingende stemmer (diarisering)

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

    OpenVINO hackathon: gjenkjenne stemme og følelser på Raspberry Pi
    Slitne, men glade er vi

    Avslutningsvis vil jeg rette en stor takk til arrangørene og deltakerne. Blant prosjektene til andre team likte vi personlig løsningen for overvåking av gratis parkeringsplasser. For oss var det en kjempekul opplevelse av fordypning i produktet og utviklingen. Jeg håper det vil bli holdt flere og flere interessante arrangementer i regionene, blant annet om AI-temaer.

Kilde: www.habr.com

Legg til en kommentar