Create a PCL visualizer in Qt with cmake
In this tutorial we will learn how to create a PCL + Qt project, we will use Cmake rather than Qmake.The program we are going to write is a simple PCL visualizer which allow to change a randomly generated point cloud color.
Contents
The project
For this project Qt is of course mandatory, make sure it is installed and PCL deals with it. qmake is a tool that helps simplify the build process for development project across different platforms, we will use cmake instead because most projects in PCL uses cmake and it is simpler in my opinion.
This is how I organized the project: the build folder contains all built files and the src folder holds all sources files
.
├── build
└── src
├── CMakeLists.txt
├── main.cpp
├── pclviewer.cpp
├── pclviewer.h
├── pclviewer.ui
└── pcl_visualizer.pro
If you want to change this layout you will have to do minor modifications in the code, especially line 2 of pclviewer.cpp
Create the folder tree and download the sources files from github.
Note
File paths should not contain any special character or the compilation might fail with a moc: Cannot open options file specified with @
error message.
Qt configuration
- First we will take a look at how Qt is configured to build this project. Simply open
pcl_visualizer.pro
with Qt (or double click on the file) and go to the Projects tab
In this example note that I deleted the Debug configuration and only kept the Release config. Use relative paths like this is better than absolute paths; this project should work wherever it has been put.
We specify in the general section that we want to build in the folder ../build
(this is a relative path from the .pro
file).
The first step of the building is to call cmake
(from the build
folder) with argument ../src
; this is gonna create all files in the
build
folder without modifying anything in the src
folder; thus keeping it clean.
Then we just have to compile our program; the argument -j2
allow to specify how many thread of your CPU you want to use for compilation. The more thread you use
the faster the compilation will be (especially on big projects); but if you take all threads from the CPU your OS will likely be unresponsive during
the compilation process.
See compiler optimizations for more information.
If you don’t want to use Qt Creator but Eclipse instead; see using PCL with Eclipse.
User interface (UI)
The point of using Qt for your projects is that you can easily build cross-platform UIs. The UI is held in the .ui
file
You can open it with a text editor or with Qt Creator, in this example the UI is very simple and it consists of :
QMainWindow, QWidget: the window (frame) of your application
qvtkWidget: The VTK widget which holds the PCLVisualizer
QLabel: Display text on the user interface
QSlider: A slider to choose a value (here; an integer value)
QLCDNumber: A digital display, 8 segment styled
If you click on Edit Signals/Slots at the top of the Qt window you will see the relationships between some of the UI objects. In our example the sliderMoved(int) signal is connected to the display(int) slot; this means that everytime we move the slider the digital display is updated accordingly to the slider value.
The code
Now, let’s break down the code piece by piece.
main.cpp
#include "pclviewer.h"
#include <QApplication>
#include <QMainWindow>
int main (int argc, char *argv[])
{
QApplication a (argc, argv);
PCLViewer w;
w.show ();
return a.exec ();
}
PCLViewer
object called w is instantiated and it’s method show()
is called.pclviewer.h
#pragma once
#include <iostream>
// Qt
#include <QMainWindow>
// Point Cloud Library
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl/visualization/pcl_visualizer.h>
typedef pcl::PointXYZRGBA PointT;
typedef pcl::PointCloud<PointT> PointCloudT;
This file is the header for the class PCLViewer; we include QMainWindow
because this class contains UI elements, we include the PCL headers we will
be using and the VTK header for the qvtkWidget
. We also define typedefs of the point types and point clouds, this improves readabily.
namespace Ui
{
class PCLViewer;
}
We declare the namespace Ui
and the class PCLViewer inside it.
class PCLViewer : public QMainWindow
{
Q_OBJECT
This is the definition of the PCLViewer class; the macro Q_OBJECT
tells the compiler that this object contains UI elements;
this imply that this file will be processed through the Meta-Object Compiler (moc).
public:
explicit PCLViewer (QWidget *parent = 0);
~PCLViewer ();
The constructor and destructor of the PCLViewer class.
public Q_SLOTS:
void
randomButtonPressed ();
void
RGBsliderReleased ();
void
pSliderValueChanged (int value);
void
redSliderValueChanged (int value);
void
greenSliderValueChanged (int value);
void
blueSliderValueChanged (int value);
These are the public slots; these functions will be linked with UI elements actions.
protected:
void
refreshView();
pcl::visualization::PCLVisualizer::Ptr viewer;
PointCloudT::Ptr cloud;
unsigned int red;
unsigned int green;
unsigned int blue;
red
, green
, blue
will help us store the value of the sliders.pclviewer.cpp
#include "pclviewer.h"
#include "ui_pclviewer.h"
#if VTK_MAJOR_VERSION > 8
#include <vtkGenericOpenGLRenderWindow.h>
#endif
PCLViewer::PCLViewer (QWidget *parent) :
QMainWindow (parent),
ui (new Ui::PCLViewer)
{
ui->setupUi (this);
this->setWindowTitle ("PCL viewer");
// Setup the cloud pointer
cloud.reset (new PointCloudT);
// The number of points in the cloud
cloud->resize (200);
We include the class header and the header for the UI object; note that this file is generated by the moc and it’s path depend on where you call cmake !
After that is the constructor implementation; we setup the ui and the window title name. | Then we initialize the cloud pointer member of the class at a newly allocated point cloud pointer. | The cloud is resized to be able to hold 200 points.
ui (new Ui::PCLViewer)
{
ui->setupUi (this);
this->setWindowTitle ("PCL viewer");
// Setup the cloud pointer
cloud.reset (new PointCloudT);
// The number of points in the cloud
cloud->resize (200);
// The default color
red = 128;
green = 128;
blue = 128;
// Fill the cloud with some points
for (auto& point: *cloud)
{
point.x = 1024 * rand () / (RAND_MAX + 1.0f);
point.y = 1024 * rand () / (RAND_MAX + 1.0f);
point.z = 1024 * rand () / (RAND_MAX + 1.0f);
point.r = red;
point.g = green;
point.b = blue;
}
red
green
and blue
protected members are initialized to their default values.red
green
and blue
colors. // Set up the QVTK window
#if VTK_MAJOR_VERSION > 8
auto renderer = vtkSmartPointer<vtkRenderer>::New();
auto renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
renderWindow->AddRenderer(renderer);
viewer.reset(new pcl::visualization::PCLVisualizer(renderer, renderWindow, "viewer", false));
ui->qvtkWidget->setRenderWindow(viewer->getRenderWindow());
viewer->setupInteractor(ui->qvtkWidget->interactor(), ui->qvtkWidget->renderWindow());
#else
viewer.reset(new pcl::visualization::PCLVisualizer("viewer", false));
ui->qvtkWidget->SetRenderWindow(viewer->getRenderWindow());
viewer->setupInteractor(ui->qvtkWidget->GetInteractor(), ui->qvtkWidget->GetRenderWindow());
#endif
viewer
and we also specify that we don’t want an interactor to be created.qvtkWidget
is already an interactor and it’s the one we want to use.qvtkWidget
. // Connect "random" button and the function
connect (ui->pushButton_random, SIGNAL (clicked ()), this, SLOT (randomButtonPressed ()));
// Connect R,G,B sliders and their functions
connect (ui->horizontalSlider_R, SIGNAL (valueChanged (int)), this, SLOT (redSliderValueChanged (int)));
connect (ui->horizontalSlider_G, SIGNAL (valueChanged (int)), this, SLOT (greenSliderValueChanged (int)));
connect (ui->horizontalSlider_B, SIGNAL (valueChanged (int)), this, SLOT (blueSliderValueChanged (int)));
connect (ui->horizontalSlider_R, SIGNAL (sliderReleased ()), this, SLOT (RGBsliderReleased ()));
connect (ui->horizontalSlider_G, SIGNAL (sliderReleased ()), this, SLOT (RGBsliderReleased ()));
connect (ui->horizontalSlider_B, SIGNAL (sliderReleased ()), this, SLOT (RGBsliderReleased ()));
// Connect point size slider
connect (ui->horizontalSlider_p, SIGNAL (valueChanged (int)), this, SLOT (pSliderValueChanged (int)));
- Here we connect slots and signals, this links UI actions to functions. Here is a summary of what we have linked :
pushButton_random
:- if button is pressed call
randomButtonPressed ()
horizontalSlider_R
:- if slider value is changed call
redSliderValueChanged(int)
with the new value as argumentif slider is released callRGBsliderReleased()
horizontalSlider_G
:- if slider value is changed call
redSliderValueChanged(int)
with the new value as argumentif slider is released callRGBsliderReleased()
horizontalSlider_B
:- if slider value is changed call
redSliderValueChanged(int)
with the new value as argumentif slider is released callRGBsliderReleased()
viewer->addPointCloud (cloud, "cloud");
pSliderValueChanged (2);
viewer->resetCamera ();
refreshView();
pSliderValueChanged
to change the point size to 2.We finally reset the camera within the PCL Visualizer not avoid the user having to zoom out and refresh the view to be sure the modifications will be displayed.
void
PCLViewer::randomButtonPressed ()
{
printf ("Random button was pressed\n");
// Set the new color
for (auto& point: *cloud)
{
point.r = 255 *(1024 * rand () / (RAND_MAX + 1.0f));
point.g = 255 *(1024 * rand () / (RAND_MAX + 1.0f));
point.b = 255 *(1024 * rand () / (RAND_MAX + 1.0f));
}
viewer->updatePointCloud (cloud, "cloud");
refreshView();
}
for
loop iterates through the point cloud and changes point cloud color to a random number (between 0 and 255).qtvtkwidget
is.void
PCLViewer::RGBsliderReleased ()
{
// Set the new color
for (auto& point: *cloud)
{
point.r = red;
point.g = green;
point.b = blue;
}
viewer->updatePointCloud (cloud, "cloud");
refreshView();
}
for
loop iterates through the point cloud and changes point cloud color to red
, green
and blue
member values.qtvtkwidget
is.void
PCLViewer::redSliderValueChanged (int value)
{
red = value;
printf ("redSliderValueChanged: [%d|%d|%d]\n", red, green, blue);
}
void
PCLViewer::greenSliderValueChanged (int value)
{
green = value;
printf ("greenSliderValueChanged: [%d|%d|%d]\n", red, green, blue);
}
void
PCLViewer::blueSliderValueChanged (int value)
{
blue = value;
printf("blueSliderValueChanged: [%d|%d|%d]\n", red, green, blue);
}
PCLViewer::~PCLViewer ()
{
delete ui;
}
The destructor.
Compiling and running
- There are two options here :
You have configured the Qt project and you can compile/run just by clicking on the bottom left “Play” button.
You didn’t configure the Qt project; just go to the build folder an run
cmake ../src && make -j2 && ./pcl_visualizer
sliderReleased ()
slot).If you wanted to update the point cloud when the slider value is changed you could just call the RGBsliderReleased ()
function inside the
*sliderValueChanged (int)
functions. The connect between sliderReleased ()
/ RGBsliderReleased ()
would become useless then.
More on Qt and PCL
If you want to know more about Qt and PCL go take a look at PCL apps like PCD video player or manual registration.
Re-use the CMakeLists.txt
from this tutorial if you want to compile the application outside of PCL.