PCLVisualizer

PCLVisualizer is PCL’s full-featured visualisation class. While more complex to use than the CloudViewer, it is also more powerful, offering features such as displaying normals, drawing shapes and multiple viewports.

This tutorial will use a code sample to illustrate some of the features of PCLVisualizer, beginning with displaying a single point cloud. Most of the code sample is boilerplate to set up the point clouds that will be visualised. The relevant code for each sample is contained in a function specific to that sample. The code is shown below. Copy it into a file named pcl_visualizer_demo.cpp.

  1/* \author Geoffrey Biggs */
  2
  3#include <iostream>
  4#include <thread>
  5
  6#include <pcl/common/angles.h> // for pcl::deg2rad
  7#include <pcl/features/normal_3d.h>
  8#include <pcl/io/pcd_io.h>
  9#include <pcl/visualization/pcl_visualizer.h>
 10#include <pcl/console/parse.h>
 11
 12using namespace std::chrono_literals;
 13
 14// --------------
 15// -----Help-----
 16// --------------
 17void
 18printUsage (const char* progName)
 19{
 20  std::cout << "\n\nUsage: "<<progName<<" [options]\n\n"
 21            << "Options:\n"
 22            << "-------------------------------------------\n"
 23            << "-h           this help\n"
 24            << "-s           Simple visualisation example\n"
 25            << "-r           RGB colour visualisation example\n"
 26            << "-c           Custom colour visualisation example\n"
 27            << "-n           Normals visualisation example\n"
 28            << "-a           Shapes visualisation example\n"
 29            << "-v           Viewports example\n"
 30            << "-i           Interaction Customization example\n"
 31            << "\n\n";
 32}
 33
 34
 35pcl::visualization::PCLVisualizer::Ptr simpleVis (pcl::PointCloud<pcl::PointXYZ>::ConstPtr cloud)
 36{
 37  // --------------------------------------------
 38  // -----Open 3D viewer and add point cloud-----
 39  // --------------------------------------------
 40  pcl::visualization::PCLVisualizer::Ptr viewer (new pcl::visualization::PCLVisualizer ("3D Viewer"));
 41  viewer->setBackgroundColor (0, 0, 0);
 42  viewer->addPointCloud<pcl::PointXYZ> (cloud, "sample cloud");
 43  viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "sample cloud");
 44  viewer->addCoordinateSystem (1.0);
 45  viewer->initCameraParameters ();
 46  return (viewer);
 47}
 48
 49
 50pcl::visualization::PCLVisualizer::Ptr rgbVis (pcl::PointCloud<pcl::PointXYZRGB>::ConstPtr cloud)
 51{
 52  // --------------------------------------------
 53  // -----Open 3D viewer and add point cloud-----
 54  // --------------------------------------------
 55  pcl::visualization::PCLVisualizer::Ptr viewer (new pcl::visualization::PCLVisualizer ("3D Viewer"));
 56  viewer->setBackgroundColor (0, 0, 0);
 57  pcl::visualization::PointCloudColorHandlerRGBField<pcl::PointXYZRGB> rgb(cloud);
 58  viewer->addPointCloud<pcl::PointXYZRGB> (cloud, rgb, "sample cloud");
 59  viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "sample cloud");
 60  viewer->addCoordinateSystem (1.0);
 61  viewer->initCameraParameters ();
 62  return (viewer);
 63}
 64
 65
 66pcl::visualization::PCLVisualizer::Ptr customColourVis (pcl::PointCloud<pcl::PointXYZ>::ConstPtr cloud)
 67{
 68  // --------------------------------------------
 69  // -----Open 3D viewer and add point cloud-----
 70  // --------------------------------------------
 71  pcl::visualization::PCLVisualizer::Ptr viewer (new pcl::visualization::PCLVisualizer ("3D Viewer"));
 72  viewer->setBackgroundColor (0, 0, 0);
 73  pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> single_color(cloud, 0, 255, 0);
 74  viewer->addPointCloud<pcl::PointXYZ> (cloud, single_color, "sample cloud");
 75  viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "sample cloud");
 76  viewer->addCoordinateSystem (1.0);
 77  viewer->initCameraParameters ();
 78  return (viewer);
 79}
 80
 81
 82pcl::visualization::PCLVisualizer::Ptr normalsVis (
 83    pcl::PointCloud<pcl::PointXYZRGB>::ConstPtr cloud, pcl::PointCloud<pcl::Normal>::ConstPtr normals)
 84{
 85  // --------------------------------------------------------
 86  // -----Open 3D viewer and add point cloud and normals-----
 87  // --------------------------------------------------------
 88  pcl::visualization::PCLVisualizer::Ptr viewer (new pcl::visualization::PCLVisualizer ("3D Viewer"));
 89  viewer->setBackgroundColor (0, 0, 0);
 90  pcl::visualization::PointCloudColorHandlerRGBField<pcl::PointXYZRGB> rgb(cloud);
 91  viewer->addPointCloud<pcl::PointXYZRGB> (cloud, rgb, "sample cloud");
 92  viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "sample cloud");
 93  viewer->addPointCloudNormals<pcl::PointXYZRGB, pcl::Normal> (cloud, normals, 10, 0.05, "normals");
 94  viewer->addCoordinateSystem (1.0);
 95  viewer->initCameraParameters ();
 96  return (viewer);
 97}
 98
 99
100pcl::visualization::PCLVisualizer::Ptr shapesVis (pcl::PointCloud<pcl::PointXYZRGB>::ConstPtr cloud)
101{
102  // --------------------------------------------
103  // -----Open 3D viewer and add point cloud-----
104  // --------------------------------------------
105  pcl::visualization::PCLVisualizer::Ptr viewer (new pcl::visualization::PCLVisualizer ("3D Viewer"));
106  viewer->setBackgroundColor (0, 0, 0);
107  pcl::visualization::PointCloudColorHandlerRGBField<pcl::PointXYZRGB> rgb(cloud);
108  viewer->addPointCloud<pcl::PointXYZRGB> (cloud, rgb, "sample cloud");
109  viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "sample cloud");
110  viewer->addCoordinateSystem (1.0);
111  viewer->initCameraParameters ();
112
113  //------------------------------------
114  //-----Add shapes at cloud points-----
115  //------------------------------------
116  viewer->addLine<pcl::PointXYZRGB> ((*cloud)[0],
117                                     (*cloud)[cloud->size() - 1], "line");
118  viewer->addSphere ((*cloud)[0], 0.2, 0.5, 0.5, 0.0, "sphere");
119
120  //---------------------------------------
121  //-----Add shapes at other locations-----
122  //---------------------------------------
123  pcl::ModelCoefficients coeffs;
124  coeffs.values.push_back (0.0);
125  coeffs.values.push_back (0.0);
126  coeffs.values.push_back (1.0);
127  coeffs.values.push_back (0.0);
128  viewer->addPlane (coeffs, "plane");
129  coeffs.values.clear ();
130  coeffs.values.push_back (0.3);
131  coeffs.values.push_back (0.3);
132  coeffs.values.push_back (0.0);
133  coeffs.values.push_back (0.0);
134  coeffs.values.push_back (1.0);
135  coeffs.values.push_back (0.0);
136  coeffs.values.push_back (5.0);
137  viewer->addCone (coeffs, "cone");
138
139  return (viewer);
140}
141
142
143pcl::visualization::PCLVisualizer::Ptr viewportsVis (
144    pcl::PointCloud<pcl::PointXYZRGB>::ConstPtr cloud, pcl::PointCloud<pcl::Normal>::ConstPtr normals1, pcl::PointCloud<pcl::Normal>::ConstPtr normals2)
145{
146  // --------------------------------------------------------
147  // -----Open 3D viewer and add point cloud and normals-----
148  // --------------------------------------------------------
149  pcl::visualization::PCLVisualizer::Ptr viewer (new pcl::visualization::PCLVisualizer ("3D Viewer"));
150  viewer->initCameraParameters ();
151
152  int v1(0);
153  viewer->createViewPort(0.0, 0.0, 0.5, 1.0, v1);
154  viewer->setBackgroundColor (0, 0, 0, v1);
155  viewer->addText("Radius: 0.01", 10, 10, "v1 text", v1);
156  pcl::visualization::PointCloudColorHandlerRGBField<pcl::PointXYZRGB> rgb(cloud);
157  viewer->addPointCloud<pcl::PointXYZRGB> (cloud, rgb, "sample cloud1", v1);
158
159  int v2(0);
160  viewer->createViewPort(0.5, 0.0, 1.0, 1.0, v2);
161  viewer->setBackgroundColor (0.3, 0.3, 0.3, v2);
162  viewer->addText("Radius: 0.1", 10, 10, "v2 text", v2);
163  pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZRGB> single_color(cloud, 0, 255, 0);
164  viewer->addPointCloud<pcl::PointXYZRGB> (cloud, single_color, "sample cloud2", v2);
165
166  viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "sample cloud1");
167  viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "sample cloud2");
168  viewer->addCoordinateSystem (1.0);
169
170  viewer->addPointCloudNormals<pcl::PointXYZRGB, pcl::Normal> (cloud, normals1, 10, 0.05, "normals1", v1);
171  viewer->addPointCloudNormals<pcl::PointXYZRGB, pcl::Normal> (cloud, normals2, 10, 0.05, "normals2", v2);
172
173  return (viewer);
174}
175
176
177unsigned int text_id = 0;
178void keyboardEventOccurred (const pcl::visualization::KeyboardEvent &event,
179                            void* viewer_void)
180{
181  pcl::visualization::PCLVisualizer *viewer = static_cast<pcl::visualization::PCLVisualizer *> (viewer_void);
182  if (event.getKeySym () == "r" && event.keyDown ())
183  {
184    std::cout << "r was pressed => removing all text" << std::endl;
185
186    char str[512];
187    for (unsigned int i = 0; i < text_id; ++i)
188    {
189      sprintf (str, "text#%03d", i);
190      viewer->removeShape (str);
191    }
192    text_id = 0;
193  }
194}
195
196void mouseEventOccurred (const pcl::visualization::MouseEvent &event,
197                         void* viewer_void)
198{
199  pcl::visualization::PCLVisualizer *viewer = static_cast<pcl::visualization::PCLVisualizer *> (viewer_void);
200  if (event.getButton () == pcl::visualization::MouseEvent::LeftButton &&
201      event.getType () == pcl::visualization::MouseEvent::MouseButtonRelease)
202  {
203    std::cout << "Left mouse button released at position (" << event.getX () << ", " << event.getY () << ")" << std::endl;
204
205    char str[512];
206    sprintf (str, "text#%03d", text_id ++);
207    viewer->addText ("clicked here", event.getX (), event.getY (), str);
208  }
209}
210
211pcl::visualization::PCLVisualizer::Ptr interactionCustomizationVis ()
212{
213  pcl::visualization::PCLVisualizer::Ptr viewer (new pcl::visualization::PCLVisualizer ("3D Viewer"));
214  viewer->setBackgroundColor (0, 0, 0);
215  viewer->addCoordinateSystem (1.0);
216
217  viewer->registerKeyboardCallback (keyboardEventOccurred, (void*)viewer.get ());
218  viewer->registerMouseCallback (mouseEventOccurred, (void*)viewer.get ());
219
220  return (viewer);
221}
222
223
224// --------------
225// -----Main-----
226// --------------
227int
228main (int argc, char** argv)
229{
230  // --------------------------------------
231  // -----Parse Command Line Arguments-----
232  // --------------------------------------
233  if (pcl::console::find_argument (argc, argv, "-h") >= 0)
234  {
235    printUsage (argv[0]);
236    return 0;
237  }
238  bool simple(false), rgb(false), custom_c(false), normals(false),
239    shapes(false), viewports(false), interaction_customization(false);
240  if (pcl::console::find_argument (argc, argv, "-s") >= 0)
241  {
242    simple = true;
243    std::cout << "Simple visualisation example\n";
244  }
245  else if (pcl::console::find_argument (argc, argv, "-c") >= 0)
246  {
247    custom_c = true;
248    std::cout << "Custom colour visualisation example\n";
249  }
250  else if (pcl::console::find_argument (argc, argv, "-r") >= 0)
251  {
252    rgb = true;
253    std::cout << "RGB colour visualisation example\n";
254  }
255  else if (pcl::console::find_argument (argc, argv, "-n") >= 0)
256  {
257    normals = true;
258    std::cout << "Normals visualisation example\n";
259  }
260  else if (pcl::console::find_argument (argc, argv, "-a") >= 0)
261  {
262    shapes = true;
263    std::cout << "Shapes visualisation example\n";
264  }
265  else if (pcl::console::find_argument (argc, argv, "-v") >= 0)
266  {
267    viewports = true;
268    std::cout << "Viewports example\n";
269  }
270  else if (pcl::console::find_argument (argc, argv, "-i") >= 0)
271  {
272    interaction_customization = true;
273    std::cout << "Interaction Customization example\n";
274  }
275  else
276  {
277    printUsage (argv[0]);
278    return 0;
279  }
280
281  // ------------------------------------
282  // -----Create example point cloud-----
283  // ------------------------------------
284  pcl::PointCloud<pcl::PointXYZ>::Ptr basic_cloud_ptr (new pcl::PointCloud<pcl::PointXYZ>);
285  pcl::PointCloud<pcl::PointXYZRGB>::Ptr point_cloud_ptr (new pcl::PointCloud<pcl::PointXYZRGB>);
286  std::cout << "Generating example point clouds.\n\n";
287  // We're going to make an ellipse extruded along the z-axis. The colour for
288  // the XYZRGB cloud will gradually go from red to green to blue.
289  std::uint8_t r(255), g(15), b(15);
290  for (float z(-1.0); z <= 1.0; z += 0.05)
291  {
292    for (float angle(0.0); angle <= 360.0; angle += 5.0)
293    {
294      pcl::PointXYZ basic_point;
295      basic_point.x = 0.5 * std::cos (pcl::deg2rad(angle));
296      basic_point.y = sinf (pcl::deg2rad(angle));
297      basic_point.z = z;
298      basic_cloud_ptr->points.push_back(basic_point);
299
300      pcl::PointXYZRGB point;
301      point.x = basic_point.x;
302      point.y = basic_point.y;
303      point.z = basic_point.z;
304      point.r = r;
305      point.g = g;
306      point.b = b;
307      point_cloud_ptr->points.push_back (point);
308    }
309    if (z < 0.0)
310    {
311      r -= 12;
312      g += 12;
313    }
314    else
315    {
316      g -= 12;
317      b += 12;
318    }
319  }
320  basic_cloud_ptr->width = basic_cloud_ptr->size ();
321  basic_cloud_ptr->height = 1;
322  point_cloud_ptr->width = point_cloud_ptr->size ();
323  point_cloud_ptr->height = 1;
324
325  // ----------------------------------------------------------------
326  // -----Calculate surface normals with a search radius of 0.05-----
327  // ----------------------------------------------------------------
328  pcl::NormalEstimation<pcl::PointXYZRGB, pcl::Normal> ne;
329  ne.setInputCloud (point_cloud_ptr);
330  pcl::search::KdTree<pcl::PointXYZRGB>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZRGB> ());
331  ne.setSearchMethod (tree);
332  pcl::PointCloud<pcl::Normal>::Ptr cloud_normals1 (new pcl::PointCloud<pcl::Normal>);
333  ne.setRadiusSearch (0.05);
334  ne.compute (*cloud_normals1);
335
336  // ---------------------------------------------------------------
337  // -----Calculate surface normals with a search radius of 0.1-----
338  // ---------------------------------------------------------------
339  pcl::PointCloud<pcl::Normal>::Ptr cloud_normals2 (new pcl::PointCloud<pcl::Normal>);
340  ne.setRadiusSearch (0.1);
341  ne.compute (*cloud_normals2);
342
343  pcl::visualization::PCLVisualizer::Ptr viewer;
344  if (simple)
345  {
346    viewer = simpleVis(basic_cloud_ptr);
347  }
348  else if (rgb)
349  {
350    viewer = rgbVis(point_cloud_ptr);
351  }
352  else if (custom_c)
353  {
354    viewer = customColourVis(basic_cloud_ptr);
355  }
356  else if (normals)
357  {
358    viewer = normalsVis(point_cloud_ptr, cloud_normals2);
359  }
360  else if (shapes)
361  {
362    viewer = shapesVis(point_cloud_ptr);
363  }
364  else if (viewports)
365  {
366    viewer = viewportsVis(point_cloud_ptr, cloud_normals1, cloud_normals2);
367  }
368  else if (interaction_customization)
369  {
370    viewer = interactionCustomizationVis();
371  }
372
373  //--------------------
374  // -----Main loop-----
375  //--------------------
376  while (!viewer->wasStopped ())
377  {
378    viewer->spinOnce (100);
379    std::this_thread::sleep_for(100ms);
380  }
381}

Compiling and running the program

Create a CMakeLists.txt file with the following contents:

 1cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
 2
 3project(pcl_visualizer_viewports)
 4
 5find_package(PCL 1.2 REQUIRED)
 6
 7include_directories(${PCL_INCLUDE_DIRS})
 8link_directories(${PCL_LIBRARY_DIRS})
 9add_definitions(${PCL_DEFINITIONS})
10
11add_executable (pcl_visualizer_demo pcl_visualizer_demo.cpp)
12target_link_libraries (pcl_visualizer_demo ${PCL_LIBRARIES})

After you have made the executable, you can run it like so:

$ ./pcl_visualizer_demo -h

Change the option to change which demo is executed. See the help output for details.

To exit the viewer application, press q. Press r to centre and zoom the viewer so that the entire cloud is visible. Use the mouse to rotate the viewpoint by clicking and dragging. You can use the scroll wheel, or right-click and drag up and down, to zoom in and out. Middle-clicking and dragging will move the camera.

Visualising a single cloud

This sample uses PCLVisualizer to display a single PointXYZ cloud. It also illustrates changing the background colour and displaying the axes. The code is in the function simpleVis.

_images/pcl_visualizer_simple.png

Explanation

The simpleVis function shows how to perform the most basic visualisation of a point cloud. Let’s take a look at the function, line-by-line.

...
pcl::visualization::PCLVisualizer::Ptr viewer (new pcl::visualization::PCLVisualizer ("3D Viewer"));
...

This creates the viewer object, giving it a nice name to display in the title bar. We are storing it in a smart pointer only so it can be passed around the demo program. Usually, you do not need to do this.

...
viewer->setBackgroundColor (0, 0, 0);
...

The background colour of the viewer can be set to any RGB colour you like. In this case, we are setting it to black.

...
viewer->addPointCloud<pcl::PointXYZ> (cloud, "sample cloud");
...

This is the most important line. We add the point cloud to the viewer, giving it an ID string that can be used to identify the cloud in other methods. Multiple point clouds can be added with multiple calls to addPointCloud(), supplying a new ID each time. If you want to update a point cloud that is already displayed, you must first call removePointCloud() and provide the ID of the cloud that is to be updated. (Note: versions 1.1 and up of PCL provide a new API method, updatePointCloud(), that allows a cloud to be updated without manually calling removePointCloud() first.)

This is the most basic of addPointCloud()’s many variations. Others are used to handle different point types, display normals, and so on. We will illustrate some others during this tutorial, or you can see the PCLVisualizer documentation for more details.

...
viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "sample cloud");
...

This next line changes the size of the rendered points. You can control the way any point cloud is rendered in the viewer using this method.

...
viewer->addCoordinateSystem (1.0);
...

Viewing complex point clouds can often be disorientating. To keep yourself aligned in the world, axes can be displayed. These will appear as three cylinders along the X (red), Y (green) and Z (blue) axes. The size of the cylinders can be controlled using the scale parameter. In this case, we have set it to 1.0 (which also happens to be the default if no value is given). An alternative version of this method can be used to place the axes at any point in the world.

...
viewer->initCameraParameters ();
...

This final call sets up some handy camera parameters to make things look nice.

There is one final piece of code relevant to all the samples. It can be found at the bottom of the sample:

...
while (!viewer->wasStopped ())
{
  viewer->spinOnce (100);
  std::this_thread::sleep_for(100ms);
}
...

These lines are running an event loop. Each call to spinOnce gives the viewer time to process events, allowing it to be interactive. There is also a spin method, which only needs to be called once.

Adding some colour

Often, a point cloud will not use the simple PointXYZ type. One common point type is PointXYZRGB, which also contains colour data. Aside from that, you may wish to colour specific point clouds to make them distinguishable in the viewer. PCLVizualizer provides facilities for displaying point clouds with the colour data stored within them, or for assigning colours to point clouds.

RGB point clouds

Many devices, such as the Microsoft Kinect, produce point clouds with RGB data. PCLVisualizer can display the cloud using this data to colour each point. The code in the rgbVis function shows how to do this.

_images/pcl_visualizer_color_rgb.png

Explanation

Not much of the code in this sample has changed from the earlier sample.

...
pcl::visualization::PCLVisualizer::Ptr rgbVis (pcl::PointCloud<pcl::PointXYZRGB>::ConstPtr cloud)
...

First, notice that the point type has changed from the simple example. We now use a point type that also provides room for RGB data. This is important; without the RGB fields in the point (the point type does not necessarily have to be PointXYZRGB, as long as it has the three colour fields), PCLVisualizer would not know what colours to use.

...
pcl::visualization::PointCloudColorHandlerRGB<pcl::PointXYZRGB> rgb(point_cloud_ptr);
...

Next, after setting the viewer’s background colour, we create a colour handler object. PCLVisualizer uses objects like this to display custom data. In this case, the object will get the RGB colour fields from each point for the viewer to use when drawing them. Many other handlers exist for a wide range of purposes. We will see another of the colour handlers in the next code sample, but handlers also exist for such purposes as drawing any other field as the colour and drawing geometry from point clouds. See the documentation for details.

...
viewer->addPointCloud<pcl::PointXYZRGB> (cloud, rgb, "sample cloud");
...

Finally, when we add the point cloud, we specify the colour handler when we add the point cloud to the viewer.

Custom colours

The second code sample demonstrates giving a point cloud a single colour. We can use this technique to give specific point clouds their own colours, allowing us to distinguish individual point clouds. In this sample, given in the customColourVis function, we have set the point cloud’s colour to green. (We have also increased the size of the points to make the colour more visible.)

_images/pcl_visualizer_color_custom.png

Explanation

Again, not much of the code in this sample has changed from the earlier sample.

...
pcl::visualization::PCLVisualizer::Ptr customColourVis (pcl::PointCloud<pcl::PointXYZ>::ConstPtr cloud)
...

The point type in use this time is back to PointXYZ again. When setting a custom colour handler for a point cloud, it doesn’t matter what the underlying data type is. None of the point fields are used for the colour with the custom colour handler.

...
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> single_color (cloud, 0, 255, 0);
...

We create a custom colour handler and assign it a nice, bright shade of green.

...
viewer->addPointCloud<pcl::PointXYZ> (cloud, single_color, "sample cloud");
...

As with the previous example, we pass the colour handler in when we call addPointCloud<>().

Normals and other information

Displaying normals is an important step in understanding a point cloud. The PCLVisualizer class has the ability to draw normals, as well as other interesting point cloud information, such as principal curvatures and geometries.

The code sample in the normalsVis function shows how to display the normals of a point cloud. The code for calculating the normals will not be explained in this tutorial. See the normals calculation tutorial for details.

_images/pcl_visualizer_normals.png

Explanation

The relevant line of code is placed after the line to draw the point cloud.

...
viewer->addPointCloudNormals<pcl::PointXYZRGB, pcl::Normal> (cloud, normals, 10, 0.05, "normals");
...

Once you have your normals, one extra line is all it takes to display them in the viewer. The parameters to this method set the number of normals to display (here, every tenth normal is displayed) and the length of the line to draw for each normal (0.05, in this case).

Drawing Shapes

PCLVisualizer allows you to draw various primitive shapes in the view. This is often used to visualise the results of point cloud processing algorithms, for example, visualising which clusters of points have been recognised as landmarks by drawing transparent spheres around them.

The sample code in the shapesVis function illustrates some of the methods used to add shapes to a viewer. It adds four shapes:

  • A line from the first point in the cloud to the last point in the cloud.

  • A plane at the origin.

  • A sphere centred on the first point in the cloud.

  • A cone along the Y-axis.

_images/pcl_visualizer_shapes.png

Explanation

The relevant parts of the code sample for drawing shapes begin after the point cloud is added to the viewer.

...
viewer->addLine<pcl::PointXYZRGB> ((*cloud)[0], (*cloud)[cloud->size() - 1], "line");
...

This line (of code) adds a line (in space) from the first point in the cloud to the last point. This method is useful, for example, for showing correspondences between point clouds. In this case, the line is using the default colour, but you can also specify the colour of the line. Drawing shapes at points from a point cloud is very common, and various shapes are available.

...
viewer->addSphere ((*cloud)[0], 0.2, 0.5, 0.5, 0.0, "sphere");
...

This next line adds a sphere centred on the first point in the cloud with a radius of 0.2. It also gives the sphere a colour.

...
pcl::ModelCoefficients coeffs;
coeffs.values.push_back(0.0);
coeffs.values.push_back(0.0);
coeffs.values.push_back(1.0);
coeffs.values.push_back(0.0);
viewer->addPlane (coeffs, "plane");
...

Next, we add a plane to the drawing. In this case, we are specifying the plane using the standard plane equation (ax + by + cz + d = 0). Our plane will be centered at the origin and oriented along the Z-axis. Many of the shape drawing functions take coefficients in this way.

...
coeffs.values.clear();
coeffs.values.push_back(0.3);
coeffs.values.push_back(0.3);
coeffs.values.push_back(0.0);
coeffs.values.push_back(0.0);
coeffs.values.push_back(1.0);
coeffs.values.push_back(0.0);
coeffs.values.push_back(5.0);
viewer->addCone (coeffs, "cone");
...

Finally, we add a cone. We are again using model coefficients to specify the cone’s parameters.

Multiple viewports

You will often want to compare multiple point clouds side-by-side. While you could draw them in the same view port, this can get confusing. PCLVisualizer allows you to draw multiple point clouds in separate viewports, making comparison easy.

The code in the viewportsVis function uses viewports to demonstrate comparing the normals calculated for a point cloud. Two sets of normals are calculated for the same cloud but using a different search radius. The first time, the search radius is 0.05. The second time, it is 0.1. The normals for the 0.05 radius search are displayed in the viewport with the black background. The normals for the 0.1 radius search are displayed in the viewport with the grey background.

Comparing the two sets of normals side-by-side makes it immediately obvious what the effects of the different algorithm parameter are. In this way, you can experiment with the parameters for algorithms to find good settings, quickly viewing the results.

_images/pcl_visualizer_viewports.png

Explanation

...
pcl::visualization::PCLVisualizer::Ptr viewer (new pcl::visualization::PCLVisualizer ("3D Viewer"));
viewer->initCameraParameters ();
...

This is our standard code for creating a viewer.

...
int v1(0);
viewer->createViewPort (0.0, 0.0, 0.5, 1.0, v1);
viewer->setBackgroundColor (0, 0, 0, v1);
viewer->addText ("Radius: 0.01", 10, 10, "v1 text", v1);
pcl::visualization::PointCloudColorHandlerRGBField<pcl::PointXYZRGB> rgb (cloud);
viewer->addPointCloud<pcl::PointXYZRGB> (cloud, rgb, "sample cloud1", v1);
...

The next step is to create a new viewport. The four parameters are the minimum and maximum ranges of the viewport on the X- and Y-axes, between 0 and 1. We are creating a viewport that will fill the left half of the window. We must store the view port ID number that is passed back in the fifth parameter and use it in all other calls where we only want to affect that viewport.

We also set the background colour of this viewport, give it a label based on what we are using the viewport to distinguish, and add our point cloud to it, using an RGB colour handler.

...
int v2(0);
viewer->createViewPort (0.5, 0.0, 1.0, 1.0, v2);
viewer->setBackgroundColor (0.3, 0.3, 0.3, v2);
viewer->addText ("Radius: 0.1", 10, 10, "v2 text", v2);
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZRGB> single_color (cloud, 0, 255, 0);
viewer->addPointCloud<pcl::PointXYZRGB> (cloud, single_color, "sample cloud2", v2);
...

Then we do the same thing again for the second viewport, making it take up the right half of the window. We make this viewport a shade of grey so it is easily distinguishable in the demonstration program. We add the same point cloud, but this time we give it a custom colour handler.

...
viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "sample cloud1");
viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "sample cloud2");
viewer->addCoordinateSystem (1.0);
...

These three lines set some properties globally for all viewports. Most of the PCLVisualizer methods accept an optional viewport ID parameter. When it is specified, they affect only that viewport. When it is not, as in this case, they affect all viewports.

...
viewer->addPointCloudNormals<pcl::PointXYZRGB, pcl::Normal> (cloud, normals1, 10, 0.05, "normals1", v1);
viewer->addPointCloudNormals<pcl::PointXYZRGB, pcl::Normal> (cloud, normals2, 10, 0.05, "normals2", v2);
...

Finally, we add the normals, one to each viewport.

Interaction Customization

You will sometimes feel that the interactivity options offered by the default mouse and key bindings do not satisfy your needs and you may want to extend functionality with features such as the possibility of saving the currently shown point clouds when pressing a button or annotating certain locations on the rendering window with your mouse etc. A very simple example of such things is found in the interactionCustomizationVis method.

Note

In Mac platforms and if using a VTK version prior to 7.0, the executable is required to be built as an Application Bundle, in order to have proper mouse and keyboard interaction support. For more instructions on how to do that, please consult the Cocoa VTK Wiki.

In this part of the tutorial you will be shown how to catch mouse and keyboard events. By right clicking on the window, a 2D text will appear and you can erase all the text instances by pressing ‘r’. The result should look something like this:

_images/pcl_visualizer_interaction_customization.png

Explanation

...
pcl::visualization::PCLVisualizer::Ptr viewer (new pcl::visualization::PCLVisualizer ("3D Viewer"));
viewer->initCameraParameters ();
...

This is the standard code for instantiating a viewer.

...
viewer->registerKeyboardCallback (keyboardEventOccurred, (void*)&viewer);
viewer->registerMouseCallback (mouseEventOccurred, (void*)&viewer);
...

These two lines of code will register the two methods, keyboardEventOccurred and mouseEventOccurred to the keyboard and mouse event callback, respectively. The second arguments for the two method calls are the so-called cookies. These are any parameters you might want to pass to the callback function. In our case, we want to pass the viewer itself, in order to do modifications on it in case of user interaction. Note that these arguments must be in the form of a single void* instance, so we need to cast the pointer to our smart pointer to void*.

...
void mouseEventOccurred (const pcl::visualization::MouseEvent &event,
                     void* viewer_void)
{
  pcl::visualization::PCLVisualizer::Ptr viewer = *static_cast<pcl::visualization::PCLVisualizer::Ptr *> (viewer_void);
  if (event.getButton () == pcl::visualization::MouseEvent::LeftButton && event.getType () == pcl::visualization::MouseEvent::MouseButtonRelease)
  {
    std::cout << "Left mouse button released at position (" << event.getX () << ", " << event.getY () << ")" << std::endl;
    char str[512];

    sprintf (str, "text#%03d", text_id ++);
    viewer->addText ("clicked here", event.getX (), event.getY (), str);
  }
}
...

This is the method that handles the mouse events. Every time any kind of mouse event is registered, this function will be called. In order to see exactly what that event is, we need to extract that information from the event instance. In our case, we are looking for left mouse button releases. Whenever such an event happens, we shall write a small text at the position of the mouse click.

...
void keyboardEventOccurred (const pcl::visualization::KeyboardEvent &event,
                        void* viewer_void)
{
  pcl::visualization::PCLVisualizer::Ptr viewer = *static_cast<pcl::visualization::PCLVisualizer::Ptr *> (viewer_void);
  if (event.getKeySym () == "r" && event.keyDown ())
  {
    std::cout << "r was pressed => removing all text" << std::endl;

    char str[512];
    for (unsigned int i = 0; i < text_id; ++i)
    {
      sprintf (str, "text#%03d", i);
      viewer->removeShape (str);
    }
    text_id = 0;
  }
}
...

The same approach applies for the keyboard events. We check what key was pressed and the action we do is to remove all the text created by our mouse clicks. Please note that when ‘r’ is pressed, the 3D camera still resets, as per the original binding of ‘r’ inside PCLVisualizer. So, our keyboard events do not overwrite the functionality of the base class.