OpenVINO hackathon: äänen ja tunteiden tunnistaminen Raspberry Pi:llä

30. marraskuuta - 1. joulukuuta Nižni Novgorodissa pidettiin OpenVINO hackathon. Osallistujia pyydettiin luomaan prototyyppi tuoteratkaisusta Intel OpenVINO -työkalupakin avulla. Järjestäjät ehdottivat listaa likimääräisistä aiheista, joita voisi ohjata tehtävää valittaessa, mutta lopullinen päätös jäi ryhmille. Lisäksi kannustettiin käyttämään sellaisia ​​malleja, jotka eivät sisälly tuotteeseen.

OpenVINO hackathon: äänen ja tunteiden tunnistaminen Raspberry Pi:llä

Tässä artikkelissa kerromme sinulle, kuinka loimme prototyyppimme tuotteesta, jolla lopulta sijoituimme.

Hackathoniin osallistui yli 10 joukkuetta. Hienoa, että osa heistä tuli muilta alueilta. Hackathonin paikka oli "Kremlinski Pochainilla" -kompleksi, jonka sisälle ripustettiin muinaisia ​​valokuvia Nižni Novgorodista seurueessa! (Muistutan, että tällä hetkellä Intelin keskustoimisto sijaitsee Nižni Novgorodissa). Osallistujille annettiin 26 tuntia koodin kirjoittamiseen, ja lopuksi heidän piti esitellä ratkaisunsa. Erillinen etu oli esittelyn läsnäolo, jolla varmistettiin, että kaikki suunniteltu toteutui todellakin eikä jäänyt esitykseen ideoita. Kauppa, välipala, ruoka, kaikki oli myös siellä!

Lisäksi Intel toimitti valinnaisesti kamerat, Raspberry PI:n ja Neural Compute Stick 2:n.

Tehtävävalinta

Yksi vaikeimmista osista valmistautuessa vapaamuotoiseen hackathoniin on haasteen valinta. Päätimme heti keksiä jotain, mitä ei vielä ollut tuotteessa, sillä tiedotteessa sanottiin, että tämä oli erittäin tervetullutta.

Analysoituaan malli, jotka sisältyvät tuotteeseen nykyisessä julkaisussa, tulemme siihen tulokseen, että useimmat niistä ratkaisevat erilaisia ​​tietokonenäköongelmia. Lisäksi tietokonenäön alalla on erittäin vaikeaa keksiä ongelmaa, jota ei voida ratkaista OpenVINOlla, ja vaikka sellainen voitaisiin keksiä, on vaikea löytää esikoulutettuja malleja julkisista. Päätämme kaivaa toiseen suuntaan - kohti puheenkäsittelyä ja analytiikkaa. Tarkastellaan mielenkiintoista tehtävää tunteiden tunnistamiseksi puheesta. On sanottava, että OpenVINOlla on jo malli, joka määrittää ihmisen tunteet hänen kasvojensa perusteella, mutta:

  • Teoriassa on mahdollista luoda yhdistetty algoritmi, joka toimii sekä äänen että kuvan kanssa, minkä pitäisi lisätä tarkkuutta.
  • Kameroilla on yleensä kapea katselukulma, suuren alueen kattamiseksi tarvitaan useampi kuin yksi kamera, äänellä ei ole tällaista rajoitusta.

Kehitetään ideaa: otetaan idea vähittäiskaupan segmentille. Voit mitata asiakastyytyväisyyttä myymälän kassoilla. Jos joku asiakkaista on tyytymätön palveluun ja alkaa nostaa äänensävyään, voit soittaa välittömästi järjestelmänvalvojalle apua.
Tässä tapauksessa meidän on lisättävä ihmisen äänentunnistus, jonka avulla voimme erottaa myymälän työntekijät asiakkaista ja tarjota analytiikkaa jokaiselle yksilölle. No, lisäksi on mahdollista analysoida itse kaupan työntekijöiden käyttäytymistä, arvioida tiimin ilmapiiriä, kuulostaa hyvältä!

Laadimme ratkaisullemme vaatimukset:

  • Kohdelaitteen pieni koko
  • Reaaliaikainen toiminta
  • Alhainen hinta
  • Helppo skaalautuvuus

Tämän seurauksena valitsemme kohdelaitteeksi Raspberry Pi 3 c:n Intel NCS 2.

Tässä on tärkeää huomata yksi tärkeä NCS:n ominaisuus - se toimii parhaiten tavallisten CNN-arkkitehtuurien kanssa, mutta jos sinun on suoritettava malli, jossa on mukautettuja kerroksia, odota matalan tason optimointia.

On vain yksi pieni asia tehtävänä: sinun on hankittava mikrofoni. Tavallinen USB-mikrofoni käy, mutta se ei näytä hyvältä yhdessä RPI:n kanssa. Mutta jopa tässä ratkaisu kirjaimellisesti "pitää lähellä". Äänen tallentamiseen päätämme käyttää sarjan Voice Bonnet -korttia Google AIY Voice Kit, jossa on langallinen stereomikrofoni.

Lataa Raspbian osoitteesta AIY-projektien arkisto ja lataa se flash-asemaan, testaa, että mikrofoni toimii seuraavalla komennolla (se tallentaa äänen 5 sekuntia ja tallentaa sen tiedostoon):

arecord -d 5 -r 16000 test.wav

Huomautan heti, että mikrofoni on erittäin herkkä ja sietää hyvin melua. Korjataksesi tämän menemällä Alsamixeriin, valitsemalla Capture devices ja laskemalla tulosignaalin tasoa 50-60 %:iin.

OpenVINO hackathon: äänen ja tunteiden tunnistaminen Raspberry Pi:llä
Muokkaamme runkoa viilalla ja kaikki sopii, voit jopa sulkea sen kannella

Ilmaisinpainikkeen lisääminen

AIY Voice Kitiä purettaessa muistamme, että siinä on RGB-painike, jonka taustavaloa voidaan ohjata ohjelmistolla. Haemme "Google AIY Led" ja löydämme dokumentaatiota: https://aiyprojects.readthedocs.io/en/latest/aiy.leds.html
Mikset käytä tätä painiketta tunnistetun tunteen näyttämiseen, meillä on vain 7 luokkaa ja painikkeessa on 8 väriä, juuri tarpeeksi!

Yhdistämme painikkeen GPIO:n kautta Voice Bonnetiin, lataamme tarvittavat kirjastot (ne on jo asennettu jakelusarjaan AIY-projekteista)

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

Tehdään sanelu, jossa jokaisella tunteella on vastaava väri RGB-tuplen muodossa ja objekti luokkaan aiy.leds.Leds, jonka kautta päivitämme värin:

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

Ja lopuksi, jokaisen uuden tunteen ennusteen jälkeen päivitämme painikkeen värin sen mukaisesti (avaimella).

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

OpenVINO hackathon: äänen ja tunteiden tunnistaminen Raspberry Pi:llä
Nappi, polta!

Työskentely äänen kanssa

Käytämme pyaudiota streamin kaappaamiseen mikrofonista ja webrtcvadia melun suodattamiseen ja äänen havaitsemiseen. Lisäksi luomme jonon, johon lisäämme ja poistamme asynkronisesti ääniotteita.

Koska webrtcvadilla on rajoitus toimitetun fragmentin kokoon - sen on oltava yhtä suuri kuin 10/20/30 ms, ja tunteiden tunnistamisen mallin koulutus (kuten opimme myöhemmin) suoritettiin 48 kHz:n tietojoukolla, kaapata paloja, joiden koko on 48000×20ms/1000×1(mono)=960 tavua. Webrtcvad palauttaa True/False jokaiselle näistä paloista, mikä vastaa äänen läsnäoloa tai puuttumista kappaleessa.

Toteutetaan seuraava logiikka:

  • Lisäämme listaan ​​ne palaset, joissa on äänestys, jos ei äänestä, lisäämme tyhjien palasten laskuria.
  • Jos tyhjien osien laskuri on >=30 (600 ms), katsotaan kerättyjen palasten listan kokoa; jos se on >250, lisätään se jonoon; jos ei, katsotaan, että pituus tietue ei riitä syöttämään sitä mallille kaiuttimen tunnistamiseksi.
  • Jos tyhjien osien laskuri on edelleen < 30 ja kerättyjen palasten luettelon koko ylittää 300, lisäämme fragmentin jonoon tarkempaa ennustetta varten. (koska tunteilla on tapana muuttua ajan myötä)

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

On aika etsiä julkisesti valmiiksi koulutettuja malleja, siirtyä githubiin, Googleen, mutta muista, että meillä on rajoituksia käytettävälle arkkitehtuurille. Tämä on melko vaikea osa, koska sinun on testattava mallit syöttötiedoillasi ja lisäksi muutettava ne OpenVINO:n sisäiseen muotoon - IR (Intermediate Representation). Kokeilimme githubista noin 5-7 erilaista ratkaisua, ja jos tunteiden tunnistamisen malli toimi heti, niin puheentunnistuksen kanssa jouduttiin odottamaan pidempään - niissä käytetään monimutkaisempia arkkitehtuureja.

Keskitymme seuraaviin:

Seuraavaksi puhumme mallien muuntamisesta alkaen teoriasta. OpenVINO sisältää useita moduuleja:

  • Avaa Model Zoo, jonka malleja voit käyttää ja sisällyttää tuotteeseesi
  • Model Optimzer, jonka ansiosta voit muuntaa mallin eri kehysformaateista (Tensorflow, ONNX jne.) Intermediate Representation -muotoon, jonka kanssa työskentelemme edelleen
  • Inference Enginen avulla voit ajaa malleja IR-muodossa Intel-prosessoreilla, Myriad-siruilla ja Neural Compute Stick -kiihdyttimillä
  • OpenCV:n tehokkain versio (Inference Engine -tuella)
    Kukin malli IR-muodossa on kuvattu kahdella tiedostolla: .xml ja .bin.
    Mallit muunnetaan IR-muotoon Model Optimizerin avulla seuraavasti:

    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 voit valita tietomuodon, jolla malli toimii. FP32, FP16, INT8 ovat tuettuja. Optimaalisen tietotyypin valitseminen voi parantaa suorituskykyä.
    --input_shape ilmaisee syöttötietojen mittasuhteen. Mahdollisuus muuttaa sitä dynaamisesti näyttää olevan olemassa C++ API:ssa, mutta emme kaivanneet niin pitkälle, vaan korjasimme sen vain yhdelle malleista.
    Seuraavaksi yritetään ladata jo muunnettu malli IR-muodossa DNN-moduulin kautta OpenCV:hen ja välittää se sille.

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

    Viimeisellä rivillä tässä tapauksessa voit ohjata laskelmat Neural Compute Stickiin, peruslaskelmat suoritetaan prosessorilla, mutta Raspberry Pi:n tapauksessa tämä ei toimi, tarvitset tikun.

    Seuraavaksi logiikka on seuraava: jaamme äänemme tietyn kokoisiin ikkunoihin (meille se on 0.4 s), muunnamme kukin näistä ikkunoista MFCC:ksi, jonka sitten syötämme ruudukkoon:

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

    Otetaan seuraavaksi yleisin luokka kaikille ikkunoille. Yksinkertainen ratkaisu, mutta hackathonia varten sinun ei tarvitse keksiä mitään liian tiukkaa, vain jos sinulla on aikaa. Meillä on vielä paljon tehtävää, joten jatketaan - käsittelemme puheentunnistusta. On tarpeen luoda jonkinlainen tietokanta, johon tallennettaisiin valmiiksi tallennettujen äänien spektrogrammit. Koska aikaa on vähän jäljellä, ratkaisemme tämän ongelman parhaamme mukaan.

    Luomme nimittäin skriptin ääniotteen tallentamiseen (toimii samalla tavalla kuin edellä on kuvattu, vain näppäimistöltä keskeytettynä se tallentaa äänen tiedostoon).

    Kokeillaan:

    python3 voice_db/record_voice.py test.wav

    Tallennamme useiden ihmisten (meissämme kolmen tiimin jäsenen) äänet
    Seuraavaksi kullekin äänitetylle äänelle suoritamme nopean Fourier-muunnoksen, hankimme spektrogrammin ja tallennamme sen numpy-taulukkona (.npy):

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

    Tarkemmat tiedot tiedostossa create_base.py
    Tämän seurauksena, kun suoritamme pääskriptiä, saamme upotukset näistä spektrogrammeista heti alussa:

    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)

    Saatuamme upotuksen soitusta segmentistä voimme määrittää, kenelle se kuuluu ottamalla kosinietäisyyden kohdasta kaikkiin tietokannan ääniin (mitä pienempi, sitä todennäköisemmin) - demolle asetamme kynnyksen 0.3:een):

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

    Lopuksi haluaisin huomauttaa, että päättelynopeus oli nopea ja mahdollisti 1-2 mallin lisäämisen (7 sekuntia pitkälle näytteelle johtopäätös kesti 2.5). Meillä ei enää ollut aikaa lisätä uusia malleja ja keskityimme web-sovelluksen prototyypin kirjoittamiseen.

    verkkosovellus

    Tärkeä seikka: otamme reitittimen mukaan kotoa ja määritämme paikallisverkkomme, se auttaa yhdistämään laitteen ja kannettavat tietokoneet verkon kautta.

    Tausta on päästä päähän -viestikanava etuosan ja Raspberry Pi:n välillä, joka perustuu websocket-tekniikkaan (http over tcp-protokolla).

    Ensimmäinen vaihe on saada vadelmasta käsiteltyä tietoa, eli json:iin pakattuja ennustajia, jotka tallennetaan tietokantaan matkan puolivälissä, jotta voidaan tuottaa tilastoja käyttäjän tunnetaustasta ajanjaksolle. Tämä paketti lähetetään sitten käyttöliittymään, joka käyttää tilausta ja vastaanottaa paketteja websocket-päätepisteestä. Koko taustamekanismi on rakennettu golang-kielellä; se valittiin, koska se soveltuu hyvin asynkronisiin tehtäviin, joita gorutiinit käsittelevät hyvin.
    Päätepisteeseen päästäessään käyttäjä rekisteröidään ja syötetään rakenteeseen, jonka jälkeen hänen viestinsä vastaanotetaan. Sekä käyttäjä että viesti syötetään yhteiseen keskittimeen, josta viestit lähetetään jo pidemmälle (tilaajille), ja jos käyttäjä sulkee yhteyden (vadelma tai etu), hänen tilauksensa peruutetaan ja hänet poistetaan keskus.

    OpenVINO hackathon: äänen ja tunteiden tunnistaminen Raspberry Pi:llä
    Odotamme yhteyttä takaapäin

    Front-end on JavaScript-kielellä kirjoitettu verkkosovellus, joka käyttää React-kirjastoa kehitysprosessin nopeuttamiseksi ja yksinkertaistamiseksi. Tämän sovelluksen tarkoitus on visualisoida tiedot, jotka on saatu taustapuolella ja suoraan Raspberry Pi:ssä toimivilla algoritmeilla. Sivulla on react-routerilla toteutettu osioreititys, mutta kiinnostava pääsivu on pääsivu, jolle palvelimelta vastaanotetaan reaaliajassa jatkuva tietovirta WebSocket-tekniikalla. Raspberry Pi havaitsee äänen, määrittää, kuuluuko se tietylle henkilölle rekisteröidystä tietokannasta ja lähettää todennäköisyysluettelon asiakkaalle. Asiakas näyttää viimeisimmät asiaankuuluvat tiedot, näyttää mikrofoniin todennäköisimmin puhuneen henkilön avatarin sekä tunteen, jolla hän lausuu sanat.

    OpenVINO hackathon: äänen ja tunteiden tunnistaminen Raspberry Pi:llä
    Etusivu päivitetyillä ennusteilla

    Johtopäätös

    Kaikkea ei ollut mahdollista toteuttaa suunnitellusti, meillä ei yksinkertaisesti ollut aikaa, joten päätoive oli demossa, että kaikki toimisi. Esityksessä he puhuivat siitä, miten kaikki toimii, mitä malleja he ottivat, mitä ongelmia he kohtasivat. Seuraavaksi oli demo-osa – asiantuntijat kävelivät ympäri huonetta satunnaisessa järjestyksessä ja lähestyivät jokaista tiimiä katsomaan toimivaa prototyyppiä. He kysyivät meiltä myös kysymyksiä, jokainen vastasi omalta osaltaan, he jättivät verkon kannettavalle tietokoneelle ja kaikki toimi odotetusti.

    Haluan huomata, että ratkaisumme kokonaishinta oli 150 dollaria:

    • Raspberry Pi 3 ~ 35 dollaria
    • Google AIY Voice Bonnet (voit ottaa puhujamaksun) ~ 15 $
    • Intel NCS 2 ~ 100 $

    Kuinka parantaa:

    • Käytä asiakkaan rekisteröintiä - pyydä lukemaan satunnaisesti luotu teksti
    • Lisää muutama malli lisää: voit määrittää sukupuolen ja iän äänellä
    • Erottele samanaikaisesti kuultavia ääniä (diarisaatio)

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

    OpenVINO hackathon: äänen ja tunteiden tunnistaminen Raspberry Pi:llä
    Olemme väsyneitä mutta onnellisia

    Lopuksi haluan kiittää järjestäjiä ja osallistujia. Muiden tiimien projekteista pidimme henkilökohtaisesti ratkaisusta ilmaisten pysäköintipaikkojen valvontaan. Meille se oli hurjan siisti kokemus uppoutua tuotteeseen ja kehitykseen. Toivon, että alueilla järjestetään yhä enemmän mielenkiintoisia tapahtumia, myös tekoälyaiheista.

Lähde: will.com

Lisää kommentti