Getting Started with CVB++, QML, CMake

A quick tutorial for getting started with CVB, QML and CMake. What will you learn from this tutorial? After completing this tutorial you will be able to load and display a camera stream in an application with a simple UI, using C++, QML and CMake.

Before we start with this tutorial make sure that you properly installed:

  • the C++ Desktop Development package of Visual Studio
  • the C++ Microsoft Foundation Classes in Visual Studio
  • the Qt Framework
  • CMake

If you have never heared of Qt or CMake I strongly suggest reading the online CVB Manual, which includes a detailed instruction on how to install CMake and Qt.

Now we can finally create our first ever CMake Project. Go to Visual Studio and select C++ as language, now you have to search for the CMake Project template and create a new one.

After creating a cmake project we can see an out folder, a cmake list, a cpp file and a header file. In this tutorial we don’t need the header file, thus we delete it.

Screenshot 1 - Creating a cmake project

CMake List

Lets start out with the CMake list, CMake uses these scripts to generate build files for a specific enviroment. As you can see visual studio already added a minimum required version, the project with the name and an add_executable. This add_executable is needed to be able to run or debug the program.

cmake_minimum_required (VERSION 3.8)

project ("Tutorial")

add_executable (Tutorial "Tutorial.cpp")

In there you can see the project name and the cpp file, that was already created, written between quotation marks there. To be able to run your program properly you have to make sure that, whenever you add a new cpp file or a header file, you write the name of the cpp or header file in the add_executable. This should happen automatically when creating a new file, but when deleting a cpp or header it can cause trouble, because its not getting removed automatically.

You can also add WIN32 between the project name and the c++ files, this will hide the console and gives the finished application a more polished look. But while developing it can be very useful, because the console shows helpfull error messages.

Summary
cmake_minimum_required (VERSION 3.8)

project ("Tutorial")

add_executable (Tutorial WIN32 "Tutorial.cpp")

Now we have to do three things:

  1. Find CVB, so we can use it in our project
file(TO_CMAKE_PATH "$ENV{CVB}/cmake" CVB_MODULE_PATH)
set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" "${CVB_MODULE_PATH}")
find_package(CVB REQUIRED COMPONENTS CvbQuick)
  1. Find Qt, so we can design our UI with Qml
find_package(Qt5Qml REQUIRED)
find_package(Qt5Quick REQUIRED)
  1. Link the wanted libraries so we make them usable in our code
target_link_libraries(${PROJECT_NAME}
  CVB::CvbDriver
  CVB::CvbUI
  Qt5::Qml
  Qt5::Quick
  )

That already wraps up our CMake list. Your CMake list should now look like this:

cmake_minimum_required (VERSION 3.8)

project ("Tutorial")

add_executable (Tutorial WIN32 "Tutorial.cpp")

file(TO_CMAKE_PATH "$ENV{CVB}/cmake" CVB_MODULE_PATH)
set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" "${CVB_MODULE_PATH}")
find_package(CVB REQUIRED COMPONENTS CvbQuick)

find_package(Qt5OpenGL REQUIRED)
find_package(Qt5Quick REQUIRED)

target_link_libraries(${PROJECT_NAME}
  CVB::CvbDriver
  CVB::CvbUI
  Qt5::Qml
  Qt5::Quick
  )

QML Design

We continue with the UI design. Right click your project folder on the top right and add a new file. Let´s name this file mainUI.qml, as you can see the icon of the file change and its now a qml file.
Before we can acutally design our UI we need to import two things, QtQuick and CvbQuick.

import QtQuick 2.3
import CvbQuick 1.0 as CvbQuick

Now we can design our UI without any problems, but don´t worry our UI will be very simple. For our UI we only need a CvbQuick ImageView, so lets create it. We start out by giving the ImageView an id, in this case I named it imageView.
After that we name the image property mainImage, this is the displayed image, we will later bind to this image from the c++ code.
Then the anchors.fill is set to parent, this means this control is set to the full size of its parent, which will be our main window. So this image view will be displayed full screen inside the application.
Last but not least we set the uploadmode to CvbQuick.UploadMode.Image, the image mode copies the complete image data, which also includes unvisible parts of the image. This can be very helpful in other, more complex projects.

At the end your UI should look like this:

import QtQuick 2.3
import CvbQuick 1.0 as CvbQuick

CvbQuick.ImageView
{
   id: imageView
   image : mainImage
   anchors.fill: parent
   uploadMode : CvbQuick.UploadMode.Image
}

C++ Code

Now we come to the actual code behind our project. We start out by adding the argument variables argc and argv to the main method:

int main(int argc, char* argv[])

Then we create a QApplication inside the main method, here we use are arguments, which we created in the step before, also don´t forget to include QApplication above your main method.

#include <QApplication>
QApplication app(argc, argv);

We then register the qml components for an image display, also don´t forget to include the image_view_item header:

#include <cvb/ui/image_view_item.hpp>
Cvb::UI::ImageViewItem::Register();

We then create a QQuickView object, that’s the window where our stream will be shown. We also have to include QQuickView, same procedure as every year :smile: :

#include <QQuickView>
QQuickView view;

After we did that lets set the resize mode of our view to SizeRootObjectToView, this allows, contrary to SizeViewToRootObject, a smooth animation when resizing the window with the cursor:

view.setResizeMode(QQuickView::SizeRootObjectToView);

We now manually load the ui, qml file by setting the source to the path, where our qml file is located (in my case C:/Users/m.glaser/source/repos/Tutorial/mainUI.qml). I don’t recommend doing that generally but in this case its ok, for simplicity reasons. A better way is to use a relative path:

view.setSource(QUrl::fromLocalFile("C:/Users/m.glaser/source/repos/Tutorial/mainUI.qml"));

After that we create a variable context and equal it to our view root context, this is the context of our view. Don’t forget to include qmlcontext:

#include <QQmlContext>
auto context = view.rootContext();

We continue with creating a controller, this is used to be able to communicate with our ui:

Cvb::UI::ImageController imageController;

We now set the context property to our previously named mainImage and the image controller:

context->setContextProperty("mainImage", &imageController);

Now the context is set but the image controller still does nothing and no picture is currently shown. Before an image is displayed, we need to get a device and a stream.
We get the device by creating a variable device, we then open the Cvb Device Factory and search for the cvb installpath and open the mock.vin, with the help of the CVB LIT macro. We also set the acquisition stack to vin and don´t forget to include the device factory :

#include <cvb/device_factory.hpp>
auto device = Cvb::DeviceFactory::Open(Cvb::InstallPath() + CVB_LIT("drivers/CVMock.vin"), Cvb::AcquisitionStack::Vin);

This example is just for the mock.vin driver but you can load any camera here.
Now we bind the image controller to the device image by calling the refresh method, in there we set the image to the deviceimage and turn the autorefresh mode on:

imageController.Refresh(device->DeviceImage(), Cvb::UI::AutoRefresh::On);

The image is now connected to the ui, but the stream is still not started, meaning we still won’t see an image when starting the application.

Now we create a variable for the stream and set it to the device stream. Using the Stream() method without a number in the brackets automatically gets the first stream of the device:

auto stream = device->Stream();

Now the only thing that’s left is running the acquisition. There are two ways, you can either do it with the stream->start() and stream ->wait() method or with a stream handler. In this case I am using a single stream handler, because we only got one stream. When you got multiple, synchronized streams you should use a multiple stream handler, but be careful if the streams/cameras are not perfectly synchronized you can loose images.
This single stream handler is created and used inside the stream handler guard, where we also set the autorun to yes, meaning we are constantly acquiring images.
The stream handler guard safely stops our stream and acquisition when closing the application. Also make sure that you include the single stream handler header.

#include <cvb/async/single_stream_handler.hpp>
Cvb::StreamHandlerGuard guard(Cvb::SingleStreamHandler::Create(stream), Cvb::AutoRun::Yes);

Last but not least we resize our view, show it, and execute our application by returning app.exec():

view.resize(640, 480);
view.show();
return app.exec();

At the end your main class should look something like this:

#include <QApplication>
#include <QQuickView>
#include <QQmlContext>

#include <cvb/ui/image_view_item.hpp>
#include <cvb/device_factory.hpp>
#include <cvb/async/single_stream_handler.hpp>
int main(int argc, char* argv[])
{              
                //include QApplication
		QApplication app(argc, argv);

                //include cvb image view item
		Cvb::UI::ImageViewItem::Register();

		QQuickView view;
		view.setResizeMode(QQuickView::SizeRootObjectToView);

		//load qml file according to your UI file path
	       view.setSource(QUrl::fromLocalFile("C:/Users/m.glaser/source/repos/Tutorial/mainUI.qml"));

		//include qqml context
		auto context = view.rootContext();

		Cvb::UI::ImageController imageController;
		context->setContextProperty("mainImage", &imageController);

		//open device, mock.vin in our case
		//include device factory
		auto device = Cvb::DeviceFactory::Open(Cvb::InstallPath() + CVB_LIT("drivers/CVMock.vin"), Cvb::AcquisitionStack::Vin);

		//refresh the image display through the controller object
		//stream still not started
		imageController.Refresh(device->DeviceImage(), Cvb::UI::AutoRefresh::On);

		// get the first stream of the device
		// or use device->Stream(index) to get another stream
		auto stream = device->Stream();


		// There are two ways to run the acquisition:
		// - Either via stream->Start() and stream->Wait() in a separate thread
		// - or via SingleStreamHandler (Run() / Finish())
		//include single stream handler
		Cvb::StreamHandlerGuard guard(Cvb::SingleStreamHandler::Create(stream), Cvb::AutoRun::Yes);

		view.resize(640, 480);
		view.show();
		return app.exec();
}

That already wraps up this tutorial :smile: . I hope you found this little tutorial helpful.

4 Likes

Note to the CMakeList:

I made a mistake there, you need to include Qt5Widgets not OpenGL, sorry for that :sweat_smile: .
This results in the CmakeList looking like this:

cmake_minimum_required (VERSION 3.8)

project ("Tutorial")

add_executable (Tutorial WIN32 "Tutorial.cpp")

file(TO_CMAKE_PATH "$ENV{CVB}/cmake" CVB_MODULE_PATH)
set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" "${CVB_MODULE_PATH}")
find_package(CVB REQUIRED COMPONENTS CvbQuick)

find_package(Qt5Widgets REQUIRED)
find_package(Qt5Quick REQUIRED)

target_link_libraries(${PROJECT_NAME}
  CVB::CvbDriver
  CVB::CvbUI
  Qt5::Qml
  Qt5::Quick
  Qt5::Widgets
  )

Another note to the main function:

If you initialize the context property after you set the source of the view it results in an error message like this: “ReferenceError: mainImage is not defined”.
Even though this error occures the application should work as intended.

To solve this error just set the context property before you set the source of the view:

//include qqml context
	auto context = view.rootContext();

	Cvb::UI::ImageController imageController;
	context->setContextProperty("mainImage", &imageController);

	//load qml file according to your UI file path
	view.setSource(QUrl::fromLocalFile("C:/Users/m.glaser/source/repos/Tutorial/mainUI.qml"));	

Sorry if you experienced some inconveniences and have fun coding :smile: .

2 Likes