OpenCV (Open Source Computer Vision) ist eine Bibliothek mit Programmierfunktionen, die vor allem für Computer Vision in Echtzeit entwickelt wurde. Sie entstand im Intel-Forschungszentrum im russischen Nischni Nowgorod. Die Bibliothek ist plattformübergreifend und konzentriert sich hauptsächlich auf die Echtzeit-Bildverarbeitung.
OpenCV wird weltweit intensiv eingesetzt. Für Anwender ist die Bibliothek ausgereift und leistungsfähig; aus Entwicklersicht ist sie sauber implementiert und gut konzipiert. Die OpenCV-Entwickler folgen grundlegenden Prinzipien, die den Code leicht verständlich und wartbar machen.
Sehen wir uns einige Designentscheidungen von OpenCV genauer an:
Modularität
1 – Bibliotheksbasierte Architektur
Eine bibliotheksbasierte Architektur erleichtert die flexible Wiederverwendung und Integration bereitgestellter Funktionen in andere Projekte. Außerdem fördert sie saubere APIs und eine klare Trennung von Verantwortlichkeiten. Entwickler können sich dadurch auf kleine, überschaubare Teile des Gesamtsystems konzentrieren.
OpenCV verfolgt diesen Ansatz mit mehreren Bibliotheken, die jeweils eine klar definierte Aufgabe haben und alle die Bibliothek opencv_core verwenden.

2 – Modularisierung durch Namespaces
OpenCV nutzt Namespaces intensiv, um seine Codebasis zu modularisieren. Hier sind beispielsweise die Namespaces des Projekts opencv_core:

OpenCV verwendet den Ansatz „Namespace by Feature“. Dabei bilden Namespaces die jeweiligen Funktionsbereiche ab: Alle Elemente, die zu einer bestimmten Funktion gehören – und nur diese – werden in einem gemeinsamen Namespace zusammengefasst. Das führt zu hoher Kohäsion und Modularität bei geringer Kopplung zwischen den Namespaces. Eng zusammenarbeitende Elemente liegen unmittelbar beieinander.
In OpenCV werden Namespaces hauptsächlich aus drei Gründen eingesetzt:
- Zur Modularisierung der Bibliotheken.
- Zum Verbergen von Implementierungsdetails, etwa mit dem Namespace „cv::detail“. Dadurch wird Bibliotheksnutzern signalisiert, dass die darin enthaltenen Typen für den internen Gebrauch bestimmt sind und nicht direkt verwendet werden sollten. In C# übernimmt das Schlüsselwort „internal“ diese Aufgabe; in C++ gibt es dagegen keine Möglichkeit, öffentliche Typen vor Bibliotheksnutzern zu verbergen.
- Anonymer Namespace: ein Namespace ohne Namen. Dadurch werden globale statische Variablen vermieden. Ein anonymer Namespace ist ausschließlich innerhalb der Datei zugänglich, in der er definiert wurde.
Das Datenmodell als POD-Typen definieren
Jedes Projekt besitzt ein Datenmodell. Es empfiehlt sich, diese Daten als POD Typen zu definieren.
Suchen wir in der OpenCV-Codebasis nach Structs ohne Methoden, die ausschließlich Felder enthalten. Dafür verwenden wir CQLinq zur Abfrage der Codebasis.

Die Ergebnisse dieser Abfrage umfassen 25 % der in den OpenCV-Projekten definierten Typen. OpenCV bildet nahezu sein gesamtes Datenmodell durch Structs ab, die ausschließlich Felder enthalten.
Mehrfachvererbung vermeiden
Mehrfachvererbung kann ein Design unnötig komplizieren und das Debugging erschweren. Viele C++-Experten empfehlen daher, darauf zu verzichten.
Suchen wir in der OpenCV-Codebasis nach Klassen, die von mehr als einer konkreten Basisklasse erben.

Nur wenige Klassen aus Testprojekten verwenden Mehrfachvererbung; in der eigentlichen OpenCV-Codebasis wird dieses Konzept praktisch vollständig vermieden.
Komplexe Funktionen vermeiden
Zur Erkennung komplexer Funktionen können zahlreiche Metriken verwendet werden. NBLinesOfCode, die Anzahl der Parameter und die Anzahl lokaler Variablen gehören zu den grundlegendsten.
Darüber hinaus gibt es weitere interessante Metriken:
- Die zyklomatische Komplexität ist eine verbreitete prozedurale Softwaremetrik und spiegelt die Anzahl der möglichen Entscheidungen innerhalb einer Prozedur wider.
- Nesting Depth beschreibt die maximale Tiefe verschachtelter Gültigkeitsbereiche innerhalb eines Methodenkörpers.
- Max Nested Loops entspricht der maximalen Verschachtelungstiefe von Schleifen innerhalb einer Funktion.
Welche Maximalwerte für diese Metriken toleriert werden, hängt vor allem vom jeweiligen Team ab; allgemein gültige Standardwerte gibt es nicht.
Suchen wir nach Methoden in der OpenCV-Codebasis, die als komplex gelten könnten.

Nur 1 % kommen für ein Refactoring zur Verringerung ihrer Komplexität infrage.
Kopplung
Eine geringe Kopplung ist wünschenswert, weil Änderungen in einem Bereich einer Anwendung dadurch weniger Anpassungen im restlichen System erfordern. Langfristig lassen sich so Zeit, Aufwand und Kosten bei Änderungen und neuen Funktionen deutlich reduzieren.
Eine geringe Kopplung lässt sich unter anderem durch abstrakte Klassen erreichen. Drei wesentliche Vorteile sind:
- Eine abstrakte Klasse definiert einen Vertrag und fördert damit die Wiederverwendbarkeit. Implementiert ein Objekt eine abstrakte Klasse, muss es deren Vorgaben erfüllen. Ein Objekt, das ein anderes Objekt verwendet, wird als Consumer bezeichnet. Die abstrakte Klasse bildet den Vertrag zwischen Objekt und Consumer.
- Eine abstrakte Klasse schafft außerdem eine Abstraktionsebene, die Programme leichter verständlich macht. Entwickler können über das allgemeine Verhalten des Codes nachdenken, ohne sich mit Implementierungsdetails befassen zu müssen.
- Eine abstrakte Klasse fördert eine geringe Kopplung zwischen Komponenten und schützt ihren Consumer damit vor Implementierungsänderungen in den konkreten Klassen.
Suchen wir nach allen abstrakten Klassen, die OpenCV definiert:

Wenn unser Hauptziel eine geringe Kopplung ist, gibt es bei abstrakten Klassen einen häufigen Fehler, der ihren Nutzen zunichtemachen kann: die Verwendung konkreter statt abstrakter Klassen. Betrachten wir dazu folgendes Beispiel:
Klasse A implementiert die abstrakte Klasse IA, die die Methode calculate() enthält; die Consumer-Klasse C ist wie folgt implementiert:
public class C
{
….
public:
void calculate()
{
…..
m_a->calculate();
….
}
A* m_a;
};Statt auf die abstrakte Klasse IA verweist Klasse C direkt auf Klasse A. Dadurch geht der Vorteil der geringen Kopplung verloren. Diese Implementierung hat zwei wesentliche Nachteile:
- Wenn wir eine andere Implementierung von IA verwenden möchten, müssen wir den Code von Klasse C ändern.
- Werden A Methoden hinzugefügt, die in IA nicht vorhanden sind, und C verwendet diese, verlieren wir ebenfalls den Vorteil des durch das Interface definierten Vertrags.
C# führte die explizite Interface-Implementierung ein, um sicherzustellen, dass eine Methode aus IA niemals über eine Referenz auf eine konkrete Klasse, sondern ausschließlich über eine Interface-Referenz aufgerufen wird. Diese Technik verhindert wirksam, dass die Vorteile von Interfaces versehentlich verloren gehen.
Kohäsion
Das Single-Responsibility-Prinzip besagt, dass eine Klasse nicht mehr als einen Grund für Änderungen haben sollte. Eine solche Klasse gilt als kohäsiv. Ein hoher LCOM-Wert weist im Allgemeinen auf geringe Kohäsion hin. Es gibt mehrere LCOM-Metriken: LCOM liegt im Bereich [0–1], LCOM HS (Henderson-Sellers) im Bereich [0–2]. Ein LCOM-HS-Wert über 1 sollte als Warnsignal betrachtet werden. Die Metriken werden wie folgt berechnet:
LCOM = 1 – (sum(MF)/M*F) LCOM HS = (M – sum(MF)/F)(M-1)
Dabei gilt:
- M ist die Anzahl der Methoden der Klasse (statische und Instanzmethoden; dazu zählen auch Konstruktoren, Property-Getter/-Setter sowie Add-/Remove-Methoden von Events).
- F ist die Anzahl der Instanzfelder der Klasse.
- MF ist die Anzahl der Methoden der Klasse, die auf ein bestimmtes Instanzfeld zugreifen.
- Sum(MF) ist die Summe von MF über alle Instanzfelder der Klasse.
Die Idee hinter diesen Formeln lautet: Eine Klasse ist vollständig kohäsiv, wenn alle ihre Methoden sämtliche Instanzfelder verwenden. Dann gilt sum(MF)=M*F und damit LCOM = 0 sowie LCOMHS = 0.
Ein LCOMHS-Wert über 1 sollte als Warnsignal betrachtet werden.

Nur wenige Typen weisen eine geringe Kohäsion auf.
Fazit
Ein Blick in den OpenCV-Quellcode überrascht durch die Einfachheit seiner Implementierung: keine übermäßig komplexen Designkonzepte und kein Over-Engineering, sondern lediglich einige grundlegende Prinzipien, die konsequent angewendet werden.
