Viele Netzwerk- und Überwachungskameras stellen ihren Videostream über RTSP bereit. Für manche Projekte ist es jedoch gar nicht notwendig, den kompletten Videostream dauerhaft zu verarbeiten.
Oft reicht es vollkommen aus, zu einem bestimmten Zeitpunkt ein einzelnes Bild – einen sogenannten Frame – aus dem RTSP-Stream zu entnehmen.
In diesem Beitrag zeige ich, wie sich mit wenigen Zeilen Python ein einzelner Frame aus einem RTSP-Stream laden und als JPEG-Datei speichern lässt. Als konkretes Beispiel verwende ich dabei eine TP-Link Tapo C500.
Warum überhaupt einen Frame aus RTSP holen?
Für mein späteres Projekt benötige ich den Videostream der Kamera nicht dauerhaft. Stattdessen möchte ich alle x Sekunden ein aktuelles Bild der Kamera automatisch analysieren.
Dafür reicht mir ein einzelnes Standbild vollkommen aus.
Die Idee ist daher recht einfach:
Ein Python-Skript verbindet sich mit dem RTSP-Stream der Kamera, liest genau einen Frame aus und speichert diesen als Bilddatei.
Anschließend wird die Verbindung zur Kamera wieder geschlossen und das Python-Skript beendet.
Der Ablauf sieht damit ungefähr so aus:

Die zeitliche Steuerung möchte ich später nicht in Python umsetzen. Dafür werde ich Node-RED verwenden.
Node-RED kann das Python-Skript beispielsweise alle 5, 10 oder 30 Sekunden starten. Bei jedem Aufruf wird dann ein aktuelles Bild der Kamera geladen und unter einem festen Dateinamen gespeichert.
Dieses Bild kann anschließend von Node-RED eingelesen und beispielsweise an eine KI zur Objekterkennung weitergegeben werden.
Wie ich diesen Ablauf später automatisiere, zeige ich in einem separaten Beitrag.
Verwendetes System: Dell OptiPlex 3050 Micro
Für diesen Aufbau verwende ich einen Dell OptiPlex 3050 Micro.
- Modell: Dell OptiPlex 3050 Micro
- Prozessor: Intel Core i5-7500T, 2,70 GHz
- Kerne / Threads: 4 / 4
- Arbeitsspeicher: 16 GB DDR4
- SSD: Intenso, 256GB, SSD
- Betriebssystem: Fedora Linux
Das reine Abrufen eines einzelnen Frames aus dem RTSP-Stream würde problemlos auch auf einem Raspberry Pi funktionieren. Dafür wird nur sehr wenig Rechenleistung benötigt.
Da das gespeicherte Kamerabild in einem späteren Projekt jedoch zusätzlich mit verschiedenen KI-Modellen zur Objekt- und Vogelerkennung analysiert werden soll, möchte ich den kompletten Aufbau auf demselben System betreiben.
Der Dell OptiPlex dient damit später nicht nur zum Abrufen und Speichern der Kamerabilder, sondern auch als Plattform für Node-RED, YOLO und die weitere KI-Auswertung.
Was ist RTSP?
RTSP steht für Real Time Streaming Protocol und wird häufig von IP- und Überwachungskameras verwendet, um einen kontinuierlichen Videostream innerhalb eines Netzwerks bereitzustellen.
Eine RTSP-Adresse kann beispielsweise folgendermaßen aussehen:
rtsp://192.168.178.50:554/stream
Benötigt die Kamera einen Benutzernamen und ein Passwort, können diese Bestandteil der URL sein:
rtsp://benutzer:passwort@192.168.178.50:554/stream
Der genaue Aufbau und insbesondere der Pfad am Ende der URL unterscheiden sich je nach Hersteller und Kamera.
Benutzername und Passwort sollten nicht in öffentlich zugänglichen Skripten oder Git-Repositories gespeichert werden.
RTSP bei der TP-Link Tapo C500 einrichten
Für dieses Beispiel verwende ich eine TP-Link Tapo C500.
Bevor auf den RTSP-Stream der Kamera zugegriffen werden kann, muss zunächst in den Einstellungen der Kamera ein separates Konto mit Benutzername und Passwort eingerichtet werden.







Dieses Konto wird anschließend für den lokalen Zugriff auf den Kamerastream verwendet.
Im begleitenden YouTube-Video zeige ich Schritt für Schritt, wo dieses Konto in der Tapo-App eingerichtet wird.
Wichtig: Das für den Kamerazugriff eingerichtete Konto ist nicht zwingend mit dem normalen TP-Link- beziehungsweise Tapo-Konto identisch.
IP-Adresse der Tapo C500 herausfinden
Damit wir später auf den RTSP-Stream zugreifen können, benötigen wir neben Benutzername und Passwort auch die IP-Adresse der Kamera im lokalen Netzwerk.
Bei der Tapo C500 findest du diese direkt in der Tapo-App unter: Einstellungen → Erweiterte Einstellungen → Netzwerk-Einstellungen




Dort wird die aktuell von deinem Netzwerk vergebene IP-Adresse der Kamera angezeigt.
Diese IP-Adresse benötigen wir anschließend für unsere RTSP-URL:
rtsp://<Benutzername>:<Passwort>@<IP-Adresse>:554/stream1
Für einen späteren dauerhaften Betrieb kann es außerdem sinnvoll sein, der Kamera im Router eine feste DHCP-Zuweisung zu geben. Dadurch bleibt die IP-Adresse der Kamera gleich und muss nicht irgendwann im Python-Skript angepasst werden.
Warum verwende ich stream1?
Bei der Tapo-Kamera stehen unterschiedliche RTSP-Streams zur Verfügung.
Für dieses Projekt verwende ich bewusst: /stream1
Dieser stellt den Stream mit der höheren Bildqualität bereit.
Für meine spätere Bildanalyse möchte ich mit einem möglichst detailreichen Kamerabild arbeiten. Gerade bei einer Objekterkennung können kleine Bilddetails später wichtig werden.
Deshalb steht bei mir zunächst die bestmögliche Bildqualität im Vordergrund.
Die vollständige RTSP-Adresse lautet damit:
rtsp://<Benutzername>:<Passwort>@<IP-Adresse>:554/stream1
Ein konkretes Beispiel könnte beispielsweise folgendermaßen aussehen:
rtsp://kamera:meinpasswort@192.168.178.73:554/stream1
Ein niedriger aufgelöster Stream würde weniger Daten übertragen und könnte für andere Anwendungen vollkommen ausreichend sein. Für meine geplante KI-Auswertung möchte ich jedoch zunächst mit dem bestmöglichen Ausgangsbild arbeiten.
Sicherheitshinweis: RTSP nicht einfach ins Internet freigeben
Der RTSP-Stream sollte möglichst nur innerhalb eines vertrauenswürdigen lokalen Netzwerks verwendet werden.
TP-Link empfiehlt ausdrücklich, den RTSP-Stream beziehungsweise den RTSP-Port nicht einfach öffentlich über das Internet zugänglich zu machen.
Ein klassischer RTSP-Stream bietet nicht automatisch eine verschlüsselte Übertragung. Abhängig von der verwendeten Konfiguration können Videodaten und Informationen zur Authentifizierung im Netzwerk leichter abgefangen werden als bei vollständig verschlüsselten Verbindungen.
Für dieses Projekt verwende ich den RTSP-Stream daher ausschließlich innerhalb meines lokalen Netzwerks.
Insbesondere der standardmäßig verwendete RTSP-Port: 554
sollte nicht einfach über den Router für das Internet freigegeben werden.
Soll später trotzdem von außerhalb des eigenen Netzwerks auf die Kamera beziehungsweise den RTSP-Stream zugegriffen werden, sollte dafür beispielsweise ein verschlüsseltes VPN verwendet werden.
RTSP-Stream zunächst testen
Bevor ich den Stream mit Python öffne, teste ich die RTSP-Adresse zunächst mit einem Videoplayer wie VLC.
Dort kann die RTSP-Adresse als Netzwerkstream geöffnet werden:
rtsp://kamera:meinpasswort@192.168.178.73:554/stream1



Wird das Kamerabild angezeigt, wissen wir bereits, dass
- die IP-Adresse stimmt,
- Benutzername und Passwort funktionieren,
- die Kamera über RTSP erreichbar ist
- und der verwendete Stream-Pfad korrekt ist.
Damit lässt sich ein Fehler in der Python-Anwendung später wesentlich leichter eingrenzen.
Voraussetzungen
Für dieses Beispiel benötige ich lediglich:
- Python
- OpenCV
- eine Kamera mit RTSP-Unterstützung
- Benutzername und Passwort für den Kamerazugriff
- die IP-Adresse der Kamera
Zum Lesen des RTSP-Streams verwende ich OpenCV.
Die Python-Bibliothek lässt sich mit pip installieren:
pip install opencv-python
Anschließend können wir den Stream direkt aus unserem Python-Skript heraus öffnen.
Einzelnen Frame aus dem RTSP-Stream laden
Das Python-Skript bleibt bewusst sehr überschaubar:
import cv2
RTSP_URL = "rtsp://kamera:meinpasswort@192.168.178.73:554/stream1"
IMAGE_PATH = "tapo-c500.jpg"
cap = cv2.VideoCapture(RTSP_URL)
if not cap.isOpened():
print("RTSP-Stream konnte nicht geöffnet werden.")
exit(1)
success, frame = cap.read()
cap.release()
if not success:
print("Es konnte kein Bild aus dem Stream gelesen werden.")
exit(2)
height, width = frame.shape[:2]
print(f"Auflösung: {width} x {height} Pixel")
saved = cv2.imwrite(IMAGE_PATH, frame)
if not saved:
print("Bild konnte nicht gespeichert werden.")
exit(3)
print(f"Bild wurde als {IMAGE_PATH} gespeichert.")
exit(0)
Das Skript erledigt genau eine Aufgabe:
Es verbindet sich mit der Kamera, liest einen Frame, speichert das Bild und beendet sich anschließend wieder.
Eine Endlosschleife ist für meinen Anwendungsfall bewusst nicht vorgesehen.
Verbindung zum RTSP-Stream herstellen
Zunächst wird der RTSP-Stream mit
cap = cv2.VideoCapture(RTSP_URL)
geöffnet.
VideoCapture kann mit OpenCV nicht nur Videodateien, sondern auch Netzwerkstreams öffnen.
Anschließend prüfe ich mit
if not cap.isOpened():
ob OpenCV tatsächlich eine Verbindung zum Stream herstellen konnte.
Schlägt dies fehl, wird das Skript mit einem Fehlercode beendet:
exit(1)
Einen einzelnen Frame lesen
Mit
success, frame = cap.read()
wird ein einzelner Frame aus dem Videostream gelesen.
Die Methode liefert dabei zwei Werte zurück.
success gibt an, ob das Lesen des Bildes erfolgreich war.
Das eigentliche Kamerabild befindet sich anschließend in
frame
und liegt bereits im Arbeitsspeicher vor.
Direkt danach kann die Verbindung zum Stream wieder geschlossen werden:
cap.release()
Für meinen Anwendungsfall wird der Stream anschließend nicht mehr benötigt.
Frame als JPEG speichern
War das Lesen des Frames erfolgreich, wird das Bild als JPEG-Datei gespeichert.
saved = cv2.imwrite(IMAGE_PATH, frame)
Ich prüfe zusätzlich, ob das Speichern tatsächlich funktioniert hat:
if not saved:
print("Bild konnte nicht gespeichert werden.")
exit(3)
Nach einem erfolgreichen Aufruf befindet sich damit die Datei tapo-c500.jpg im aktuellen Arbeitsverzeichnis.
Damit haben wir aus einem kontinuierlichen Videostream ein einzelnes Standbild erzeugt.
Welche Auflösung hat der geladene Frame?
Da ich für die spätere Bildanalyse bewusst stream1 verwende, interessiert mich auch, welche Auflösung OpenCV tatsächlich erhält.
Diese lässt sich direkt über das frame-Objekt auslesen:
height, width = frame.shape[:2]
print(f"Auflösung: {width} x {height} Pixel")
Beim Ausführen des Skripts wird dadurch zusätzlich die Auflösung des empfangenen Bildes ausgegeben.
Gerade für die spätere KI-Auswertung ist dieser Wert interessant. Eine höhere Auflösung kann mehr Bilddetails liefern, erzeugt gleichzeitig jedoch auch mehr Daten, die verarbeitet werden müssen.
Ob die höhere Auflösung bei meiner späteren Objekterkennung tatsächlich einen messbaren Vorteil bringt, werde ich noch untersuchen. Für den ersten Aufbau möchte ich jedoch mit dem bestmöglichen Ausgangsbild starten.
Warum ich den Frame als Datei speichere
Technisch könnte das geladene Kamerabild direkt innerhalb von Python weiterverarbeitet werden.
Für meinen geplanten Aufbau möchte ich den Frame jedoch ganz bewusst als Bilddatei speichern.
Der Grund dafür ist Node-RED.
Später soll Node-RED den kompletten Ablauf steuern und das Python-Skript in einem festgelegten Intervall aufrufen.
Das Python-Skript hat dabei lediglich die Aufgabe, ein aktuelles Bild aus dem RTSP-Stream zu laden und als
tapo-c500.jpg
abzulegen.
Der spätere Ablauf kann damit sehr einfach aufgebaut werden:

Die einzelnen Komponenten bleiben damit sauber voneinander getrennt.
Das Python-Skript kümmert sich ausschließlich um das Abrufen und Speichern des Kamerabildes.
Node-RED muss anschließend lediglich wissen, unter welchem Pfad die Bilddatei liegt.
Immer denselben Dateinamen verwenden
Für meinen geplanten Aufbau benötige ich zunächst keine Historie der aufgenommenen Bilder.
Deshalb verwende ich immer denselben Dateinamen:
tapo-c500.jpg
Mit
cv2.imwrite("tapo-c500.jpg", frame)
wird die vorhandene Datei bei jedem neuen Aufruf überschrieben.
Das hat für die spätere Verarbeitung mit Node-RED einen großen Vorteil:
Node-RED muss nicht erst herausfinden, welche Bilddatei die aktuellste ist. Stattdessen kann der Flow immer auf denselben Pfad zugreifen.
Für den späteren Betrieb bietet es sich an, einen festen beziehungsweise absoluten Pfad zu verwenden, beispielsweise:
/home/stefandraeger/rtsp-python/tapo-c500.jpg
Dadurch ist eindeutig festgelegt, wo sich das aktuelle Kamerabild befindet.
Zusätzlich entstehen keine ständig wachsenden Verzeichnisse mit alten Bildern, die später wieder aufgeräumt werden müssten.
Warum keine Endlosschleife?
Man könnte natürlich innerhalb von Python eine Endlosschleife verwenden und beispielsweise alle fünf Sekunden einen neuen Frame lesen.
Für meinen späteren Aufbau wäre das jedoch unnötig.
Die zeitliche Steuerung übernimmt Node-RED.
Damit bleiben die einzelnen Aufgaben sauber getrennt:
Node-RED kümmert sich um Automatisierung und Zeitsteuerung.
Python und OpenCV kümmern sich darum, ein aktuelles Bild aus dem RTSP-Stream zu holen und als Datei abzulegen.
Die spätere KI-Auswertung verarbeitet anschließend genau diese Datei.
Node-RED sollte dabei einen neuen Durchlauf erst starten, wenn der vorherige abgeschlossen wurde. So wird verhindert, dass mehrere Python-Prozesse gleichzeitig versuchen, dieselbe Bilddatei zu überschreiben.
Rückgabewerte für Node-RED
Für die spätere Automatisierung ist es praktisch, wenn das Python-Skript eindeutig signalisiert, ob das Abrufen des Bildes funktioniert hat.
Deshalb verwende ich unterschiedliche Exit-Codes:
- 0 = Bild erfolgreich gespeichert
- 1 = RTSP-Stream konnte nicht geöffnet werden
- 2 = Frame konnte nicht gelesen werden
- 3 = Bild konnte nicht gespeichert werden
Node-RED kann später auf diese Rückgabewerte reagieren.
So kann beispielsweise verhindert werden, dass bei einem fehlgeschlagenen Abruf trotzdem eine KI-Auswertung mit einem alten oder nicht vorhandenen Bild gestartet wird.
Benutzername und Passwort besser auslagern
Für einen ersten Test ist es praktisch, die komplette RTSP-Adresse direkt im Python-Skript zu hinterlegen:
RTSP_URL = "rtsp://kamera:meinpasswort@192.168.178.50:554/stream1"
Für ein dauerhaft verwendetes Projekt ist das jedoch nicht optimal.
Vor allem wenn der Quellcode später über GitHub oder ein anderes Repository veröffentlicht wird, gehören Benutzername und Passwort nicht direkt in den Quellcode.
Stattdessen können die Zugangsdaten beispielsweise
- über Umgebungsvariablen,
- aus einer Konfigurationsdatei
- oder später über Node-RED
an das Python-Skript übergeben werden.
Für das grundlegende Beispiel reicht die direkte Variante zunächst aus.
Enthalten Benutzername oder Passwort Sonderzeichen wie @, :, / oder #, müssen diese gegebenenfalls URL-kodiert werden, da solche Zeichen innerhalb einer URL eine besondere Bedeutung besitzen.
Fazit
Mit Python und OpenCV lässt sich mit sehr wenig Aufwand ein einzelner Frame aus einem RTSP-Stream laden und als Bilddatei speichern.
Für meinen Anwendungsfall ist genau das ausreichend: Das Skript holt ein aktuelles Kamerabild, speichert es unter einem festen Dateinamen und beendet sich anschließend wieder.
Damit ist die Grundlage für den nächsten Schritt geschaffen. Später soll Node-RED das Skript in einem festen Intervall starten und das erzeugte Bild anschließend automatisch an eine KI zur Objekterkennung weitergeben.
Wie dieser Ablauf umgesetzt wird, zeige ich in einem separaten Beitrag.
Letzte Aktualisierung am: 21. September 2026





