1. 环境搭建与配置在开始使用QTVS2019PCL进行激光雷达点云可视化开发之前我们需要先搭建好开发环境。这个过程可能会遇到一些坑我把自己踩过的坑和解决方案都整理出来希望能帮你少走弯路。首先需要安装VS2019社区版这个直接去微软官网下载安装即可。安装时记得勾选C桌面开发工作负载建议把Windows 10 SDK也一起装上。QT我推荐使用5.12.10版本这个版本比较稳定与PCL的兼容性也比较好。PCL库的安装是个重头戏。我建议使用PCL 1.11.1 All-in-One安装包这个版本比较稳定配套的VTK版本是8.2.0。安装时要注意以下几点安装路径最好不要有中文和空格我一般安装在D:\PCL1.11.1安装过程中会提示安装第三方依赖库一定要全部勾选安装完成后需要手动添加环境变量PCL_ROOT D:\PCL1.11.1在Path中添加%PCL_ROOT%\bin;%PCL_ROOT%\3rdParty\FLANN\bin;%PCL_ROOT%\3rdParty\VTK\bin安装完成后我们可以创建一个简单的QT Widgets Application项目来测试环境是否配置成功。在项目属性中需要配置以下内容C/C - 常规 - 附加包含目录$(PCL_ROOT)\include\pcl-1.11$(PCL_ROOT)\3rdParty\Eigen\eigen3$(PCL_ROOT)\3rdParty\Boost\include\boost-1_74$(PCL_ROOT)\3rdParty\VTK\include\vtk-8.2链接器 - 常规 - 附加库目录$(PCL_ROOT)\lib$(PCL_ROOT)\3rdParty\Boost\lib$(PCL_ROOT)\3rdParty\VTK\lib链接器 - 输入 - 附加依赖项添加pcl开头的lib文件如pcl_common_debug.lib、pcl_visualization_debug.lib等2. QT界面设计与PCL集成环境配置好后我们就可以开始设计可视化界面了。QT提供了强大的UI设计工具我们可以通过拖拽的方式快速搭建界面。首先创建一个QWidget作为主窗口然后添加一个QVtkWidget用于显示点云。QVtkWidget是PCL提供的用于在QT中显示VTK渲染窗口的控件它是实现点云可视化的核心组件。在头文件中我们需要包含以下关键头文件#include pcl/visualization/pcl_visualizer.h #include vtkRenderWindow.h #include QVTKOpenGLWidget.h然后定义一个继承自QWidget的类用于管理点云可视化class PointCloudViewer : public QWidget { Q_OBJECT public: explicit PointCloudViewer(QWidget *parent nullptr); ~PointCloudViewer(); void updatePointCloud(pcl::PointCloudpcl::PointXYZ::Ptr cloud); private: pcl::visualization::PCLVisualizer::Ptr viewer; QVTKOpenGLWidget *qvtkWidget; };在构造函数中我们需要初始化PCLVisualizer并将其与QVtkWidget关联PointCloudViewer::PointCloudViewer(QWidget *parent) : QWidget(parent) { // 初始化QT界面 qvtkWidget new QVTKOpenGLWidget(this); QVBoxLayout *layout new QVBoxLayout(this); layout-addWidget(qvtkWidget); // 初始化PCL可视化器 viewer.reset(new pcl::visualization::PCLVisualizer(Point Cloud Viewer, false)); viewer-setBackgroundColor(0, 0, 0); // 设置背景为黑色 // 将PCL可视化器与QT控件关联 qvtkWidget-SetRenderWindow(viewer-getRenderWindow()); viewer-setupInteractor(qvtkWidget-GetInteractor(), qvtkWidget-GetRenderWindow()); // 添加坐标系 viewer-addCoordinateSystem(1.0); }3. 点云数据加载与实时更新有了可视化界面后我们需要实现点云数据的加载和实时更新功能。这里我介绍两种常见的数据源从文件加载和从激光雷达实时获取。3.1 从文件加载点云PCL提供了多种点云文件格式的读写接口最常用的是PLY和PCD格式。我们可以使用以下代码加载点云文件pcl::PointCloudpcl::PointXYZ::Ptr loadPointCloud(const std::string filename) { pcl::PointCloudpcl::PointXYZ::Ptr cloud(new pcl::PointCloudpcl::PointXYZ); if (pcl::io::loadPCDFilepcl::PointXYZ(filename, *cloud) -1) { PCL_ERROR(Couldnt read PCD file \n); return nullptr; } return cloud; }加载完成后我们可以通过之前定义的updatePointCloud方法更新显示void PointCloudViewer::updatePointCloud(pcl::PointCloudpcl::PointXYZ::Ptr cloud) { viewer-removeAllPointClouds(); // 清除当前显示的点云 // 添加新的点云设置颜色为绿色 pcl::visualization::PointCloudColorHandlerCustompcl::PointXYZ green(cloud, 0, 255, 0); viewer-addPointCloudpcl::PointXYZ(cloud, green, cloud); // 设置点云显示大小 viewer-setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, cloud); // 刷新显示 qvtkWidget-update(); }3.2 实时更新激光雷达点云对于实时激光雷达数据我们需要创建一个单独的线程来接收数据然后通过信号槽机制通知主线程更新显示。这里以模拟数据为例class LidarThread : public QThread { Q_OBJECT public: explicit LidarThread(QObject *parent nullptr) : QThread(parent), running(false) {} void run() override { running true; while (running) { // 模拟生成点云数据 pcl::PointCloudpcl::PointXYZ::Ptr cloud(new pcl::PointCloudpcl::PointXYZ); cloud-width 1000; cloud-height 1; cloud-points.resize(cloud-width * cloud-height); for (auto point : cloud-points) { point.x 10 * (rand() / (RAND_MAX 1.0f)); point.y 10 * (rand() / (RAND_MAX 1.0f)); point.z 10 * (rand() / (RAND_MAX 1.0f)); } emit newPointCloud(cloud); msleep(100); // 模拟10Hz的更新频率 } } void stop() { running false; } signals: void newPointCloud(pcl::PointCloudpcl::PointXYZ::Ptr cloud); private: bool running; };在主窗口中我们连接信号槽来实现实时更新// 创建雷达线程 lidarThread new LidarThread(this); connect(lidarThread, LidarThread::newPointCloud, pointCloudViewer, PointCloudViewer::updatePointCloud); // 启动线程 lidarThread-start();4. 交互功能实现基本的点云显示功能实现后我们可以添加一些交互功能来提升用户体验。PCLVisualizer本身提供了一些内置的交互功能如旋转、平移、缩放等我们还可以通过QT实现更多自定义交互。4.1 视角控制我们可以添加按钮来控制视角比如重置视角、俯视图、前视图等// 重置视角 void PointCloudViewer::resetView() { viewer-resetCamera(); qvtkWidget-update(); } // 设置俯视图 void PointCloudViewer::setTopView() { viewer-setCameraPosition(0, 0, 10, // 相机位置 0, 0, 0, // 观察点 0, 1, 0); // 上方向 qvtkWidget-update(); } // 设置前视图 void PointCloudViewer::setFrontView() { viewer-setCameraPosition(0, -10, 0, 0, 0, 0, 0, 0, 1); qvtkWidget-update(); }4.2 点云颜色调整我们可以提供接口让用户调整点云显示颜色void PointCloudViewer::setPointCloudColor(int r, int g, int b) { if (!viewer-contains(cloud)) return; viewer-updatePointCloudRenderingProperties( pcl::visualization::PCL_VISUALIZER_COLOR, r/255.0, g/255.0, b/255.0, cloud); qvtkWidget-update(); }4.3 点云选择与测量实现点云选择功能需要处理鼠标事件。我们可以继承QVTKOpenGLWidget并重写鼠标事件处理方法class CustomVTKWidget : public QVTKOpenGLWidget { Q_OBJECT public: explicit CustomVTKWidget(QWidget *parent nullptr) : QVTKOpenGLWidget(parent) {} signals: void pointSelected(double x, double y, double z); protected: void mousePressEvent(QMouseEvent *event) override { if (event-button() Qt::LeftButton) { // 获取鼠标点击位置 int x event-pos().x(); int y event-pos().y(); // 转换为VTK坐标 vtkRenderWindowInteractor *interactor GetInteractor(); interactor-SetEventInformationFlipY(x, y); // 获取选中的点 vtkSmartPointervtkCellPicker picker vtkSmartPointervtkCellPicker::New(); picker-Pick(x, y, 0, GetRenderWindow()-GetRenderers()-GetFirstRenderer()); double *pos picker-GetPickPosition(); if (picker-GetCellId() ! -1) { emit pointSelected(pos[0], pos[1], pos[2]); } } QVTKOpenGLWidget::mousePressEvent(event); } };4.4 点云滤波与处理在实际应用中我们可能需要对点云进行滤波处理。PCL提供了丰富的滤波算法这里以体素格滤波为例pcl::PointCloudpcl::PointXYZ::Ptr voxelFilter( pcl::PointCloudpcl::PointXYZ::Ptr cloud, float leafSize) { pcl::PointCloudpcl::PointXYZ::Ptr filtered(new pcl::PointCloudpcl::PointXYZ); pcl::VoxelGridpcl::PointXYZ voxel; voxel.setInputCloud(cloud); voxel.setLeafSize(leafSize, leafSize, leafSize); voxel.filter(*filtered); return filtered; }我们可以在界面上添加一个滑块来控制滤波参数实时观察滤波效果// 连接滑块值变化信号 connect(ui-filterSlider, QSlider::valueChanged, [](int value) { float leafSize value / 100.0f; // 将滑块值转换为滤波参数 auto filtered voxelFilter(currentCloud, leafSize); updatePointCloud(filtered); });
QT+VS2019+PCL实现激光雷达点云动态交互可视化
1. 环境搭建与配置在开始使用QTVS2019PCL进行激光雷达点云可视化开发之前我们需要先搭建好开发环境。这个过程可能会遇到一些坑我把自己踩过的坑和解决方案都整理出来希望能帮你少走弯路。首先需要安装VS2019社区版这个直接去微软官网下载安装即可。安装时记得勾选C桌面开发工作负载建议把Windows 10 SDK也一起装上。QT我推荐使用5.12.10版本这个版本比较稳定与PCL的兼容性也比较好。PCL库的安装是个重头戏。我建议使用PCL 1.11.1 All-in-One安装包这个版本比较稳定配套的VTK版本是8.2.0。安装时要注意以下几点安装路径最好不要有中文和空格我一般安装在D:\PCL1.11.1安装过程中会提示安装第三方依赖库一定要全部勾选安装完成后需要手动添加环境变量PCL_ROOT D:\PCL1.11.1在Path中添加%PCL_ROOT%\bin;%PCL_ROOT%\3rdParty\FLANN\bin;%PCL_ROOT%\3rdParty\VTK\bin安装完成后我们可以创建一个简单的QT Widgets Application项目来测试环境是否配置成功。在项目属性中需要配置以下内容C/C - 常规 - 附加包含目录$(PCL_ROOT)\include\pcl-1.11$(PCL_ROOT)\3rdParty\Eigen\eigen3$(PCL_ROOT)\3rdParty\Boost\include\boost-1_74$(PCL_ROOT)\3rdParty\VTK\include\vtk-8.2链接器 - 常规 - 附加库目录$(PCL_ROOT)\lib$(PCL_ROOT)\3rdParty\Boost\lib$(PCL_ROOT)\3rdParty\VTK\lib链接器 - 输入 - 附加依赖项添加pcl开头的lib文件如pcl_common_debug.lib、pcl_visualization_debug.lib等2. QT界面设计与PCL集成环境配置好后我们就可以开始设计可视化界面了。QT提供了强大的UI设计工具我们可以通过拖拽的方式快速搭建界面。首先创建一个QWidget作为主窗口然后添加一个QVtkWidget用于显示点云。QVtkWidget是PCL提供的用于在QT中显示VTK渲染窗口的控件它是实现点云可视化的核心组件。在头文件中我们需要包含以下关键头文件#include pcl/visualization/pcl_visualizer.h #include vtkRenderWindow.h #include QVTKOpenGLWidget.h然后定义一个继承自QWidget的类用于管理点云可视化class PointCloudViewer : public QWidget { Q_OBJECT public: explicit PointCloudViewer(QWidget *parent nullptr); ~PointCloudViewer(); void updatePointCloud(pcl::PointCloudpcl::PointXYZ::Ptr cloud); private: pcl::visualization::PCLVisualizer::Ptr viewer; QVTKOpenGLWidget *qvtkWidget; };在构造函数中我们需要初始化PCLVisualizer并将其与QVtkWidget关联PointCloudViewer::PointCloudViewer(QWidget *parent) : QWidget(parent) { // 初始化QT界面 qvtkWidget new QVTKOpenGLWidget(this); QVBoxLayout *layout new QVBoxLayout(this); layout-addWidget(qvtkWidget); // 初始化PCL可视化器 viewer.reset(new pcl::visualization::PCLVisualizer(Point Cloud Viewer, false)); viewer-setBackgroundColor(0, 0, 0); // 设置背景为黑色 // 将PCL可视化器与QT控件关联 qvtkWidget-SetRenderWindow(viewer-getRenderWindow()); viewer-setupInteractor(qvtkWidget-GetInteractor(), qvtkWidget-GetRenderWindow()); // 添加坐标系 viewer-addCoordinateSystem(1.0); }3. 点云数据加载与实时更新有了可视化界面后我们需要实现点云数据的加载和实时更新功能。这里我介绍两种常见的数据源从文件加载和从激光雷达实时获取。3.1 从文件加载点云PCL提供了多种点云文件格式的读写接口最常用的是PLY和PCD格式。我们可以使用以下代码加载点云文件pcl::PointCloudpcl::PointXYZ::Ptr loadPointCloud(const std::string filename) { pcl::PointCloudpcl::PointXYZ::Ptr cloud(new pcl::PointCloudpcl::PointXYZ); if (pcl::io::loadPCDFilepcl::PointXYZ(filename, *cloud) -1) { PCL_ERROR(Couldnt read PCD file \n); return nullptr; } return cloud; }加载完成后我们可以通过之前定义的updatePointCloud方法更新显示void PointCloudViewer::updatePointCloud(pcl::PointCloudpcl::PointXYZ::Ptr cloud) { viewer-removeAllPointClouds(); // 清除当前显示的点云 // 添加新的点云设置颜色为绿色 pcl::visualization::PointCloudColorHandlerCustompcl::PointXYZ green(cloud, 0, 255, 0); viewer-addPointCloudpcl::PointXYZ(cloud, green, cloud); // 设置点云显示大小 viewer-setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, cloud); // 刷新显示 qvtkWidget-update(); }3.2 实时更新激光雷达点云对于实时激光雷达数据我们需要创建一个单独的线程来接收数据然后通过信号槽机制通知主线程更新显示。这里以模拟数据为例class LidarThread : public QThread { Q_OBJECT public: explicit LidarThread(QObject *parent nullptr) : QThread(parent), running(false) {} void run() override { running true; while (running) { // 模拟生成点云数据 pcl::PointCloudpcl::PointXYZ::Ptr cloud(new pcl::PointCloudpcl::PointXYZ); cloud-width 1000; cloud-height 1; cloud-points.resize(cloud-width * cloud-height); for (auto point : cloud-points) { point.x 10 * (rand() / (RAND_MAX 1.0f)); point.y 10 * (rand() / (RAND_MAX 1.0f)); point.z 10 * (rand() / (RAND_MAX 1.0f)); } emit newPointCloud(cloud); msleep(100); // 模拟10Hz的更新频率 } } void stop() { running false; } signals: void newPointCloud(pcl::PointCloudpcl::PointXYZ::Ptr cloud); private: bool running; };在主窗口中我们连接信号槽来实现实时更新// 创建雷达线程 lidarThread new LidarThread(this); connect(lidarThread, LidarThread::newPointCloud, pointCloudViewer, PointCloudViewer::updatePointCloud); // 启动线程 lidarThread-start();4. 交互功能实现基本的点云显示功能实现后我们可以添加一些交互功能来提升用户体验。PCLVisualizer本身提供了一些内置的交互功能如旋转、平移、缩放等我们还可以通过QT实现更多自定义交互。4.1 视角控制我们可以添加按钮来控制视角比如重置视角、俯视图、前视图等// 重置视角 void PointCloudViewer::resetView() { viewer-resetCamera(); qvtkWidget-update(); } // 设置俯视图 void PointCloudViewer::setTopView() { viewer-setCameraPosition(0, 0, 10, // 相机位置 0, 0, 0, // 观察点 0, 1, 0); // 上方向 qvtkWidget-update(); } // 设置前视图 void PointCloudViewer::setFrontView() { viewer-setCameraPosition(0, -10, 0, 0, 0, 0, 0, 0, 1); qvtkWidget-update(); }4.2 点云颜色调整我们可以提供接口让用户调整点云显示颜色void PointCloudViewer::setPointCloudColor(int r, int g, int b) { if (!viewer-contains(cloud)) return; viewer-updatePointCloudRenderingProperties( pcl::visualization::PCL_VISUALIZER_COLOR, r/255.0, g/255.0, b/255.0, cloud); qvtkWidget-update(); }4.3 点云选择与测量实现点云选择功能需要处理鼠标事件。我们可以继承QVTKOpenGLWidget并重写鼠标事件处理方法class CustomVTKWidget : public QVTKOpenGLWidget { Q_OBJECT public: explicit CustomVTKWidget(QWidget *parent nullptr) : QVTKOpenGLWidget(parent) {} signals: void pointSelected(double x, double y, double z); protected: void mousePressEvent(QMouseEvent *event) override { if (event-button() Qt::LeftButton) { // 获取鼠标点击位置 int x event-pos().x(); int y event-pos().y(); // 转换为VTK坐标 vtkRenderWindowInteractor *interactor GetInteractor(); interactor-SetEventInformationFlipY(x, y); // 获取选中的点 vtkSmartPointervtkCellPicker picker vtkSmartPointervtkCellPicker::New(); picker-Pick(x, y, 0, GetRenderWindow()-GetRenderers()-GetFirstRenderer()); double *pos picker-GetPickPosition(); if (picker-GetCellId() ! -1) { emit pointSelected(pos[0], pos[1], pos[2]); } } QVTKOpenGLWidget::mousePressEvent(event); } };4.4 点云滤波与处理在实际应用中我们可能需要对点云进行滤波处理。PCL提供了丰富的滤波算法这里以体素格滤波为例pcl::PointCloudpcl::PointXYZ::Ptr voxelFilter( pcl::PointCloudpcl::PointXYZ::Ptr cloud, float leafSize) { pcl::PointCloudpcl::PointXYZ::Ptr filtered(new pcl::PointCloudpcl::PointXYZ); pcl::VoxelGridpcl::PointXYZ voxel; voxel.setInputCloud(cloud); voxel.setLeafSize(leafSize, leafSize, leafSize); voxel.filter(*filtered); return filtered; }我们可以在界面上添加一个滑块来控制滤波参数实时观察滤波效果// 连接滑块值变化信号 connect(ui-filterSlider, QSlider::valueChanged, [](int value) { float leafSize value / 100.0f; // 将滑块值转换为滤波参数 auto filtered voxelFilter(currentCloud, leafSize); updatePointCloud(filtered); });