总结Effective C++条款

最近重读了《Effective C++》,对一些复杂的知识点会专门写文章分析,这里将一些小的知识点总结一下,但不会涵盖书里的所有内容。

条款2

  • 良好的用户自定义类型的特征是它尽量能与内置类型兼容

  • 宁可用编译器替换预处理器

  • 尽量多用const,而不是宏定义.宏属于模块化的设计概念,会破坏封装性

  • 常量可能比#define产生较小量的代码.

  • 如果类需要用到多个常量,不要用const数组,而是改用枚举

  • 对于宏形式的函数,最好用内联函数取代

条款3

  • 希望迭代器指向的东西不可改动,需要const_iterator

条款4

  • 永远在使用对象前初始化.对于int x;,有些编译器会将x初始化为0,有些却不会,所以无论之后有没有用到x,都要先对其初始化,比如int x=0;

  • 成员变量如果是const或引用,它们只能用成员列表初始化的方法进行初始化

条款5

  • 编译器自动生成的析构函数不是virtual,除非这个类的基类的析构函数是virtural

  • 编译器自动生成的4个函数都是public

  • 如果基类的copy构造函数和copy运算符是private,编译器不会为派生类生成它们

  • 派生类的copy构造函数可以尽量不定义

条款6

  • 定义uncopyable类是很好的禁止使用copy构造函数和copy运算符的方法,它的析构函数可以不是virtual,派生类不必以public继承它,也没有成员变量,也可以用于多重继承

  • 类的不可拷贝特性是可以继承的,例如凡是继承自uncopyable的类都不能使用copy构造函数和赋值运算符

条款7

  • 如果基类析构函数不声明为virtual,析构时不会调用派生类的析构,可查看原因分析

  • 如果一个类不做基类,就不要有virtual析构函数或其他虚函数,因为虚指针会增大类的体积。 反过来,只要一个类做基类,就要有virtual函数

  • 当类至少有一个虚函数时,为它声明virtual析构函数,否则编译器有报警

  • 对于抽象类,可以将析构函数声明为pure virtual析构函数

条款8

  • 析构函数里不要抛出异常,否则会导致不确定行为

条款9

  • 构造函数和析构函数中都不要调用virtual函数,因为基类构造时,virtual函数不会下降到派生类阶层,或者说此时的virtual函数还不是virtual函数

  • 对于存在多个构造函数的情况,为避免代码重复,要把同样的代码放到一个函数里,比如init

条款10

  • 赋值运算符(包括+= -= *=)的返回最好是return *this,这符合STL等标准库的风格

条款12

  • 自定义copy构造函数或运算符时,需要复制所有的成员变量,如果少复制了,编译器不会报警或报错

  • 派生类的copy构造函数或运算符,无法像平时那样对基类的private成员变量赋值,因为无法访问private的变量,只能显式调用基类的operator =, 比如: Base::operator=(obj);

条款15

  • 多使用智能指针shared_ptr,它可以返回原始指针,显式方法是get()函数,隐式方法是取指针操作符->

条款16

  • new和delete必须配对,[]必须都有或都没有。千万避免new没有[],delete有[],这会导致程序不停运行析构函数

条款18

  • 有时的形参可以用wrapper类型,而不是内置类型

  • 保证接口的一致,比如STL容器都有个size的成员函数

  • 可以让一些返回指针的函数返回智能指针,比如工厂函数

条款21

  • 函数内返回对象时,不要返回其引用。因为引用指向局部变量,而局部变量在函数退出前销毁,所以会出现无定义行为。

条款22

  • 成员变量应该都是private,将它们隐藏在函数接口的背后,如果放到public,直接使用成员变量会降低封装性。如果破坏了成员变量,会破坏太多的客户码

  • 成员变量的封装性和成员变量的内容改变时破坏的代码量成反比

  • protected变量被消灭时,所有用到它的派生类的代码都要破坏,所以封装性也很差

条款24

  • 若函数参数都要进行类型转换,应该使用非成员函数

  • 成员函数的反面是非成员函数,不是friend函数,friend应该尽量避免

条款26

  • 如果某个类的对象没有用到,就不应该声明,否则运行构造和析构函数都会耗费成本

  • 对象初始化用构造函数比=运算符的效率高

  • 对变量定义后应马上初始化,然后马上使用,中间不要隔太远

  • 变量定义在循环内比外面更好,后者造成作用域更大,对程序维护性不好

条款27

  • 代码中尽量避免dynamic_cast,它会降低效率

  • 避免连续的cast转型,尤其是dynamic_cast

条款32

  • public继承是一种is-a的关系,每一个派生类的对象也是基类的对象

  • 程序的错误最好能在编译期检测出来,而不是运行期

条款34

  • public继承涉及到函数接口继承和实现的继承

  • 纯虚函数实际上是可以提供定义的,这个用法比较罕见,知道即可

  • 虚函数使派生类继承了其缺省实现,但这可能造成危险

条款36

  • 不要重新定义继承而来的non-virtual函数,虽然没有错误,但违反了is-a原则,这种情况下干脆不要使用public继承

构造函数的成员初始化列表 (二)

根据 Effective C++,类的成员变量最好 都用成员列表初始化 的方法进行初始化,对内置类型没什么不同,但对类对象会提高效率。 成员初始化顺序是其声明的顺序,不是初始化列表中的顺序,二者最好一致

构造函数内赋值成员变量,先调用默认构造函数,再调用赋值运算符。 如果用成员列表初始化, 只调用拷贝构造函数,提高了效率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Derived : public Base
{
public:
Derived();
Derived(Base obj)
{
m_b = obj;
cout<<"derived constructor"<<endl;
}
~Derived();
Derived(const Derived& obj);
Derived& operator=(const Derived& obj);

private:
Base m_b;
};

实际调用:

1
2
3
Base b;
cout<<"----------------"<<endl;
Derived d(b);

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
base constructor
----------------
base copy constructor
base constructor
base constructor
base operator =
derived constructor
base destructor
----------------
derived destructor
base destructor
base destructor
base destructor

第一行是Base b的结果,第二行是运行构造函数时,b传递的参数副本,所以调用copy构造函数;
第三行是成员变量m_b的构造函数; 第四行是运行派生类的构造函数前先运行的基类构造函数;
第五行就是=运算符; 析构过程就简单了,不再分析



但实际中一般不这么用,Derived的成员变量会是Base* m_b,此时的调用是这样的:

1
2
3
4
5
Base *b = new Base();
Derived d(b);

delete b;
b = NULL;

结果是:

1
2
3
4
5
6
7
8
base constructor
----------------
base constructor
derived constructor
base destructor
----------------
derived destructor
base destructor

这样简单多了


const成员变量和成员函数
  • 在类成员函数后面加const关键字用于声明const成员函数,它不能改变类的成员变量,也不能调用该类中普通的成员函数,最好不要涉及非const的成员变量

  • const成员变量只能通过成员列表初始化进行初始化,任何成员函数均不能改变它的值

  • 只能调用const成员函数访问const对象,那是它的唯一对外接口

  • 若不希望传入的实参对象被修改,应该将形参定义为const的指针变量或者引用

const成员函数的定义是这样形式:

1
int getConst() const;

前面不用加const了,否则会有报警:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Base
{
public:
void setM(int M) //setter都不能加const
{
m = M;
}
int getM() const
{
return m;
}
private:
int m;
}

const Base b;
b.setM(2);

getM就是常量成员函数,如果给setM加const,那么就会报错,因为它修改m的值。定义的常对象b只能调用getM,调setM会报错。


问题

1
2
3
4
5
6
7
8
9
10
template <typename T>
void registerNav2Action(const BT::BehaviorTreeFactory& factory, const string& action_name, const T& nav2_action)
{
BT::NodeBuilder builder =
[](const std::string & action_name, const BT::NodeConfig & config)
{
return std::make_unique<T>(action_name, config);
};
factory->registerBuilder<T>(action_name, builder);
}

代码会报错: error: passing ‘const xxx’ as ‘this’ argument discards qualifiers

在一个加了const限定符的成员函数中,调用了非const成员函数,报错的意思就是缺少限定符。

factory作为const引用,调用了非const函数 BehaviorTreeFactory::registerBuilder,所以报错


使用const引用做函数形参类型

本问题是Effective C++的条款20

提高代码效率

我们知道函数的实际参数是实参的一个副本,它是由copy构造函数产生的,但这种传const引用的方式可以提高函数调用效率,它不会涉及副本的构造函数、析构函数和copy构造函数

测试代码如下,对基类定义了copy构造函数,但派生类没有定义copy构造函数,信息中会输出this指针的地址

1
2
3
4
5
6
7
8
9
void useDerived(const Derived& obj)
{
cout<<" use Derived"<<endl;
}

Derived f;
cout<<"-------------"<<endl;
useDerived(f);
cout<<"*************"<<endl;

运行结果:

1
2
3
4
5
6
7
Base constrct  0x75fd20
Derived construct 0x75fd20
-------------
use Derived
*************
Derived deconstruct 0x75fd20
Base deconstrct 0x75fd20

显然只有f的构造和析构过程,对于副本,只调用了useDerived函数,没有构造和析构,也没有copy构造。

如果改用值传递的方式,结果就变成下面这样:

1
2
3
4
5
6
7
8
9
10
Base constrct  0x75fd10
Derived construct 0x75fd10
-------------
Base copy constrct ---0x75fd40
use Derived
Derived deconstruct 0x75fd40
Base deconstrct 0x75fd40
*************
Derived deconstruct 0x75fd10
Base deconstrct 0x75fd10

显然副本的copy构造和析构都有了,但没有副本的构造函数,效率明显降低

避免对象切割问题( object sliced )

如果上面的函数改成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void useDerived(Base f)
{
f.testConst();
cout<<"value use Derived"<<endl;
}

// testConst声明为虚函数
void Base::testConst() const
{
cout << "base test const" <<endl;
}
void Derived::testConst() const
{
cout << "derived test const" <<endl;
}
// 调用
Derived d;
useDerived(d);

结果为:

1
2
3
4
5
6
7
8
9
10
base constructor
derived constructor
# 开始副本
base copy constructor
base test const // 基类版本
value use Derived
base destructor

derived destructor
base destructor

此时如果传入f,会把它按Base对象处理,而丧失了派生类的特性,调用的还是基类版本的函数,这不是我们的目的。但是如果用const Base& obj,就会根据传入的类型处理,结果会调用派生类版本的函数,也就是体现了多态。 但是注意:形参为常引用时,只能调用const成员函数。

不过对于内置类型和STL的iterator,函数对象,还是用值传递的方式比较好


机器人运动模型

用于估计的二轮差速里程计模型

二轮差速模型的航迹推演原理图,后方两个驱动轮,前面两个万向轮

  • IMU只提供yaw
  • 根据航迹推演,两驱车的线速度是两个轮子线速度的平均值,角速度是 差/底盘长度;两个参数作为raw_vel话题发布
  • 机器人切线运动模型
    base_controller节点正确读取到底层(比如嵌入式控制板)传回的速度后进行积分,计算出机器人的估计位置和姿态,并将里程计信息和tf变换发布出去。
  • 机器人中通过运行底盘控制ROS驱动,来实现读取串口的速度反馈,利用航迹推演算法计算得到里程计并发布到/odom这个主题

之所以说这个是粗略的定位,是因为在实际情况中可能会碰到轮子打滑,地面不平整等因素的干扰,里程计的运动增量带有噪声,对速度积分进行航迹推算得到的里程计累积误差会越来越大。当然上层会通过激光信息来匹配校准。

IMU姿态的协方差矩阵代表了姿态测量的不确定度。

用于规划的速度模型


IMU在ROS中的使用 (三) 标定内外参

IMU线加速度噪声积分后根本不能用做线速度

IMU在制造过程中,由于物理因素,导致实际的坐标轴与理想的坐标轴之间会有一定的偏差,同时三轴加速度、三轴角速度、三轴磁力计的原始值会与真实值有一个固定的偏差等。自校准就是要通过给的补偿值来减小坐标轴的偏差及原始值的固定偏差,也就是所谓的IMU内参标定,是为了获得噪声参数。

如果将IMU安装到机器人或摄像头上后,需要知道IMU与机器人或摄像头的相对位置,这个时候进行的标定就是所谓的IMU外参标定


IMU在ROS中的使用 (二) 发布话题

通过IMU获得的是odom坐标系下的坐标,sensor_msgs/Imu消息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 保存IMU的数据
# 加速度单位 m/s^2 (not in g's), 角速度单位 rad/sec
#
# If the covariance of the measurement is known, it should be filled in (if all you know is the
# variance of each measurement, e.g. from the datasheet, just put those along the diagonal)
# 如果协方差矩阵元素全是0,那么认为是未知协方差。
# If you have no estimate for one of the data elements (e.g. your IMU doesn't produce an orientation
# estimate), please set element 0 of the associated covariance matrix to -1
# If you are interpreting this message, please check for a value of -1 in the first element of each
# covariance matrix, and disregard the associated estimate.

Header header
geometry_msgs/Quaternion orientation # IMU的当前姿态,4元数的形式表示
float64[9] orientation_covariance # 姿态的协方差

geometry_msgs/Vector3 angular_velocity # IMU的3轴角速度
float64[9] angular_velocity_covariance

geometry_msgs/Vector3 linear_acceleration # IMU的3轴加速度
float64[9] linear_acceleration_covariance

滤波工具

ROS官方提供了滤波工具,可以直接安装:sudo apt-get install ros-kinetic-imu-tools,包括两个package和一个rviz插件:

  • imu_filter_madgwick: 将IMU设备读取的角速度、加速度和磁力计融合成方位信息

  • imu_complementary_filter: 一种基于强制融合的新方法将IMU设备读取的角速度、加速度和磁力计融合成方位信息。

  • rviz_imu_plugin:显示sensor_msgs::Imu的rviz插件

两种滤波器都是订阅IMU节点发布的imu/data_raw话题,经过滤波处理后,再发布imu/data话题。但是我用两种滤波器处理IMU数据后,没有发现明显的改善,也许是设备的原因,本来数据浮动就不大

参考:imu_tools


IMU在ROS中的使用(一) 基于串口通信获取欧拉角,角速度,线加速度
abstract Welcome to my blog, enter password to read.
Read more
laser_geometry将雷达scan发布为点云(scan to PointCloud2)

目前导航功能包只接受使用sensor_msgs/LaserScan或sensor_msgs/PointCloud消息类型发布的传感器数据。

sensor_msgs/LaserScansensor_msgs/PointCloud跟其他的消息一样,包括tf帧和与时间相关的信息。字段frame_id存储与数据相关联的tf帧信息。以激光扫描为例,它将是激光数据所在帧。

我们使用laser_geometry包将雷达的scan转换为点云,它只有一个C++类LaserProjection,没有ROS API.

有两个函数可以将sensor_msgs/LaserScan转换为sensor_msgs/PointCloud或者sensor_msgs/PointCloud2projectLaser()简单快速,不改变数据的frame; transformLaserScanToPointCloud()速度慢但是精度高,使用tfsensor_msgs/LaserScantime_increment转换每束激光,对倾斜的雷达或移动的机器人,推荐这种方法。

sensor_msgs/PointCloud还包括一些额外的通道,例如intensities, distances, timestamps, index 或者thew viewpoint
支持将三维空间中的点的数组以及任何保存在一个信道中的相关数据。例如,一条带有intensity信道的 PointCloud 可以保持点云数据中每一个点的强度。

projectLaser

它执行最简单的激光投射,每束激光投射出去的角度根据的是以下公式:

但最后形成的点云消息,坐标系还是laser,不适用于雷达移动或畸变不能忽略的情况。

transformLaserScanToPointCloud

这种方法需要tf变换,由于我们是扫描过程中一直收集数据,选择的target_frame必须是固定的。因为sensor_msgs/LaserScan的时间戳是第一次测量的时间,这个时间还无法转换到target_frame,得等到最后一个雷达的测量时间完成坐标系转换。

代码

依赖包是laser_geometry, pcl_conversions, pcl_ros
修改package.xml文件,添加

1
2
<build_depend>libpcl-all-dev</build_depend>
<exec_depend>libpcl-all</exec_depend>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include "ros/ros.h"
#include "laser_geometry/laser_geometry.h"
#include "sensor_msgs/LaserScan.h"
#include "sensor_msgs/PointCloud.h"
#include "tf/transform_listener.h"
#include <boost/shared_ptr.hpp>

laser_geometry::LaserProjection projector;
ros::Publisher pub;
boost::shared_ptr<tf::TransformListener> listener_;

void chatterCallback(const sensor_msgs::LaserScan::ConstPtr& msg)
{
sensor_msgs::PointCloud2 cloud;
// projector.projectLaser(*msg, cloud); // 第一种方法

if(!listener_->waitForTransform(
"/base_link",
msg->header.frame_id,
msg->header.stamp + ros::Duration().fromSec(msg->ranges.size()*msg->time_increment),
ros::Duration(2.0)) )
{
ROS_WARN("no transform");
return;
}
projector.transformLaserScanToPointCloud("/base_link",*msg,
cloud, *listener_);
pub.publish(cloud);
}

int main(int argc, char **argv)
{
ros::init(argc, argv, "laser_toCloud");
ros::NodeHandle nh;
listener_ = boost::make_shared<tf::TransformListener>();
pub = nh.advertise<sensor_msgs::PointCloud2>("cloud",10);
ros::Subscriber sub = nh.subscribe("/scan", 1000, chatterCallback); // 先有发布的scan话题

ros::spin();
return 0;
}

参考:
LaserScan转pcl::PointCloud
laser_geometry


(一) 概述,安装配置(避免找不到库文件),常用类

PCL包括多个子模块库。最重要的PCL模块库有如下:过滤器Filters、特征Features、关键点Keypoints、配准Registration、Kd树Kd-tree、八叉树Octree、切分Segmentation、Sample Consensus、Surface、Range Image、文件读写I/O、Visualization、通用库Common、Search
PCL框架.png

PCL中的所有模块和算法都是通过Boost共享指针来传送数据的,因而避免了多次复制系统中已存在的数据的需要

ROS中已经安装了pcl,不必再安装了,而且网上给的那个源一直添加失败,服务器在国外。但是ROS的pcl缺工具包,比如查看点云图片的pcl_viewer。它的安装命令是:sudo apt-get install pcl-tools

优点:

  • 可以表达物体的空间轮廓和具体位置

  • 点云本身和视角无关,也就是你可以任意旋转,可以从不同角度和方向观察一个点云,而且不同的点云只要在同一个坐标系下就可以直接融合

缺点:

  • 点云并不是稠密的表达,一般比较稀疏,放大了看,会看到一个个孤立的点。它的周围是黑的,也就是没有信息。所以在空间很多位置其实没有点云,这部分的信息是缺失的。

  • 点云的分辨率和离相机的距离有关。离近了看不清是个什么东西,只能拉的很远才能看个大概。

安装

编译pcl-1.8

1
2
3
4
5
6
mkdir build
cd build
# 配置cmake
cmake -DCMAKE_BUILD_TYPE=None -DCMAKE_INSTALL_PREFIX=/usr \ -DBUILD_GPU=ON-DBUILD_apps=ON -DBUILD_examples=ON \ -DCMAKE_INSTALL_PREFIX=/usr ..
make
sudo make install

如果ubuntu 22.04且通过sudo apt install libpcl-dev安装的pcl。那么pcl版本可能是1.12,如果vtk版本正好又是9.1,二者存在冲突会导致不能正常显示。

cmake配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cmake_minimum_required(VERSION 2.6)
project(pcl_test)

find_package(PCL 1.7 REQUIRED)
# 可选的工具包
find_package(catkin REQUIRED COMPONENTS
pcl_conversions
pcl_ros
)
list(REMOVE_ITEM PCL_LIBRARIES "vtkproj4")
include_directories(${PCL_INCLUDE_DIRS})
link_directories(${PCL_LIBRARY_DIRS})
add_definitions(${PCL_DEFINITIONS})

add_executable(pcl_test main.cpp)
target_link_libraries (pcl_test ${PCL_LIBRARIES})

PCL中常见的PointT类型

PointXYZ是使用最常见的一个点数据类型,因为他之包含三维XYZ坐标信息,这三个浮点数附加一个浮点数来满足存储对齐,可以通过points[i].data[0]或者points[i].x访问点X的坐标值

1
2
3
4
5
6
7
8
9
10
union
{
float data[4];
struct
{
float x;
float y;
float z;
};
};

PointXYZRGB也是个union,成员如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
union{
float data[4];
struct
{
float x;
float y;
float z;
};
};
union{
struct{
float rgb;
};
float data_c[4];
};

在使用PCL库的时候,经常需要显示点云,可以用下面这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <pcl/visualization/cloud_viewer.h>

typedef pcl::PointXYZRGB PointT;
typedef pcl::PointCloud<PointT> PointCloudT;

PointCloudT::Ptr cloud(new PointCloudT);

cloud->is_dense = false;
// 打开点云文件,获取的是vertex
if(pcl::io::loadPLYFile("file.ply", *cloud) <0 )
{
PCL_ERROR("loading cloud failed !");
return -1;
}
cout<< "点云共有: "<< cloud->size()<<"个点"<<endl; //文件中vertex的个数

pcl::visualization::CloudViewer viewer ("Viewer");
viewer.showCloud(cloud);

while (!viewer.wasStopped ())
{

}

变换点云

1
pcl::transformPointCloud (*cloud_in, *cloud_out, matrix);

cloud_in为源点云,cloud_out为变换后的点云,注意这里的matrix是4X4的欧拉矩阵,也就是第一行为R,t;第二行为0,1的矩阵

PCLVisualizer类与CloudViewer类

对于CloudViewer类来说,其方法有大致以下几类

1
2
3
4
5
6
7
8
9
10
11
12
void showCloud()

void wasStopped() // 关闭窗口

void runOnVisualizationThread()
void runOnVisualizationThreadOnce ()

void removeVisualizationCallable()
// 键盘鼠标的响应
boost::signals2::connection registerKeyboardCallback()
boost::signals2::connection registerMouseCallback()
boost::signals2::connection registerPointPickingCallback()

常用代码:

1
2
3
4
5
6
pcl::visualization::CloudViewer viewer("3D Point Cloud Viewer");
// 除了显示什么都不能干
viewer.showCloud(m_cloud);
while(!viewer.wasStopped())
{
}


如果只是简单显示点云,调用CloudViewer就可以了。PCLVisualizer更加强大,可以改变窗口背景,还可以添加球体,设置文本,功能特别丰富

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer(new pcl::visualization::PCLVisualizer("3D Viewer"));
viewer->setBackgroundColor(0, 0, 0);

int v1(0);
int v1(1);
// 视窗的x轴最小值,y轴最小值,x轴最大值,y轴最大值
viewer->createViewPort(0.0, 0, 0.5, 1.0, v1); //创建左视窗
viewer->createViewPort(0.5, 0, 1.0, 1.0, v2); //创建右视窗
viewer->addPointCloud<pcl::PointXYZ>(cloud, "sample cloud");
viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "sample cloud");
viewer->addCoordinateSystem(1.0);
viewer->initCameraParameters();
// 代码最后,都会出现这样一个while循环,在spinOnce中为界面刷新保留了时间
while (!viewer->wasStopped())
{
viewer->spinOnce(100); // 多长时间调用内部渲染一次
boost::this_thread::sleep(boost::posix_time::microseconds(100000)); //延时0.1秒
}

addPointCloud函数将点云添加到视窗对象中,并定义一个唯一的字符串作为ID号,利用此字符串保证在其他成员方法中也能标识引用该点云,多次调用addPointCloud(),可以实现多个点云的添加,每调用一次就创建一个新的ID号。如果想更新一个已经显示的点云,可以使用updatePointCloud函数。删除有removePointCloud(ID号)removeAllPointClouds。 这里使用的是最简单的一个重载,此外可以在第二个参数添加PointCloudColorHandlerPointCloudColorHandler

PCLVisualizer类的键盘事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
bool next_iteration = false;
void keyboardEventOccurred(const pcl::visualization::KeyboardEvent& event, void* nothing){
if(event.getKeySym() == "space" && event.keyDown()){
next_iteration = true;
}
}

int main()
{
pcl::visualization::PCLVisualizer *viewer;
// 必须要给PCLVisualizer指针赋值实例化对象
viewer = new pcl::visualization::PCLVisualizer("PCL Windows");
viewer->registerKeyboardCallback(&keyboardEventOccurred, (void*)NULL);

while(!viewer->wasStopped())
{
viewer->spinOnce(100);
boost::this_thread::sleep(boost::posix_time::microseconds (100000));
if(next_iteration)
{}
}
return 0;
}

其他常用函数

1
2
3
4
5
6
7
8
// 初始化相机参数
initCameraParameters();
setCameraPosition(
0, 0, 5, // camera位置
0, 0, 4, // view向量--相机朝向
0, 1, 5 // up向量
);

还可以对不同的视窗添加文字和背景颜色

参考:
PCL库简要说明
PCL中可用的PointT类型
PCLVisualizer
PCLVisualizer可视化类