Bemerkung

Hallo, willkommen in der SunFounder Raspberry Pi & Arduino & ESP32 Enthusiasten-Community auf Facebook! Tauchen Sie mit anderen Enthusiasten tiefer in Raspberry Pi, Arduino und ESP32 ein.

Warum beitreten?

  • Expertenunterstützung: Lösen Sie Probleme nach dem Kauf und technische Herausforderungen mit Hilfe unserer Community und unseres Teams.

  • Lernen & Teilen: Tauschen Sie Tipps und Tutorials aus, um Ihre Fähigkeiten zu verbessern.

  • Exklusive Vorschauen: Erhalten Sie frühzeitigen Zugang zu neuen Produktankündigungen und Sneak Peeks.

  • Sonderrabatte: Genießen Sie exklusive Rabatte auf unsere neuesten Produkte.

  • Festliche Aktionen und Gewinnspiele: Nehmen Sie an Gewinnspielen und Feiertagsaktionen teil.

👉 Bereit, mit uns zu entdecken und zu gestalten? Klicken Sie auf [here] und treten Sie noch heute bei!

5. MeanShift-Objektverfolgung

MeanShift ist ein klassischer histogrammbasierter Algorithmus zur Objektverfolgung. In dieser Lektion implementieren wir nicht nur ein vollständiges Beispiel für MeanShift-Tracking, sondern erklären auch, warum jeder Schritt durchgeführt wird und was intern dabei passiert.

1. Was ist MeanShift?

MeanShift verschiebt ein Fenster iterativ anhand der Wahrscheinlichkeitsdichte, um die wahrscheinlichste Position des Zielobjekts zu finden.

Einfach ausgedrückt: Zuerst geben Sie dem Algorithmus einen „anfänglichen Zielbereich“. Er berechnet daraus die Farbmerkmale dieses Bereichs (zum Beispiel das Farbhistogramm des Zielobjekts) und sucht dann in jedem folgenden Frame den Bereich, der dieser Farbe am ähnlichsten ist, und verschiebt das Rechteck dorthin.

Dieser Prozess basiert nicht auf Deep Learning und benötigt kein Vortraining – er ist daher sehr leichtgewichtig.

MeanShift-Verfolgung

2. Code ausführen

Wichtig

Stellen Sie vor dem Start sicher, dass:

  • das Pan-Tilt-Modul montiert ist

  • Sie Zugriff auf den Raspberry-Pi-Desktop haben

  • das Codepaket installiert ist

  • das Fusion HAT+ installiert und konfiguriert ist

  • OpenCV installiert ist

Detaillierte Anweisungen finden Sie unter 0. OpenCV einrichten.

  1. Öffnen Sie das Terminal und geben Sie den folgenden Befehl ein:

    cd ~/ai-lab-kit/opencv_python
    python3 cv_5_meanshift.py
    
  2. Wenn Sie das Programm ausführen, erscheint ein OpenCV-Fenster mit dem Namen MeanShift Tracker und beginnt mit der Wiedergabe der Videodatei sample2.mp4.

    Ein grünes Rechteck wird um das Zielobjekt gezeichnet und mithilfe des MeanShift-Tracking-Algorithmus in Echtzeit aktualisiert.

    Das Tracking-Fenster bewegt sich mit, wenn sich das Objekt im Video bewegt.

    Sie können das Programm auf zwei Arten beenden:

    • Drücken Sie die q-Taste auf der Tastatur

    • Schließen Sie das Fenster über die Schaltfläche zum Schließen (X)

    Nach dem Beenden stoppt die Videowiedergabe und alle OpenCV-Fenster werden geschlossen.

3. Vollständiger Code

Unten finden Sie das vollständige MeanShift-Tracking-Skript (cv_5_meanshift.py):

import numpy as np
import cv2

cap = cv2.VideoCapture("sample2.mp4")

# Read the first frame
ret, frame = cap.read()
if not ret:
   raise RuntimeError("Cannot read the video file.")

# Initial tracking window (x, y, w, h)
x, y, w, h = 80, 100, 80, 80
track_window = (x, y, w, h)

# Convert the first frame to HSV
hsv_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)

# Extract ROI in HSV (ONLY the selected area)
roi_hsv = hsv_frame[y:y+h, x:x+w]

# Create a mask for ROI (filter out low saturation/value pixels)
roi_mask = cv2.inRange(
   roi_hsv,
   np.array((0, 61, 33), dtype=np.uint8),
   np.array((180, 255, 255), dtype=np.uint8)
)

# Compute histogram of ROI (Hue channel)
roi_hist = cv2.calcHist([roi_hsv], [0], roi_mask, [180], [0, 180])

# Normalize histogram for better tracking
cv2.normalize(roi_hist, roi_hist, 0, 255, cv2.NORM_MINMAX)

# Termination criteria: max 15 iterations or move by at least 2 pixels
termination = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 15, 2)

# FPS settings (fallback if FPS is unavailable)
fps = cap.get(cv2.CAP_PROP_FPS)
if not fps or fps <= 1e-3:
   fps = 30.0
delay_ms = int(1000 / fps)

WINDOW_NAME = "MeanShift Tracker"

while True:
   ret, frame = cap.read()

   # Loop video
   if not ret:
      cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
      continue

   # Convert frame to HSV
   hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)

   # Back projection: probability map of where the ROI histogram appears in the frame
   bp = cv2.calcBackProject([hsv], [0], roi_hist, [0, 180], scale=1)

   # Apply meanShift to update tracking window
   _, track_window = cv2.meanShift(bp, track_window, termination)

   # Draw tracking window
   x, y, w, h = track_window
   cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
   cv2.putText(frame, "MeanShift Tracker", (10, 30),
               cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)

   cv2.imshow(WINDOW_NAME, frame)

   # Handle keyboard input and GUI events
   key = cv2.waitKey(delay_ms) & 0xFF
   if key == ord("q"):
      break

   # Exit if window is closed
   if cv2.getWindowProperty(WINDOW_NAME, cv2.WND_PROP_VISIBLE) < 1:
      break

cap.release()
cv2.destroyAllWindows()

4. Erklärung

  1. Die Videodatei öffnen:

    cap = cv2.VideoCapture("sample2.mp4")
    

    Dadurch wird ein VideoCapture-Objekt erstellt, damit OpenCV Frames aus der Datei lesen kann.

  2. Das erste Frame lesen und sicherstellen, dass es funktioniert:

    ret, frame = cap.read()
    if not ret:
        raise RuntimeError("Cannot read the video file.")
    

    MeanShift-Tracking benötigt ein initiales Frame, um zu lernen, was verfolgt werden soll.

  3. Das anfängliche Tracking-Fenster festlegen (das Objekt, das verfolgt werden soll):

    x, y, w, h = 80, 100, 80, 80
    track_window = (x, y, w, h)
    

    Dieses Rechteck ist die Startposition des Zielobjekts (ROI). In der Regel passen Sie diese Werte so an, dass sie dem Objekt im ersten Frame entsprechen.

  4. Das erste Frame in HSV umwandeln und die ROI extrahieren:

    hsv_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
    roi_hsv = hsv_frame[y:y+h, x:x+w]
    

    HSV wird häufig für Tracking verwendet, weil der Hue-Kanal die Farbe konsistenter beschreibt als RGB/BGR.

  5. Eine Maske erstellen, um schwache oder ungültige Pixel in der ROI zu ignorieren:

    roi_mask = cv2.inRange(
        roi_hsv,
        np.array((0, 61, 33), dtype=np.uint8),
        np.array((180, 255, 255), dtype=np.uint8)
    )
    

    Dadurch werden Pixel mit sehr niedriger Sättigung oder Helligkeit herausgefiltert (oft Schatten oder Rauschen), was die Tracking-Stabilität verbessert.

  6. Das ROI-Histogramm berechnen und normalisieren (Hue-Kanal):

    roi_hist = cv2.calcHist([roi_hsv], [0], roi_mask, [180], [0, 180])
    cv2.normalize(roi_hist, roi_hist, 0, 255, cv2.NORM_MINMAX)
    
    • Das Histogramm beschreibt die Farbverteilung des Zielobjekts (Hue).

    • Die Normalisierung sorgt dafür, dass die Histogrammskala bei unterschiedlichen Lichtverhältnissen oder ROI-Größen konsistent bleibt.

  7. Abbruchkriterien für MeanShift definieren:

    termination = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 15, 2)
    

    MeanShift stoppt, wenn entweder: - 15 Iterationen erreicht sind, oder - die Fensterbewegung kleiner als 2 Pixel ist.

  8. Eine Wiedergabeverzögerung basierend auf der Video-FPS festlegen:

    fps = cap.get(cv2.CAP_PROP_FPS)
    if not fps or fps <= 1e-3:
        fps = 30.0
    delay_ms = int(1000 / fps)
    

    Dadurch bleibt die Wiedergabe nahe an der ursprünglichen Videogeschwindigkeit. Wenn die FPS nicht gelesen werden können, wird auf 30 FPS zurückgegriffen.

  9. Jedes Frame in HSV umwandeln (für das Tracking):

    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
    

    Das Tracking wird in HSV durchgeführt, damit das Hue-Histogramm des Zielobjekts abgeglichen werden kann.

  10. Back Projection anwenden (finden, wo die Zielfarbe wahrscheinlich ist):

    bp = cv2.calcBackProject([hsv], [0], roi_hist, [0, 180], scale=1)
    

    Die Back Projection erzeugt eine Wahrscheinlichkeitskarte: helle Bereiche entsprechen mit höherer Wahrscheinlichkeit dem ROI-Histogramm.

  11. Das Tracking-Fenster mit MeanShift aktualisieren:

    _, track_window = cv2.meanShift(bp, track_window, termination)
    

    MeanShift verschiebt das Tracking-Fenster in Richtung des Bereichs mit der höchsten Dichte in der Wahrscheinlichkeitskarte und aktualisiert so die Zielposition Frame für Frame.

  12. Das Tracking-Ergebnis zeichnen:

    x, y, w, h = track_window
    cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
    

    Dadurch wird das aktuelle Tracking-Rechteck auf das Videobild gezeichnet.

  13. Das Fenster anzeigen und Abbruchbedingungen prüfen:

    key = cv2.waitKey(delay_ms) & 0xFF
    if key == ord("q"):
        break
    
    if cv2.getWindowProperty(WINDOW_NAME, cv2.WND_PROP_VISIBLE) < 1:
        break
    
    • Drücken Sie q, um das Programm zu beenden.

    • Das Schließen des Fensters beendet das Programm ebenfalls sicher.

  14. Ressourcen freigeben:

    cap.release()
    cv2.destroyAllWindows()
    

    Geben Sie das Video immer frei und schließen Sie die Fenster, um Systemressourcen freizugeben.

5. MeanShift vs. CAMShift

Merkmal

MeanShift

CAMShift

Fenstergröße

Fest

Passt sich automatisch an (an die Zielgröße)

Rotierendes Zielobjekt

Nicht unterstützt

Unterstützt

Geeignete Szenarien

Zielgröße relativ stabil

Ziel kann skaliert oder gedreht werden

Anwendungen

Einfaches Tracking, Bälle, Marker

Praktisches Tracking, Überwachung, Erkennung

6. Erweitert: ROI mit der Maus auswählen

Bisher haben wir feste Werte verwendet:

x, y, w, h = 150, 200, 80, 80

Das ist einfach, aber nicht flexibel. Wenn Sie das Video wechseln oder das Zielobjekt an einer anderen Stelle startet, müssten Sie den Code anpassen.

OpenCV bietet cv2.selectROI, sodass Sie die Zielregion im ersten Frame interaktiv mit der Maus auswählen können und das Programm (x, y, w, h) dann automatisch erhält.

Geänderter Initialisierungscode

Führen Sie cv_5_meanshift_auto.py aus, um den angepassten Code zu verwenden.

cd ~/ai-lab-kit/opencv_python
python3 cv_5_meanshift_auto.py
import numpy as np
import cv2
from pathlib import Path

# -----------------------------
# Load video
# -----------------------------
BASE_DIR = Path(__file__).resolve().parent
video_path = str(BASE_DIR / "sample3.mp4")

cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
   raise RuntimeError("Error opening video file")

# Read the first frame (needed for ROI selection and building the target model)
ret, frame = cap.read()
if not ret:
   raise RuntimeError("Cannot read the first frame from the video")

# -----------------------------
# Select ROI with mouse
# -----------------------------
# Press Enter/Space to confirm, press Esc to cancel
roi_box = cv2.selectROI("Select ROI", frame, fromCenter=False, showCrosshair=True)
cv2.destroyWindow("Select ROI")
...

Wenn Sie das Programm ausführen, wird das erste Frame des Videos angezeigt, und Sie werden aufgefordert, mit der Maus eine Region of Interest (ROI) auszuwählen.

Ziehen Sie mit der Maus ein Rechteck um das Zielobjekt und drücken Sie dann Enter oder Leertaste, um die Auswahl zu bestätigen. Drücken Sie Esc, um die Auswahl abzubrechen.

Nach dem Bestätigen der ROI erscheint ein Fenster mit dem Namen MeanShift Tracker. Das ausgewählte Objekt wird mit einem grünen Begrenzungsrahmen verfolgt, und der Rahmen bewegt sich mit dem Objekt im Video mit.

So beenden Sie das Programm:

  • Drücken Sie die q-Taste auf der Tastatur

  • Oder schließen Sie das Anzeigefenster über die Schaltfläche zum Schließen (X)

Nach dem Beenden stoppt die Videowiedergabe und alle OpenCV-Fenster werden geschlossen.

Fenster zur interaktiven ROI-Auswahl

Hinweise

cv2.selectROI ist der integrierte interaktive ROI-Selektor von OpenCV und eignet sich hervorragend für die manuelle Initialisierung. Er gibt (x, y, w, h) zurück, was vollständig mit track_window kompatibel ist, sodass Sie die Hauptlogik von CAMShift/MeanShift nicht ändern müssen. Dadurch können Sie dasselbe Programm für verschiedene Videos und Zielobjekte wiederverwenden.

7. Erweitert II: HSV-Schwellenwerte für die ROI dynamisch berechnen

Das ursprüngliche cv_5_meanshift.py verwendet manuell festgelegte HSV-Schwellenwerte, was geeignet ist, wenn die Zielfarbe fest ist und die Beleuchtung stabil bleibt.

# apply mask on the HSV frame
roi_mask = cv2.inRange(roi_hsv, lower, upper)

Wenn sich die Beleuchtung stark verändert oder die Zielfarbe nicht fest definiert ist, sind fest codierte inRange-Grenzen möglicherweise nicht optimal. Ein intelligenterer Ansatz besteht darin, die unteren und oberen HSV-Grenzen automatisch aus der ausgewählten ROI zu berechnen.

Beispiel: HSV-Schwellenwerte automatisch berechnen

Führen Sie cv_5_meanshift_auto.py aus, um den angepassten Code zu verwenden.

cd ~/ai-lab-kit/opencv_python
python3 cv_5_meanshift_auto.py
hsv0 = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
roi_hsv = hsv0[y:y + h, x:x + w]

# Split ROI HSV channels
h_roi = roi_hsv[:, :, 0]
s_roi = roi_hsv[:, :, 1]
v_roi = roi_hsv[:, :, 2]

# Use percentiles to get robust ranges (ignore outliers)
h_low, h_high = np.percentile(h_roi, [5, 95])
s_low, s_high = np.percentile(s_roi, [5, 95])
v_low, v_high = np.percentile(v_roi, [5, 95])

# Add padding so the range is not too tight
pad_h, pad_s, pad_v = 10, 20, 20

lower = np.array([
   max(int(h_low) - pad_h, 0),
   max(int(s_low) - pad_s, 0),
   max(int(v_low) - pad_v, 0)
], dtype=np.uint8)

upper = np.array([
   min(int(h_high) + pad_h, 180),
   min(int(s_high) + pad_s, 255),
   min(int(v_high) + pad_v, 255)
], dtype=np.uint8)

# Mask ONLY the ROI (do not use the whole frame mask)
roi_mask = cv2.inRange(roi_hsv, lower, upper)

Bei der Auswahl sehr dunkler oder sehr heller Zielobjekte müssen Sie die Schwellenwerte nicht mehr manuell anpassen; das Verfahren passt sich auch schnell an unterschiedliche Lichtverhältnisse und Farben an.

Bemerkung

  • np.percentile (5 %–95 %) entfernt extreme Werte (Ränder, Schatten, Glanzlichter usw.) innerhalb der ROI und verbessert dadurch die Robustheit.

  • pad_h, pad_s, pad_v bieten eine Toleranz, sodass leichte Farbverschiebungen weiterhin erfasst werden.

  • lower und upper sind die dynamischen HSV-Grenzen, die direkt mit cv2.inRange verwendet werden.

Zusammenfassung

  • Verwenden Sie cv2.selectROI für eine flexible Initialisierung des Zielobjekts.

  • Verwenden Sie np.percentile, um HSV-Grenzen automatisch zu berechnen und die Anpassungsfähigkeit zu verbessern.

  • In Kombination mit cv2.inRange und CAMShift/MeanShift bleibt dieser Ansatz auch bei schwierigen Lichtverhältnissen und unterschiedlichen Zielobjekten stabil.