Skip to content

Technik Blog

IoT, Smart Home & Mikrocontroller einfach erklärt

Menu
  • Smarthome
  • Gartenautomation
  • Mikrocontroller
    • Arduino
    • ESP32 & Co.
    • Calliope Mini
    • Raspberry Pi & Pico
  • Solo Mining
  • Über mich
Menu

Raspberry Pi Kamera im Browser anzeigen – Livebild, Foto & Autofokus mit Picamera2

Veröffentlicht am 10. Oktober 20269. Oktober 2026 von Stefan Draeger

Im letzten Beitrag habe ich gezeigt, wie ich die Waveshare IMX708 am Raspberry Pi 5 angeschlossen und unter Raspberry Pi OS eingerichtet habe.

Die Kamera funktioniert inzwischen problemlos und mit rpicam-still lassen sich bereits Fotos aufnehmen. Ein kleines Problem bleibt aber: Mein Raspberry Pi läuft überwiegend ohne angeschlossenen Monitor im Konsolenmodus.

Damit sehe ich vor einer Aufnahme weder den aktuellen Bildausschnitt noch, ob das gewünschte Objekt tatsächlich scharf dargestellt wird.

Gerade bei meiner späteren Anwendung, bei der elektronische Bauteile, Sensoren und Mikrocontroller mit der Kamera aufgenommen werden sollen, möchte ich jedoch vor der Aufnahme kontrollieren können, was die Kamera gerade sieht.

Die Lösung ist ein kleines Webinterface.

In diesem Beitrag zeige ich dir deshalb, wie du das Livebild einer Raspberry-Pi-Kamera mit Picamera2 direkt im Browser anzeigen kannst. Zusätzlich erweitern wir die Oberfläche um zwei praktische Funktionen: Ein Standbild kann direkt über den Browser gespeichert und der Autofokus der IMX708 manuell ausgelöst werden.

Inhaltsverzeichnis

  • Ziel: Kamera komplett über den Browser bedienen
  • Picamera2 installieren
  • Python-Skript für Livebild, Autofokus und Foto-Download
  • Skript speichern und starten
  • Livebild im Browser anzeigen
  • Autofokus per Schaltfläche auslösen
  • Standbild direkt herunterladen
  • Fazit
  • Ausblick: Livebild direkt auf einem Display anzeigen

Ziel: Kamera komplett über den Browser bedienen

Mein Raspberry Pi läuft ohne angeschlossenen Monitor. Deshalb möchte ich nicht nur das Livebild der Waveshare IMX708 im Browser sehen, sondern die Kamera auch direkt darüber bedienen können.

Die Weboberfläche soll deshalb drei Funktionen bieten:

  • Livebild der Kamera anzeigen
  • Autofokus manuell auslösen
  • aktuelles Standbild als JPEG herunterladen

Das Ganze läuft über einen kleinen Python-Webserver mit Picamera2. Zusätzliche Webframeworks wie Flask oder FastAPI sind dafür nicht notwendig.

Picamera2 installieren

Falls Picamera2 noch nicht installiert ist, kannst du die Bibliothek über apt nachinstallieren:

sudo apt update
sudo apt install python3-picamera2 -y

Ob Picamera2 korrekt verfügbar ist, lässt sich schnell testen:

python3 -c "from picamera2 import Picamera2; print('Picamera2 funktioniert')"

Python-Skript für Livebild, Autofokus und Foto-Download

Das folgende Skript stellt die komplette Weboberfläche bereit.

Python Skript für das anzeigen eines Live Bildes der Waveshare IMX708 am Raspberry Pi 5
#!/usr/bin/env python3

# --------------------------------------------------
# Benötigte Python-Module importieren
# --------------------------------------------------

# io wird verwendet, um Bilddaten im Arbeitsspeicher
# statt direkt in einer Datei abzulegen.
import io

# time benötigen wir unter anderem für den Zeitstempel
# im Dateinamen eines heruntergeladenen Standbildes.
import time

# socketserver stellt die Basis für unseren
# multithreaded HTTP-Server bereit.
import socketserver

# Das Modul http.server enthält die Klassen,
# mit denen wir unseren kleinen Webserver aufbauen.
from http import server

# Condition wird benötigt, damit der MJPEG-Stream
# auf neue Kamerabilder warten kann.
from threading import Condition

# Picamera2 ist die aktuelle Python-Bibliothek
# für die Kamerasteuerung unter Raspberry Pi OS.
from picamera2 import Picamera2

# JpegEncoder erzeugt aus dem Kamerastream
# fortlaufend JPEG-Bilder.
from picamera2.encoders import JpegEncoder

# FileOutput verbindet den JPEG-Encoder mit
# unserer eigenen StreamingOutput-Klasse.
from picamera2.outputs import FileOutput

# Über controls greifen wir auf Kameraeinstellungen
# wie Autofokus-Modus und Autofokus-Trigger zu.
from libcamera import controls


# --------------------------------------------------
# HTML-Oberfläche
# --------------------------------------------------

# Die komplette Webseite wird direkt als String
# innerhalb des Python-Skripts hinterlegt.
#
# Dadurch benötigen wir keine zusätzliche HTML-Datei
# und können das gesamte Projekt mit nur einer
# Python-Datei betreiben.

PAGE = """<!DOCTYPE html>
<html lang="de">
<head>

    <!-- Zeichencodierung -->
    <meta charset="UTF-8">

    <!-- Optimierung für Smartphones und Tablets -->
    <meta name="viewport"
          content="width=device-width, initial-scale=1.0">

    <title>Waveshare IMX708 Livebild</title>

    <style>

        /* --------------------------------------------------
           Grunddesign der Webseite
           -------------------------------------------------- */

        body {
            font-family: Arial, sans-serif;
            background: #1e1e1e;
            color: white;

            text-align: center;

            margin: 0;
            padding: 20px;
        }


        /* --------------------------------------------------
           Container für die komplette Oberfläche
           -------------------------------------------------- */

        .container {
            max-width: 1280px;
            margin: 0 auto;
        }


        /* Überschrift */

        h1 {
            margin-bottom: 20px;
        }


        /* --------------------------------------------------
           Livebild
           -------------------------------------------------- */

        .stream {

            /* Bild nutzt die verfügbare Breite */

            width: 100%;

            /* maximal jedoch 1280 Pixel */

            max-width: 1280px;

            /* leicht abgerundete Ecken */

            border-radius: 10px;

            /* Rahmen um das Kamerabild */

            border: 2px solid #555;

            /* schwarzer Hintergrund,
               falls noch kein Bild verfügbar ist */

            background: #000;
        }


        /* --------------------------------------------------
           Bereich für die Schaltflächen
           -------------------------------------------------- */

        .buttons {
            margin-top: 20px;
        }


        /* Das Formular des Autofokus-Buttons
           soll nicht die ganze Breite einnehmen. */

        form {
            display: inline-block;
            margin: 8px;
        }


        /* Auch der Download-Link soll wie ein Button
           neben dem Autofokus erscheinen. */

        .download-link {
            display: inline-block;
            margin: 8px;

            text-decoration: none;
        }


        /* --------------------------------------------------
           Allgemeines Design der Buttons
           -------------------------------------------------- */

        button,
        .download-button {

            font-size: 18px;

            padding: 12px 20px;

            cursor: pointer;

            border: none;

            border-radius: 8px;
        }


        /* Autofokus-Button */

        .focus {
            background: #2196F3;
            color: white;
        }


        /* Foto-Button */

        .photo {
            background: #4CAF50;
            color: white;
        }


        /* --------------------------------------------------
           Statusanzeige
           -------------------------------------------------- */

        .status {
            margin-top: 15px;

            font-size: 16px;

            color: #ccc;
        }


        /* Zusätzlicher Hinweis unterhalb der Buttons */

        .info {
            margin-top: 25px;

            font-size: 14px;

            color: #aaa;
        }

    </style>
</head>


<body>

<div class="container">

    <h1>Waveshare IMX708 Livebild</h1>


    <!-- --------------------------------------------------
         Livebild

         Der Browser ruft /stream.mjpg auf.

         Dieser Endpunkt liefert fortlaufend einzelne
         JPEG-Bilder als MJPEG-Stream.
         -------------------------------------------------- -->

    <img class="stream"
         src="/stream.mjpg"
         alt="Livebild der Waveshare IMX708">


    <!-- --------------------------------------------------
         Bedienung der Kamera
         -------------------------------------------------- -->

    <div class="buttons">


        <!-- --------------------------------------------------
             Autofokus

             Beim Klick sendet der Browser einen
             HTTP-POST an /autofocus.

             Dafür ist kein JavaScript notwendig.
             -------------------------------------------------- -->

        <form method="POST"
              action="/autofocus">

            <button type="submit"
                    class="focus">

                Autofokus

            </button>

        </form>


        <!-- --------------------------------------------------
             Standbild herunterladen

             Der Browser ruft /photo auf.

             Der Python-Webserver nimmt daraufhin ein
             aktuelles Kamerabild auf und liefert dieses
             als JPEG-Datei zurück.
             -------------------------------------------------- -->

        <a class="download-link"
           href="/photo">

            <button type="button"
                    class="photo download-button">

                Standbild herunterladen

            </button>

        </a>


    </div>


    <!-- Kleine Statusanzeige -->

    <div class="status">
        Kamera bereit
    </div>


    <!-- Hinweis zum Autofokus -->

    <div class="info">
        Autofokus wird per HTTP POST ausgelöst.
    </div>

</div>

</body>
</html>
"""


# --------------------------------------------------
# MJPEG Streaming Output
# --------------------------------------------------

# Picamera2 erzeugt permanent JPEG-Bilder.
#
# Diese Klasse speichert immer nur das aktuellste
# JPEG-Bild im Arbeitsspeicher.
#
# Der Browser kann dieses Bild anschließend über
# /stream.mjpg abrufen.


class StreamingOutput(io.BufferedIOBase):

    def __init__(self):

        # Hier wird das zuletzt erzeugte JPEG gespeichert.
        self.frame = None

        # Condition ermöglicht es mehreren Threads,
        # auf ein neues Bild zu warten.
        self.condition = Condition()


    def write(self, buf):

        # Sobald der JPEG-Encoder ein neues Bild erzeugt,
        # landet es hier in "buf".

        with self.condition:

            # Aktuelles Bild speichern
            self.frame = buf

            # Alle wartenden Browser-Threads informieren,
            # dass ein neues Bild verfügbar ist.
            self.condition.notify_all()

        # Anzahl der geschriebenen Bytes zurückgeben.
        return len(buf)


# --------------------------------------------------
# HTTP Handler
# --------------------------------------------------

# StreamingHandler verarbeitet alle HTTP-Anfragen,
# die vom Browser an den Raspberry Pi gesendet werden.


class StreamingHandler(server.BaseHTTPRequestHandler):


    # --------------------------------------------------
    # HTTP GET
    # --------------------------------------------------

    def do_GET(self):


        # --------------------------------------------------
        # Hauptseite
        # --------------------------------------------------

        if self.path == "/":

            # HTML-Code in Bytes umwandeln.
            content = PAGE.encode("utf-8")

            # HTTP Status 200 = OK
            self.send_response(200)

            # Browser mitteilen, dass HTML geliefert wird.
            self.send_header(
                "Content-Type",
                "text/html; charset=utf-8"
            )

            # Größe des Inhalts übertragen.
            self.send_header(
                "Content-Length",
                len(content)
            )

            # HTTP-Header abschließen.
            self.end_headers()

            # Webseite an den Browser senden.
            self.wfile.write(content)


        # --------------------------------------------------
        # MJPEG-Livestream
        # --------------------------------------------------

        elif self.path == "/stream.mjpg":

            # HTTP Status 200
            self.send_response(200)


            # --------------------------------------------------
            # Browser-Caching deaktivieren
            #
            # Der Browser soll keine alten Kamerabilder
            # zwischenspeichern.
            # --------------------------------------------------

            self.send_header(
                "Age",
                "0"
            )

            self.send_header(
                "Cache-Control",
                "no-cache, private"
            )

            self.send_header(
                "Pragma",
                "no-cache"
            )


            # --------------------------------------------------
            # MJPEG Content-Type
            #
            # multipart/x-mixed-replace bedeutet:
            #
            # Der Server liefert permanent neue Inhalte.
            #
            # FRAME trennt dabei die einzelnen JPEG-Bilder.
            # --------------------------------------------------

            self.send_header(
                "Content-Type",
                "multipart/x-mixed-replace; boundary=FRAME"
            )

            self.end_headers()


            try:

                # Solange der Browser verbunden ist,
                # werden ständig neue JPEG-Bilder übertragen.

                while True:


                    # --------------------------------------------------
                    # Auf neues Kamerabild warten
                    # --------------------------------------------------

                    with output.condition:

                        output.condition.wait()

                        frame = output.frame


                    # Falls noch kein Bild vorhanden ist,
                    # einfach auf das nächste warten.

                    if frame is None:
                        continue


                    # --------------------------------------------------
                    # Beginn eines neuen MJPEG-Frames
                    # --------------------------------------------------

                    self.wfile.write(
                        b"--FRAME\r\n"
                    )


                    # Bildtyp: JPEG

                    self.send_header(
                        "Content-Type",
                        "image/jpeg"
                    )


                    # Größe des JPEG-Bildes

                    self.send_header(
                        "Content-Length",
                        len(frame)
                    )


                    self.end_headers()


                    # JPEG-Bild an den Browser senden

                    self.wfile.write(frame)


                    # Zeilenumbruch als Abschluss

                    self.wfile.write(
                        b"\r\n"
                    )


            # --------------------------------------------------
            # Browser wurde geschlossen
            #
            # In diesem Fall entsteht häufig ein
            # BrokenPipeError oder ConnectionResetError.
            #
            # Das ist normal und kein echter Fehler.
            # --------------------------------------------------

            except (
                BrokenPipeError,
                ConnectionResetError
            ):
                pass


            # Andere Fehler ausgeben.

            except Exception as e:

                print(
                    "Stream beendet:",
                    self.client_address,
                    e
                )


        # --------------------------------------------------
        # Standbild aufnehmen
        # --------------------------------------------------

        elif self.path == "/photo":

            try:


                # --------------------------------------------------
                # Temporären Speicher erzeugen
                #
                # Das Foto wird nicht auf dem Raspberry Pi
                # gespeichert, sondern zunächst im RAM.
                # --------------------------------------------------

                stream = io.BytesIO()


                # --------------------------------------------------
                # Aktuelles Kamerabild aufnehmen
                #
                # Das Ergebnis wird direkt als JPEG
                # in den Speicher geschrieben.
                # --------------------------------------------------

                picam2.capture_file(
                    stream,
                    format="jpeg"
                )


                # Bytes des JPEG-Bildes abrufen.

                image = stream.getvalue()


                # --------------------------------------------------
                # Dateiname erzeugen
                #
                # Beispiel:
                #
                # imx708_2026-10-09_16-45-27.jpg
                # --------------------------------------------------

                filename = time.strftime(
                    "imx708_%Y-%m-%d_%H-%M-%S.jpg"
                )


                # --------------------------------------------------
                # JPEG an Browser senden
                # --------------------------------------------------

                self.send_response(200)


                self.send_header(
                    "Content-Type",
                    "image/jpeg"
                )


                # --------------------------------------------------
                # Content-Disposition: attachment
                #
                # Dadurch behandelt der Browser das Bild
                # als Download und öffnet es nicht nur.
                # --------------------------------------------------

                self.send_header(
                    "Content-Disposition",
                    f'attachment; filename="{filename}"'
                )


                self.send_header(
                    "Content-Length",
                    len(image)
                )


                # Download soll nicht gecacht werden.

                self.send_header(
                    "Cache-Control",
                    "no-store"
                )


                self.end_headers()


                # JPEG-Daten übertragen.

                self.wfile.write(image)


                # Ausgabe in der Konsole.

                print(
                    "Standbild aufgenommen:",
                    filename
                )


            except Exception as e:

                print(
                    "Fehler beim Standbild:",
                    e
                )


                # HTTP Fehler 500 an Browser senden.

                self.send_error(
                    500,
                    str(e)
                )


        # --------------------------------------------------
        # Unbekannte URL
        # --------------------------------------------------

        else:

            self.send_error(404)


    # --------------------------------------------------
    # HTTP POST
    # --------------------------------------------------

    def do_POST(self):


        # --------------------------------------------------
        # Autofokus auslösen
        # --------------------------------------------------

        if self.path == "/autofocus":

            try:


                # --------------------------------------------------
                # Autofokus konfigurieren
                #
                # AfModeEnum.Auto:
                # Autofokus läuft nicht permanent,
                # sondern wird gezielt ausgelöst.
                #
                # AfTriggerEnum.Start:
                # Startet einen Autofokus-Vorgang.
                # --------------------------------------------------

                picam2.set_controls({

                    "AfMode":
                        controls.AfModeEnum.Auto,

                    "AfTrigger":
                        controls.AfTriggerEnum.Start

                })


                print(
                    "Autofokus ausgelöst"
                )


                # --------------------------------------------------
                # Browser zurück auf Hauptseite leiten
                #
                # HTTP 303 bedeutet:
                #
                # Der POST wurde verarbeitet.
                # Danach soll der Browser eine GET-Anfrage
                # auf die angegebene Location durchführen.
                # --------------------------------------------------

                self.send_response(303)

                self.send_header(
                    "Location",
                    "/"
                )

                self.end_headers()


            except Exception as e:

                print(
                    "Fehler beim Autofokus:",
                    e
                )


                self.send_error(
                    500,
                    str(e)
                )


        # Unbekannte POST-URL

        else:

            self.send_error(404)


    # --------------------------------------------------
    # HTTP-Log deaktivieren
    # --------------------------------------------------

    # Normalerweise würde Python jede Browser-Anfrage
    # in der Konsole ausgeben.
    #
    # Beispiel:
    #
    # GET /stream.mjpg HTTP/1.1
    #
    # Für dieses Projekt benötigen wir diese Meldungen
    # nicht und unterdrücken sie daher.

    def log_message(self, format, *args):

        return


# --------------------------------------------------
# HTTP Server
# --------------------------------------------------

# ThreadingMixIn sorgt dafür, dass mehrere HTTP-Anfragen
# parallel verarbeitet werden können.
#
# Das ist wichtig, weil der MJPEG-Stream dauerhaft eine
# Verbindung geöffnet hält.
#
# Gleichzeitig sollen trotzdem Autofokus- und
# Foto-Anfragen verarbeitet werden können.


class StreamingServer(

    socketserver.ThreadingMixIn,

    server.HTTPServer

):

    # Port kann nach einem Neustart schneller
    # erneut verwendet werden.

    allow_reuse_address = True


    # Threads werden automatisch beendet,
    # sobald der Hauptprozess endet.

    daemon_threads = True


# --------------------------------------------------
# Kamera initialisieren
# --------------------------------------------------

# Picamera2-Objekt erzeugen.
#
# Picamera2 verwendet automatisch die vom System
# erkannte Kamera – in unserem Fall die IMX708.

picam2 = Picamera2()


# --------------------------------------------------
# Kamera-Konfiguration
# --------------------------------------------------

# Für das Browser-Livebild verwenden wir
# eine Auflösung von 1280 x 720 Pixeln.
#
# Das ist ein guter Kompromiss zwischen:
#
# - Bildqualität
# - Datenmenge
# - CPU-Auslastung
# - Netzwerkbelastung


config = picam2.create_video_configuration(

    main={

        "size": (
            1280,
            720
        )

    }

)


# Konfiguration auf die Kamera anwenden.

picam2.configure(
    config
)


# --------------------------------------------------
# Autofokus-Grundmodus
# --------------------------------------------------

# Wir setzen den Autofokus auf "Auto".
#
# Anders als bei "Continuous" fokussiert die Kamera
# damit nicht permanent.
#
# Ein Fokusvorgang startet erst, wenn wir über
# /autofocus den AfTrigger auslösen.

picam2.set_controls({

    "AfMode":
        controls.AfModeEnum.Auto

})


# --------------------------------------------------
# MJPEG-Ausgabe erzeugen
# --------------------------------------------------

# Unsere StreamingOutput-Klasse erzeugen.

output = StreamingOutput()


# --------------------------------------------------
# Kamera und JPEG-Encoding starten
# --------------------------------------------------

# start_recording startet den Kamerastream.
#
# JpegEncoder:
# Wandelt jedes Kamerabild in JPEG um.
#
# FileOutput(output):
# Übergibt die JPEG-Daten an unsere
# StreamingOutput-Klasse.

picam2.start_recording(

    JpegEncoder(),

    FileOutput(
        output
    )

)


# --------------------------------------------------
# Webserver starten
# --------------------------------------------------

try:


    # --------------------------------------------------
    # Adresse des Servers
    #
    # "" bedeutet:
    #
    # Der Server lauscht auf allen
    # Netzwerkinterfaces des Raspberry Pi.
    #
    # Port 8000 ist der verwendete HTTP-Port.
    # --------------------------------------------------

    address = (

        "",

        8000

    )


    # HTTP-Server erzeugen.

    httpd = StreamingServer(

        address,

        StreamingHandler

    )


    # --------------------------------------------------
    # Informationen in der Konsole ausgeben
    # --------------------------------------------------

    print()

    print(
        "Waveshare IMX708 Webserver gestartet"
    )

    print()

    print(
        "Browser öffnen:"
    )

    print(
        "http://<IP-des-Raspberry-Pi>:8000"
    )

    print()

    print(
        "IP-Adresse anzeigen mit:"
    )

    print(
        "hostname -I"
    )

    print()


    # --------------------------------------------------
    # Webserver dauerhaft laufen lassen
    # --------------------------------------------------

    httpd.serve_forever()


# --------------------------------------------------
# CTRL+C wurde gedrückt
# --------------------------------------------------

except KeyboardInterrupt:

    print()

    print(
        "Webserver wird beendet ..."
    )


# --------------------------------------------------
# Kamera sauber stoppen
# --------------------------------------------------

# Dieser Block wird immer ausgeführt,
# egal ob das Programm normal oder mit CTRL+C
# beendet wurde.

finally:

    picam2.stop_recording()

    print(
        "Kamera gestoppt."
    )
Python-Skript: Livebild mit Raspberry Pi 5 & Waveshare IMX708Herunterladen

Skript speichern und starten

Speichere das Skript beispielsweise unter:

nano camera_web.py

Anschließend startest du es mit:

python3 camera_web.py

Die IP-Adresse deines Raspberry Pi bekommst du mit:

hostname -I

Danach öffnest du auf einem anderen Rechner im gleichen Netzwerk: http://raspberry:8000

Alternativ kannst du direkt die IP-Adresse verwenden: http://192.168.178.50:8000

Livebild im Browser anzeigen

Das Kamerabild wird als MJPEG-Stream bereitgestellt.

Der Stream läuft über: /stream.mjpg

Im HTML wird er einfach über:

<img src="/stream.mjpg">

eingebunden.

Dadurch benötigt der Browser weder ein Plugin noch zusätzliche Software.

Der Vorteil dabei ist, dass sich das Livebild problemlos auf einem anderen PC, Tablet oder Smartphone im gleichen Netzwerk öffnen lässt.

Gerade bei einem Raspberry Pi ohne angeschlossenen Monitor ist das sehr praktisch.

Livebild der Waveshare IMX708 Cam am Raspberry Pi im Browser ohne Autofokus
Livebild der Waveshare IMX708 Cam am Raspberry Pi im Browser ohne Autofokus
Livebild der Waveshare IMX708 Cam am Raspberry Pi im Browser mit aktivierten Autofokus
Livebild der Waveshare IMX708 Cam am Raspberry Pi im Browser mit aktivierten Autofokus

Autofokus per Schaltfläche auslösen

Die Waveshare IMX708 unterstützt Autofokus.

Im Webinterface befindet sich dafür eine eigene Schaltfläche:

<form method="POST" action="/autofocus">
    <button type="submit" class="focus">
        Autofokus
    </button>
</form>
Schaltfläche - Autofokus
Schaltfläche – Autofokus

Beim Klick sendet der Browser einen HTTP-POST an: /autofocus

Das Python-Skript reagiert darauf mit:

picam2.set_controls({    "AfMode": controls.AfModeEnum.Auto,    "AfTrigger": controls.AfTriggerEnum.Start})

Damit wird ein neuer Autofokus-Vorgang gestartet.

Anschließend sendet der Webserver einen HTTP-Status 303 zurück und leitet den Browser wieder auf die Hauptseite:

self.send_response(303)self.send_header(    "Location",    "/")self.end_headers()

Der Vorteil dieser Lösung ist, dass keinerlei JavaScript notwendig ist.

Der Browser sendet einen ganz normalen HTTP-POST an den Raspberry Pi.

Gerade bei Nahaufnahmen von Sensoren, Mikrocontrollern oder elektronischen Bauteilen ist es praktisch, den Fokus bei Bedarf gezielt neu auslösen zu können.

Standbild direkt herunterladen

Neben dem Livebild möchte ich auch ein einzelnes Foto aufnehmen können.

Dafür gibt es die Schaltfläche: Standbild herunterladen

Beim Klick wird die URL: /photo aufgerufen.

Im Python-Skript wird anschließend ein aktuelles Bild der Kamera aufgenommen:

Schaltfläche - Standbild herunterladen
Schaltfläche – Standbild herunterladen
stream = io.BytesIO()picam2.capture_file(    stream,    format="jpeg")

Das Bild befindet sich zunächst nur im Arbeitsspeicher.

Danach wird ein Dateiname mit aktuellem Datum und Uhrzeit erzeugt:

filename = time.strftime(    "imx708_%Y-%m-%d_%H-%M-%S.jpg")

Ein Dateiname könnte beispielsweise so aussehen: imx708_2026-10-09_16-45-27.jpg

Über den HTTP-Header:

self.send_header(    "Content-Disposition",    f'attachment; filename="{filename}"')

wird dem Browser mitgeteilt, dass die Datei als Download angeboten werden soll.

Anschließend wird die Größe des Bildes übermittelt:

self.send_header(    "Content-Length",    len(image))

Zusätzlich verhindere ich mit:

self.send_header(    "Cache-Control",    "no-store")

dass der Browser eine ältere Aufnahme aus dem Cache verwendet.

Zum Schluss werden die JPEG-Daten an den Browser gesendet:

self.wfile.write(image)

Damit wird das aktuelle Kamerabild direkt als JPEG-Datei heruntergeladen.

Ein zusätzlicher Schritt wie SCP, FTP oder das manuelle Kopieren vom Raspberry Pi ist dadurch nicht notwendig.

Fazit

Mit Picamera2 lässt sich die Waveshare IMX708 nicht nur über die Konsole ansprechen, sondern auch sehr komfortabel über eine kleine Weboberfläche bedienen.

Das Livebild wird als MJPEG-Stream direkt im Browser angezeigt. Dadurch kann ich auch bei einem Raspberry Pi ohne angeschlossenen Monitor jederzeit kontrollieren, was die Kamera gerade sieht. Über zwei zusätzliche Schaltflächen kann ich den Autofokus gezielt neu auslösen und ein aktuelles Standbild direkt als JPEG herunterladen.

Besonders praktisch finde ich, dass dafür kein zusätzliches Webframework notwendig ist. Der komplette Aufbau funktioniert mit einem einzigen Python-Skript und den bereits vorhandenen Python-Bibliotheken von Raspberry Pi OS.

Für meinen späteren KI-Werkstatt-Assistenten ist das ein wichtiger Zwischenschritt. Bevor Bilder automatisch analysiert werden, kann ich damit zunächst bequem überprüfen, ob das Bauteil vollständig im Bild liegt und korrekt fokussiert wurde.

Ausblick: Livebild direkt auf einem Display anzeigen

Bisher rufe ich das Kamerabild über einen Browser auf einem anderen Rechner oder Smartphone ab. Der Raspberry Pi 5 bietet aber noch eine weitere interessante Möglichkeit: Über seinen GPIO-Header lassen sich verschiedene Displays anschließen.

Dabei gibt es beispielsweise größere Displays mit etwa 3,5 Zoll, die direkt auf den GPIO-Header des Raspberry Pi gesteckt werden. Viele dieser Module kommunizieren intern über SPI und benötigen dadurch keine separate HDMI-Verbindung.

Daneben gibt es kleinere TFT-Displays, die ebenfalls über SPI angesteuert werden können. Diese lassen sich flexibel mit einzelnen Jumper- beziehungsweise Breadboard-Kabeln mit den entsprechenden GPIO-Pins des Raspberry Pi verbinden.

Genau das möchte ich als Nächstes ausprobieren.

In einem weiteren Beitrag werde ich ein kleines SPI-TFT-Display am Raspberry Pi 5 anschließen und versuchen, das Livebild der Waveshare IMX708 direkt darauf darzustellen.

Damit wäre der Raspberry Pi nicht mehr auf einen externen Browser angewiesen. Das Kamerabild könnte direkt am Gerät angezeigt werden – beispielsweise später als Bestandteil eines kompakten Kamera- oder KI-Systems.

Transparenzhinweis: Dieser Beitrag wurde mit Unterstützung künstlicher Intelligenz erstellt und redaktionell geprüft. Mehr zur Nutzung von KI

Letzte Aktualisierung am: 09. Oktober 2026

Foto von Stefan Draeger
Über den Autor

Stefan Draeger — Entwickler & Tech-Blogger

Ich zeige praxisnah, wie du Projekte mit Arduino, ESP32 und Smarthome-Komponenten umsetzt – Schritt für Schritt, mit Code und Schaltplänen.

Mehr Artikel von Stefan →

Schreibe einen Kommentar Antwort abbrechen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Kategorien

Kontakt & Hilfe

Fragen oder Feedback?

Du hast eine Idee, brauchst Hilfe oder möchtest Feedback loswerden? Erstelle einfach ein Support-Ticket.

Support-Ticket erstellen
Keine Beiträge verpassen

Newsletter abonnieren

Erhalte regelmäßig Updates zu neuen Projekten, Tutorials und Tipps rund um Arduino, ESP32, Smarthome und IoT direkt in dein Postfach.

Newsletter abonnieren
Videos & Tutorials

Draeger-IT auf YouTube

Entdecke meine Videos zu Arduino, ESP32, Raspberry Pi, Smarthome und spannenden Elektronikprojekten – mit Tutorials, Tests und Praxisbeispielen.

YouTube-Kanal besuchen
Freiwillige Unterstützung

Unterstütze meinen Blog

Wenn dir meine Inhalte gefallen, freue ich mich über deine Unterstützung auf Tipeee. So hilfst du dabei, den Blog am Leben zu halten und neue Beiträge zu ermöglichen.

Auf Tipeee unterstützen

Vielen Dank für deinen Support!
– Stefan Draeger

Kostenlose Online-Werkzeuge

Tools

Praktische Generatoren, Rechner und Hilfsmittel für deine Elektronik- und Entwicklungsprojekte.

Generatoren & Konverter

  • QR-Code Generator
  • Passwort Generator
  • PNG zu WebP konvertieren
  • Bilder online komprimieren
  • Code online formatieren

Rechner & Tabellen

  • Unix-Zeitstempel-Rechner
  • ASCII-Tabelle
  • ASCII Code umrechnen – Text, Dezimal, Hex & Binär
  • Spannung, Strom, Widerstand und Leistung berechnen
  • Widerstandsrechner

LED-Matrix

  • 8×8 LED Matrix Tool
  • 8×16 LED Matrix Modul von Keyestudio
  • 16×16 LED Matrix Generator
Kontakt

Anschrift & Kontaktdaten

Stefan Draeger

Königsberger Str. 13
38364 Schöningen

Telefon 01556 5432686

E-Mail info@draeger-it.blog

Blogverzeichnis Bloggerei.de TopBlogs.de das Original - Blogverzeichnis | Blog Top Liste Blogverzeichnis trusted-blogs.com
Social Media

Folge mir auf

Weitere Projekte, Videos und Neuigkeiten findest du auch auf meinen Social-Media-Kanälen.

  • f Facebook
  • in LinkedIn
  • ▶ YouTube
  • ♪ TikTok
  • P Pinterest
  • ◎ Instagram
Rechtliche Informationen

Rechtliches

  • Impressum
  • Datenschutzerklärung
  • Disclaimer
  • Cookie-Richtlinie (EU)
  • KI-Transparenz bei draeger-it.blog
©2026 Technik Blog | Built using WordPress and Responsive Blogily theme by Superb
Cookie-Zustimmung verwalten
Wir verwenden Technologien wie Cookies, um Geräteinformationen zu speichern und/oder darauf zuzugreifen. Wir tun dies, um das Surferlebnis zu verbessern und um personalisierte Werbung anzuzeigen. Wenn Sie diesen Technologien zustimmen, können wir Daten wie das Surfverhalten oder eindeutige IDs auf dieser Website verarbeiten. Wenn Sie Ihre Zustimmung nicht erteilen oder zurückziehen, können bestimmte Funktionen beeinträchtigt werden.
Funktional Immer aktiv
Die technische Speicherung oder der Zugang ist unbedingt erforderlich für den rechtmäßigen Zweck, die Nutzung eines bestimmten Dienstes zu ermöglichen, der vom Teilnehmer oder Nutzer ausdrücklich gewünscht wird, oder für den alleinigen Zweck, die Übertragung einer Nachricht über ein elektronisches Kommunikationsnetz durchzuführen.
Vorlieben
Die technische Speicherung oder der Zugriff ist für den rechtmäßigen Zweck der Speicherung von Präferenzen erforderlich, die nicht vom Abonnenten oder Benutzer angefordert wurden.
Statistiken
Die technische Speicherung oder der Zugriff, der ausschließlich zu statistischen Zwecken erfolgt. Die technische Speicherung oder der Zugriff, der ausschließlich zu anonymen statistischen Zwecken verwendet wird. Ohne eine Vorladung, die freiwillige Zustimmung deines Internetdienstanbieters oder zusätzliche Aufzeichnungen von Dritten können die zu diesem Zweck gespeicherten oder abgerufenen Informationen allein in der Regel nicht dazu verwendet werden, dich zu identifizieren.
Marketing
Die technische Speicherung oder der Zugriff ist erforderlich, um Nutzerprofile zu erstellen, um Werbung zu versenden oder um den Nutzer auf einer Website oder über mehrere Websites hinweg zu ähnlichen Marketingzwecken zu verfolgen.
  • Optionen verwalten
  • Dienste verwalten
  • Verwalten von {vendor_count}-Lieferanten
  • Lese mehr über diese Zwecke
Einstellungen anzeigen
  • {title}
  • {title}
  • {title}