Videoobjektverfolgung mit OpenCV

C++Beginner
Jetzt üben

Einführung

In diesem Lab implementieren wir die Videoverfolgung von Objekten mit OpenCV.
Du musst den Kurs "Building Solar System with C++" abschließen, bevor du dieses Projekt lernst.

Dinge zum Lernen

  • C++-Grundlagen
  • g++-Grundlagen
  • Bildrepräsentation
  • OpenCV-Anwendung
  • Meanshift- und Camshift-Algorithmen

Endresultate

Dieses Experiment implementiert ein Programm, das die Planeten im Sonnensystem verfolgen kann. (In dem folgenden Bild haben wir Jupiter aus der gelben Umlaufbahn ausgewählt, und du kannst sehen, dass das verfolgte Objekt mit einem roten Eklips markiert ist):

image desc

Bevor du dieses Projekt schreibst, musst du unseren Kurs "Building Solar System with C++" abschließen.

Ein Videodatei erstellen

Im LabEx-Umgebung unterstützen wir keine Kameraumgebung. Daher müssen wir eine Videodatei für unser Projekt erstellen.

Lassen Sie uns das Videorekordertool installieren:

sudo apt-get update && sudo apt-get install gtk-recordmydesktop

Nach der Installation können wir die Aufnahmesoftware im Anwendungsmenü finden:

image desc

Dann können Sie das Sonnensystemprogramm ./solarsystem ausführen und mit RecordMyDesktop den Desktopbildschirm aufnehmen (10 bis 30 Sekunden sind ausreichend), und es unter ~/Code/camshift mit dem Namen video speichern:

image desc

Wenn Sie die Aufzeichnung beenden möchten, können Sie die Schaltfläche Stop in der unteren rechten Ecke klicken. Dann erhalten Sie eine video.ogv-Datei:

image desc

Grundlagen digitaler Bilder

OpenCV ist eine quelloffene plattformübergreifende Computervisionsbibliothek. Im Gegensatz zur Bildrendering von OpenGL implementiert OpenCV viele gängige Algorithmen für die Bildverarbeitung und die Computervision. Bevor wir OpenCV lernen, müssen wir einige grundlegende Konzepte von Bildern und Videos im Computer verstehen.

Zunächst müssen wir verstehen, wie das Bild oder das Image im Computer dargestellt wird. Es gibt zwei gängige Arten, Bilder zu speichern: eine ist die Vektorkarte und die andere ist die Pixelkarte.

Bei der Vektorkarte werden Bilder mathematisch als eine Reihe von Punkten definiert, die durch Linien verbunden sind. Das grafische Element in einer Vektorkarten-Datei wird als Objekt bezeichnet. Jedes Objekt ist eine eigenständige Entität, die Eigenschaften wie Farbe, Form, Umriss, Größe und Bildschirmposition hat.

Die weitaus üblichere ist die Pixelkarte. Beispielsweise hat die Größe eines Bildes oft 1024 * 768. Dies bedeutet, dass das Bild in horizontaler Richtung 1024 Pixel und in vertikaler Richtung 768 Pixel hat.

Der Pixel ist die Grundeinheit der Pixelkarte. Normalerweise ist ein Pixel eine Mischung aus drei Primärfarben (Rot, Grün und Blau). Da die Natur des Computers die Erkennung von Zahlen ist, stellen wir normalerweise eine Primärfarbe in Bezug auf die Helligkeit von 0 bis 255 dar. Mit anderen Worten, für die Primärfarbe Rot bedeutet 0 das dunkelste, d.h. schwarz, und 255 bedeutet das hellste, d.h. reines Rot.

Ein Pixel kann daher als Tripel (R,G,B) dargestellt werden, sodass weiß als (255,255,255) dargestellt werden kann und schwarz (0,0,0) ist. Dann nennen wir dieses Bild ein Bild im RGB-Farbraum. R, G und B werden zu den drei Kanälen des Bildes; und es gibt viele andere Farbräume außerhalb des RGB-Farbraums, wie HSV, YCrCb usw.

Wie der Pixel zur Pixelkarte gehört, ist das Bild die Grundeinheit des Videos. Ein Video besteht aus einer Reihe von Bildern, von denen wir jedes Bild als ein Frame bezeichnen. Und was wir normalerweise als Videoframerate bezeichnen, bedeutet, dass dieses Video pro Sekunde so viele Frame-Bilder enthält. Beispielsweise, wenn die Framerate 25 ist, spielt dieses Video 25 Bilder pro Sekunde ab.

Wenn es 1000 Millisekunden in 1 Sekunde gibt und die Framerate rate ist, dann ist das Zeitintervall zwischen den Frame-Bildern 1000/rate.

Farbenhistogramm eines Bildes

Ein Farbhistogramm ist ein Werkzeug zur Beschreibung eines Bildes. Es ähnelt einem normalen Histogramm, mit dem Unterschied, dass das Farbhistogramm aus einem bestimmten Bild berechnet werden muss.

Wenn ein Bild im RGB-Farbraum vorliegt, können wir die Anzahl der Vorkommen jeder Farbe im R-Kanal zählen. So erhalten wir ein Array von 256 Elementen (Farbwahrscheinlichkeits-Lookup-Tabelle). Teilen Sie alle Werte gleichzeitig durch die Gesamtzahl der Pixel (Breite mal Höhe) im Bild und konvertieren Sie die resultierende Sequenz in ein Histogramm. Das Ergebnis ist ein Farbhistogramm des R-Kanals. Auf ähnliche Weise können Sie die Histogramme im G-Kanal und B-Kanal erhalten.

Rückprojektion des Histogramms

Es hat sich gezeigt, dass im RGB-Farbraum das Histogramm empfindlich gegenüber Änderungen der Beleuchtung ist. Um die Auswirkungen dieser Änderung auf die Verfolgungseffekte zu reduzieren, muss das Histogramm zurückprojektiert werden. Dieser Prozess wird in drei Schritte unterteilt:

  1. Zunächst konvertieren wir das Bild vom RGB-Raum in den HSV-Raum.
  2. Dann berechnen wir das Histogramm des H-Kanals.
  3. Der Wert jedes Pixels im Bild wird durch die entsprechende Wahrscheinlichkeit in der Farbwahrscheinlichkeits-Lookup-Tabelle ersetzt, um eine Farbwahrscheinlichkeitsverteilungsabbild zu erhalten.

Dieser Prozess wird als Rückprojektion bezeichnet, und das Farbwahrscheinlichkeitsverteilungsabbild ist ein Graustufenbild.

Grundlagen von OpenCV

Wir müssen zuerst OpenCV installieren:

sudo apt update
sudo apt-get install libopencv-dev

Wir nehmen an, dass Sie bereits die grundlegende Syntax von C++ kennen. Sie wissen, dass fast jedes Programm die Headerdatei #include <iostream> und using namespace std; oder std::cout verwenden wird. OpenCV hat auch seinen eigenen Namensraum.

Um OpenCV zu verwenden, müssen wir nur die folgende Headerdatei einbinden:

#include <opencv2/opencv.hpp>

Dann:

using namespace cv;

um den OpenCV-Namensraum zu aktivieren (oder direkt den cv::-Prefix für alle APIs verwenden).

Dies ist Ihre erste Verwendung von OpenCV und Sie sind möglicherweise nicht vertraut mit den OpenCV-Schnittstellen. Daher empfehlen wir, den cv::-Prefix zu verwenden, um die OpenCV-APIs zu lernen.

Schreiben wir unser erstes Programm, um das aufgenommene Video abzuspielen:

//
// main.cpp
//
#include <opencv2/opencv.hpp> // OpenCV Head-Datei

int main() {

    // Erstellen eines Video-Capture-Objekts
    // OpenCV bietet das VideoCapture-Objekt an und
    // behandelt das Lesen eines Videos aus einer Datei genauso wie das Lesen von einer Kamera.
    // Wenn der Eingabeparameter ein Dateipfad ist, wird eine Videodatei gelesen;
    // Wenn es eine Identifikationsnummer der Kamera ist (normalerweise ist es 0),
    // wird die Kamera gelesen
    cv::VideoCapture video("video.ogv"); // Aus Datei lesen
    // cv::VideoCapture video(0);        // Aus Kamera lesen

    // Container für das gelesene Bildrahmen, Mat-Objekt in OpenCV
    // Die Schlüsselklasse in OpenCV ist Mat, was Matrix bedeutet
    // OpenCV verwendet Matrizen, um Bilder zu beschreiben
    cv::Mat frame;
    while(true) {

        // Schreiben Sie die Videodaten in frame, >> wird von OpenCV überschrieben
        video >> frame;

        // Wenn es keinen Rahmen gibt, brechen Sie die Schleife ab
        if(frame.empty()) break;

        // Visualisieren Sie den aktuellen Rahmen
        cv::imshow("test", frame);

        // Die Videoframerate ist 15, daher müssen wir 1000/15 warten, um glatt abzuspielen
        // waitKey(int delay) ist eine Wartefunktion in OpenCV
        // An dieser Stelle wird das Programm `delay` Millisekunden lang auf eine Tastatureingabe warten
        int key = cv::waitKey(1000/15);

        // Brechen Sie die Schleife ab, wenn Sie die ECS-Taste auf der Tastatur drücken
        if (key == 27) break;
    }
    // Speicher freigeben
    cv::destroyAllWindows();
    video.release();
    return 0;

}

Legen Sie diese main.cpp-Datei im selben Ordner wie video.ogv in ~/Code/camshift ab und kompilieren Sie das Programm:

g++ main.cpp `pkg-config opencv --libs --cflags opencv` -o  main

Wenn wir das Programm ausführen, können wir sehen, dass das Video abgespielt wird:

./main
image desc

Hinweis

Sie können folgende Fehlermeldung beobachten:

libdc1394 error: Failed to initialize libdc1394

Dies ist ein Bug in OpenCV und hat keinen Einfluss auf unseren Betrieb.

Wenn Sie das Problem beheben möchten, können Sie den folgenden Code vor dem Ausführen des Programms ausführen:

sudo ln /dev/null /dev/raw1394

Meanshift- und Camshift-Algorithmen

  • Meanshift
  • Camshift
  • Um das Mausrückmeldeereignis zu setzen, um das zu verfolgende Ziel auszuwählen
  • Um das Bild aus dem Videostream zu lesen
  • Um die Camshift zu implementieren

Meanshift

Die Meanshift- und Camshift-Algorithmen sind zwei klassische Algorithmen für die Objektverfolgung. Camshift basiert auf Meanshift. Ihre mathematische Interpretation ist komplex, aber die Grundidee ist relativ einfach. Also überspringen wir diese mathematischen Fakten und führen zunächst den Meanshift-Algorithmus ein.

Angenommen, es gibt eine Menge rote Punkte auf dem Bildschirm. Der blaue Kreis (Fenster) muss an die Stellen bewegt werden, an denen sich der dichteste Bereich befindet (oder an denen die Punkte am meisten sind):

image desc

Wie in dem obigen Bild gezeigt, markieren Sie den blauen Kreis als C1 und den Mittelpunkt des Kreises als C1_o. Der Schwerpunkt dieses Kreises ist jedoch C1_r, markiert als ein blauer Vollkreis.

Wenn C1_o und C1_r nicht überlappen, bewegen Sie den Kreis C1 wiederholt zum Mittelpunkt des Kreises C1_r. Schließlich wird er auf dem Kreis C2 mit der höchsten Dichte verbleiben.

Für die Bildverarbeitung verwenden wir normalerweise das zurückprojizierte Histogramm des Bildes. Wenn das zu verfolgende Ziel sich bewegt, ist es klar, dass dieser Bewegungsprozess durch das zurückprojizierte Histogramm widergespiegelt werden kann. Daher wird der Meanshift-Algorithmus schließlich unser ausgewähltes Fenster an die Position des bewegten Ziels bewegen. (Der Algorithmus hat sich am Ende als konvergent erwiesen.)

Camshift

Nach der vorherigen Beschreibung haben wir gesehen, dass der Meanshift-Algorithmus immer eine feste Fenstergröße verfolgt, was unseren Anforderungen nicht entspricht, da im Video das Zielobjekt nicht immer groß sein muss.

Deshalb wurde Camshift entwickelt, um dieses Problem zu verbessern. Dies kann auch aus der kontinuierlichen adaptiven Meanshift von Camshift gesehen werden.

Seine Grundidee ist: Zuerst wird der Meanshift-Algorithmus angewendet. Sobald die Meanshift-Ergebnisse konvergieren, aktualisiert Camshift die Fenstergröße, berechnet eine Richtungsellipse, um das Fenster zu entsprechen, und wendet dann die Ellipse als neues Fenster an, um den Meanshift-Algorithmus anzuwenden.

OpenCV bietet eine generische Schnittstelle zum Camshift-Algorithmus an:

RotatedRect CamShift(InputArray probImage, Rect& window, TermCriteria criteria)

Der erste Parameter, probImage, ist die Rückprojektion des Zielhistogramms. Der zweite Parameter, window, ist das Suchfenster des Camshift-Algorithmus. Der dritte Parameter, criteria, ist die Bedingung für das Ende (die Abbruchbedingung) des Algorithmus.

Analyse

Nachdem wir die Grundidee des Camshift-Algorithmus verstanden haben, können wir analysieren, dass die Implementierung dieses Codes hauptsächlich in mehrere Schritte unterteilt ist:

  1. Um das Mausrückmeldeereignis zu setzen, um das zu verfolgende Ziel auszuwählen.
  2. Um das Bild aus dem Videostream zu lesen.
  3. Um den Camshift-Prozess zu implementieren.

Im Folgenden modifizieren wir den Code in main.cpp weiter.

Um das zu verfolgende Objekt über die Mausrückruffunktion auszuwählen

OpenCV unterscheidet sich von OpenGL. Es werden fünf Parameter für die Mausrückmeldefunktion angegeben. Die ersten drei sind die, die wir am meisten benötigen: Durch den Wert von event können wir das Ereignis des gedrückten linken Mausknopfes (CV_EVENT_LBUTTONDOWN), das Ereignis des losgelassenen linken Mausknopfes (CV_EVENT_LBUTTONUP) usw. erhalten:

bool selectObject = false; // wird verwendet, um festzustellen, ob ein Objekt ausgewählt wurde oder nicht
int trackObject = 0;       // 1 bedeutet, dass ein Verfolgungsobjekt vorhanden ist, 0 bedeutet, dass kein Objekt vorhanden ist, und -1 bedeutet, dass die Camshift-Eigenschaft noch nicht berechnet wurde
cv::Rect selection;        // speichert die von der Maus ausgewählte Region
cv::Mat image;             // Zwischenspeicher für das Bild aus dem Video

// Mausrückmeldefunktion von OpenCV:
// void onMouse(int event, int x, int y, int flag, void *param)
// Der vierte Parameter `flag` repräsentiert zusätzlichen Zustand,
// param bedeutet Benutzerparameter, wir brauchen sie nicht, daher keine Namen.
void onMouse( int event, int x, int y, int, void* ) {
    static cv::Point origin;
    if(selectObject) {
        // Bestimmung der ausgewählten Höhe, Breite und der Position der oberen linken Ecke
        selection.x = MIN(x, origin.x);
        selection.y = MIN(y, origin.y);
        selection.width = std::abs(x - origin.x);
        selection.height = std::abs(y - origin.y);

        // & wird von cv::Rect überschrieben
        // Es bedeutet die Schnittmenge von zwei Regionen,
        // Der Hauptzweck hier ist, die Region außerhalb der ausgewählten Region zu verarbeiten
        selection &= cv::Rect(0, 0, image.cols, image.rows);
    }

    switch(event) {
            // Verarbeitung, wenn der linke Mausknopf gedrückt wird
        case CV_EVENT_LBUTTONDOWN:
            origin = cv::Point(x, y);
            selection = cv::Rect(x, y, 0, 0);
            selectObject = true;
            break;
            // Verarbeitung, wenn der linke Mausknopf losgelassen wird
        case CV_EVENT_LBUTTONUP:
            selectObject = false;
            if( selection.width > 0 && selection.height > 0 )
                trackObject = -1; // Verfolgungsobjekt hat noch keine berechnete Camshift-Eigenschaft
            break;
    }
}

Bilder aus einem Videostream lesen

Wir haben die Struktur zum Lesen eines Videostreams implementiert. Lasst uns noch detaillierter schreiben:

int main() {
    cv::VideoCapture video("video.ogv");
    cv::namedWindow("CamShift at LabEx");

    // 1. Registrieren des Mausereignisrückrufs
    cv::setMouseCallback("CamShift at LabEx", onMouse, NULL);

    cv::Mat frame;

    // 2. Bild aus dem Video lesen
    while(true) {
        video >> frame;
        if(frame.empty()) break;

        // Bild aus frame in die globale Variable image kopieren, um es zu zwischenspeichern
        frame.copyTo(image);

        // Rechteck zeichnen, wenn ein Objekt ausgewählt wird
        if( selectObject && selection.width > 0 && selection.height > 0 ) {
            cv::Mat roi(image, selection);
            bitwise_not(roi, roi);
        }
        imshow("CamShift at LabEx", image);
        int key = cv::waitKey(1000/15.0);
        if(key == 27) break;
    }
    // Zugewiesenen Speicher freigeben
    cv::destroyAllWindows();
    video.release();
    return 0;
}

Hinweis:

ROI (Region of Interest): In der Bildverarbeitung kann jede zu verarbeitende Region ein Bereich von Interesse sein, nämlich ROI.

Camshift mit OpenCV implementieren

Das zurückprojizierte Histogramm zur Berechnung des zu verfolgenden Ziels muss die cvtColor-Funktion verwenden, die das ursprüngliche Bild im RGB-Farbraum in den HSV-Farbraum umwandeln kann. Das Histogramm muss nach der Auswahl des initialen Ziels berechnet werden, daher:

int main() {
    cv::VideoCapture video("video.ogv");
    cv::namedWindow("CamShift at LabEx");
    cv::setMouseCallback("CamShift at LabEx", onMouse, NULL);

    cv::Mat frame;
    cv::Mat hsv, hue, mask, hist, backproj;
    cv::Rect trackWindow;             // Verfolgungsfenster
    int hsize = 16;                   // für das Histogramm
    float hranges[] = {0,180};        // für das Histogramm
    const float* phranges = hranges;  // für das Histogramm

    while(true) {
        video >> frame;
        if(frame.empty()) break;
        frame.copyTo(image);

        // in den HSV-Raum umwandeln
        cv::cvtColor(image, hsv, cv::COLOR_BGR2HSV);
        // Verarbeitung, wenn ein Objekt vorhanden ist
        if(trackObject) {

            // nur H: 0~180, S: 30~256, V: 10~256 verarbeiten, die anderen filtern und den restlichen Teil in mask kopieren
            cv::inRange(hsv, cv::Scalar(0, 30, 10), cv::Scalar(180, 256, 10), mask);
            // Kanal h aus hsv trennen
            int ch[] = {0, 0};
            hue.create(hsv.size(), hsv.depth());
            cv::mixChannels(&hsv, 1, &hue, 1, ch, 1);

            // Eigenschaftserfassung, wenn das Verfolgungsobjekt noch nicht berechnet wurde
            if( trackObject < 0 ) {

                // Kanal h und mask ROI einrichten
                cv::Mat roi(hue, selection), maskroi(mask, selection);
                // ROI-Histogramm berechnen
                calcHist(&roi, 1, 0, maskroi, hist, 1, &hsize, &phranges);
                // Normalisierung des Histogramms
                normalize(hist, hist, 0, 255, CV_MINMAX);

                // Verfolgungsobjekt einrichten
                trackWindow = selection;

                // markieren, dass das Verfolgungsobjekt berechnet wurde
                trackObject = 1;
            }
            // Histogramm zurückprojizieren
            calcBackProject(&hue, 1, 0, hist, backproj, &phranges);
            // gemeinsame Region abrufen
            backproj &= mask;
            // Camshift-Algorithmus aufrufen
            cv::RotatedRect trackBox = CamShift(backproj, trackWindow, cv::TermCriteria( CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 10, 1 ));
            // Verarbeitungsregion ist zu klein für das Zeichnen
            if( trackWindow.area() <= 1 ) {
                int cols = backproj.cols, rows = backproj.rows, r = (MIN(cols, rows) + 5)/6;
                trackWindow = cv::Rect(trackWindow.x - r, trackWindow.y - r,
                                       trackWindow.x + r, trackWindow.y + r) & cv::Rect(0, 0, cols, rows);
            }
            // Verfolgungsbereich zeichnen
            ellipse( image, trackBox, cv::Scalar(0,0,255), 3, CV_AA );

        }


        if( selectObject && selection.width > 0 && selection.height > 0 ) {
            cv::Mat roi(image, selection);
            bitwise_not(roi, roi);
        }
        imshow("CamShift at LabEx", image);
        int key = cv::waitKey(1000/15.0);
        if(key == 27) break;
    }
    cv::destroyAllWindows();
    video.release();
    return 0;
}

Zusammenfassung

Das Folgende zeigt alles, was wir in diesem Projekt geschrieben haben:

#include <opencv2/opencv.hpp>

bool selectObject = false; // wird verwendet, um festzustellen, ob ein Objekt ausgewählt wurde oder nicht
int trackObject = 0;       // 1 bedeutet, dass ein Verfolgungsobjekt vorhanden ist, 0 bedeutet, dass kein Objekt vorhanden ist, und -1 bedeutet, dass die Camshift-Eigenschaft noch nicht berechnet wurde
cv::Rect selection;        // speichert die von der Maus ausgewählte Region
cv::Mat image;             // Zwischenspeicher für das Bild aus dem Video

// Mausrückmeldefunktion von OpenCV:
// void onMouse(int event, int x, int y, int flag, void *param)
// Der vierte Parameter `flag` repräsentiert zusätzlichen Zustand,
// param bedeutet Benutzerparameter, wir brauchen sie nicht, daher keine Namen.
void onMouse( int event, int x, int y, int, void* ) {
    static cv::Point origin;
    if(selectObject) {
        // Bestimmung der ausgewählten Höhe, Breite und der Position der oberen linken Ecke
        selection.x = MIN(x, origin.x);
        selection.y = MIN(y, origin.y);
        selection.width = std::abs(x - origin.x);
        selection.height = std::abs(y - origin.y);

        // & wird von cv::Rect überschrieben
        // Es bedeutet die Schnittmenge von zwei Regionen,
        // Der Hauptzweck hier ist, die Region außerhalb der ausgewählten Region zu verarbeiten
        selection &= cv::Rect(0, 0, image.cols, image.rows);
    }

    switch(event) {
            // Verarbeitung, wenn der linke Mausknopf gedrückt wird
        case CV_EVENT_LBUTTONDOWN:
            origin = cv::Point(x, y);
            selection = cv::Rect(x, y, 0, 0);
            selectObject = true;
            break;
            // Verarbeitung, wenn der linke Mausknopf losgelassen wird
        case CV_EVENT_LBUTTONUP:
            selectObject = false;
            if( selection.width > 0 && selection.height > 0 )
                trackObject = -1; // Verfolgungsobjekt hat noch keine berechnete Camshift-Eigenschaft
            break;
    }
}

int main( int argc, const char** argv ) {
    cv::VideoCapture video("video.ogv");
    cv::namedWindow("CamShift at LabEx");
    cv::setMouseCallback("CamShift at LabEx", onMouse, NULL);

    cv::Mat frame, hsv, hue, mask, hist, backproj;
    cv::Rect trackWindow;             // Verfolgungsfenster
    int hsize = 16;                   // für das Histogramm
    float hranges[] = {0,180};        // für das Histogramm
    const float* phranges = hranges;  // für das Histogramm

    while(true) {
        video >> frame;
        if(frame.empty()) break;
        frame.copyTo(image);

        // in den HSV-Raum umwandeln
        cv::cvtColor(image, hsv, cv::COLOR_BGR2HSV);
        // Verarbeitung, wenn ein Objekt vorhanden ist
        if(trackObject) {

            // nur H: 0~180, S: 30~256, V: 10~256 verarbeiten, die anderen filtern und den restlichen Teil in mask kopieren
            cv::inRange(hsv, cv::Scalar(0, 30, 10), cv::Scalar(180, 256, 256), mask);
            // Kanal h aus hsv trennen
            int ch[] = {0, 0};
            hue.create(hsv.size(), hsv.depth());
            cv::mixChannels(&hsv, 1, &hue, 1, ch, 1);

            // Eigenschaftserfassung, wenn das Verfolgungsobjekt noch nicht berechnet wurde
            if( trackObject < 0 ) {

                // Kanal h und mask ROI einrichten
                cv::Mat roi(hue, selection), maskroi(mask, selection);
                // ROI-Histogramm berechnen
                calcHist(&roi, 1, 0, maskroi, hist, 1, &hsize, &phranges);
                // Normalisierung des Histogramms
                normalize(hist, hist, 0, 255, CV_MINMAX);

                // Verfolgungsobjekt einrichten
                trackWindow = selection;

                // markieren, dass das Verfolgungsobjekt berechnet wurde
                trackObject = 1;
            }
            // Histogramm zurückprojizieren
            calcBackProject(&hue, 1, 0, hist, backproj, &phranges);
            // gemeinsame Region abrufen
            backproj &= mask;
            // Camshift-Algorithmus aufrufen
            cv::RotatedRect trackBox = CamShift(backproj, trackWindow, cv::TermCriteria( CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 10, 1 ));
            // Verarbeitungsregion ist zu klein für das Zeichnen
            if( trackWindow.area() <= 1 ) {
                int cols = backproj.cols, rows = backproj.rows, r = (MIN(cols, rows) + 5)/6;
                trackWindow = cv::Rect(trackWindow.x - r, trackWindow.y - r,
                                       trackWindow.x + r, trackWindow.y + r) & cv::Rect(0, 0, cols, rows);
            }
            // Verfolgungsbereich zeichnen
            ellipse( image, trackBox, cv::Scalar(0,0,255), 3, CV_AA );

        }


        if( selectObject && selection.width > 0 && selection.height > 0 ) {
            cv::Mat roi(image, selection);
            bitwise_not(roi, roi);
        }
        imshow("CamShift at LabEx", image);
        int key = cv::waitKey(1000/15.0);
        if(key == 27) break;
    }
    cv::destroyAllWindows();
    video.release();
    return 0;
}

Lassen Sie uns main.cpp erneut kompilieren:

g++ main.cpp $(pkg-config opencv --libs --cflags opencv) -o main

und ausführen:

./main

Jetzt können wir im Programm das Objekt auswählen und die Verfolgung starten:

image desc

In dem obigen Bild haben wir Jupiter ausgewählt und das Verfolgungsfenster ist eine rote Ellipse.