OpenVINO-Hackathon: Stimme und Emotionen auf Raspberry Pi erkennen

Vom 30. November bis 1. Dezember fand in Nischni Nowgorod statt OpenVINO-Hackathon. Die Teilnehmer wurden gebeten, mithilfe des Intel OpenVINO-Toolkits einen Prototyp einer Produktlösung zu erstellen. Die Organisatoren schlugen eine Liste ungefährer Themen vor, an denen man sich bei der Auswahl einer Aufgabe orientieren konnte, die endgültige Entscheidung lag jedoch bei den Teams. Darüber hinaus wurde die Verwendung von Modellen gefördert, die nicht im Produkt enthalten sind.

OpenVINO-Hackathon: Stimme und Emotionen auf Raspberry Pi erkennen

In diesem Artikel erzählen wir Ihnen, wie wir unseren Prototyp des Produkts erstellt haben, mit dem wir schließlich den ersten Platz belegten.

Mehr als 10 Teams nahmen am Hackathon teil. Es ist schön, dass einige von ihnen aus anderen Regionen kamen. Der Veranstaltungsort des Hackathons war der Komplex „Kremlinsky on Pochain“, in dem alte Fotografien von Nischni Nowgorod in einem Gefolge aufgehängt waren! (Ich erinnere Sie daran, dass sich die Zentrale von Intel derzeit in Nischni Nowgorod befindet). Die Teilnehmer hatten 26 Stunden Zeit, Code zu schreiben, und am Ende mussten sie ihre Lösung präsentieren. Ein weiterer Vorteil war die Anwesenheit einer Demo-Session, um sicherzustellen, dass alles Geplante tatsächlich umgesetzt wurde und keine Ideen in der Präsentation blieben. Merch, Snacks, Essen, alles war auch da!

Darüber hinaus lieferte Intel optional Kameras, Raspberry PI, Neural Compute Stick 2.

Aufgabenauswahl

Einer der schwierigsten Teile bei der Vorbereitung auf einen Hackathon in freier Form ist die Auswahl einer Herausforderung. Wir haben uns sofort entschieden, etwas zu entwickeln, was noch nicht im Produkt enthalten war, da es in der Ankündigung hieß, dass dies sehr willkommen sei.

Analysiert Modelle, die in der aktuellen Version im Produkt enthalten sind, kommen wir zu dem Schluss, dass die meisten davon verschiedene Computer-Vision-Probleme lösen. Darüber hinaus ist es sehr schwierig, ein Problem im Bereich Computer Vision zu finden, das nicht mit OpenVINO gelöst werden kann, und selbst wenn eines erfunden werden kann, ist es schwierig, vorab trainierte Modelle im öffentlichen Bereich zu finden. Wir beschließen, in eine andere Richtung zu graben – in Richtung Sprachverarbeitung und -analyse. Betrachten wir eine interessante Aufgabe, Emotionen aus der Sprache zu erkennen. Es muss gesagt werden, dass OpenVINO bereits über ein Modell verfügt, das die Emotionen einer Person anhand ihres Gesichts bestimmt, aber:

  • Theoretisch ist es möglich, einen kombinierten Algorithmus zu erstellen, der sowohl auf Ton als auch auf Bild wirkt, was zu einer höheren Genauigkeit führen sollte.
  • Kameras haben normalerweise einen engen Betrachtungswinkel; um einen großen Bereich abzudecken, ist mehr als eine Kamera erforderlich; beim Ton gibt es keine solche Einschränkung.

Lassen Sie uns die Idee weiterentwickeln: Nehmen wir die Idee für den Einzelhandel als Basis. Sie können die Kundenzufriedenheit an den Ladenkassen messen. Wenn einer der Kunden mit dem Service unzufrieden ist und anfängt, den Ton zu erhöhen, können Sie sofort den Administrator um Hilfe bitten.
In diesem Fall müssen wir die menschliche Spracherkennung hinzufügen, damit wir Filialmitarbeiter von Kunden unterscheiden und Analysen für jeden Einzelnen bereitstellen können. Darüber hinaus wird es möglich sein, das Verhalten der Filialmitarbeiter selbst zu analysieren und die Atmosphäre im Team zu bewerten. Das hört sich gut an!

Wir formulieren die Anforderungen an unsere Lösung:

  • Kleine Größe des Zielgeräts
  • Echtzeitbetrieb
  • Faire Preis
  • Einfache Skalierbarkeit

Daher wählen wir als Zielgerät den Raspberry Pi 3 c aus Intel NCS 2.

Hier ist es wichtig, ein wichtiges Merkmal von NCS zu beachten: Es funktioniert am besten mit Standard-CNN-Architekturen. Wenn Sie jedoch ein Modell mit benutzerdefinierten Ebenen ausführen müssen, müssen Sie mit einer Optimierung auf niedriger Ebene rechnen.

Es gibt nur eine Kleinigkeit: Sie benötigen ein Mikrofon. Ein normales USB-Mikrofon reicht aus, aber zusammen mit dem RPI sieht es nicht gut aus. Aber auch hier liegt die Lösung im wahrsten Sinne des Wortes „nahe“. Um Sprache aufzunehmen, entscheiden wir uns für die Verwendung des Voice Bonnet-Boards aus dem Kit Google AIY Sprachkit, an dem sich ein kabelgebundenes Stereomikrofon befindet.

Laden Sie Raspbian herunter von AIY-Projekt-Repository und laden Sie es auf ein Flash-Laufwerk hoch. Testen Sie mit dem folgenden Befehl, ob das Mikrofon funktioniert (es nimmt Audio 5 Sekunden lang auf und speichert es in einer Datei):

arecord -d 5 -r 16000 test.wav

Mir fällt sofort auf, dass das Mikrofon sehr empfindlich ist und Geräusche gut aufnimmt. Um dies zu beheben, gehen wir zu Alsamixer, wählen Capture-Geräte aus und reduzieren den Eingangssignalpegel auf 50-60 %.

OpenVINO-Hackathon: Stimme und Emotionen auf Raspberry Pi erkennen
Wir modifizieren den Korpus mit einer Feile und alles passt, man kann ihn sogar mit einem Deckel verschließen

Hinzufügen einer Anzeigeschaltfläche

Beim Zerlegen des AIY Voice Kit fällt uns auf, dass es eine RGB-Taste gibt, deren Hintergrundbeleuchtung per Software gesteuert werden kann. Wir suchen nach „Google AIY Led“ und finden Dokumentation: https://aiyprojects.readthedocs.io/en/latest/aiy.leds.html
Warum nicht diese Schaltfläche verwenden, um die erkannte Emotion anzuzeigen? Wir haben nur 7 Klassen und die Schaltfläche hat 8 Farben, gerade genug!

Wir verbinden den Button über GPIO mit Voice Bonnet, laden die notwendigen Bibliotheken (sie sind bereits im Distributionskit von AIY-Projekten installiert)

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

Erstellen wir ein Diktat, in dem jede Emotion eine entsprechende Farbe in Form eines RGB-Tupels und eines Objekts der Klasse aiy.leds.Leds hat, durch das wir die Farbe aktualisieren:

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

Und schließlich aktualisieren wir nach jeder neuen Vorhersage einer Emotion die Farbe der Schaltfläche entsprechend (nach Schlüssel).

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

OpenVINO-Hackathon: Stimme und Emotionen auf Raspberry Pi erkennen
Knopf, brenn!

Arbeiten mit Stimme

Wir werden Pyaudio verwenden, um den Stream vom Mikrofon zu erfassen, und Webrtcvad, um Geräusche zu filtern und Stimmen zu erkennen. Darüber hinaus erstellen wir eine Warteschlange, zu der wir Sprachauszüge asynchron hinzufügen und entfernen.

Da webrtcvad eine Beschränkung hinsichtlich der Größe des bereitgestellten Fragments hat – diese muss 10/20/30 ms betragen, und das Training des Modells zur Erkennung von Emotionen (wie wir später erfahren werden) an einem 48-kHz-Datensatz durchgeführt wurde, werden wir dies tun Erfassen Sie Blöcke mit einer Größe von 48000 x 20 ms/1000 x 1 (Mono) = 960 Byte. Webrtcvad gibt für jeden dieser Blöcke „True/False“ zurück, was dem Vorhandensein oder Fehlen einer Stimme im Block entspricht.

Lassen Sie uns die folgende Logik implementieren:

  • Wir fügen der Liste die Blöcke hinzu, in denen es eine Abstimmung gibt; wenn es keine Abstimmung gibt, erhöhen wir den Zähler der leeren Blöcke.
  • Wenn der Zähler der leeren Chunks >=30 (600 ms) ist, dann schauen wir uns die Größe der Liste der akkumulierten Chunks an; wenn er >250 ist, dann fügen wir ihn der Warteschlange hinzu; wenn nicht, betrachten wir die Länge Der Datensatz reicht nicht aus, um ihn dem Modell zur Identifizierung des Sprechers zuzuführen.
  • Wenn der Zähler der leeren Chunks immer noch < 30 ist und die Größe der Liste der angesammelten Chunks 300 überschreitet, fügen wir das Fragment für eine genauere Vorhersage zur Warteschlange hinzu. (weil Emotionen dazu neigen, sich mit der Zeit zu ändern)

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

Es ist an der Zeit, nach vorab trainierten Modellen im öffentlichen Bereich zu suchen, zu Github oder Google zu gehen, aber denken Sie daran, dass wir eine Einschränkung hinsichtlich der verwendeten Architektur haben. Dies ist ein ziemlich schwieriger Teil, da Sie die Modelle anhand Ihrer Eingabedaten testen und sie außerdem in das interne Format von OpenVINO konvertieren müssen – IR (Intermediate Representation). Wir haben ungefähr 5-7 verschiedene Lösungen von Github ausprobiert, und wenn das Modell zur Erkennung von Emotionen sofort funktionierte, mussten wir mit der Spracherkennung länger warten – sie verwenden komplexere Architekturen.

Wir konzentrieren uns auf Folgendes:

Als nächstes werden wir über die Konvertierung von Modellen sprechen, beginnend mit der Theorie. OpenVINO umfasst mehrere Module:

  • Öffnen Sie den Modellzoo, dessen Modelle verwendet und in Ihr Produkt integriert werden können
  • Model Optimzer, mit dem Sie ein Modell aus verschiedenen Framework-Formaten (Tensorflow, ONNX usw.) in das Intermediate Representation-Format konvertieren können, mit dem wir weiter arbeiten werden
  • Mit der Inference Engine können Sie Modelle im IR-Format auf Intel-Prozessoren, Myriad-Chips und Neural Compute Stick-Beschleunigern ausführen
  • Die effizienteste Version von OpenCV (mit Inference Engine-Unterstützung)
    Jedes Modell im IR-Format wird durch zwei Dateien beschrieben: .xml und .bin.
    Modelle werden über Model Optimizer wie folgt in das IR-Format konvertiert:

    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 ermöglicht Ihnen die Auswahl des Datenformats, mit dem das Modell arbeiten soll. FP32, FP16, INT8 werden unterstützt. Die Wahl des optimalen Datentyps kann zu einer guten Leistungssteigerung führen.
    --input_shape gibt die Dimension der Eingabedaten an. Die Möglichkeit, es dynamisch zu ändern, scheint in der C++-API vorhanden zu sein, aber wir haben nicht so weit gegraben und es einfach für eines der Modelle behoben.
    Als nächstes versuchen wir, das bereits konvertierte Modell im IR-Format über das DNN-Modul in OpenCV zu laden und an dieses weiterzuleiten.

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

    In der letzten Zeile können Sie in diesem Fall Berechnungen auf den Neural Compute Stick umleiten. Grundlegende Berechnungen werden auf dem Prozessor durchgeführt. Beim Raspberry Pi funktioniert dies jedoch nicht. Sie benötigen einen Stick.

    Als nächstes ist die Logik wie folgt: Wir teilen unser Audio in Fenster einer bestimmten Größe auf (bei uns sind es 0.4 s), wir konvertieren jedes dieser Fenster in MFCC, das wir dann in das Raster einspeisen:

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

    Als nächstes nehmen wir die für alle Fenster am häufigsten verwendete Klasse. Eine einfache Lösung, aber für einen Hackathon muss man sich nichts allzu Abstruses einfallen lassen, nur wenn man Zeit hat. Wir haben noch viel Arbeit vor uns, also machen wir weiter – wir beschäftigen uns mit der Spracherkennung. Es ist notwendig, eine Art Datenbank zu erstellen, in der Spektrogramme zuvor aufgezeichneter Stimmen gespeichert werden. Da uns nur noch wenig Zeit bleibt, werden wir dieses Problem so gut wie möglich lösen.

    Wir erstellen nämlich ein Skript zum Aufnehmen eines Sprachausschnitts (es funktioniert auf die gleiche Weise wie oben beschrieben, nur wenn es über die Tastatur unterbrochen wird, wird die Stimme in einer Datei gespeichert).

    Lass es uns versuchen:

    python3 voice_db/record_voice.py test.wav

    Wir nehmen die Stimmen mehrerer Personen auf (in unserem Fall drei Teammitglieder)
    Als nächstes führen wir für jede aufgenommene Stimme eine schnelle Fourier-Transformation durch, erhalten ein Spektrogramm und speichern es als Numpy-Array (.npy):

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

    Weitere Details in der Datei create_base.py
    Wenn wir das Hauptskript ausführen, erhalten wir daher gleich zu Beginn Einbettungen aus diesen Spektrogrammen:

    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)

    Nachdem wir die Einbettung des erklingenden Segments erhalten haben, können wir bestimmen, zu wem es gehört, indem wir den Kosinusabstand von der Passage zu allen Stimmen in der Datenbank nehmen (je kleiner, desto wahrscheinlicher) – für die Demo legen wir den Schwellenwert fest bis 0.3):

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

    Abschließend möchte ich anmerken, dass die Inferenzgeschwindigkeit hoch war und es ermöglichte, 1-2 weitere Modelle hinzuzufügen (für eine Probe mit einer Länge von 7 Sekunden dauerte die Inferenz 2.5). Wir hatten keine Zeit mehr, neue Modelle hinzuzufügen und konzentrierten uns auf das Schreiben eines Prototyps der Webanwendung.

    Internetanwendung

    Ein wichtiger Punkt: Wir nehmen von zu Hause einen Router mit und richten unser lokales Netzwerk ein, er hilft, das Gerät und die Laptops über das Netzwerk zu verbinden.

    Das Backend ist ein End-to-End-Nachrichtenkanal zwischen Front und Raspberry Pi, basierend auf der Websocket-Technologie (http-over-tcp-Protokoll).

    Der erste Schritt besteht darin, verarbeitete Informationen von Raspberry zu erhalten, d. h. in JSON gepackte Prädiktoren, die nach der Hälfte ihrer Reise in der Datenbank gespeichert werden, sodass Statistiken über den emotionalen Hintergrund des Benutzers für den Zeitraum erstellt werden können. Dieses Paket wird dann an das Frontend gesendet, das ein Abonnement verwendet und Pakete vom Websocket-Endpunkt empfängt. Der gesamte Backend-Mechanismus ist in der Golang-Sprache aufgebaut; sie wurde ausgewählt, weil sie sich gut für asynchrone Aufgaben eignet, die Goroutinen gut bewältigen.
    Beim Zugriff auf den Endpunkt wird der Benutzer registriert und in die Struktur eingetragen, anschließend wird seine Nachricht empfangen. Sowohl der Benutzer als auch die Nachricht werden in einen gemeinsamen Hub eingegeben, von dem aus Nachrichten bereits weiter gesendet werden (an die abonnierte Front). Wenn der Benutzer die Verbindung (Himbeere oder Front) schließt, wird sein Abonnement gekündigt und er wird entfernt der Hub.

    OpenVINO-Hackathon: Stimme und Emotionen auf Raspberry Pi erkennen
    Wir warten auf eine Verbindung von hinten

    Frontend ist eine in JavaScript geschriebene Webanwendung, die die React-Bibliothek nutzt, um den Entwicklungsprozess zu beschleunigen und zu vereinfachen. Der Zweck dieser Anwendung besteht darin, Daten zu visualisieren, die mithilfe von Algorithmen gewonnen wurden, die auf der Back-End-Seite und direkt auf dem Raspberry Pi ausgeführt werden. Die Seite verfügt über abschnittsweises Routing, das mit React-Router implementiert wurde, aber die Hauptseite von Interesse ist die Hauptseite, auf der mithilfe der WebSocket-Technologie ein kontinuierlicher Datenstrom in Echtzeit vom Server empfangen wird. Raspberry Pi erkennt eine Stimme, ermittelt anhand der registrierten Datenbank, ob sie einer bestimmten Person gehört, und sendet eine Wahrscheinlichkeitsliste an den Client. Der Kunde zeigt die neuesten relevanten Daten an, zeigt den Avatar der Person an, die höchstwahrscheinlich in das Mikrofon gesprochen hat, sowie die Emotion, mit der er die Worte ausspricht.

    OpenVINO-Hackathon: Stimme und Emotionen auf Raspberry Pi erkennen
    Startseite mit aktualisierten Vorhersagen

    Abschluss

    Es war nicht möglich, alles wie geplant fertigzustellen, wir hatten einfach keine Zeit, also lag die Haupthoffnung in der Demo, dass alles funktionieren würde. In der Präsentation sprachen sie darüber, wie alles funktioniert, welche Modelle sie genommen haben und auf welche Probleme sie gestoßen sind. Als nächstes folgte der Demo-Teil – Experten gingen in zufälliger Reihenfolge durch den Raum und kamen auf jedes Team zu, um sich den funktionierenden Prototyp anzusehen. Sie haben uns auch Fragen gestellt, jeder hat seinen Teil geantwortet, sie haben das Internet auf dem Laptop belassen und alles hat wirklich wie erwartet funktioniert.

    Ich möchte anmerken, dass die Gesamtkosten unserer Lösung 150 US-Dollar betrugen:

    • Raspberry Pi 3 ~ 35 $
    • Google AIY Voice Bonnet (Sie können eine Respeaker-Gebühr nehmen) ~ 15 $
    • Intel NCS 2 ~ 100 $

    So verbessern Sie:

    • Nutzen Sie die Registrierung des Kunden – bitten Sie ihn, den zufällig generierten Text zu lesen
    • Fügen Sie ein paar weitere Modelle hinzu: Sie können Geschlecht und Alter anhand der Stimme bestimmen
    • Separate gleichzeitig erklingende Stimmen (Diarisierung)

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

    OpenVINO-Hackathon: Stimme und Emotionen auf Raspberry Pi erkennen
    Wir sind müde, aber glücklich

    Abschließend möchte ich mich bei den Organisatoren und Teilnehmern bedanken. Unter den Projekten anderer Teams gefiel uns persönlich die Lösung zur Überwachung freier Parkplätze. Für uns war es eine unglaublich coole Erfahrung, in das Produkt und die Entwicklung einzutauchen. Ich hoffe, dass es in den Regionen immer mehr interessante Veranstaltungen auch zu KI-Themen geben wird.

Source: habr.com

Kommentar hinzufügen