OpenVINO hackathon: känner igen röst och känslor på Raspberry Pi

30 november - 1 december i Nizhny Novgorod hölls OpenVINO hackathon. Deltagarna ombads skapa en prototyp av en produktlösning med hjälp av Intel OpenVINO-verktygssatsen. Arrangörerna föreslog en lista med ungefärliga ämnen som kunde vägledas av vid val av uppgift, men det slutliga beslutet låg kvar hos teamen. Dessutom uppmuntrades användningen av modeller som inte ingår i produkten.

OpenVINO hackathon: känner igen röst och känslor på Raspberry Pi

I den här artikeln kommer vi att berätta om hur vi skapade vår prototyp av produkten, som vi så småningom tog förstaplatsen med.

Mer än 10 lag deltog i hackathonet. Det är trevligt att några av dem kom från andra regioner. Platsen för hackathonet var komplexet "Kremlinsky on Pochain", där gamla fotografier av Nizhny Novgorod hängdes inuti, i ett entourage! (Jag påminner er om att Intels centralkontor för närvarande ligger i Nizhny Novgorod). Deltagarna fick 26 timmar på sig att skriva kod, och i slutet fick de presentera sin lösning. En separat fördel var närvaron av en demosession för att säkerställa att allt planerat faktiskt genomfördes och inte förblev idéer i presentationen. Merch, snacks, mat, allt fanns också!

Dessutom tillhandahöll Intel valfritt kameror, Raspberry PI, Neural Compute Stick 2.

Uppgiftsval

En av de svåraste delarna med att förbereda sig för ett hackathon i fritt format är att välja en utmaning. Vi bestämde oss direkt för att komma på något som ännu inte fanns i produkten, eftersom tillkännagivandet sa att detta var mycket välkommet.

Efter att ha analyserat modell, som ingår i produkten i den aktuella versionen, kommer vi till slutsatsen att de flesta av dem löser olika datorseendeproblem. Dessutom är det mycket svårt att komma på ett problem inom datorseende som inte kan lösas med OpenVINO, och även om en kan uppfinnas är det svårt att hitta förutbildade modeller i det offentliga området. Vi bestämmer oss för att gräva i en annan riktning - mot talbearbetning och analys. Låt oss överväga en intressant uppgift att känna igen känslor från tal. Det måste sägas att OpenVINO redan har en modell som bestämmer en persons känslor baserat på deras ansikte, men:

  • I teorin går det att skapa en kombinerad algoritm som ska fungera på både ljud och bild, vilket borde ge en ökad noggrannhet.
  • Kameror har vanligtvis en smal betraktningsvinkel, mer än en kamera krävs för att täcka ett stort område, ljud har inte en sådan begränsning.

Låt oss utveckla idén: låt oss ta idén för detaljhandelssegmentet som grund. Du kan mäta kundnöjdheten i butikskassorna. Om någon av kunderna är missnöjd med tjänsten och börjar höja tonen kan du omedelbart ringa administratören för att få hjälp.
I det här fallet måste vi lägga till mänsklig röstigenkänning, detta gör att vi kan skilja butiksanställda från kunder och tillhandahålla analyser för varje individ. Tja, dessutom kommer det att vara möjligt att analysera beteendet hos butiksanställda själva, utvärdera atmosfären i teamet, låter bra!

Vi formulerar kraven för vår lösning:

  • Liten storlek på målenheten
  • Drift i realtid
  • Lågt pris
  • Enkel skalbarhet

Som ett resultat väljer vi Raspberry Pi 3 c som målenhet Intel NCS 2.

Här är det viktigt att notera en viktig egenskap hos NCS - den fungerar bäst med standard CNN-arkitekturer, men om du behöver köra en modell med anpassade lager på den, förvänta dig lågnivåoptimering.

Det finns bara en liten sak att göra: du måste skaffa en mikrofon. En vanlig USB-mikrofon duger, men den ser inte bra ut tillsammans med RPI. Men även här ligger lösningen bokstavligen i närheten. För att spela in röst bestämmer vi oss för att använda Voice Bonnet-brädet från satsen Google AIY Voice Kit, på vilken det finns en trådbunden stereomikrofon.

Ladda ner Raspbian från AIY-projektförråd och ladda upp den till en flashenhet, testa att mikrofonen fungerar med följande kommando (den kommer att spela in ljud i 5 sekunder och spara det i en fil):

arecord -d 5 -r 16000 test.wav

Jag bör omedelbart notera att mikrofonen är väldigt känslig och tar upp brus bra. För att fixa detta, låt oss gå till alsamixer, välj Capture devices och minska ingångssignalnivån till 50-60%.

OpenVINO hackathon: känner igen röst och känslor på Raspberry Pi
Vi modifierar kroppen med en fil och allt passar, du kan till och med stänga den med ett lock

Lägger till en indikatorknapp

När vi tar isär AIY Voice Kit kommer vi ihåg att det finns en RGB-knapp, vars bakgrundsbelysning kan styras av programvara. Vi söker efter "Google AIY Led" och hittar dokumentation: https://aiyprojects.readthedocs.io/en/latest/aiy.leds.html
Varför inte använda den här knappen för att visa den erkända känslan, vi har bara 7 klasser och knappen har 8 färger, precis tillräckligt!

Vi ansluter knappen via GPIO till Voice Bonnet, laddar de nödvändiga biblioteken (de är redan installerade i distributionssatsen från AIY-projekt)

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

Låt oss skapa ett dikt där varje känsla kommer att ha en motsvarande färg i form av en RGB Tuple och ett objekt av klassen aiy.leds.Leds, genom vilket vi kommer att uppdatera färgen:

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

Och slutligen, efter varje ny förutsägelse av en känsla, kommer vi att uppdatera knappens färg i enlighet med den (med nyckel).

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

OpenVINO hackathon: känner igen röst och känslor på Raspberry Pi
Knapp, bränn!

Arbeta med röst

Vi kommer att använda pyaudio för att fånga strömmen från mikrofonen och webrtcvad för att filtrera brus och upptäcka röst. Dessutom kommer vi att skapa en kö till vilken vi asynkront lägger till och tar bort röstutdrag.

Eftersom webrtcvad har en begränsning av storleken på det levererade fragmentet - det måste vara lika med 10/20/30ms, och träningen av modellen för att känna igen känslor (som vi kommer att lära oss senare) utfördes på en 48kHz datauppsättning, kommer vi att fånga bitar av storleken 48000×20ms/1000×1(mono)=960 byte. Webrtcvad kommer att returnera True/False för var och en av dessa bitar, vilket motsvarar närvaron eller frånvaron av en röst i biten.

Låt oss implementera följande logik:

  • Vi kommer att lägga till de bitar där det finns en röst till listan; om det inte finns någon röst kommer vi att öka räknaren för tomma bitar.
  • Om räknaren för tomma bitar är >=30 (600 ms), så tittar vi på storleken på listan över ackumulerade bitar; om den är >250 lägger vi till den i kön; om inte, anser vi att längden av skivan räcker inte för att mata den till modellen för att identifiera högtalaren.
  • Om räknaren för tomma bitar fortfarande är < 30, och storleken på listan över ackumulerade bitar överstiger 300, så kommer vi att lägga till fragmentet i kön för en mer exakt förutsägelse. (eftersom känslor tenderar att förändras med tiden)

 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 är dags att leta efter förtränade modeller i det offentliga området, gå till github, Google, men kom ihåg att vi har en begränsning på vilken arkitektur som används. Detta är en ganska svår del, eftersom du måste testa modellerna på dina indata, och dessutom konvertera dem till OpenVINOs interna format - IR (Intermediate Representation). Vi provade cirka 5-7 olika lösningar från github, och om modellen för att känna igen känslor fungerade direkt, så med röstigenkänning fick vi vänta längre - de använder mer komplexa arkitekturer.

Vi fokuserar på följande:

Därefter kommer vi att prata om att konvertera modeller, börja med teori. OpenVINO innehåller flera moduler:

  • Open Model Zoo, modeller som kan användas och inkluderas i din produkt
  • Model Optimzer, tack vare vilken du kan konvertera en modell från olika ramverksformat (Tensorflow, ONNX etc) till formatet Intermediate Representation, som vi kommer att arbeta vidare med
  • Inference Engine låter dig köra modeller i IR-format på Intel-processorer, Myriad-chips och Neural Compute Stick-acceleratorer
  • Den mest effektiva versionen av OpenCV (med Inference Engine-stöd)
    Varje modell i IR-format beskrivs av två filer: .xml och .bin.
    Modeller konverteras till IR-format via Model Optimizer enligt följande:

    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 låter dig välja det dataformat som modellen ska fungera med. FP32, FP16, INT8 stöds. Att välja den optimala datatypen kan ge en bra prestandaboost.
    --input_shape anger dimensionen av indata. Möjligheten att dynamiskt ändra det verkar finnas i C++ API, men vi grävde inte så långt och fixade det helt enkelt för en av modellerna.
    Låt oss sedan försöka ladda den redan konverterade modellen i IR-format via DNN-modulen till OpenCV och vidarebefordra den till den.

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

    Den sista raden i det här fallet låter dig omdirigera beräkningar till Neural Compute Stick, grundläggande beräkningar utförs på processorn, men i fallet med Raspberry Pi kommer detta inte att fungera, du behöver en sticka.

    Därefter är logiken följande: vi delar upp vårt ljud i fönster av en viss storlek (för oss är det 0.4 s), vi konverterar vart och ett av dessa fönster till MFCC, som vi sedan matar till nätet:

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

    Låt oss sedan ta den vanligaste klassen för alla fönster. En enkel lösning, men för ett hackathon behöver du inte hitta på något för abstrut, bara om du har tid. Vi har fortfarande mycket att göra, så låt oss gå vidare – vi kommer att ta itu med röstigenkänning. Det är nödvändigt att göra någon form av databas där spektrogram av förinspelade röster skulle lagras. Eftersom det är kort tid kvar kommer vi att lösa problemet så gott vi kan.

    Vi skapar nämligen ett skript för att spela in ett röstutdrag (det fungerar på samma sätt som beskrivits ovan, bara när det avbryts från tangentbordet kommer det att spara rösten till en fil).

    Låt oss försöka:

    python3 voice_db/record_voice.py test.wav

    Vi spelar in röster från flera personer (i vårt fall tre teammedlemmar)
    Därefter utför vi en snabb fouriertransform för varje inspelad röst, skaffar ett spektrogram och sparar 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)

    Mer detaljer i filen create_base.py
    Som ett resultat, när vi kör huvudskriptet, kommer vi att få inbäddningar från dessa spektrogram i början:

    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 att ha mottagit inbäddningen från det ljudade segmentet kommer vi att kunna avgöra vem det tillhör genom att ta cosinusavståndet från passagen till alla röster i databasen (ju mindre, desto mer sannolikt) - för demot ställer vi in ​​tröskeln till 0.3):

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

    Till sist skulle jag vilja notera att inferenshastigheten var snabb och gjorde det möjligt att lägga till 1-2 modeller till (för ett prov som var 7 sekunder långt tog det 2.5 för slutledning). Vi hann inte längre lägga till nya modeller och fokuserade på att skriva en prototyp av webbapplikationen.

    webbapplikation

    En viktig punkt: vi tar med oss ​​en router hemifrån och ställer in vårt lokala nätverk, det hjälper till att ansluta enheten och bärbara datorer över nätverket.

    Backend är en end-to-end-meddelandekanal mellan fronten och Raspberry Pi, baserad på websocket-teknik (http over tcp-protokoll).

    Det första steget är att ta emot bearbetad information från hallon, det vill säga prediktorer packade i json, som sparas i databasen halvvägs under sin resa så att statistik kan genereras om användarens känslomässiga bakgrund för perioden. Detta paket skickas sedan till frontend, som använder prenumeration och tar emot paket från websockets slutpunkt. Hela backend-mekanismen är byggd i golang-språket, den valdes för att den är väl lämpad för asynkrona uppgifter, som goroutiner hanterar bra.
    Vid åtkomst till slutpunkten registreras användaren och skrivs in i strukturen, sedan tas hans meddelande emot. Både användaren och meddelandet läggs in i ett gemensamt nav, från vilket meddelanden redan skickas vidare (till den prenumererade fronten), och om användaren stänger anslutningen (hallon eller front), så avbryts hans prenumeration och han tas bort från navet.

    OpenVINO hackathon: känner igen röst och känslor på Raspberry Pi
    Vi väntar på en anslutning bakifrån

    Front-end är en webbapplikation skriven i JavaScript med hjälp av React-biblioteket för att påskynda och förenkla utvecklingsprocessen. Syftet med denna applikation är att visualisera data som erhållits med hjälp av algoritmer som körs på back-end-sidan och direkt på Raspberry Pi. Sidan har sektionsrouting implementerad med hjälp av react-router, men huvudsidan av intresse är huvudsidan, där en kontinuerlig ström av data tas emot i realtid från servern med hjälp av WebSocket-teknik. Raspberry Pi upptäcker en röst, avgör om den tillhör en specifik person från den registrerade databasen och skickar en sannolikhetslista till klienten. Klienten visar den senaste relevanta informationen, visar avataren för den person som sannolikt talade i mikrofonen, såväl som den känsla med vilken han uttalar orden.

    OpenVINO hackathon: känner igen röst och känslor på Raspberry Pi
    Hemsida med uppdaterade prognoser

    Slutsats

    Det var inte möjligt att slutföra allt som planerat, vi hade helt enkelt inte tid, så det främsta hoppet var i demon, att allt skulle fungera. I presentationen pratade de om hur allt fungerar, vilka modeller de tagit, vilka problem de stött på. Nästa var demodelen - experter gick runt i rummet i slumpmässig ordning och gick fram till varje team för att titta på den fungerande prototypen. De ställde frågor till oss också, alla svarade på sin del, de lämnade webben på den bärbara datorn och allt fungerade verkligen som förväntat.

    Låt mig notera att den totala kostnaden för vår lösning var $150:

    • Raspberry Pi 3 ~ $35
    • Google AIY Voice Bonnet (du kan ta en högtalaravgift) ~ 15$
    • Intel NCS 2 ~ 100 $

    Hur man förbättrar:

    • Använd registrering från klienten – be att få läsa texten som genereras slumpmässigt
    • Lägg till några fler modeller: du kan bestämma kön och ålder med rösten
    • Separera röster som låter samtidigt (diarisering)

    Förvar: https://github.com/vladimirwest/OpenEMO

    OpenVINO hackathon: känner igen röst och känslor på Raspberry Pi
    Trötta men glada är vi

    Avslutningsvis vill jag tacka arrangörerna och deltagarna. Bland andra teams projekt gillade vi personligen lösningen för att övervaka gratis parkeringsplatser. För oss var det en galet häftig upplevelse av fördjupning i produkten och utvecklingen. Jag hoppas att fler och fler intressanta evenemang kommer att hållas i regionerna, bland annat om AI-ämnen.

Källa: will.com

Lägg en kommentar