OpenCV (Open Source Computer Vision) is a library of programming functions mainly aimed at real-time computer vision, developed by Intel’s research center in Nizhny Novgorod, Russia. The library is cross-platform and focuses mainly on real-time image processing.
OpenCV is widely used and adopted all around the world. For end users, it’s very mature and powerful; for developers, it’s well implemented and well designed. The OpenCV developers used very basic principles, which makes it simple to understand and maintain.
Let’s discover some of OpenCV’s design choices:
Modularity
1- Library-based architecture
A library-based architecture makes the reuse and integration of the provided functionality more flexible and easier to integrate into other projects. In addition, the library-based architecture encourages clean APIs and separation of concerns, making it easier for developers to understand, since they only have to understand small pieces of the big picture.
OpenCV adopts this approach and defines many libraries; each one has a specific responsibility, and all of them use the opencv_core library.

2- Modularize by namespaces
OpenCV uses namespaces widely to modularize its code base; here are, for example, the namespaces of the opencv_core project:

OpenCV uses the “Namespace-by-feature” approach. Namespace-by-feature uses namespaces to reflect the feature set. It places all items related to a single feature (and only that feature) into a single namespace. This results in namespaces with high cohesion and high modularity, and with minimal coupling between namespaces. Items that work closely together are placed next to each other.
In the case of OpenCV, namespaces are used for three main reasons:
- Modularize the libraries.
- Hide details, like for the “cv::detail” namespace; this approach is very interesting if we want to inform the library user that they don’t need to use the types inside this namespace directly and that it’s only for internal use. In C# the “internal” keyword does the job, but in C++ there’s no way to hide public types from the library user.
- Anonymous namespace: a namespace with no name. It avoids the need for global static variables. The anonymous namespace you create is only accessible within the file you created it in.
Define the data model as POD types
Every project has its data model, and it’s recommended to define this data as POD types.
Let’s search the OpenCV code base for structs with no methods and only fields. For that, CQLinq will be used to query the code base.

The result of this query concerns 25% of the types defined in the OpenCV projects. OpenCV defines almost all of its data model as structs with only fields.
Avoid multiple inheritance
Multiple inheritance can complicate the design, and debuggers can have a hard time with it; therefore, it’s not recommended by many C++ experts.
Let’s search for classes that inherit from more than one concrete base class in the OpenCV code base.

Only a few classes from test projects use multiple inheritance; this concept is avoided throughout the entire OpenCV code base.
Avoid defining complex functions
Many metrics exist to detect complex functions; NBLinesOfCode, the number of parameters and the number of local variables are the basic ones.
There are other interesting metrics to detect complex functions:
- Cyclomatic complexity is a popular procedural software metric equal to the number of decisions that can be taken in a procedure.
- Nesting Depth is a metric defined on methods, relative to the maximum depth of the most nested scope in a method body.
- Max Nested Loops equals the maximum level of loop nesting in a function.
The maximum value tolerated for these metrics depends mostly on the team’s choices; there are no standard values.
Let’s search for methods that could be considered complex in the OpenCV code base.

Only 1% are candidates for refactoring to minimize their complexity.
Coupling
Low coupling is desirable because a change in one area of an application will require fewer changes throughout the entire application. In the long run, this could save a lot of time, effort, and cost associated with modifying and adding new features to an application.
Low coupling can be achieved by using abstract classes. Here are three key benefits of using them:
- An abstract class provides a way to define a contract that promotes reuse. If an object implements an abstract class, then that object has to conform to a standard. An object that uses another object is called a consumer. An abstract class is a contract between an object and its consumer.
- An abstract class also provides a level of abstraction that makes programs easier to understand. An abstract class allows developers to start talking about the general way that code behaves without having to get into a lot of detailed specifics.
- An abstract class enforces low coupling between components, which makes it easy to protect the abstract class consumer from any implementation changes in the classes implementing the abstract class.
Let’s search for all abstract classes defined by OpenCV:

If our primary goal is to enforce low coupling, there’s a common mistake when using abstract classes that could defeat the purpose of using them: using the concrete classes instead of the abstract ones. To better explain this problem, let’s take the following example:
Class A implements the abstract class IA, which contains the calculate() method; the consumer class C is implemented like this:
public class C
{
….
public:
void calculate()
{
…..
m_a->calculate();
….
}
A* m_a;
};Class C, instead of referencing the abstract class IA, references class A; in this case, we lose the low coupling benefit. This implementation has two major drawbacks:
- If we decide to use another implementation of IA, we must change the code of class C.
- If some methods are added to A that don’t exist in IA, and C uses them, we also lose the contract benefit of using interfaces.
C# introduced the explicit interface implementation capability to the language to ensure that a method from IA will never be called from a reference to a concrete class, but only from a reference to the interface. This technique is very useful to protect developers from losing the benefit of using interfaces.
Cohesion
The single responsibility principle states that a class should not have more than one reason to change. Such a class is said to be cohesive. A high LCOM value generally pinpoints a poorly cohesive class. There are several LCOM metrics. The LCOM takes its values in the range [0-1]. The LCOM HS (HS stands for Henderson-Sellers) takes its values in the range [0-2]. An LCOM HS value higher than 1 should be considered alarming. Here is how to compute the LCOM metrics:
LCOM = 1 – (sum(MF)/M*F) LCOM HS = (M – sum(MF)/F)(M-1)
Where:
- M is the number of methods in class (both static and instance methods are counted, it includes also constructors, properties getters/setters, events add/remove methods).
- F is the number of instance fields in the class.
- MF is the number of methods of the class accessing a particular instance field.
- Sum(MF) is the sum of MF overall instance fields of the class.
The underlying idea behind these formulas can be stated as follows: a class is utterly cohesive if all its methods use all its instance fields, which means that sum(MF)=M*F, and then LCOM = 0 and LCOMHS = 0.
An LCOMHS value higher than 1 should be considered alarming.

Only a few types are not cohesive.
Conclusion
If you take a look at the OpenCV source code, you will be surprised by the simplicity of its implementation: no advanced design concepts are used, no over-engineering — just some basic principles applied.
