OpenVINO hackathon: pagkilala sa boses at emosyon sa Raspberry Pi

Nobyembre 30 - Disyembre 1 sa Nizhny Novgorod ay ginanap OpenVINO hackathon. Ang mga kalahok ay hiniling na lumikha ng isang prototype ng isang solusyon sa produkto gamit ang toolkit ng Intel OpenVINO. Ang mga organizer ay nagmungkahi ng isang listahan ng mga tinatayang paksa na maaaring magabayan kapag pumipili ng isang gawain, ngunit ang pangwakas na desisyon ay nanatili sa mga koponan. Bilang karagdagan, hinikayat ang paggamit ng mga modelo na hindi kasama sa produkto.

OpenVINO hackathon: pagkilala sa boses at emosyon sa Raspberry Pi

Sa artikulong ito sasabihin namin sa iyo ang tungkol sa kung paano namin ginawa ang aming prototype ng produkto, kung saan kami ay nauna sa huli.

Mahigit 10 koponan ang lumahok sa hackathon. Buti na lang galing sa ibang rehiyon ang iba sa kanila. Ang venue para sa hackathon ay ang "Kremlinsky on Pochain" complex, kung saan ang mga sinaunang litrato ng Nizhny Novgorod ay nakabitin sa loob, sa isang entourage! (Ipapaalala ko sa iyo na sa sandaling ito ang sentral na tanggapan ng Intel ay matatagpuan sa Nizhny Novgorod). Ang mga kalahok ay binigyan ng 26 na oras upang magsulat ng code, at sa dulo ay kailangan nilang ipakita ang kanilang solusyon. Ang isang hiwalay na kalamangan ay ang pagkakaroon ng isang demo session upang matiyak na ang lahat ng binalak ay aktwal na ipinatupad at hindi nanatiling mga ideya sa pagtatanghal. Merch, meryenda, pagkain, lahat ay nandoon din!

Bilang karagdagan, opsyonal na ibinigay ng Intel ang mga camera, Raspberry PI, Neural Compute Stick 2.

Pagpili ng gawain

Isa sa pinakamahirap na bahagi ng paghahanda para sa isang free-form na hackathon ay ang pagpili ng hamon. Agad kaming nagpasya na magkaroon ng isang bagay na wala pa sa produkto, dahil sinabi ng anunsyo na ito ay lubos na tinatanggap.

Nasuri mga modelo, na kung saan ay kasama sa produkto sa kasalukuyang release, dumating kami sa konklusyon na karamihan sa kanila ay malulutas ang iba't ibang mga problema sa computer vision. Bukod dito, napakahirap na magkaroon ng problema sa larangan ng computer vision na hindi malulutas gamit ang OpenVINO, at kahit na maiimbento, mahirap makahanap ng mga pre-trained na modelo sa pampublikong domain. Nagpasya kaming maghukay sa ibang direksyon - patungo sa pagproseso ng pagsasalita at analytics. Isaalang-alang natin ang isang kawili-wiling gawain ng pagkilala sa mga emosyon mula sa pagsasalita. Dapat sabihin na ang OpenVINO ay mayroon nang modelo na tumutukoy sa emosyon ng isang tao batay sa kanilang mukha, ngunit:

  • Sa teorya, posible na lumikha ng isang pinagsamang algorithm na gagana sa parehong tunog at imahe, na dapat magbigay ng pagtaas sa katumpakan.
  • Ang mga camera ay karaniwang may makitid na anggulo sa pagtingin; higit sa isang camera ang kinakailangan upang masakop ang isang malaking lugar; ang tunog ay walang ganoong limitasyon.

Buuin natin ang ideya: gawin natin ang ideya para sa retail segment bilang batayan. Masusukat mo ang kasiyahan ng customer sa mga pag-checkout sa tindahan. Kung ang isa sa mga customer ay hindi nasisiyahan sa serbisyo at nagsimulang itaas ang kanilang tono, maaari mong agad na tawagan ang administrator para sa tulong.
Sa kasong ito, kailangan naming magdagdag ng pagkilala sa boses ng tao, magbibigay-daan ito sa amin na makilala ang mga empleyado ng tindahan mula sa mga customer at magbigay ng analytics para sa bawat indibidwal. Buweno, bilang karagdagan, posible na pag-aralan ang pag-uugali ng mga empleyado ng tindahan mismo, suriin ang kapaligiran sa koponan, maganda ang tunog!

Binubuo namin ang mga kinakailangan para sa aming solusyon:

  • Maliit na sukat ng target na device
  • Real time na operasyon
  • Mababang presyo
  • Madaling scalability

Bilang resulta, pipiliin namin ang Raspberry Pi 3 c bilang target na device Intel NCS 2.

Narito mahalagang tandaan ang isang mahalagang tampok ng NCS - ito ay pinakamahusay na gumagana sa mga karaniwang arkitektura ng CNN, ngunit kung kailangan mong magpatakbo ng isang modelo na may mga pasadyang layer dito, pagkatapos ay asahan ang mababang antas ng pag-optimize.

Mayroon lamang isang maliit na bagay na dapat gawin: kailangan mong kumuha ng mikropono. Magagawa ang isang regular na USB microphone, ngunit hindi ito magiging maganda kasama ng RPI. Ngunit kahit dito ang solusyon ay literal na "namamalagi sa malapit." Para mag-record ng boses, nagpasya kaming gamitin ang Voice Bonnet board mula sa kit Google AIY Voice Kit, kung saan mayroong wired stereo microphone.

I-download ang Raspbian mula sa Imbakan ng mga proyekto ng AIY at i-upload ito sa isang flash drive, subukan kung gumagana ang mikropono gamit ang sumusunod na command (ito ay magre-record ng audio ng 5 segundo ang haba at i-save ito sa isang file):

arecord -d 5 -r 16000 test.wav

Dapat kong tandaan kaagad na ang mikropono ay napaka-sensitibo at mahusay na nakakakuha ng ingay. Upang ayusin ito, pumunta tayo sa alsamixer, piliin ang Capture device at bawasan ang antas ng signal ng input sa 50-60%.

OpenVINO hackathon: pagkilala sa boses at emosyon sa Raspberry Pi
Binabago namin ang katawan gamit ang isang file at lahat ay umaangkop, maaari mo ring isara ito gamit ang isang takip

Pagdaragdag ng pindutan ng tagapagpahiwatig

Habang pinaghiwalay ang AIY Voice Kit, natatandaan namin na mayroong RGB button, ang backlight na maaaring kontrolin ng software. Hinahanap namin ang "Google AIY Led" at nakahanap kami ng dokumentasyon: https://aiyprojects.readthedocs.io/en/latest/aiy.leds.html
Bakit hindi gamitin ang button na ito para ipakita ang kinikilalang emosyon, mayroon lang kaming 7 klase, at ang button ay may 8 kulay, sapat lang!

Ikinonekta namin ang pindutan sa pamamagitan ng GPIO sa Voice Bonnet, i-load ang mga kinakailangang aklatan (naka-install na sila sa distribution kit mula sa mga proyekto ng AIY)

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

Gumawa tayo ng dict kung saan ang bawat emosyon ay magkakaroon ng kaukulang kulay sa anyo ng RGB Tuple at isang object ng class aiy.leds.Leds, kung saan ia-update natin ang kulay:

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

At sa wakas, pagkatapos ng bawat bagong hula ng isang damdamin, ia-update namin ang kulay ng pindutan alinsunod dito (sa pamamagitan ng key).

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

OpenVINO hackathon: pagkilala sa boses at emosyon sa Raspberry Pi
Pindutan, paso!

Paggawa gamit ang boses

Gagamitin namin ang pyaudio upang makuha ang stream mula sa mikropono at webrtcvad upang i-filter ang ingay at makita ang boses. Bilang karagdagan, gagawa kami ng isang queue kung saan kami ay asynchronously magdagdag at mag-aalis ng mga sipi ng boses.

Dahil may limitasyon ang webrtcvad sa laki ng ibinigay na fragment - dapat itong katumbas ng 10/20/30ms, at ang pagsasanay ng modelo para sa pagkilala ng mga emosyon (tulad ng malalaman natin sa ibang pagkakataon) ay isinagawa sa isang 48kHz na dataset, gagawin natin kumuha ng mga tipak na may sukat na 48000Γ—20ms/1000Γ—1(mono)=960 byte. Ibabalik ng Webrtcvad ang True/False para sa bawat isa sa mga chunk na ito, na tumutugma sa presensya o kawalan ng boto sa chunk.

Ipatupad natin ang sumusunod na lohika:

  • Idaragdag namin sa listahan ang mga chunks kung saan may boto; kung walang boto, dagdagan namin ang counter ng mga walang laman na chunks.
  • Kung ang counter ng mga walang laman na chunks ay >=30 (600 ms), pagkatapos ay titingnan namin ang laki ng listahan ng mga naipon na chunks; kung ito ay >250, pagkatapos ay idagdag namin ito sa queue; kung hindi, isinasaalang-alang namin na ang haba ng record ay hindi sapat upang ipakain ito sa modelo upang makilala ang nagsasalita.
  • Kung ang counter ng mga walang laman na chunks ay <30 pa rin, at ang laki ng listahan ng mga naipon na chunks ay lumampas sa 300, pagkatapos ay idaragdag namin ang fragment sa queue para sa isang mas tumpak na hula. (dahil ang mga emosyon ay may posibilidad na magbago sa paglipas ng panahon)

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

Oras na para maghanap ng mga pre-trained na modelo sa pampublikong domain, pumunta sa github, Google, ngunit tandaan na mayroon tayong limitasyon sa ginamit na arkitektura. Ito ay medyo mahirap na bahagi, dahil kailangan mong subukan ang mga modelo sa iyong input data, at bilang karagdagan, i-convert ang mga ito sa panloob na format ng OpenVINO - IR (Intermediate Representation). Sinubukan namin ang tungkol sa 5-7 iba't ibang mga solusyon mula sa github, at kung ang modelo para sa pagkilala sa mga emosyon ay gumana kaagad, pagkatapos ay sa pagkilala ng boses kailangan naming maghintay nang mas matagal - gumagamit sila ng mas kumplikadong mga arkitektura.

Nakatuon kami sa mga sumusunod:

  • Mga emosyon mula sa boses - https://github.com/alexmuhr/Voice_Emotion
    Gumagana ito ayon sa sumusunod na prinsipyo: ang audio ay pinuputol sa mga sipi ng isang tiyak na laki, para sa bawat isa sa mga talatang ito na pipiliin namin MFCC at pagkatapos ay isumite ang mga ito bilang input sa CNN
  • Pagkilala sa boses - https://github.com/linhdvu14/vggvox-speaker-identification
    Dito, sa halip na MFCC, nagtatrabaho kami sa isang spectrogram, pagkatapos ng FFT pinapakain namin ang signal sa CNN, kung saan sa output ay nakakakuha kami ng representasyon ng vector ng boses.

Susunod na pag-uusapan natin ang tungkol sa pag-convert ng mga modelo, simula sa teorya. Kasama sa OpenVINO ang ilang mga module:

  • Buksan ang Model Zoo, mga modelo kung saan maaaring gamitin at isama sa iyong produkto
  • Model Optimzer, salamat sa kung saan maaari mong i-convert ang isang modelo mula sa iba't ibang mga format ng framework (Tensorflow, ONNX atbp) sa Intermediate Representation na format, kung saan kami ay gagana nang higit pa
  • Binibigyang-daan ka ng Inference Engine na magpatakbo ng mga modelo sa IR format sa mga Intel processor, Myriad chips at Neural Compute Stick accelerators
  • Ang pinaka-epektibong bersyon ng OpenCV (na may suporta sa Inference Engine)
    Ang bawat modelo sa IR na format ay inilalarawan ng dalawang file: .xml at .bin.
    Ang mga modelo ay na-convert sa IR na format sa pamamagitan ng Model Optimizer tulad ng sumusunod:

    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 nagbibigay-daan sa iyo na piliin ang format ng data kung saan gagana ang modelo. Sinusuportahan ang FP32, FP16, INT8. Ang pagpili ng pinakamainam na uri ng data ay makakapagbigay ng magandang performance boost.
    --input_shape ay nagpapahiwatig ng sukat ng data ng pag-input. Ang kakayahang dynamic na baguhin ito ay tila naroroon sa C++ API, ngunit hindi kami naghukay ng ganoon kalayo at naayos lang ito para sa isa sa mga modelo.
    Susunod, subukan nating i-load ang na-convert na modelo sa IR format sa pamamagitan ng DNN module sa OpenCV at ipasa ito dito.

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

    Ang huling linya sa kasong ito ay nagpapahintulot sa iyo na i-redirect ang mga kalkulasyon sa Neural Compute Stick, ang mga pangunahing kalkulasyon ay isinasagawa sa processor, ngunit sa kaso ng Raspberry Pi hindi ito gagana, kakailanganin mo ng isang stick.

    Susunod, ang lohika ay ang mga sumusunod: hinahati namin ang aming audio sa mga window ng isang tiyak na laki (para sa amin ito ay 0.4 s), kino-convert namin ang bawat isa sa mga window na ito sa MFCC, na pagkatapos ay i-feed namin sa grid:

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

    Susunod, kunin natin ang pinakakaraniwang klase para sa lahat ng mga bintana. Isang simpleng solusyon, ngunit para sa isang hackathon hindi mo kailangang magkaroon ng isang bagay na masyadong abstruse, kung mayroon kang oras. Marami pa tayong dapat gawin, kaya magpatuloy tayo - haharapin natin ang pagkilala sa boses. Kinakailangang gumawa ng ilang uri ng database kung saan iimbak ang mga spectrogram ng mga pre-record na boses. Dahil kaunti na lang ang natitira, lulutasin namin ang isyung ito sa abot ng aming makakaya.

    Lalo na, lumikha kami ng isang script para sa pag-record ng isang sipi ng boses (gumagana ito sa parehong paraan tulad ng inilarawan sa itaas, kapag nagambala lamang mula sa keyboard, mai-save nito ang boses sa isang file).

    Subukan Natin:

    python3 voice_db/record_voice.py test.wav

    Nire-record namin ang boses ng ilang tao (sa aming kaso, tatlong miyembro ng team)
    Susunod, para sa bawat na-record na boses nagsasagawa kami ng mabilis na fourier transform, kumuha ng spectrogram at i-save ito bilang isang numpy array (.npy):

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

    Higit pang mga detalye sa file create_base.py
    Bilang resulta, kapag pinatakbo namin ang pangunahing script, makakakuha kami ng mga pag-embed mula sa mga spectrogram na ito sa pinakadulo simula:

    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)

    Pagkatapos matanggap ang pag-embed mula sa pinatunog na segment, matutukoy namin kung kanino ito kabilang sa pamamagitan ng pagkuha ng cosine distance mula sa passage hanggang sa lahat ng boses sa database (mas maliit, mas malamang) - para sa demo na itinakda namin ang threshold hanggang 0.3):

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

    Sa huli, gusto kong tandaan na ang bilis ng inference ay mabilis at ginawang posible na magdagdag ng 1-2 pang mga modelo (para sa isang sample na 7 segundo ang haba ay tumagal ng 2.5 para sa inference). Wala na kaming panahon para magdagdag ng mga bagong modelo at nakatuon sa pagsusulat ng prototype ng web application.

    Web application

    Isang mahalagang punto: kumukuha kami ng router mula sa bahay at i-set up ang aming lokal na network, nakakatulong itong ikonekta ang device at mga laptop sa network.

    Ang backend ay isang end-to-end na channel ng mensahe sa pagitan ng harap at Raspberry Pi, batay sa teknolohiya ng websocket (http over tcp protocol).

    Ang unang yugto ay ang pagtanggap ng naprosesong impormasyon mula sa raspberry, iyon ay, mga predictor na naka-pack sa json, na naka-save sa database sa kalagitnaan ng kanilang paglalakbay upang ang mga istatistika ay mabuo tungkol sa emosyonal na background ng user para sa panahon. Ang packet na ito ay ipapadala sa frontend, na gumagamit ng subscription at tumatanggap ng mga packet mula sa websocket endpoint. Ang buong mekanismo ng backend ay binuo sa wikang golang; pinili ito dahil angkop ito para sa mga asynchronous na gawain, na mahusay na pinangangasiwaan ng mga goroutine.
    Kapag na-access ang endpoint, ang gumagamit ay nakarehistro at pumasok sa istraktura, pagkatapos ay natanggap ang kanyang mensahe. Parehong ang user at ang mensahe ay ipinasok sa isang karaniwang hub, kung saan ang mga mensahe ay naipadala na (sa naka-subscribe na harap), at kung isasara ng user ang koneksyon (raspberry o harap), pagkatapos ay ang kanyang subscription ay kanselahin at siya ay aalisin mula sa ang hub.

    OpenVINO hackathon: pagkilala sa boses at emosyon sa Raspberry Pi
    Naghihintay kami ng koneksyon mula sa likod

    Ang Front-end ay isang web application na nakasulat sa JavaScript gamit ang React library upang pabilisin at pasimplehin ang proseso ng pagbuo. Ang layunin ng application na ito ay upang mailarawan ang data na nakuha gamit ang mga algorithm na tumatakbo sa back-end na bahagi at direkta sa Raspberry Pi. Ang page ay may sectional routing na ipinatupad gamit ang react-router, ngunit ang pangunahing page ng interes ay ang pangunahing page, kung saan ang tuluy-tuloy na stream ng data ay natatanggap sa real time mula sa server gamit ang WebSocket technology. Nakikita ng Raspberry Pi ang isang boses, tinutukoy kung kabilang ito sa isang partikular na tao mula sa nakarehistrong database, at nagpapadala ng listahan ng posibilidad sa kliyente. Ipinapakita ng kliyente ang pinakabagong may-katuturang data, ipinapakita ang avatar ng taong malamang na nagsalita sa mikropono, pati na rin ang damdamin kung saan binibigkas niya ang mga salita.

    OpenVINO hackathon: pagkilala sa boses at emosyon sa Raspberry Pi
    Home page na may na-update na mga hula

    Konklusyon

    Hindi posible na kumpletuhin ang lahat tulad ng binalak, wala kaming oras, kaya ang pangunahing pag-asa ay nasa demo, na lahat ay gagana. Sa pagtatanghal ay napag-usapan nila kung paano gumagana ang lahat, kung anong mga modelo ang kanilang kinuha, kung anong mga problema ang kanilang naranasan. Sumunod ay ang demo part - ang mga eksperto ay naglakad-lakad sa paligid ng silid sa random na pagkakasunud-sunod at lumapit sa bawat koponan upang tingnan ang gumaganang prototype. Nagtanong din sila sa amin, sinagot ng lahat ang kanilang bahagi, iniwan nila ang web sa laptop, at lahat ay talagang gumana tulad ng inaasahan.

    Hayaan akong tandaan na ang kabuuang halaga ng aming solusyon ay $150:

    • Raspberry Pi 3 ~ $35
    • Google AIY Voice Bonnet (maaari kang kumuha ng respeaker fee) ~ 15$
    • Intel NCS 2 ~ 100$

    Paano paunlarin:

    • Gumamit ng pagpaparehistro mula sa kliyente - hilingin na basahin ang teksto na random na nabuo
    • Magdagdag ng ilan pang modelo: matutukoy mo ang kasarian at edad sa pamamagitan ng boses
    • Paghiwalayin ang sabay-sabay na tunog (diarization)

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

    OpenVINO hackathon: pagkilala sa boses at emosyon sa Raspberry Pi
    Pagod pero masaya kami

    Bilang pagtatapos, nais kong magpasalamat sa mga organizer at kalahok. Kabilang sa mga proyekto ng iba pang mga koponan, personal naming nagustuhan ang solusyon para sa pagsubaybay sa mga libreng parking space. Para sa amin, ito ay isang napakagandang karanasan ng pagsasawsaw sa produkto at pag-unlad. Umaasa ako na mas marami at mas kawili-wiling mga kaganapan ang gaganapin sa mga rehiyon, kasama ang mga paksa ng AI.

Pinagmulan: www.habr.com

Magdagdag ng komento