PCL1.11 + VS +Qt点云数据可视化,鼠标点拾取,绝对是你想要的

2 篇文章 0 订阅

动机

之前用OpenGL+Qt的方式进行点云可视化,奈何OpenGL太高级,太底层了,什么都要自己搞,虽然最后也搞出来一套,但是花了太大的力气才搞完,现在在回过头看看以前的代码,好多都要想一下才能明白。

最近项目要用到PCL进行点云处理,然后了解了PCL使用vtk进行可视化。然后研究了一下vtk,过程中走了不少弯路,现在把一些经验总结一下,算是给自己这段时间的研究一个交代。

环境

我用的环境是VS2019 + Qt5.13 + PCL 1.11 + 自己编译的VTK8.2,基本上算是最新的了。

PCL编译安装配置

PCL的安装相对简单,直接去官网下载安装即可,安装完后如下图所示。其中,PCL用到的第三方库都在3rdParty文件夹中。安装完后需要重新编译VTK,因为安装版的VTK是不支持Qt插件的。

我们下载PCL对应的VTK版本,这里一定要下载与PCL对应的VTK源码,然后编译即可,具体编译可参考其他文章,这里没什么难度的。

 

在编译VTK8.2的时候我遇到的一个错误是:export EXPORT or TARGETS specifier missing,解决办法是:打开VTK目录下的cmakelist.txt文件,复制156~157到154~155,最后,cmakelist.txt看起来像这样:

# Add a virtual target that can be used to build all compile tools.
add_custom_target(vtkCompileTools)
if (_vtk_compiletools_targets)
  list(REMOVE_DUPLICATES _vtk_compiletools_targets)
  export(TARGETS ${_vtk_compiletools_targets}
    FILE ${VTK_BINARY_DIR}/VTKCompileToolsConfig.cmake)
  add_dependencies(vtkCompileTools ${_vtk_compiletools_targets})
endif()

unset(_vtk_targets)
unset(_vtk_compiletools_targets)
unset(_vtk_all_targets)

编译完后把VTK的动态库,静态库和头文件替换PCL中的VTK即可,编译完VTK8.2应该像这样,其中,plugins文件夹中存放的是Qt设计师的控件动态库,但是我从来没用过Qt设计师,所以,这个对我没什么用。然后有一个QVTKWidget头文件,有了这个头文件就表示你的VTK此时可支持嵌入在Qt中显示了。

PCL可视化

网上找的资料绝大部分都是基于Qt设计师,把VTK的Qt控件提升为VTKWidget,然后各种操作。但是这篇文章我想用纯代码来实现。一方面我从来不用Qt设计师,另一方面我是基于VS+Qt插件来写的,在VS使用QT设计师很麻烦。

PCL可视化通过PCL的可视化类管理实现,PCL可视化类PCLVisualizer负责管理数据,QVTKWidget才是真正渲染数据的地方,类似于画布。所以,可视化类一定要设置渲染窗口,即最终要把数据显示在哪里。关键代码如下:

this->SetRenderWindow(_viewer->getRenderWindow());

有了可视化的容器,我们现在还不清楚到底怎么把数据刷到QVTKWidget上,刚才说过,PCL可视化器是管理数据的,所以,我们想显示谁就把谁添加到PCL可视化器上。

_viewer->addPointCloud(_cloud);

最终,我们的核心代码如下:

初始化VTK

void PCLViewer::initVtk()
{
	_viewer->addPointCloud(_cloud);
	_viewer->setBackgroundColor(0.0, 0.0, 0.0);
	_viewer->setPointCloudRenderingProperties(
		pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1.0);
	_viewer->setPointCloudRenderingProperties(
		pcl::visualization::PCL_VISUALIZER_COLOR, 1.0, 1.0, 1.0);
	
	cb_args.clicked_points_3d = clicked_points_3d;
	cb_args.viewerPtr = pcl::visualization::PCLVisualizer::Ptr(_viewer);
	_viewer->registerPointPickingCallback(
		click_point_callback, (void*)&cb_args); // 注册鼠标拾取点回调函数

	this->SetRenderWindow(_viewer->getRenderWindow()); // 设置PCLViewer可视化器渲染窗口
}

刷新显示点云数据

void PCLViewer::updateData(const QVector<QVector<QVector3D>>& data)
{
	_cloud.reset(new pcl::PointCloud<pcl::PointXYZ>);
	std::async(std::launch::async, [=]() {
		for (int i = 0; i < data.size(); ++i)
		{
			for (int j = 0; j < data[0].size(); ++j)
			{
				_cloud->points.push_back(
					pcl::PointXYZ(data[i][j].x(),
						data[i][j].y(), data[i][j].z()));
			}
		}
		}).get();
	
	_viewer->updatePointCloud(_cloud, pointID);
	_viewer->resetCamera();
	this->update();

}

其中,PCLViewer继承自QVTKWidget,定义如下:

class PCLViewer : public QVTKWidget
{
	Q_OBJECT
public:
	PCLViewer(QVTKWidget* parent = nullptr);

public slots:
	void updateData(const QVector<QVector<QVector3D>>& data);
protected:
	virtual QSize minimumSizeHint() const override;                 // 作为子窗口时设置子窗口最小尺寸
	virtual QSize sizeHint() const override;                        // 作为子窗口时设置子窗口默认尺寸
private:
	void initVtk();
private:
	pcl::PointCloud<pcl::PointXYZ>::Ptr _cloud;				// 点云
	std::string pointID;									// 点云ID
	pcl::visualization::PCLVisualizer::Ptr _viewer;			// pcl可视化器

	pcl::PointCloud<pcl::PointXYZ>::Ptr clicked_points_3d;	// 存储鼠标拾取点
};

实际上,PCLViewer就是Qt设计师把VTKWidgets提升为类的操作后的类。有了PCLViewer窗口控件,我们就可以把他放到QMainWindow窗口上合适的位置了。

鼠标点拾取

鼠标点拾取背后的原理实际上比较复杂的,是一个从二维到三维映射的过程,结合光线追踪,从相机原点出发,与世界坐标下的第一个交点就是鼠标拾取的点。但是PCL可视化器为我们封装了这个过程,不需要我们自己去计算。PCL可视化器捕获鼠标位置是通过回调函数实现的,我们定义相应的回调函数即可。

// 拾取鼠标位置回调函数声明
void point_pick_callback(
	const pcl::visualization::PointPickingEvent& event, void* args);
void point_pick_callback(const pcl::visualization::PointPickingEvent& event,
	void* args)
{
	struct pointpick_args* data =
		(struct pointpick_args*)args;
	if (-1 == event.getPointIndex())
	{
		return;
	}

	pcl::PointXYZ cur_point;
	event.getPoint(cur_point.x, cur_point.y, cur_point.z);
	data->clicked_points_3d->points.push_back(cur_point);
	// 开始画拾取点
	pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> red(
		data->clicked_points_3d, 255, 0, 0);
	data->viewerPtr->removePointCloud("clicked_points");
	data->viewerPtr->addPointCloud(
		data->clicked_points_3d, red, "clicked_points");
	data->viewerPtr->setPointCloudRenderingProperties(
		pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 10, "clicked_points");
	qDebug() << cur_point.x << " " << cur_point.y << " " << cur_point.z;
}

最终,我们的软件实现的效果如下:

 

最终的工程代码可按下面的连接下载:

pcl+Qt可视化工程代码

 

更新

最近有小伙伴想把鼠标点选的点在控件中显示,而不是在控制台上显示。具体实现起来就犯难了,主要是回调函数不支持类普通成员函数,要么回调函数是全局函数,要么是类的静态成员函数。全局函数和类的静态成员函数都不支持发送信号,鼠标点选点更新后不知道如何才能通知到控件让其感知到,那么到底要怎样才能把点选点发送到具体的控件实例上呢显示呢?

最近抽空看了一下PCL提供的注册回调函数原型,发现其参数可以是函数对象,如下图所示。这样我们可以把类的成员函数封装成函数对象,然后就可以愉快地注册类的成员函数作为回调函数了。

 

关键代码如下:

    // 把类成员函数封装成函数对象
	std::function<void(const pcl::visualization::PointPickingEvent&)> fun = 
		std::bind(&PCLViewer::point_pick_callback, this, std::placeholders::_1);

	_viewer->registerPointPickingCallback(fun);

最后,我把点显示在QLineEdit控件上,最终具体效果如下图所示:

 

最后

工程代码中包含了我编译好的VTK8.2动态库,如果你想自己编译也可以,遇到的问题可在下面评论中指出,大家一起交流学习。

最后再次强调一下,PCL与依赖的第三方的库版本一定要匹配。

  • 15
    点赞
  • 104
    收藏
    觉得还不错? 一键收藏
  • 18
    评论
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值