Universelle Wake Word Engine für FHEM - nicht nur für Rhasspy

Begonnen von Prof. Dr. Peter Henning, 19 November 2021, 19:59:51

Vorheriges Thema - Nächstes Thema

Prof. Dr. Peter Henning

Das Ziel dieses Threads ist die Bereitstellung einer allgemeinen Wake Word Engine für FHEM, die auf einfacher Hardware läuft und bei Erkennung des Wake Word eine beliebige Aktion in FHEM auslöst. Das könnnte dann z.B. eine Spracherkennung sein - aber auch eine Aktion wie z.B. "Tür auf". Wesentliche Anforderung: Das System soll einfach selbst trainierbar sein und ohne Cloud-Zugriff auskommen.

Als Software dazu habe ich precise ausgesucht. Wo man das findet, und wie man es trainiert, hat einer meiner Studenten sehr gut dokumentiert (siehe Anlage. Maximilian Kühn hat dafür eine sehr gute Note bekommen).

Er hat das mit Rhasspy ganz gut zum Laufen bekommen - ich will es aber mit einem anderen Backend verwenden.

Also habe ich erst einmal von  github.com/MycroftAI/mycroft-precise#source-install den Quellcode heruntergeladen. Wie in der Dokumentation beschrieben, lässt sich das unter Ubuntu 20.04 nicht kompilieren. Der Grund ist, dass die darin enthaltene Version von Python 3.9 inkompatibel mit den älteren 1er Versionen von Tensorflow ist, der Code von precise wiederum inkompatibel mit den aktuellen 2er Versionen von Tensorflow.

Im zweiten Schritt habe ich daher in einer virtuellen Maschine (VirtualBox) Ubuntu 18.04 LTS installiert - precise ließ sich darin problemlos übersetzen. Ein Modell für das Wake Word "Hallo Jeannie" habe ich entsprechend der o.a. Doku trainiert und getestet. Mit einem kleinen Python-Programm (Datei Fhemlisten.py) #!/usr/bin/env python3
# Copyright 2019 Mycroft AI Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Run a model on microphone audio input

:model str
    Either Keras (.net) or TensorFlow (.pb) model to run

:-c --chunk-size int 2048
    Samples between inferences

:-l --trigger-level int 3
    Number of activated chunks to cause an activation

:-s --sensitivity float 0.5
    Network output required to be considered activated

:-b --basic-mode
    Report using . or ! rather than a visual representation

:-d --save-dir str -
    Folder to save false positives

:-p --save-prefix str -
    Prefix for saved filenames
"""
import numpy as np
from os.path import join
from precise_runner import PreciseRunner
from precise_runner.runner import ListenerEngine
from prettyparse import Usage
from random import randint
from shutil import get_terminal_size
from threading import Event

from precise.network_runner import Listener
from precise.scripts.base_script import BaseScript
from precise.util import save_audio, buffer_to_audio, activate_notify

import urllib.request
import urllib.error
import ssl
#import urlparse


class ListenScript(BaseScript):
    usage = Usage(__doc__)

    def __init__(self, args):
        super().__init__(args)
        self.listener = Listener(args.model, args.chunk_size)
        self.audio_buffer = np.zeros(self.listener.pr.buffer_samples, dtype=float)
        self.engine = ListenerEngine(self.listener, args.chunk_size)
        self.engine.get_prediction = self.get_prediction
        self.runner = PreciseRunner(self.engine, args.trigger_level, sensitivity=args.sensitivity,
                                    on_activation=self.on_activation, on_prediction=self.on_prediction)
        self.session_id, self.chunk_num = '%09d' % randint(0, 999999999), 0

    def on_activation(self):
        #activate_notify()
        url = "http://192.168.0.94:8083/fhem?XHR=1&cmd=set%20precise%20ring"
        #if "@" in url:
    #    nurl = urlparse.urlsplit(url)
    #    username = nurl.username
    #    password = nurl.password
    #    url = url.replace(username + ':' + password + '@', '')
        url = url.replace(" ", "%20")
        try:
            response = urllib.request.urlopen(url)
        except urllib.error.HTTPError as e:
        # do something
            print('Error code: ', e.code)
   except urllib.error.URLError as e:
        # do something
            print('Reason: ', e.reason)
        else:
       # do something
            html = response.read()

        if self.args.save_dir:
            nm = join(self.args.save_dir, self.args.save_prefix + self.session_id + '.' + str(self.chunk_num) + '.wav')
            save_audio(nm, self.audio_buffer)
            print()
            print('Saved to ' + nm + '.')
            self.chunk_num += 1

    def on_prediction(self, conf):
        if self.args.basic_mode:
            print('!' if conf > 0.7 else '.', end='', flush=True)
        else:
            max_width = 80
            width = min(get_terminal_size()[0], max_width)
            units = int(round(conf * width))
            bar = 'X' * units + '-' * (width - units)
            cutoff = round((1.0 - self.args.sensitivity) * width)
            print(bar[:cutoff] + bar[cutoff:].replace('X', 'x'))

    def get_prediction(self, chunk):
        audio = buffer_to_audio(chunk)
        self.audio_buffer = np.concatenate((self.audio_buffer[len(audio):], audio))
        return self.listener.update(chunk)

    def run(self):
        self.runner.start()
        Event().wait()  # Wait forever


main = ListenScript.run_main

if __name__ == '__main__':
    main()




kann man dann das Modell laufen lassen. Wird das Wake Word erkannt, wird auf dem FHEM-Server (hier 192.168.0.94) ein "set precise ring" ausgeführt, daran kann man per notify beliebige Aktionen anbinden.

(to be continued - Erfahrungen auf einem Raspberry Pi)

pah

dkreutz

Schöne Arbeit! Da Precise bzw. Tensorflow auf dem Raspberry Pi3 recht ressourcenhungrig ist, hat das Mycroft-Team inzwischen eine Precise-Variante auf Basis Tensorflow-Lite entwickelt: https://github.com/MycroftAI/plugin-wake-word-precise-lite (damit sollte auch die Abhängigskeitsprobleme entschärft werden).
Raspberry Pi3B+ (Bullseye) / JeeLink868v3c (LaCrosse), nanoCUL433 (a-culfw V1.24.02), HM-MOD-UART (1.4.1), TEK603, MapleCUL / diverse Sensoren/Sender/Aktoren von Technoline, Intertechno, Shelly, Homematic und MAX!, Froggit Wetterstation, Luftdaten.info / Autor des fhem-skill für Mycroft.ai

Prof. Dr. Peter Henning

#2
Danke, gute Ergänzung.

Schafft aber wieder das Problem, dass damit kein eigenes Modell trainiert werden kann   :(

Und das mit dem Fressen von Ressourcen ist eigentlich nicht das Problem, wenn die WWE auf einem dezidierten Pi läuft.

LG

pah

Prof. Dr. Peter Henning

OK, weiter im Text - denn ich will das Ding ja nicht nur in einer virtuellen Maschine laufen lassen.

Das Problem auf einem Raspberry Pi sind derzeit die unterschiedlichen Versionen von Python. Am Weitesten bin ich bisher mit dem fertigen PiCroft-Image der MyCroft.AI-Community gekommen, das findet man hier: https://mycroft-ai.gitbook.io/docs/using-mycroft-ai/get-mycroft/picroft

Kommt auf einem Pi 3 oder 4 problemlos zum Laufen. Allerdings ist auch in den neuen Versionen des Images nur die Version 0.2.0 der Precise-Engine enthalten - und die ist stark verbesserungsbedürftig, und kann mit den Modellen der Version 0.3.0 nichts anfangen.

Also habe ich in dem Ordner /home/pi/.mycroft den bisherigen Unterordner precise umbenannt, mir von hier https://github.com/MycroftAI/mycroft-precise/releases
die Version 0.3.0 geholt und entpackt. Mein auf der oben genannten Ubuntu 18.04-Installation trainiertes Modell lief damit problemlos.

Wermutstropfen derzeit noch: Ich muss tatsächlich das neue Modell (Wake Word ist "Hallo Jeannie") umbenennen in hey-mycroft.pb und hey-mycroft.pb.params, weil die Anleitung, mycroft mit einem anderen Wake Word auszustatten (https://mycroft-ai.gitbook.io/docs/using-mycroft-ai/customizations/wake-word#precise) nicht korrekt ist.

Dann aber, oh Freude, lief zunächst der mycroft-Client auch mit dem selbst trainierten 0.3.0 Wake Word.

Interessanterweise ist in dem hier genannten 0.3.0-Release von precise auch alles enthalten, was man zum Training eines Modells braucht (die Anleitung ist in der oben genannten Projektarbeit drin). Außerdem ist enthalten das Skript precise-listen, mit dem auch außerhalb von mycroft auf die Wake Word-Funktionalität zurückgegriffen werden kann.

Was bisher noch scheitert, ist der damit durchzuführende Aufruf von FHEM, so wie das im ersten Post beschrieben ist. Denn die heruntergeladene 0.3.0 precise-Engine kommt nur mit den Binaries, der gesamte Code wurde mit Python 3.4 übersetzt und lässt sich derzeit nicht mit 3.7 oder 3.9 übersetzen. Die Binaries laufen zwar in der Python 3.7-Umgebung von mycroft, geben aber derzeit noch in anderen Versionen des Raspberry OS Laufzeitfehler.

LG

pah


dkreutz

Zitat von: Prof. Dr. Peter Henning am 20 November 2021, 09:52:10
Schafft aber wieder das Problem, dass damit kein eigenes Modell trainiert werden kann   :(

Hier gibt es einen Fork mit Skripten für Training und Konvertierung zu Tflite: https://github.com/OpenVoiceOS/precise-lite
Raspberry Pi3B+ (Bullseye) / JeeLink868v3c (LaCrosse), nanoCUL433 (a-culfw V1.24.02), HM-MOD-UART (1.4.1), TEK603, MapleCUL / diverse Sensoren/Sender/Aktoren von Technoline, Intertechno, Shelly, Homematic und MAX!, Froggit Wetterstation, Luftdaten.info / Autor des fhem-skill für Mycroft.ai

Prof. Dr. Peter Henning

Hmmm. Erstens verlangt die Übersetzung von precise-lite immer noch die volle tensorflow-Installation, zweites gibt es dabei nach wie vor widersprechende Versionsabhängigkeiten. Nach etlichen Stunden immer noch nicht wirklich weiter.

LG

pah

Sailor

Zitat von: Prof. Dr. Peter Henning am 20 November 2021, 10:12:48
Wermutstropfen derzeit noch: Ich muss tatsächlich das neue Modell (Wake Word ist "Hallo Jeannie") umbenennen in

"Elfriede"!

https://www.youtube.com/watch?v=vEEl0hLZwcM

;D

Gruß
    Sailor
******************************
Man wird immer besser...