Raspberry Pi

Raspberry Pi Kamera Bewegungserkennung mit MQTT-Alarmen und Picamera2

Bauen Sie einen Bewegungsmelder mit Raspberry-Pi-Kamera und Picamera2, speichern Sie bei Bewegung einen Snapshot und veröffentlichen Sie Bewegungs-Events mit Snapshot-Links per MQTT, damit Dashboards, Smartphones und Automationen reagieren können.

Raspberry-Pi-Kameramodul überwacht einen Eingang und sendet Bewegungsalarme mit Snapshot-Links – Raspberry Pi Kamera Bewegungserkennung per MQTT
Probieren Sie es auf Ihrem eigenen MQTT-Broker ausHolen Sie sich im Free-Tarif einen privaten, verwalteten MQTT-Broker und starten Sie mit fertigen Codebeispielen.
Kostenlos testen Codebeispiele Keine Kreditkarte erforderlich

Eine Raspberry Pi Kamera mit Bewegungserkennung per MQTT macht aus einem Kameramodul eine Ereignisquelle: Statt den ganzen Tag Video zu streamen, beobachtet der Pi einen niedrig aufgelösten Bildstrom, speichert bei einer Veränderung einen Snapshot und veröffentlicht ein kleines JSON-Event, das jeder abonnieren kann. In diesem Tutorial bauen Sie diesen Detektor mit Picamera2 und Python, stimmen ihn gegen Fehlalarme ab und liefern die Events über einen MQTT-Broker aus.

So funktioniert der Detektor

Picamera2 kann gleichzeitig zwei Streams von der Kamera liefern: einen main-Stream in voller Auflösung und einen winzigen lores-Stream. Der Detektor vergleicht aufeinanderfolgende Graustufen-Frames des lores-Streams, was selbst auf einem Pi Zero 2 W kaum CPU kostet, und greift nur dann auf den main-Stream zu, wenn bei erkannter Bewegung ein JPEG gespeichert wird.

Der Ablauf:

  1. Die Luminanzebene des 320x240-lores-Frames abgreifen.
  2. Die mittlere quadratische Abweichung zum vorherigen Frame berechnen.
  3. Überschreitet die Abweichung einen Schwellwert, ein Bewegungs-Event starten, einen Snapshot in voller Größe speichern und eine MQTT-Nachricht mit dessen Link veröffentlichen.
  4. Weitere Auslösungen während einer Abklingzeit ignorieren.
  5. Nach einer ruhigen Phase ein Event „Bewegung beendet“ veröffentlichen und den Retained-Zustand auf idle zurücksetzen.

Bilder per MQTT zu senden ist möglich, aber ein Broker ist ein schlechter Dateiserver: Große Payloads sind langsam, und der öffentliche MQTT-Broker begrenzt Nachrichten auf 64 KB. Das robuste Muster ist, ein Event mit Link zu veröffentlichen und das Bild auf einem Webserver oder Objektspeicher unter Ihrer Kontrolle abzulegen.

Hardware und Software

Komponente Hinweise
Raspberry Pi 4, 5 oder Zero 2 W Raspberry Pi OS Bookworm
Camera Module 2, 3 oder HQ Camera Pi 5 und Zero benötigen das passende schmale Flachbandkabel
Picamera2 Auf Raspberry Pi OS vorinstalliert
paho-mqtt 2.x In einer virtuellen Umgebung installiert

Picamera2 und NumPy stammen aus den Betriebssystempaketen, legen Sie die virtuelle Umgebung daher mit Zugriff darauf an:

sudo apt update
sudo apt install -y python3-picamera2 python3-numpy python3-venv
rpicam-hello --list-cameras
mkdir -p ~/motion-mqtt/snapshots && cd ~/motion-mqtt
python3 -m venv --system-site-packages .venv
source .venv/bin/activate
pip install "paho-mqtt>=2.0"

Zeigt rpicam-hello --list-cameras keine Kamera an, schalten Sie aus, stecken Sie das Flachbandkabel mit korrekt ausgerichteten Kontakten neu ein und versuchen Sie es erneut. Das Picamera2-Handbuch dokumentiert die unten verwendete Stream-Konfiguration.

Event-Topics

Topic Payload Retained
connectmq/demo/e71b04/frontdoor/motion JSON-Event: Start oder Ende, Score, Snapshot-URL Nein
connectmq/demo/e71b04/frontdoor/motion/state active oder idle Ja
connectmq/demo/e71b04/frontdoor/status online oder offline (Last Will) Ja

Ein typisches Start-Event sieht so aus:

{
  "event": "motion_start",
  "score": 18.4,
  "snapshot": "http://192.168.1.50:8000/20261016-142233.jpg",
  "ts": 1792160553
}

Das Skript für die Raspberry Pi Kamera Bewegungserkennung per MQTT

Speichern Sie es als motion_mqtt.py:

#!/usr/bin/env python3
"""Detect motion with Picamera2 and publish MQTT events with snapshot links."""
import json
import os
import socket
import time
import uuid
from datetime import datetime
from pathlib import Path

import numpy as np
import paho.mqtt.client as mqtt
from picamera2 import Picamera2

BROKER = os.getenv("MQTT_HOST", "broker.connectmq.com")
PORT = int(os.getenv("MQTT_PORT", "1883"))
USERNAME = os.getenv("MQTT_USERNAME")
PASSWORD = os.getenv("MQTT_PASSWORD")
CLIENT_ID = os.getenv("MQTT_CLIENT_ID", f"cam-{uuid.uuid4().hex[:8]}")
PREFIX = os.getenv("MQTT_PREFIX", "connectmq/demo/e71b04/frontdoor")

THRESHOLD = float(os.getenv("MOTION_THRESHOLD", "12"))
COOLDOWN = 10       # Sekunden zwischen Start-Events
QUIET_TIME = 15     # Sekunden ohne Bewegung bis "motion_end"
SNAP_DIR = Path(os.getenv("SNAP_DIR", str(Path.home() / "motion-mqtt" / "snapshots")))
HTTP_PORT = int(os.getenv("SNAP_HTTP_PORT", "8000"))
LORES = (320, 240)


def local_ip():
    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
        s.connect(("8.8.8.8", 80))
        return s.getsockname()[0]


BASE_URL = os.getenv("SNAP_BASE_URL", f"http://{local_ip()}:{HTTP_PORT}")


def on_connect(client, userdata, flags, reason_code, properties):
    if reason_code.is_failure:
        print(f"Connection refused: {reason_code}")
        return
    print(f"Connected as {CLIENT_ID}")
    client.publish(f"{PREFIX}/status", "online", qos=1, retain=True)
    client.publish(f"{PREFIX}/motion/state", "idle", qos=1, retain=True)


client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=CLIENT_ID)
client.on_connect = on_connect
client.will_set(f"{PREFIX}/status", "offline", qos=1, retain=True)
client.reconnect_delay_set(min_delay=1, max_delay=60)
if USERNAME:
    client.username_pw_set(USERNAME, PASSWORD)
if PORT == 8883:
    client.tls_set()
client.connect(BROKER, PORT, keepalive=30)
client.loop_start()

picam2 = Picamera2()
config = picam2.create_video_configuration(
    main={"size": (1280, 720), "format": "RGB888"},
    lores={"size": LORES, "format": "YUV420"},
)
picam2.configure(config)
picam2.start()
time.sleep(2)  # Belichtung und Weißabgleich einpendeln lassen

SNAP_DIR.mkdir(parents=True, exist_ok=True)
w, h = LORES
previous = None
active = False
last_start = 0.0
last_motion = 0.0


def publish_event(event, score, snapshot=None):
    payload = {"event": event, "score": round(score, 1), "ts": int(time.time())}
    if snapshot:
        payload["snapshot"] = snapshot
    client.publish(f"{PREFIX}/motion", json.dumps(payload), qos=1)
    print(payload)


try:
    while True:
        frame = picam2.capture_buffer("lores")
        current = frame[: w * h].reshape(h, w).astype(np.int16)
        if previous is not None:
            score = float(np.square(current - previous).mean())
            now = time.time()
            if score > THRESHOLD:
                last_motion = now
                if not active and now - last_start > COOLDOWN:
                    name = datetime.now().strftime("%Y%m%d-%H%M%S") + ".jpg"
                    picam2.capture_file(str(SNAP_DIR / name))
                    publish_event("motion_start", score, f"{BASE_URL}/{name}")
                    client.publish(f"{PREFIX}/motion/state", "active", qos=1, retain=True)
                    active = True
                    last_start = now
            elif active and now - last_motion > QUIET_TIME:
                publish_event("motion_end", score)
                client.publish(f"{PREFIX}/motion/state", "idle", qos=1, retain=True)
                active = False
        previous = current
        time.sleep(0.1)
except KeyboardInterrupt:
    pass
finally:
    picam2.stop()
    client.publish(f"{PREFIX}/status", "offline", qos=1, retain=True).wait_for_publish(2)
    client.loop_stop()
    client.disconnect()

Die Umwandlung mit astype(np.int16) ist wichtig: Die Subtraktion zweier vorzeichenloser 8-Bit-Arrays läuft über und erzeugt riesige Scheindifferenzen. Wenn Sie nur die ersten w * h Bytes des YUV420-Puffers nehmen, erhalten Sie die Graustufen-Luminanzebene – mehr braucht die Bewegungserkennung nicht.

Die Snapshots bereitstellen

Für eine schnelle lokale Lösung stellen Sie den Snapshot-Ordner in einem zweiten Terminal mit dem integrierten Webserver von Python bereit:

python3 -m http.server 8000 --directory ~/motion-mqtt/snapshots

Die Links in jedem Event öffnen sich jetzt auf jedem Gerät in Ihrem Netzwerk. Dieser Server hat keine Authentifizierung, also lassen Sie ihn ausschließlich im LAN. Für den Zugriff von außen laden Sie Snapshots mit einem eigenen Skript in einen Speicher unter Ihrer Kontrolle hoch und setzen SNAP_BASE_URL auf diesen Ort; das Format der MQTT-Events ändert sich nicht. Ergänzen Sie außerdem einen Cronjob oder systemd-Timer, der alte Bilder löscht, sonst läuft die SD-Karte voll.

Testen und Feinabstimmung

Verfolgen Sie die Events von einem beliebigen Rechner aus:

mosquitto_sub -h broker.connectmq.com -p 1883 -i viewer-e71b04 \
  -t 'connectmq/demo/e71b04/frontdoor/#' -v

Bewegen Sie eine Hand vor dem Objektiv, und Sie sollten motion_start mit einem Snapshot-Link sehen, dann motion_end nach 15 ruhigen Sekunden. Die Feinabstimmung dreht sich vor allem um den Schwellwert:

Symptom Anpassung
Events durch Wolken, Flackern oder Änderungen der Belichtungsautomatik MOTION_THRESHOLD erhöhen
Vorbeigehende Personen werden nicht erkannt Schwellwert oder Schlafintervall senken
Ständige Start- und End-Events QUIET_TIME erhöhen
Nachts viele Fehlalarme IR-Licht ergänzen oder Alarme zeitgesteuert deaktivieren
Bäume oder eine Straße im Bild lösen aus Das lores-Array vor dem Vergleich auf einen Interessenbereich zuschneiden

Geben Sie den Score einige Minuten lang ohne Bewegung aus, um das Grundrauschen Ihrer Szene zu sehen, und setzen Sie den Schwellwert dann mit ausreichendem Abstand darüber.

Kameraerkennung vs. PIR-Sensor

Ein passiver Infrarotsensor (PIR) ist der andere gängige Weg, Bewegung zu erkennen, und es lohnt sich zu wissen, wann welcher passt. Ein PIR-Sensor reagiert auf Körperwärme, verbraucht kaum Strom und ignoriert Lichtänderungen, daher löst er seltener durch Schatten und Wolken falsch aus. Er kann Ihnen aber nicht sagen, was sich bewegt hat, und liefert kein Bild zum Anhängen. Die Differenzbildung mit der Kamera reagiert auf jede visuelle Veränderung, auch auf einen Vorhang im Wind, liefert dafür aber einen Snapshot und erlaubt es, die Erkennung auf einen genauen Bildbereich zu beschränken.

Beide lassen sich gut kombinieren. Schließen Sie einen PIR-Sensor an einen freien GPIO-Pin an und akzeptieren Sie eine Kamera-Auslösung nur, wenn die PIR-Leitung innerhalb derselben ein bis zwei Sekunden ebenfalls High ist. Die Kamera bestätigt und dokumentiert dann, was der PIR erkannt hat, und das MQTT-Event kann beide Signale enthalten, sodass Abonnenten entscheiden können, wie sehr sie ihm vertrauen. Halten Sie diese Bestätigungslogik auf dem Pi, damit das veröffentlichte Event einfach bleibt: ein motion_start, ein motion_end, ein Link.

Auf Bewegungs-Events reagieren

Da der Detektor nur Events veröffentlicht, liegt die Reaktion woanders und lässt sich ändern, ohne den Kameracode anzufassen. Ein Node-RED-Flow kann eine Push-Benachrichtigung senden; ein kleiner Python-Subscriber kann den Snapshot per E-Mail verschicken, wie in MQTT-E-Mail-Alarme mit Python gezeigt; und ein zweiter Pi kann mit der Steuerung aus Raspberry Pi Relais per MQTT einen Strahler einschalten, indem er das Topic motion/state abonniert. Auch Home Assistant kann den Retained-Zustand als Binärsensor nutzen. Wenn Sie einen günstigeren Kameraknoten für eine zweite Tür möchten, nutzt der Leitfaden zur ESP32-CAM über MQTT dieselbe Event-Idee auf einem Mikrocontroller.

Datenschutz und Produktivbetrieb

Bewegungs-Events verraten, wann jemand zu Hause ist, daher sollten sie in einer echten Installation nie über einen öffentlichen Broker laufen. Der öffentliche MQTT-Broker unter broker.connectmq.com ist für alle lesbar und nur für den ersten Test gedacht. Für eine echte Kamera nutzen Sie einen privaten Broker von ConnectMQ mit TLS und generierten Zugangsdaten:

export MQTT_PORT=8883
export MQTT_USERNAME=YOUR_USERNAME
export MQTT_PASSWORD=YOUR_PASSWORD
export MQTT_CLIENT_ID=YOUR_CLIENT_ID
export MQTT_PREFIX=home/frontdoor
python motion_mqtt.py

Mit einem verwalteten MQTT-Broker bauen Smartphone, Heimserver und Kamera alle ausgehende Verbindungen zum selben TLS-Endpunkt auf, sodass Sie keine Portweiterleitung benötigen. Betreiben Sie Detektor und Snapshot-Server als systemd-Dienste, damit sie nach einem Stromausfall wieder starten; das Raspberry Pi MQTT Tutorial enthält eine fertige Dienstvorlage.

Jetzt mit ConnectMQ starten

Geben Sie Ihrer Kamera einen sicheren Ort für ihre Alarme. Erstellen Sie einen kostenlosen Serverless-Broker bei ConnectMQ ohne Kreditkarte und verbinden Sie sich mit TLS auf Port 8883. Der Live-Topic-Explorer im Portal zeigt jedes Bewegungs-Event und jeden Retained-Zustand in Echtzeit, was die Abstimmung der Schwellwerte erheblich erleichtert.

Häufig gestellte Fragen

Kann ich das Snapshot-Bild selbst per MQTT senden?

Sie können JPEG-Bytes als Payload veröffentlichen, aber das ist ineffizient und stößt schnell an Größenlimits für Nachrichten. Einen Link zum Bild zu veröffentlichen und die Datei anderswo zu speichern, skaliert deutlich besser.

Wie viel CPU braucht die Bewegungserkennung mit Picamera2?

Sehr wenig, da der Vergleich auf einem 320x240-Graustufen-Stream läuft. Selbst ein Raspberry Pi Zero 2 W schafft das neben dem MQTT-Client.

Warum erhalte ich Bewegungs-Events, obwohl sich nichts bewegt?

Änderungen der Belichtungsautomatik, flackernde Leuchten und wandernde Schatten verändern Pixelwerte. Erhöhen Sie den Schwellwert, schneiden Sie auf einen Interessenbereich zu oder fixieren Sie die Belichtung über die Picamera2-Controls.

Funktioniert das mit dem Raspberry Pi Camera Module 3?

Ja. Picamera2 unterstützt Camera Module 2, Camera Module 3 und die HQ Camera; der Code ändert sich nicht.

Verwandte Artikel

Raspberry Pi RFID MQTT: Zutrittsprotokollierung mit dem RC522-Leser

Schließen Sie einen RC522-RFID-Leser an einen Raspberry Pi an, lesen Sie Tags mit Python, prüfen Sie sie gegen eine lokale Allowlist und veröffentlichen Sie jeden Zutrittsversuch als MQTT-Ereignis, das Logger, Dashboards und Alarmwerkzeuge verarbeiten können.

7 Min. Lesezeit

Pico W vs. Pico 2 W für MQTT: RP2040 vs. RP2350 bei Leistung, Speicher und TLS

Der Pico 2 W behält Formfaktor und WLAN-Chip des Pico W, ersetzt aber den RP2040 durch den schnelleren RP2350. Dieser Vergleich zeigt, was das für MQTT-Geräte bedeutet: CPU, Speicher, TLS, Sicherheitsfunktionen, Softwareunterstützung und welches Board für welches Projekt.

8 Min. Lesezeit

Alle Artikel →

Ihr privater MQTT-Broker ist nur wenige Klicks entfernt

Konto anlegen, Broker-Zugangsdaten erhalten und das erste Gerät verbinden — ohne Server, die installiert, gepatcht oder skaliert werden müssen.