Retour d'expérience IA et Projet de reconnaissance de bruit de sonnette

Bonjour,

L’objet est donc de pouvoir détecter un bruit de sonnette dans l’environnement sonore de mon salon, au moyen d’un micro Respeaker 4-array monté en pi-hat sur un PI3B+ et de communiquer l’information à une VM Home Assistant via MQTT.

Avec toute l’énergie que j’ai passé (quelques heures en soirée) vous allez me dire qu’il y avait peut-être plus simple en terme de reconnaissance audio :sweat_smile:, mais mes recherches ne m’avaient amené nulle part ailleurs qu’un github sounddetector que j’ai testé d’ailleurs. Je sais aussi qu’il y avait encore plus simple avec un contact sec sur l’interphone, mais que je ne souhaitais pas et aurais eu des difficultés à poser. En fait, je trouvais le projet et l’expérience intéressants à partager. :slight_smile:

J’ai donc utilisé Perplexity.ai pour m’aider à mettre au point le procédé, les codages et même le tutoriel :slight_smile:
Compte-tenu de mon niveau je dirais intermédiaire en python et analyse fréquentielle, j’ai procédé par itération d’erreurs / corrections pour progresser pas à pas jusqu’au résultat final. Il fait des erreurs et les résultats ne sont pas bons du 1er coup, mais en lui donnant les erreurs retournés et ma part d’analyse, on arrive progressivement à un code sans erreur, capable ensuite d’analyser la situation sonore pour ensuite toucher plutôt aux réglages, voire adapter la méthodologie.

Je dirai que l’approche m’a beaucoup plu car j’ai apprécié voir la manière avec laquelle l’interaction a permis d’aboutir à un résultat pour l’instant de bonne détection, sans faux-positif, mais à continuer à tester…

La 2ème remarque est qu’à l’évidence un copier-coller de ces codes ne marchera pas pour vous, car les réglages sont tellement liés à la typologie du son détecté que vous voulez détecter, la sensibilité du micro et les niveaux de bruit environnant, qu’il faudra forcément a minima tuner les réglages, au pire à faire évoluer le procédé de détection par des filtrages, moyennes glissantes… des choses que Perplexity m’a aidé à faire.

La dernière remarque est évidemment que cette manière de procédé correspond à mon niveau et mon intérêt pour l’IA, le python, l’analyse fréquentielle et la domotique, et donc qu’en fonction de votre propre expérience et votre propre situation de détection, vous ne la trouverez pas forcément adapté.

Ce tutoriel proposé par Perplexity peut servir de point de départ à vos recherches :

Tutoriel : Détection de sonnette avec Raspberry Pi, Respeaker 4-array Seed et Home AssistantMatériel nécessaire :

  • Raspberry Pi 3B+
  • Microphone Respeaker 4-array Seed
  • Carte SD (min. 8 Go)
  • Connexion Internet

Étape 1 : Configuration initiale de la Raspberry Pi

  1. Téléchargez et installez Raspberry Pi OS sur votre carte SD.
  2. Insérez la carte SD dans la Raspberry Pi et démarrez-la.
  3. Connectez-vous à la Raspberry Pi (par défaut, utilisateur : pi, mot de passe : raspberry).
  4. Exécutez sudo raspi-config pour configurer le Wi-Fi, changer le mot de passe, etc.

Étape 2 : Installation et configuration du Respeaker 4-array Seed

  1. Connectez le Respeaker 4-array Seed à votre Raspberry Pi.
  2. Ouvrez un terminal et exécutez :
sudo apt-get update
sudo apt-get install git
git clone https://github.com/respeaker/seeed-voicecard.git
cd seeed-voicecard
sudo ./install.sh
sudo reboot
  1. Après le redémarrage, vérifiez que le microphone est détecté :
arecord -l

Étape 3 : Installation des dépendances

sudo apt-get install python3-pip portaudio19-dev
pip3 install pyaudio numpy paho-mqtt scipy sounddevice
pip3 install -U scikit-learn
pip3 install pyAudioAnalysis

Étape 4 : Collecte des échantillons audio

  1. Créez deux répertoires :
mkdir sonnette
mkdir non-sonnette
  1. Enregistrez 10-15 échantillons de sonnette dans le répertoire « sonnette » :
arecord -d 2 -f cd -t wav sonnette/sonnette_01.wav

Répétez cette commande pour chaque échantillon, en changeant le nom du fichier.

  1. Enregistrez 10-15 échantillons de bruits environnants dans le répertoire « non-sonnette » de la même manière.

Étape 5 : Entraînement du modèle

Créez un script Python nommé train_model.py :

from pyAudioAnalysis import audioTrainTest as aT

aT.extract_features_and_train(["sonnette", "non-sonnette"], 1.0, 1.0, aT.shortTermWindow, aT.shortTermStep, "svm", "doorbell_model", False)

Exécutez le script :

python3 train_model.py

Étape 6 : Création du script de détection

Créez un fichier doorbell_detection.py :

import pyaudio
import numpy as np
import time
import paho.mqtt.client as mqtt
from pyAudioAnalysis import audioTrainTest as aT
import scipy.fft
import tempfile
import sounddevice as sd
import wave

# Paramètres audio
CHUNK = 11025
FORMAT = pyaudio.paFloat32
CHANNELS = 1
RATE = 44100

# Paramètres de détection
MIN_PROB_THRESHOLD = 0.6
MIN_POWER_THRESHOLD = 0.02
DETECTION_WINDOW = 3

# Paramètres MQTT
MQTT_BROKER = "adresse_ip_de_home_assistant"
MQTT_PORT = 1883
MQTT_TOPIC = "doorbell/state"
MQTT_USER = "votre_utilisateur_mqtt"
MQTT_PASSWORD = "votre_mot_de_passe_mqtt"

# Variables globales
detection_buffer = []
is_detecting = False
detection_start_time = 0

def on_connect(client, userdata, flags, rc):
    print(f"Connecté au broker MQTT avec le code : {rc}")

client = mqtt.Client()
client.username_pw_set(MQTT_USER, MQTT_PASSWORD)
client.on_connect = on_connect
client.connect(MQTT_BROKER, MQTT_PORT, 60)
client.loop_start()

def record_audio(duration, fs, channels):
    recording = sd.rec(int(duration * fs), samplerate=fs, channels=channels)
    sd.wait()
    return recording

def save_audio(audio, filename, fs):
    with wave.open(filename, 'wb') as wf:
        wf.setnchannels(1)
        wf.setsampwidth(2)
        wf.setframerate(fs)
        wf.writeframes(audio.tobytes())

def analyze_frequency(audio):
    fft = scipy.fft.fft(audio)
    freqs = scipy.fft.fftfreq(len(audio), 1/RATE)
    peak_freq = freqs[np.argmax(np.abs(fft))]
    return abs(peak_freq)

def classify_audio(audio):
    with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as temp_file:
        save_audio(audio, temp_file.name, RATE)
        features = aT.extract_features(temp_file.name, RATE, CHUNK, CHANNELS)
        result = aT.file_classification(features, "doorbell_model", "svm")
    return result[1][0]  # Probabilité de la classe "sonnette"

def notify_home_assistant(client, state, probability):
    message = "on" if state == "on" else "off"
    client.publish(MQTT_TOPIC, message)
    print(f"Notification envoyée à Home Assistant : Sonnette {message} (Probabilité: {probability:.2f})")

def audio_callback(indata, frame_count, time_info, status):
    global detection_buffer, is_detecting, detection_start_time

    audio = np.frombuffer(indata, dtype=np.float32)
    audio_power = np.mean(np.abs(audio))
    print(f"Puissance audio: {audio_power:.4f}")

    if audio_power >= MIN_POWER_THRESHOLD:
        probability = classify_audio(audio)
        peak_freq = analyze_frequency(audio)
        print(f"Probabilité: {probability:.2f}, Fréquence de pic: {peak_freq:.2f} Hz")

        detection_buffer.append((probability, audio_power, peak_freq))
        if len(detection_buffer) > DETECTION_WINDOW:
            detection_buffer.pop(0)

        if any(p >= MIN_PROB_THRESHOLD for p, _, _ in detection_buffer):
            if not is_detecting:
                is_detecting = True
                detection_start_time = time.time()
                notify_home_assistant(client, "on", max(p for p, _, _ in detection_buffer))
        elif is_detecting:
            detection_duration = time.time() - detection_start_time
            if detection_duration >= 0.5:  # Durée minimale de détection
                is_detecting = False
                notify_home_assistant(client, "off", max(p for p, _, _ in detection_buffer))

    else:
        print("Puissance audio insuffisante")
        detection_buffer.clear()
        if is_detecting:
            is_detecting = False
            notify_home_assistant(client, "off", 0)

    return (indata, pyaudio.paContinue)

p = pyaudio.PyAudio()
stream = p.open(format=FORMAT,
                channels=CHANNELS,
                rate=RATE,
                input=True,
                frames_per_buffer=CHUNK,
                stream_callback=audio_callback)

print("Démarrage de la détection de sonnette...")
stream.start_stream()

try:
    while stream.is_active():
        time.sleep(0.1)
except KeyboardInterrupt:
    print("Arrêt de la détection")
finally:
    stream.stop_stream()
    stream.close()
    p.terminate()
    client.loop_stop()
    client.disconnect()

Étape 7 : Configuration du service systemdCréez un fichier /etc/systemd/system/doorbell-detection.service :

[Unit]
Description=Doorbell Detection Service
After=network.target

[Service]
ExecStart=/usr/bin/python3 /home/pi/doorbell_detection.py
WorkingDirectory=/home/pi
User=pi
Group=pi
Restart=always
RestartSec=10s
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

Activez et démarrez le service :

sudo systemctl daemon-reload
sudo systemctl enable doorbell-detection.service
sudo systemctl start doorbell-detection.service

Étape 8 : Configuration de Home Assistant

  1. Assurez-vous que l’intégration MQTT est configurée dans Home Assistant.
  2. Ajoutez un capteur binaire dans votre fichier configuration.yaml :
binary_sensor:
  - platform: mqtt
    name: "Sonnette"
    state_topic: "doorbell/state"
    payload_on: "on"
    payload_off: "off"
    device_class: sound
  1. Redémarrez Home Assistant pour appliquer les changements.

Ce tutoriel complet couvre toutes les étapes nécessaires, de la configuration initiale à l’entraînement du modèle avec pyAudioAnalysis et à la mise en place du système de détection. N’oubliez pas d’ajuster les paramètres (comme MIN_PROB_THRESHOLD) en fonction de vos besoins spécifiques et des résultats obtenus lors des tests.

1 « J'aime »

Hello

Sympa comme projet, merci pour le partage :wink:
Comme retour d’expérience sur l’IA pour ce projet , ce qui serait sympa c’est d’avoir les prompts que tu lui a donné

sinon, dans le même ordre d’idée, Frigate sait faire de la détection uniquement audio ( 500 modèles différents) , mais pas de possibilité d’apprentissage comme ici.

@+

1 « J'aime »

Merci @Krull56 pour ton retour.
Mais vaste sujet que de te donner les prompts ! J’ai dû en taper une centaine en procédant par itérations :sweat_smile: Mes prompts sont en langage « naturel » en allant à l’essentiel.
Le tout premier prompt était : Comment procéder pour que le son d'une sonnette de portillon soit reconnu avec respeaker sur raspberry pi et que Home Assistant en soit informé ? puis voilà le genre de messages que je posais :
Peux-tu coder le script de l'étape 3 ?
J'ai 1 seconde d'échantillon, c'est suffisant j'espère ?
Rappelle-moi ce que je dois installer au préalable par pip3 install

En fait, les questions posées dans un fil de discussion permet que Perplexity ne perde pas les informations que tu lui donnes au fur-et-à-mesure : les brokers, le résultat des premiers tests, la méthode qui n’a pas marché, le fait d’utiliser un raspberry PI3B+ pour les performances…

Puis j’ai lancé un 2ème fil de discussion pour repartir de la feuille blanche, en précisant des informations/précisions issues du 1er fil. Le premier prompt était : Comment procéder pour que le son d'une sonnette de portillon soit reconnu avec respeaker Seeed 4-array avec 44100 en rate et 2 en device_index sur raspberry pi, à partir d'un fichier étalon "sonnette_echantillon.wav" dont l'anlayse spectrale stipule une dominante à 1270Hz et que Home Assistant en soit informé via mqtt par un message home/sonnette indiquant notamment la date de l'évènement ?

Hello @Krull56 ,
J’ai voulu tester l’audio detection de Frigate que tu cites, mais pour l’instant je dois mal m’y prendre; je crois lire qu’il s’agit de sensors et non d’events, mais je ne trouve rien = saurais-tu me guider un peu en code que j’aurais loupé ? :sweat_smile:
Voilà la partie de code que j’ai testé sans succès :

    ffmpeg:
      inputs:
        - path: rtsp://zzz:yyyyy@192.168.1.aa:554
          roles:
            - detect
            - record
            - audio
      hwaccel_args:
        - -hwaccel_output_format
        - yuv420p

    detect:
      width: 1920 
      height: 1080 
      fps: 10
      enabled: True
    objects:
      track:
        - person
    record: # <----- Enable recording
      enabled: False
      retain:
        days: 2
        mode: all
      events:
        retain:
          default: 2
          mode: all
    snapshots: # <----- Enable snapshots
      enabled: True
      bounding_box: true
      retain:
        default: 2 #days
    audio:
      enabled: True
      max_not_heard: 30
      min_volume: 100
      listen:
        - microwave_oven
        - doorbell
      filters:
        microwave_oven:
          threshold: 0.6
        doorbell:
          threshold: 0.6

Hello

Je n’utilise pas cette fonctionnalité mais effectivement ce sont des sensors qui doivent apparaître dans ton intégration Frigate ( sup à version 5)

1 « J'aime »

Tu n’as pas été voir du côté d’un Shelly uni

Mais le projet est marrant

Tu as pensé a regarder un projet des frères Poulain le claptrap je crois

1 « J'aime »

Le truc tout pourri qui ne fonctionne pas/plus ? Faut suivre les actualités @ddfdom :stuck_out_tongue_closed_eyes:

Ps: tu racles les fonds de tiroir ? De forum je voulais dire :smile:
Le dernier post a un an et j’ai eu l’occasion d’échanger sur ce sujet dernièrement avec @Christianb233 . En MP pour que tu ne sois pas tenté d’y ajouter ta graine :crazy_face:

2 « J'aime »

ça n’as donc jamais trop marché, je n’avais pas vu l’intérêt de la chose mais vu quo’n est dans le délire ici :stuck_out_tongue_winking_eye:

1 « J'aime »

Salut moi je me lance dans la détection du chant du coq ! J’en ai 2 à côté de chez moi qui se croient à l’opéra à 3 ou 4h du matin et se répondent l’un l’autre. J’ai commencé à compter à la main et je me suis dit qu’il devait exister des solutions ! J’ai une caméra juste devant où ils chantent, on verra ce que ça donne.
À la main, j’en suis déjà à 127 chants depuis 8h :sweat_smile:
Je suis en télétravail et ça commence à me déranger…

Je vais regarder ce que ça donne sur 1 semaine !