C++, OpenCV and Gtk are a nice triplet to build applications that run on a Raspberry PI, taking images from the camera, process them, display them and have an unlimited user interface. In this example I'm showing some design patterns that I find useful when implementing a computer vision application with a graphic user interface. In a prior example gtk-opencv-simple, I show a much simpler way to display images captured with the camera in a full screen window. I hope these two examples can help you through the boring problems you need to solve before reaching the place where you can have fun.
I'm using the following technical stack:
- Raspberry Pi - The ultimate goal is to launch the application on it.
- CMake - This is the needed ingredient to make your code buildable on most of the platforms. It also exports your project to Xcode, VisualStudio, Eclipse and a long list of others, so you will be able to choose your preferred IDE.
- Mac OS X, Ubuntu, Windows - Where you can write and test your application.
- C++ - Python is fashionable, young, efficient and well supported by Raspberry folks. But I happen to like C++ more. If Python is your thing, then stop reading.
- OpenCV - One very widely used open source (hence the Open) computer vision (hence the CV) library.
- Gtkmm, which is the C++ oriented port of Gtk - Although OpenCV lets you display images on screen, it is somewhat limited when interacting with the user. Being easily compatible, Gtk / Gtkmm are a great complement to OpenCV for building real user interfaces around computer vision applications.
- Unit testing - This is the single most efficient strategy if you want to code fast and happy. To put it in practice requires you to design your application so it can be tested, in particular loose coupling.
- Event Bus - This is great for separated processes producing images or process data to communicate between themselves without unhealthy coupling.
- Inversion of Control - Another great strategy to make loosely coupled components to cooperate.
- Singleton - The most basic pattern of them all, useful to hold services or any single-state component.
- Service Locator - This strategy allows any element of your application to be able to access any singleton.
- Conditional modules (via cmake) - Conditional modules help to deal with the sad fact that Windows does not follow Unix standards.
In the Wiki articles I'm explaining the code quite in detail. To help the exposition order, I split the code into multiple steps:
master
branch contains the final code, all steps together.Stepxx
branches contain the progressive evolution of the code.
An important objective of this project is to be able to build and test it in your preferred desktop computer or notebook. Raspberry Pi is meant to be an embedded system platform, and it is an amazing one. Still, it lacks the right keyboard, mouse, monitor or amount of memory required to be a comfortable as a development tool.
The project is configured via cmake which makes it compatible with XCode, Code::Build, gcc and other development tools, and this is what you need to do from either a Mac OS X Terminal, a Linux Terminal or a MinGW Terminal:
cd ~/to/your/working/folder
git clone https://github.com/raspberry-cpp-tutorials/gtk-opencv-patterns.git
cd gtk-opencv-patterns.git
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Debug ../src
ctest --output-on-failure
To run the application:
./app/rascapp
There are more detailed instructions to install a working development environment in the three major operative systems:
- Installing Mac OS X development environment
- Installing Windows development environment
- Installing Linux development environment
Last but not least:
There are several great articles that discuss about the best folder structure for a project built with CMake:
- https://arne-mertz.de/2018/06/cmake-project-structure/: I like this one because it discusses how to integrate those header-only libraries, and uses Catch as an example.
- https://rix0r.nl/blog/2015/08/13/cmake-guide/: I like this one because it shows in detail how to configure CMake and why, and also acknowledges the difference between library (which is easily unit tested) and application (which is the user interface, and not very easy to unit test).
The project has two modules:
- The application, containing the
main
routine, plus the Gtk widgets composing the user interface. The code of the application is not unit tested, therefore it should not contain complex routines, but merely call the library. - The library, containing all complex elements. The library contains the complex image processing routines, which are unit tested using Catch.
This is the project's folder structure:
.gitignore <-- Ignore xcode, codeb, build folders.
/src <-- All sources are here
CMakeList.txt <--- The top CMake configuration file.
/app <----- Contains the executable application.
/cpp <-------- C++ source files for the application.
/hpp <-------- C++ header files for the application.
/res <-------- Resource files for the application.
/lib <----- Contains the library.
/cpp <-------- C++ source files for the appl.
/hpp <-------- C++ header files for the application.
/tst <-------- Unit test for the library classes.
/res <-------- Resources for unit tests.
/build <-- Contains the temporary build files.
Not under version control
/xcode <-- Contains the XCode project.
Not under version control.
/codeb <-- Contains the Code::Blocks project.
Not under version control.