OpenVINO hackathon: stem en emoties herkennen op Raspberry Pi

30 november - 1 december werd gehouden in Nizjni Novgorod OpenVINO-hackathon. Deelnemers werd gevraagd een prototype van een productoplossing te maken met behulp van de Intel OpenVINO-toolkit. De organisatoren stelden een lijst voor met onderwerpen bij benadering die als leidraad konden dienen bij het kiezen van een taak, maar de uiteindelijke beslissing bleef bij de teams. Bovendien werd het gebruik van modellen die niet in het product zijn inbegrepen, aangemoedigd.

OpenVINO hackathon: stem en emoties herkennen op Raspberry Pi

In dit artikel vertellen we hoe we ons prototype van het product hebben gemaakt, waarmee we uiteindelijk de eerste plaats behaalden.

Ruim 10 teams namen deel aan de hackathon. Het is leuk dat sommigen van hen uit andere regio's kwamen. De locatie voor de hackathon was het ‘Kremlinsky on Pochain’-complex, waar oude foto’s van Nizjni Novgorod binnen werden opgehangen, in een entourage! (Ik herinner u eraan dat het centrale kantoor van Intel momenteel in Nizjni Novgorod is gevestigd). Deelnemers kregen 26 uur de tijd om code te schrijven en aan het einde moesten ze hun oplossing presenteren. Een apart voordeel was de aanwezigheid van een demosessie om er zeker van te zijn dat alles wat gepland was ook daadwerkelijk werd uitgevoerd en geen ideeën in de presentatie bleef. Merch, snacks, eten, alles was er ook!

Daarnaast leverde Intel optioneel camera's, Raspberry PI, Neural Compute Stick 2.

Taakselectie

Een van de moeilijkste onderdelen van de voorbereiding op een hackathon in vrije vorm is het kiezen van een uitdaging. We hebben meteen besloten iets te verzinnen wat nog niet in het product zat, aangezien in de aankondiging stond dat dit zeer welkom was.

Na analyse model, die in de huidige release in het product zijn opgenomen, komen we tot de conclusie dat de meeste ervan verschillende computervisieproblemen oplossen. Bovendien is het erg moeilijk om een ​​probleem op het gebied van computer vision te bedenken dat niet kan worden opgelost met behulp van OpenVINO, en zelfs als er wel een kan worden uitgevonden, is het moeilijk om vooraf getrainde modellen in het publieke domein te vinden. We besluiten een andere richting in te slaan: richting spraakverwerking en -analyse. Laten we eens kijken naar een interessante taak: het herkennen van emoties uit spraak. Het moet gezegd worden dat OpenVINO al een model heeft dat de emoties van een persoon bepaalt op basis van zijn gezicht, maar:

  • In theorie is het mogelijk om een ​​gecombineerd algoritme te creëren dat zowel op geluid als op beeld werkt, wat een verhoging van de nauwkeurigheid zou moeten opleveren.
  • Camera's hebben doorgaans een smalle kijkhoek; er is meer dan één camera nodig om een ​​groot gebied te bestrijken; geluid kent deze beperking niet.

Laten we het idee uitwerken: laten we het idee voor het retailsegment als basis nemen. U kunt de klanttevredenheid meten bij de kassa's van winkels. Als een van de klanten ontevreden is over de service en zijn toon begint te verheffen, kunt u onmiddellijk de beheerder bellen voor hulp.
In dit geval moeten we menselijke stemherkenning toevoegen, zodat we winkelmedewerkers van klanten kunnen onderscheiden en analyses voor elk individu kunnen bieden. Welnu, daarnaast wordt het mogelijk om het gedrag van de winkelmedewerkers zelf te analyseren, de sfeer in het team te evalueren, klinkt goed!

Wij formuleren de eisen voor onze oplossing:

  • Klein formaat van het doelapparaat
  • Realtime werking
  • Lage prijs
  • Gemakkelijke schaalbaarheid

Als gevolg hiervan selecteren we Raspberry Pi 3 c als doelapparaat Intel NCS2.

Hier is het belangrijk om één belangrijk kenmerk van NCS op te merken: het werkt het beste met standaard CNN-architecturen, maar als je een model met aangepaste lagen erop moet draaien, verwacht dan optimalisatie op laag niveau.

Er zit maar één klein dingetje op: je hebt een microfoon nodig. Een gewone USB-microfoon is voldoende, maar zal er niet goed uitzien in combinatie met de RPI. Maar zelfs hier ligt de oplossing letterlijk ‘dichtbij’. Om stem op te nemen, besluiten we het Voice Bonnet-bord uit de kit te gebruiken Google AIY-spraakkit, waarop zich een bedrade stereomicrofoon bevindt.

Raspbian downloaden van AIY-projectenrepository en upload het naar een flashstation, test of de microfoon werkt met behulp van de volgende opdracht (hij neemt audio van 5 seconden op en slaat deze op in een bestand):

arecord -d 5 -r 16000 test.wav

Ik moet meteen opmerken dat de microfoon erg gevoelig is en geluid goed oppikt. Om dit op te lossen, gaan we naar alsamixer, selecteren Capture devices en verlagen het ingangssignaalniveau naar 50-60%.

OpenVINO hackathon: stem en emoties herkennen op Raspberry Pi
We passen de body aan met een vijl en alles past, je kunt hem zelfs afsluiten met een deksel

Een indicatorknop toevoegen

Terwijl we de AIY Voice Kit uit elkaar halen, herinneren we ons dat er een RGB-knop is waarvan de achtergrondverlichting softwarematig kan worden aangestuurd. We zoeken naar “Google AIY Led” en vinden documentatie: https://aiyprojects.readthedocs.io/en/latest/aiy.leds.html
Waarom gebruik je deze knop niet om de herkende emotie weer te geven, we hebben slechts 7 klassen en de knop heeft 8 kleuren, net genoeg!

We verbinden de knop via GPIO met Voice Bonnet, laden de benodigde bibliotheken (ze zijn al geïnstalleerd in de distributiekit van AIY-projecten)

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

Laten we een dictaat maken waarin elke emotie een overeenkomstige kleur heeft in de vorm van een RGB Tuple en een object van de klasse aiy.leds.Leds, waarmee we de kleur zullen bijwerken:

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

En ten slotte zullen we na elke nieuwe voorspelling van een emotie de kleur van de knop overeenkomstig bijwerken (per sleutel).

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

OpenVINO hackathon: stem en emoties herkennen op Raspberry Pi
Knop, branden!

Werken met stem

We zullen pyaudio gebruiken om de stream van de microfoon op te vangen en webrtcvad om ruis te filteren en stem te detecteren. Daarnaast maken we een wachtrij aan waaraan we asynchroon stemfragmenten toevoegen en verwijderen.

Omdat webrtcvad een beperking heeft op de grootte van het geleverde fragment - het moet gelijk zijn aan 10/20/30 ms, en de training van het model voor het herkennen van emoties (zoals we later zullen leren) werd uitgevoerd op een 48 kHz dataset, zullen we leg brokken vast met een grootte van 48000×20ms/1000×1(mono)=960 bytes. Webrtcvad retourneert True/False voor elk van deze chunks, wat overeenkomt met de aan- of afwezigheid van een stem in de chunk.

Laten we de volgende logica implementeren:

  • We zullen aan de lijst de blokken toevoegen waarvoor er gestemd is; als er geen stem is, verhogen we de teller van de lege blokken.
  • Als de teller van lege chunks >=30 (600 ms) is, kijken we naar de grootte van de lijst met verzamelde chunks; als deze >250 is, voegen we deze toe aan de wachtrij; zo niet, dan beschouwen we de lengte van het record is niet voldoende om het aan het model door te geven om de spreker te identificeren.
  • Als de teller van lege chunks nog steeds < 30 is, en de omvang van de lijst met verzamelde chunks groter is dan 300, dan zullen we het fragment aan de wachtrij toevoegen voor een nauwkeurigere voorspelling. (omdat emoties de neiging hebben om in de loop van de tijd te veranderen)

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

Het is tijd om te zoeken naar vooraf getrainde modellen in het publieke domein, ga naar github, Google, maar onthoud dat we een beperking hebben op de gebruikte architectuur. Dit is een nogal moeilijk onderdeel, omdat u de modellen op uw invoergegevens moet testen en ze bovendien moet converteren naar het interne formaat van OpenVINO - IR (Intermediate Representation). We hebben ongeveer 5-7 verschillende oplossingen van github geprobeerd, en als het model voor het herkennen van emoties onmiddellijk werkte, moesten we met stemherkenning langer wachten - ze gebruiken complexere architecturen.

Wij richten ons op het volgende:

Vervolgens zullen we het hebben over het converteren van modellen, te beginnen met de theorie. OpenVINO bevat verschillende modules:

  • Open Model Zoo, waarvan modellen kunnen worden gebruikt en in uw product kunnen worden opgenomen
  • Model Optimzer, waarmee u een model uit verschillende raamwerkformaten (Tensorflow, ONNX enz.) kunt converteren naar het Intermediate Representation-formaat, waarmee we verder zullen werken
  • Met Inference Engine kunt u modellen in IR-formaat uitvoeren op Intel-processors, Myriad-chips en Neural Compute Stick-accelerators
  • De meest efficiënte versie van OpenCV (met ondersteuning voor Inference Engine)
    Elk model in IR-formaat wordt beschreven door twee bestanden: .xml en .bin.
    Modellen worden als volgt via Model Optimizer naar IR-formaat geconverteerd:

    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 Hiermee kunt u het gegevensformaat selecteren waarmee het model zal werken. FP32, FP16, INT8 worden ondersteund. Het kiezen van het optimale gegevenstype kan een goede prestatieverbetering opleveren.
    --input_shape geeft de dimensie van de invoergegevens aan. De mogelijkheid om dit dynamisch te veranderen lijkt aanwezig te zijn in de C++ API, maar we hebben niet zo ver gegraven en het eenvoudigweg voor een van de modellen opgelost.
    Laten we vervolgens proberen het reeds geconverteerde model in IR-formaat via de DNN-module in OpenCV te laden en ernaar door te sturen.

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

    Met de laatste regel kun je in dit geval berekeningen omleiden naar de Neural Compute Stick, basisberekeningen worden uitgevoerd op de processor, maar in het geval van de Raspberry Pi zal dit niet werken, je hebt een stick nodig.

    Vervolgens is de logica als volgt: we verdelen onze audio in vensters van een bepaalde grootte (voor ons is dit 0.4 s), we zetten elk van deze vensters om in MFCC, die we vervolgens aan het raster doorgeven:

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

    Laten we vervolgens de meest voorkomende klasse voor alle vensters nemen. Een simpele oplossing, maar voor een hackathon hoef je niet iets te diepzinnigs te bedenken, alleen als je tijd hebt. We hebben nog veel werk te doen, dus laten we verder gaan: we zullen ons bezighouden met stemherkenning. Het is noodzakelijk om een ​​soort database te maken waarin spectrogrammen van vooraf opgenomen stemmen worden opgeslagen. Omdat er nog maar weinig tijd over is, zullen we dit probleem zo goed mogelijk oplossen.

    We maken namelijk een script voor het opnemen van een stemfragment (het werkt op dezelfde manier als hierboven beschreven, alleen wanneer het wordt onderbroken vanaf het toetsenbord, wordt de stem in een bestand opgeslagen).

    Laten we proberen:

    python3 voice_db/record_voice.py test.wav

    We nemen de stemmen van meerdere mensen op (in ons geval drie teamleden)
    Vervolgens voeren we voor elke opgenomen stem een ​​snelle fourier-transformatie uit, verkrijgen we een spectrogram en slaan we dit op als een numpy-array (.npy):

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

    Meer details in het bestand create_base.py
    Als gevolg hiervan krijgen we, wanneer we het hoofdscript uitvoeren, vanaf het begin insluitingen van deze spectrogrammen:

    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)

    Nadat we de inbedding van het beluisterde segment hebben ontvangen, kunnen we bepalen van wie het is door de cosinusafstand van de passage naar alle stemmen in de database te nemen (hoe kleiner, hoe waarschijnlijker) - voor de demo stellen we de drempel in tot 0.3):

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

    Uiteindelijk zou ik willen opmerken dat de inferentiesnelheid hoog was en het mogelijk maakte om nog 1-2 modellen toe te voegen (voor een monster van 7 seconden duurde het 2.5 voor de inferentie). We hadden geen tijd meer om nieuwe modellen toe te voegen en concentreerden ons op het schrijven van een prototype van de webapplicatie.

    web applicatie

    Een belangrijk punt: we nemen een router mee van huis en zetten ons lokale netwerk op, het helpt om het apparaat en de laptops via het netwerk te verbinden.

    De backend is een end-to-end berichtenkanaal tussen de voorkant en de Raspberry Pi, gebaseerd op websocket-technologie (http over tcp-protocol).

    De eerste fase is het ontvangen van verwerkte informatie van Raspberry, dat wil zeggen voorspellers verpakt in json, die halverwege hun reis in de database worden opgeslagen, zodat statistieken kunnen worden gegenereerd over de emotionele achtergrond van de gebruiker voor de periode. Dit pakket wordt vervolgens naar de frontend verzonden, die een abonnement gebruikt en pakketten ontvangt van het websocket-eindpunt. Het gehele backend-mechanisme is gebouwd in de golang-taal; er is voor gekozen omdat het zeer geschikt is voor asynchrone taken, die goroutines goed afhandelen.
    Bij toegang tot het eindpunt wordt de gebruiker geregistreerd en in de structuur ingevoerd, waarna zijn bericht wordt ontvangen. Zowel de gebruiker als het bericht worden ingevoerd in een gemeenschappelijke hub, van waaruit berichten al verder worden verzonden (naar het geabonneerde front), en als de gebruiker de verbinding verbreekt (framboos of front), wordt zijn abonnement opgezegd en wordt hij verwijderd uit de hub.

    OpenVINO hackathon: stem en emoties herkennen op Raspberry Pi
    We wachten op een verbinding vanaf de achterkant

    Front-end is een webapplicatie geschreven in JavaScript met behulp van de React-bibliotheek om het ontwikkelingsproces te versnellen en te vereenvoudigen. Het doel van deze applicatie is om gegevens te visualiseren die zijn verkregen met behulp van algoritmen die aan de back-endzijde en rechtstreeks op de Raspberry Pi draaien. Op de pagina is sectieroutering geïmplementeerd met behulp van react-router, maar de belangrijkste pagina van belang is de hoofdpagina, waar een continue stroom gegevens in realtime wordt ontvangen van de server met behulp van WebSocket-technologie. Raspberry Pi detecteert een stem, bepaalt of deze bij een specifieke persoon uit de geregistreerde database hoort en stuurt een waarschijnlijkheidslijst naar de klant. De cliënt toont de laatste relevante gegevens, toont de avatar van de persoon die hoogstwaarschijnlijk in de microfoon heeft gesproken, evenals de emotie waarmee hij de woorden uitspreekt.

    OpenVINO hackathon: stem en emoties herkennen op Raspberry Pi
    Startpagina met bijgewerkte voorspellingen

    Conclusie

    Het was niet mogelijk om alles zoals gepland af te ronden, we hadden simpelweg geen tijd, dus de voornaamste hoop was in de demo dat alles zou werken. In de presentatie vertelden ze hoe alles werkt, welke modellen ze gebruikten, welke problemen ze tegenkwamen. Het volgende was het demogedeelte: experts liepen in willekeurige volgorde door de kamer en benaderden elk team om naar het werkende prototype te kijken. Ze stelden ons ook vragen, iedereen beantwoordde zijn deel, ze verlieten het internet op de laptop en alles werkte echt zoals verwacht.

    Ik wil er rekening mee houden dat de totale kosten van onze oplossing $ 150 bedroegen:

    • Frambozen Pi 3 ~ $35
    • Google AIY Voice Bonnet (u kunt een respeakervergoeding betalen) ~ 15 $
    • Intel NCS 2 ~ 100$

    Hoe te verbeteren:

    • Gebruik registratie van de klant - vraag om de tekst te lezen die willekeurig wordt gegenereerd
    • Voeg nog een paar modellen toe: je kunt het geslacht en de leeftijd met je stem bepalen
    • Afzonderlijke gelijktijdig klinkende stemmen (diarisering)

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

    OpenVINO hackathon: stem en emoties herkennen op Raspberry Pi
    Moe maar blij zijn we

    Tot slot wil ik graag de organisatoren en deelnemers bedanken. Van de projecten van andere teams waren we persoonlijk gecharmeerd van de oplossing voor het monitoren van vrije parkeerplaatsen. Voor ons was het een enorm coole ervaring van onderdompeling in het product en de ontwikkeling. Ik hoop dat er in de regio’s steeds meer interessante evenementen zullen plaatsvinden, ook over AI-onderwerpen.

Bron: www.habr.com

Voeg een reactie