Liebe Leserinnen, liebe Leser,

wie es in dem letzten Beitrag 2018 erwähnt wurde, ich habe mich vor Kurzem in die OpenCV Bibliothek eingeführt, und während dieser Zeit habe ich (natürlich) die folgende Funktion begegnet: cv::namedWindow(). Wie der Name und die Dokumentation es vermuten lässt, “erstellt sie ein Fenster, das als ein Platzhalter für Bilder und Trackbar verwendet werden kann”. Eine selbsterklärende Nutzung ist in den folgende Codeausschnitt zu sehen:

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main() {
    cv::Mat img = cv::imread("./Img.png", cv::IMREAD_COLOR);
    
    if (img.empty()) {
        std::cerr << "Couldn't load image" << std::endl;
        return -1;
    }
    
    cv::namedWindow("Image Window");
    cv::imshow("Image Window", img);
    
    cv::Mat img2 = doInterestingStuff(img);
    
    // already open window will be used to display img2
    cv::imshow("Image Window", img2);
    cv::waitKey(0);
}

Dieser Code tut, was drauf steht: Es zeigt ein Bild in einem Fenster namens “Image Window” an. In diesem einfachen Beispiel könnte der Aufruf der namedWindow Funktion vermieden werden, weil imshow auch ein Fenster erstellt, das das Bild anzeigt. Warum soll das dann benutzt werden? Der Zweck der namedWindow Funktion ist die Nutztung des Fensters als die einzige Ausgang für alle Bilder, weil der nachfolgende Aufruf von imshow ohne namedWindow zu mehrere Fenster führen wird, auch wenn der Name dieser Fenster ist gleich:

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int main() {
    
    cv::Mat img = cv::imread("./Img.png", cv::IMREAD_COLOR);
    
    if (img.empty() || img2.empty()) {
        std::cerr << "Couldn't load an image" << std::endl;
        return -1;
    }
    
    cv::imshow("Image Window", img);
    
    cv::Mat img2 = doInterestingStuff(img);
    
    // a second window will be opened here
    cv::imshow("Image window", img2);
    cv::waitKey(0);
    
    return 0;
}

Die Nutzbarkeit von namedWindow ist bei diesem Punkt selbsterklärend, aber der nicht so angekündigte Teil ist wie Scoping funktioniert. Was wäre, wenn die doInterestingStuff Funktion mit einem Bild aufgerufen wird, ohne etwas zurückzugeben, und dann sollte die Ergebnis im Fenster dennoch gezeigt werden. Wenn man die Definition von namedWindow unterscuht, wird man das sofort erkennen, dass diese Funktion keinen Rückgabewert hat, und deswegen kann das ergebene Fenster an der doInterestingStuff Funktion nicht weitergegeben werden:

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// none of it works
void doInterestingStuff(cv::Mat img, ??? window)
{
    // ...
    // display processed image
}

int main() {
    
    // ...
    
    cv::namedWindow("Image Window");
    doInterestingStuff(img, window);
    
    // ...
}

Die Lösung ist ganz eifach: Gib nichts nirgendwo weiter. Anscheinend, namedWindow erstellt ein global vorhandenes Fenster, und die einzige zu machende Sache ist der Aufruf von imshow mit dem gleichen Namen wie namedWindow:

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void doInterestingStuff(cv::Mat img)
{
    // ...
    cv::imshow("Image Window", img);
}

int main() {
    
    // ...
    
    cv::namedWindow("Image Window");
    doInterestingStuff(img);
    
    // ...
}

Man sollte dennoch mit dem Aufruf von namedWindow vorsichtig sein, weil es ungeachtet des Aufrufortes andauert, auch wenn der Codeteil schnon den Scope verlässt:

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void doInterestingStuff(cv::Mat img)
{
    cv::namedWindow("Image Window");
    // ...
    cv::imshow("Image Window", img);
}

int main() {
    
    cv::Mat img = cv::imread("./Img.png", cv::IMREAD_COLOR);
    cv::Mat img2 = cv::imread("./Img2.png", cv::IMREAD_COLOR);
    
    if (img.empty() || img2.empty()) {
        std::cerr << "Couldn't load an image" << std::endl;
        return -1;
    }
    
    doInterestingStuff(img2);
    
    // img will be shown instead of img2 in the window here. No new window is opened.
    cv::imshow("Image Window", img);
    cv::waitKey(0);
    
    return 0;
}

Einfach gesagt bedeutet das, dass alle durch namedWindow erzeugtes Fenster verfolgen sollen, genauso wie ein durch den new Operator erzeugtes Objekt. Man könnte an dieser Funktion denken, wie es ein Äquivalent von new für OpenCV Fenster, wo das Gegenstück cv::destroyWindow (oder cv::destroyAllWindows) ist, genauso wie delete ist für andere Objekte. Das hat ernste Auswirkungen, weil Speicherlecks (besonders bei Videodateien) garantiert sind, falls man nicht genug vorsichtig ist.

Die Lektion des Tages ist:

cv::namedWindow sollte verwendet werden, falls ein einziges Fenster für alle Darstellungen genug ist. Man sollte dennoch mit der Speicherverwaltung vorsichtig sein, wenn man kompliziertere Programme hat, weil die Fenster, die auf diese Weise erstellt wurden, nicht automatisch zerstört werden.

Wie immer, vielen Dank fürs Lesen.