OpenVINO hackathon: hang és érzelmek felismerése Raspberry Pi-n

Nyizsnyij Novgorodban november 30-tól december 1-ig tartották OpenVINO hackathon. A résztvevőket arra kérték, hogy készítsenek egy termékmegoldás prototípusát az Intel OpenVINO eszközkészletével. A szervezők egy listát javasoltak azokról a hozzávetőleges témákról, amelyek alapján a feladat kiválasztásakor vezérelhető volt, de a végső döntés a csapatoknál maradt. Ezenkívül ösztönözték a termékben nem szereplő modellek használatát.

OpenVINO hackathon: hang és érzelmek felismerése Raspberry Pi-n

Ebben a cikkben arról fogunk beszélni, hogyan hoztuk létre a termék prototípusát, amellyel végül az első helyezést értük el.

A hackathonon több mint 10 csapat vett részt. Jó, hogy néhányan más vidékről érkeztek. A hackathon helyszíne a „Kremlinszkij a Pocsainon” komplexum volt, ahol Nyizsnyij Novgorod ősi fényképeit függesztették fel, kíséretben! (Emlékeztetlek, hogy jelenleg az Intel központi irodája Nyizsnyij Novgorodban található). A résztvevők 26 órát kaptak a kódírásra, a végén pedig bemutatniuk kellett a megoldásukat. Külön előnyt jelentett a demó munkamenet jelenléte, amely megbizonyosodott arról, hogy minden eltervezett megvalósul, és nem marad ötlet a bemutatóban. Áru, uzsonna, kaja, minden volt ott is!

Ezen kívül az Intel opcionálisan biztosított kamerákat, Raspberry PI-t és Neural Compute Stick 2-t.

Feladat kiválasztása

A szabad formájú hackathonra való felkészülés egyik legnehezebb része a kihívás kiválasztása. Azonnal úgy döntöttünk, hogy kitalálunk valamit, ami még nem volt a termékben, mivel a bejelentés szerint ez nagyon üdvözlendő.

Miután elemezte modell, amelyek a jelenlegi kiadásban szerepelnek a termékben, arra a következtetésre jutottunk, hogy ezek többsége különféle számítógépes látási problémákat old meg. Ráadásul a számítógépes látás területén nagyon nehéz olyan problémát kitalálni, amelyet az OpenVINO segítségével nem lehet megoldani, és még ha fel is lehet találni, nehéz előre betanított modelleket találni a nyilvánosságban. Úgy döntünk, hogy egy másik irányba ásunk – a beszédfeldolgozás és az analitika felé. Vegyünk egy érdekes feladatot az érzelmek beszédből történő felismerésére. El kell mondanunk, hogy az OpenVINO-nak már van egy olyan modellje, amely az arca alapján határozza meg az ember érzelmeit, de:

  • Elméletileg lehetséges egy olyan kombinált algoritmus létrehozása, amely hangon és képen is működik, ami növeli a pontosságot.
  • A kamerák általában szűk betekintési szöggel rendelkeznek, egynél több kamera szükséges egy nagy terület lefedéséhez, a hangnak nincs ilyen korlátozása.

Fejlesszük az ötletet: vegyük a kiskereskedelmi szegmensre vonatkozó ötletet alapul. A vásárlók elégedettségét a bolti pénztáraknál mérheti. Ha valamelyik ügyfél elégedetlen a szolgáltatással, és elkezdi emelni a hangját, azonnal hívhatja a rendszergazdát.
Ebben az esetben emberi hangfelismerést kell hozzáadnunk, ez lehetővé teszi számunkra, hogy megkülönböztessük az üzletek alkalmazottait a vásárlóktól, és elemzést biztosítsunk minden egyes személy számára. Nos, ezen kívül lehetőség lesz elemezni maguknak az üzlet alkalmazottainak viselkedését, értékelni a csapat légkörét, jól hangzik!

Megoldásunkkal szemben megfogalmazzuk a követelményeket:

  • A céleszköz kis mérete
  • Valós idejű működés
  • Alacsony ár
  • Könnyű skálázhatóság

Ennek eredményeként a Raspberry Pi 3 c-t választottuk céleszközként Intel NCS 2.

Itt fontos megjegyezni az NCS egy fontos jellemzőjét - szabványos CNN architektúrákkal működik a legjobban, de ha egyéni rétegekkel kell futtatnia egy modellt, akkor alacsony szintű optimalizálásra számítson.

Csak egy apró dolgot kell tenni: mikrofont kell szerezned. Egy normál USB-mikrofon is megteszi, de az RPI-vel együtt nem néz ki jól. De a megoldás még itt is „a közelben van”. A hang rögzítéséhez úgy döntünk, hogy a készletben található Voice Bonnet táblát használjuk Google AIY Voice Kit, amelyen vezetékes sztereó mikrofon található.

Töltse le a Raspbiant innen AIY projektek tárháza és töltse fel egy flash meghajtóra, tesztelje a mikrofon működését a következő paranccsal (5 másodpercig rögzíti a hangot, és elmenti egy fájlba):

arecord -d 5 -r 16000 test.wav

Azonnal meg kell jegyeznem, hogy a mikrofon nagyon érzékeny és jól veszi a zajt. Ennek kijavításához menjünk az alsamixerhez, válasszuk a Capture devices menüpontot, és csökkentsük a bemeneti jelszintet 50-60%-ra.

OpenVINO hackathon: hang és érzelmek felismerése Raspberry Pi-n
A testet reszelővel módosítjuk és minden belefér, akár fedővel is le lehet zárni

Jelző gomb hozzáadása

Az AIY Voice Kit szétszedésekor emlékezünk rá, hogy van egy RGB gomb, melynek háttérvilágítása szoftveresen vezérelhető. Keresünk a „Google AIY Led” kifejezésre, és megtaláljuk a dokumentációt: https://aiyprojects.readthedocs.io/en/latest/aiy.leds.html
Miért ne használja ezt a gombot a felismert érzelem megjelenítésére, csak 7 osztályunk van, és a gombnak 8 színe van, éppen elég!

A gombot GPIO-n keresztül csatlakoztatjuk a Voice Bonnethez, betöltjük a szükséges könyvtárakat (már telepítve vannak az AIY projektekből származó disztribúciós készletben)

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

Hozzunk létre egy diktátumot, amelyben minden érzelemnek megfelelő színe lesz egy RGB Tuple formájában és egy aiy.leds.Leds osztályú objektummal, amelyen keresztül frissítjük a színt:

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

És végül egy érzelem minden új előrejelzése után frissítjük a gomb színét annak megfelelően (kulcsonként).

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

OpenVINO hackathon: hang és érzelmek felismerése Raspberry Pi-n
Gomb, égess!

Munka hanggal

A pyaudio segítségével rögzítjük az adatfolyamot a mikrofonból, a webrtcvad-ot pedig a zajszűréshez és a hang észleléséhez. Ezenkívül létrehozunk egy sort, amelyhez aszinkron módon hozzáadunk és eltávolítunk hangrészleteket.

Mivel a webrtcvad korlátozza a szállított töredék méretét - 10/20/30 ms-nak kell lennie, és az érzelmek felismerésére szolgáló modell betanítása (ahogy később megtudjuk) 48 kHz-es adathalmazon történt, 48000×20ms/1000×1(mono)=960 bájt méretű darabok rögzítése. A Webrtcvad mindegyik darabnál igaz/hamis értéket ad vissza, ami megfelel a szavazat meglétének vagy hiányának a darabban.

Valósítsuk meg a következő logikát:

  • Felvesszük a listára azokat a darabokat, ahol van szavazás, ha nincs szavazat, akkor növeljük az üres darabok számlálóját.
  • Ha az üres darabok számlálója >=30 (600 ms), akkor megnézzük a felhalmozott darabok listájának méretét, ha >250, akkor hozzáadjuk a sorhoz, ha nem, akkor a hosszt tekintjük a rekordból nem elegendő a modellnek betáplálni a beszélő azonosításához.
  • Ha az üres darabok számlálója még mindig < 30, és a felhalmozott darabok listájának mérete meghaladja a 300-at, akkor a pontosabb előrejelzés érdekében hozzáadjuk a töredéket a sorhoz. (mert az érzelmek hajlamosak idővel változni)

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

Itt az ideje, hogy előzetesen kiképzett modelleket keressünk a nyilvánosság számára, keressük fel a github-ot, a Google-t, de ne feledjük, hogy a használt architektúra korlátozott. Ez egy meglehetősen nehéz rész, mert a modelleket tesztelni kell a bemeneti adatokon, és emellett konvertálni kell őket az OpenVINO belső formátumába - IR (Intermediate Representation). Körülbelül 5-7 különböző megoldást próbáltunk ki a githubból, és ha az érzelmek felismerésének modellje azonnal működött, akkor a hangfelismeréssel tovább kellett várni - ők összetettebb architektúrákat használnak.

A következőkre összpontosítunk:

Ezután a modellek konvertálásáról fogunk beszélni, kezdve az elmélettel. Az OpenVINO több modult tartalmaz:

  • Nyissa meg a Model Zoo-t, amelynek modelljeit felhasználhatja és beillesztheti a termékébe
  • Model Optimzer, melynek köszönhetően különböző keretformátumokból (Tensorflow, ONNX stb.) konvertálhat egy modellt Intermediate Representation formátumba, amellyel tovább dolgozunk
  • Az Inference Engine lehetővé teszi a modellek infravörös formátumú futtatását Intel processzorokon, Myriad chipeken és Neural Compute Stick gyorsítókon
  • Az OpenCV leghatékonyabb verziója (Inference Engine támogatással)
    Minden infravörös formátumú modellt két fájl ír le: .xml és .bin.
    A modellek konvertálása infravörös formátumba történik a Model Optimizer segítségével az alábbiak szerint:

    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 lehetővé teszi az adatformátum kiválasztását, amellyel a modell működni fog. Az FP32, FP16, INT8 támogatott. Az optimális adattípus kiválasztása jó teljesítménynövekedést adhat.
    --input_shape jelzi a bemeneti adatok méretét. Úgy tűnik, hogy a dinamikus változtatás lehetősége a C++ API-ban megtalálható, de nem ástunk odáig, és egyszerűen kijavítottuk az egyik modellnél.
    Ezután próbáljuk meg a már konvertált modellt IR formátumban betölteni a DNN modulon keresztül az OpenCV-be, és továbbítani neki.

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

    Az utolsó sor ebben az esetben lehetővé teszi a számítások átirányítását a Neural Compute Stickre, az alapvető számításokat a processzoron végzik el, de a Raspberry Pi esetében ez nem fog működni, szükség lesz egy botra.

    Ezután a logika a következő: a hangunkat egy bizonyos méretű ablakokra osztjuk (nálunk ez 0.4 s), ezeket az ablakokat MFCC-vé alakítjuk, majd betápláljuk a rácsba:

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

    Ezután vegyük a leggyakoribb osztályt az összes ablakhoz. Egyszerű megoldás, de egy hackathonhoz nem kell valami túl elgondolkodtatót kitalálni, csak ha van időd. Rengeteg dolgunk van még, úgyhogy menjünk tovább – a hangfelismeréssel fogunk foglalkozni. Valamilyen adatbázist kellene készíteni, amiben az előre felvett hangok spektrogramjait tárolnák. Mivel kevés idő van hátra, a lehető legjobban megoldjuk ezt a problémát.

    Ugyanis egy hangrészlet rögzítésére készítünk egy szkriptet (ugyanúgy működik, mint fentebb leírtuk, csak a billentyűzetről megszakítva menti fájlba a hangot).

    Próbáljuk meg:

    python3 voice_db/record_voice.py test.wav

    Több ember hangját rögzítjük (esetünkben három csapattag)
    Ezután minden felvett hangnál végrehajtunk egy gyors Fourier-transzformációt, előállítunk egy spektrogramot, és elmentjük numpy tömbként (.npy):

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

    További részletek a fájlban create_base.py
    Ennek eredményeként a fő szkript futtatásakor a legelején ezekből a spektrogramokból fogunk beágyazni:

    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)

    Miután megkaptuk a beágyazást a megszólaltatott szegmensből, meg tudjuk határozni, hogy kihez tartozik, ha a koszinusz távolságot vesszük az átjárótól az adatbázisban lévő összes hangig (minél kisebb, annál valószínűbb) - a demónál beállítjuk a küszöböt 0.3-ig):

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

    Végül szeretném megjegyezni, hogy a következtetési sebesség gyors volt, és lehetővé tette további 1-2 modell hozzáadását (egy 7 másodperces mintához 2.5 kellett a következtetéshez). Már nem volt időnk új modellek hozzáadására, és a webalkalmazás prototípusának megírására koncentráltunk.

    webalkalmazás

    Egy fontos szempont: otthonról viszünk magunkkal egy routert és beállítjuk a helyi hálózatunkat, ez segít a készülék és a laptopok hálózaton keresztüli összekapcsolásában.

    A háttér egy végpontok közötti üzenetcsatorna az előlap és a Raspberry Pi között, amely websocket technológián (http over tcp protokoll) alapul.

    Az első lépés a Raspberry-ből feldolgozott információk, azaz json-ba csomagolt prediktorok fogadása, amelyeket útjuk felénél elmentenek az adatbázisba, így statisztika készülhet a felhasználó érzelmi hátteréről az adott időszakra vonatkozóan. Ez a csomag ezután elküldésre kerül a frontendnek, amely előfizetést használ, és csomagokat fogad a websocket végponttól. A teljes háttér-mechanizmus golang nyelven épült, azért esett erre a választásra, mert kiválóan alkalmas aszinkron feladatokra, amelyeket a gorutinok jól kezelnek.
    A végpont elérésekor a felhasználó regisztrálásra kerül és bekerül a struktúrába, majd megérkezik az üzenete. Mind a felhasználó, mind az üzenet bekerül egy közös hubba, ahonnan már továbbküldésre kerülnek az üzenetek (az előfizetett frontra), és ha a felhasználó megszakítja a kapcsolatot (málna vagy front), akkor az előfizetése törlődik és eltávolítják a hub.

    OpenVINO hackathon: hang és érzelmek felismerése Raspberry Pi-n
    Csatlakozást várunk hátulról

    A Front-end egy JavaScript nyelven írt webalkalmazás, amely a React könyvtárat használja a fejlesztési folyamat felgyorsítására és egyszerűsítésére. Ennek az alkalmazásnak a célja a háttéroldalon és közvetlenül a Raspberry Pi-n futó algoritmusokkal nyert adatok megjelenítése. Az oldal react-routerrel megvalósított szekcionált útválasztással rendelkezik, de az érdeklődésre számot tartó főoldal a főoldal, ahol valós időben folyamatos adatfolyam érkezik a szervertől a WebSocket technológia segítségével. A Raspberry Pi érzékeli a hangot, megállapítja, hogy egy adott személyhez tartozik-e a regisztrált adatbázisból, és egy valószínűségi listát küld a kliensnek. Az ügyfél megjeleníti a legfrissebb releváns adatokat, megjeleníti annak a személynek az avatarját, aki valószínűleg a mikrofonba beszélt, valamint azt az érzelmet, amellyel a szavakat kiejti.

    OpenVINO hackathon: hang és érzelmek felismerése Raspberry Pi-n
    Kezdőlap frissített előrejelzésekkel

    Következtetés

    Nem sikerült mindent úgy kivitelezni, ahogy elterveztük, egyszerűen nem volt időnk, így a fő remény a demóban volt, hogy minden működni fog. Az előadáson beszéltek arról, hogyan működik minden, milyen modelleket vettek fel, milyen problémákkal találkoztak. Következett a bemutató rész – a szakértők véletlenszerű sorrendben körbejárták a termet, és felkerestek minden csapatot, hogy megnézzék a működő prototípust. Nekünk is kérdezősködtek, mindenki válaszolt a maga részéről, laptopon hagyták a netet, és tényleg minden a várt módon működött.

    Hadd jegyezzem meg, hogy megoldásunk összköltsége 150 dollár volt:

    • Raspberry Pi 3 ~ 35 dollár
    • Google AIY Voice Bonnet (bemondói díjat vehet fel) ~ 15 USD
    • Intel NCS 2 ~ 100 dollár

    Hogyan fejlődjön:

    • Használja a kliens regisztrációját - kérje meg a véletlenszerűen generált szöveg elolvasását
    • Adjon hozzá még néhány modellt: hanggal meghatározhatja a nemet és az életkort
    • Egyidejűleg megszólaló hangok különválasztása (diarizálás)

    Adattár: https://github.com/vladimirwest/OpenEMO

    OpenVINO hackathon: hang és érzelmek felismerése Raspberry Pi-n
    Fáradtak, de boldogok vagyunk

    Végezetül szeretnék köszönetet mondani a szervezőknek és a résztvevőknek. Más csapatok projektjei közül nekünk személy szerint tetszett a szabad parkolóhelyek figyelésének megoldása. Számunkra vadul menő élmény volt a termékben és a fejlesztésben való elmerülés. Bízom benne, hogy a régiókban egyre több érdekes esemény kerül megrendezésre, többek között mesterséges intelligenciával kapcsolatos témákban is.

Forrás: will.com

Hozzászólás