QT cmake cc级联pcl 魔改项目 二开记录 2

文章详细讲述了在QT5项目中使用CCLib库进行点云处理,包括SOR滤波、点云间距离计算,以及对CC文件加载和数据格式的深入分析。作者还分享了如何在QT中使用GridLayout布局和CMake构建项目的实践记录。
摘要由CSDN通过智能技术生成

目录

一、缘起及环境

二、所需功能

三、代码分析

1.SOR的分析,

2. compute could/could distance 分析

3.深入分析CC的文件加载及数据格式

四、数据显示的完整代码

五、QT的一些使用记录


一、缘起及环境

主要还是项目需要,同时整个项目已经初见雏形,所以对于接下来的一些功能性需求(非算法)实际上处理起来也就没什么压力了,

但毕竟年纪大了记忆不怎么好,所以主要就是记录一下,谨防遗忘。

之前已经做过了一些相关的分析,从做开始的一步步编译,到构建项目,及CC界面层qCC源码的分析及了解并做初步的魔改,相关参考如下:

CMAKE构建 QT5 CC PCL 点云相关-CSDN博客

QT cmake cc级联pcl 魔改项目-CSDN博客

基于QT CC 点云二开及魔改记录_gpp6025的博客-CSDN博客

二、所需功能

1.SOR  (Statistical Outlier Filter)

直译大概就是外轮廓的统计学过滤,实际应该就是通过统计学方式去噪。

2. compute could/could distance

直译大概就是计算点云和点云之间的距离,笔者的实际理解是将点云对齐后云点距计算,反正有点类似与配准的那意思味道。

3.采集数据显示,

这个实际就是通过传感器采集数据并通过CC显示,

有点SLAM的味道,不过没有SLAM的即时/实时性,只是一个模型构建的应用,总之就是采集空间中点云数据并在采集完成后构建出模型。

实际这个功能应该也是比较基本的功能需求。

三、代码分析

1.SOR的分析,

比较简单,主要就是CCLib核心库中的API 调用,如下:

/*
--SOR(SOR filter)---
--Statistical Outlier Filter (remove the points far from their neighbors)-分析-----
*/

mainwindows->actionSORFilter=>
MainWindow::doActionSORFilter()
{
	.....
	//输入参数对话框弹窗
	//进度条窗口
	.....
	//核心工具 依赖头文件 #include <CloudSamplingTools.h>
    CCLib::ReferenceCloud* selection = CCLib::CloudSamplingTools::sorFilter(cloud,
                                                                            s_sorFilterKnn,
                                                                            s_sorFilterNSigma,
                                                                            nullptr,
                                                                            &pDlg);


}
2. compute could/could distance 分析

相对也是比较简单,主要就是CCLib核心库中的API 调用,分析如下:

//mainwindows->actionCloudCloudDist=>
MainWindow::doActionCloudCloudDist(){
	.......
	/*------ccOrderChoiceDlg.h---比较简单的参数前置准备,可以优化掉这个对话框--*/
	ccOrderChoiceDlg dlg(	m_selectedEntities[0], "Compared",
                         /*m_selectedEntities[1]*/secondPc, "Reference",
                         this );
    if (!dlg.exec())
        return;
	.......
	//两幅点云图的 输入处理		dlg-》ccOrderChoiceDlg dlg
	ccGenericPointCloud* compCloud = ccHObjectCaster::ToGenericPointCloud(dlg.getFirstEntity());
	ccGenericPointCloud* refCloud = ccHObjectCaster::ToGenericPointCloud(dlg.getSecondEntity());
	....
	....
	/*依赖文件
	ccComparisonDlg.h--->ccHistogramWindow.h->ccQCustomPlot.h
	*/
	//比较设定对话框即
	m_compDlg = new ccComparisonDlg(compCloud, refCloud, ccComparisonDlg::CLOUDCLOUD_DIST, this);
	if (!m_compDlg->initDialog())//计算功能都在此对话框内完成
	........
	//设定对话框中处理完成后信号到deactivateComparisonMode()方法...
	connect(m_compDlg, &QDialog::finished, this, &MainWindow::deactivateComparisonMode);
	m_compDlg->show();
	
}

//比较执行的对话框中的内容流程
class ccComparisonDlg: public QDialog, public Ui::ComparisonDialog{

	computeButton;//计算执行按钮
	okButton;//完成后OK按钮
	connect(computeButton,&QPushButton::clicked,this,&ccComparisonDlg::computeDistances);
	connect(okButton,&QPushButton::clicked,this,&ccComparisonDlg::applyAndExit);

	inline bool initDialog() { return computeApproxDistances(); }
	bool computeApproxDistances(){
		......
		........
		.........
		updateDisplay(...){


		//	MainWindow::UpdateUI();
		//	MainWindow::RefreshAllGLWindow(false);		
		
		}
	}


	computeDistances(){
	
	
	}
	
	void applyAndExit(){
	
	}

}

如果仅是拿来使用,基本上不需要做什么变更,

但是笔者不需要相关的对话框输入过程,所以做了一些变更,

但就是这一变更就是直接踩坑了:唯一需要注意的点是输入输出参数的操作,特别是优化传入参数时候需要注意参数的转换问题。

3.深入分析CC的文件加载及数据格式

在实际应用中从传感器采集到的数据都是直接数据,不可能保存成文件后在重新载入,这样速度就太慢了,所以为了提高性能,就必须得要能直接显示数据了。

虽然前面有过对载入文件的分析,也是知道的主要是对 ccHObject 对象的创建和填充,

但,当时没有深入去分析如何进行创建及填充,以及对扩展插件的文件格式支持等调用机制实现

其内部宏条件编译的DXF\SHP\GDAL格式的支持

所以这一次就深入分析一下,开始对CC核心库中的东西做一次分析,这里主要是拿CC自带的ASCII格式做了分析,详细分析如下:

//-----------文件解析分析----------------------------
//注意调用是静态方法调用
ccHObject* newGroup = FileIoFilter::LoadFromFile(...);

//libs/qCC_io/FileIOFilter.cpp

FileIoFilter::LoadFromFile(const QString& filename,LoadParameters& loadParameters,CC_FILE_ERROR& result,const QString& fileFilter){
	Shared filter(nullptr);//过滤器的数据类型是 Shared

	if (!fileFilter.isEmpty())
	{
		filter = GetFilter(fileFilter, true);//过滤器的确定
		......
	}else{
		//过滤器的确定,CC自带的格式
		QString extension = QFileInfo(filename).suffix();//截取后缀名
		
		filter = FindBestFilterForExtension(extension);
		.........
	}

	..................

	//在此之前需要确定过滤器参数 filter
	return LoadFromFile(filename, loadParameters, filter, result);
}

/*
主要是对ccHObject对象的创建和填充,
核心由filter变量提供的loadFile方法完成,
filter在传入之前已经确定。
*/
ccHObject* FileIOFilter::LoadFromFile(const QString& filename,LoadParameters& loadParameters,Shared filter,CC_FILE_ERROR& result)
{
	..................
	ccHObject* container = new ccHObject();
	
	try
	{
		.........
		//核心loadFile方法;
		result = filter->loadFile(filename,*container,loadParameters);
	}
	...........
	
	return container;
}



FileIOFilter::Shared FileIOFilter::FindBestFilterForExtension(const QString& ext){
	const QString lowerExt = ext.toLower();
	
	for ( const auto &filter : s_ioFilters )//通过对 s_ioFilters 的遍历由文件后缀名获取对应的 过滤器实例
	{
		if ( filter->m_filterInfo.importExtensions.contains( lowerExt ) )
		{
			return filter;
		}
	}

	return FileIOFilter::Shared( nullptr );
}

//s_ioFilters  相关信息,是个静态变量,是个动态数组容器
static FileIOFilter::FilterContainer s_ioFilters;
//由Register 方法提供对此变量的插入
void FileIOFilter::Register(Shared filter)
{
	..................	
	s_ioFilters.insert( pos, filter );
}

//由此方法对CC自身支持的格式的过滤器进行注册,
/*
所以只要找对这些对象内部的loadFile方法,
看对ccHObject* container 的操作就可以得知其相关信息和填充内容
*/
void FileIOFilter::InitInternalFilters()
{
	//from the most useful to the less one!
	Register(Shared(new BinFilter()));
	Register(Shared(new AsciiFilter()));

	Register(Shared(new PlyFilter()));

#ifdef CC_DXF_SUPPORT
	Register(Shared(new DxfFilter()));
#endif
#ifdef CC_SHP_SUPPORT
	Register(Shared(new ShpFilter()));
#endif
#ifdef CC_GDAL_SUPPORT
	Register(Shared(new RasterGridFilter()));
#endif
	Register(Shared(new ImageFileFilter()));
	Register(Shared(new DepthMapFileFilter()));
}

//此处找个 AsciiFilter对象分析,应为是txt 文件格式比较方便
//libs/qCC_io/AsciiFilter.h
class QCC_IO_LIB_API AsciiFilter : public FileIOFilter{


CC_FILE_ERROR loadFile(const QString& filename, ccHObject& container, LoadParameters& parameters) override;
{
	//....一些文件名,
	//前置解析条件对话框交互选择的处理
	如果要对前置解析处理的对话框定制的话就对此AsciiOpenDlg对话框进行魔改吧
	AsciiOpenDlg* openDialog = GetOpenDialog(parameters.parentWidget);
	assert(openDialog);
	openDialog->setFilename(filename);
	.....
	....
	....
	//我们关心的是container 这个变量的操作
	return loadCloudFromFormatedAsciiFile(filename,container,openSequence,separator,commaAsDecimal,approximateNumberOfLines,
					fileSize,maxCloudSize,skipLineCount,parameters,showLabelsIn2D);
	
}

CC_FILE_ERROR AsciiFilter::loadCloudFromFormatedAsciiFile(const QString& filename,ccHObject& container,const AsciiOpenDlg::Sequence& openSequence,
						char separator,bool commaAsDecimal,unsigned approximateNumberOfLines,qint64 fileSize,unsigned maxCloudSize,
						unsigned skipLines,LoadParameters& parameters,bool showLabelsIn2D/*=false*/)
{
	//点云的相关属性描述对象....
	cloudAttributesDescriptor cloudDesc = prepareCloud(openSequence, cloudChunkSize, maxPartIndex, chunkRank);
	//只读打开文件解析
	............
	...........
	//解析进度条,如有需要可对其魔改
	QScopedPointer<ccProgressDialog> pDlg(nullptr);
	if (parameters.parentWidget)
	{
		pDlg.reset(new ccProgressDialog(true, parameters.parentWidget));
		pDlg->setMethodTitle(QObject::tr("Open ASCII file [%1]").arg(filename));
		pDlg->setInfo(QObject::tr("Approximate number of points: %1").arg(approximateNumberOfLines));
		pDlg->start();
	}
	CCLib::NormalizedProgress nprogress(pDlg.data(), approximateNumberOfLines);
	............
	...........
	while (true)//注意此处是在循环中,
	{
		QString currentLine = stream.readLine();//是在按行操作
		............
		...........
		if (!cloudDesc.cloud->reserve(cloudChunkSize))
		if (!cloudDesc.cloud->resize(cloudChunkSize))
		...........
		..........
		cloudDesc.cloud->setCurrentDisplayedScalarField(0);
		cloudDesc.cloud->showSF(true);
		//我们需要的关键操作,回看上方对cloudDesc变量的操作,是一些结构操作
		container.addChild(cloudDesc.cloud);
		cloudDesc.reset();//重置之后-----注意整个逻辑,下方close 文件后又对 cloudDesc.cloud 做了此操作
		...........
		cloudDesc.cloud->setGlobalShift(Pshift);
		............
		//插入点
		cloudDesc.cloud->addPoint(CCVector3::fromArray((P + Pshift).u));
		//法向量
		if (cloudDesc.hasNorms)
		{
			if (cloudDesc.xNormIndex >= 0)
				N.x = static_cast<PointCoordinateType>(locale.toDouble(parts[cloudDesc.xNormIndex]));
			if (cloudDesc.yNormIndex >= 0)
				N.y = static_cast<PointCoordinateType>(locale.toDouble(parts[cloudDesc.yNormIndex]));
			if (cloudDesc.zNormIndex >= 0)
				N.z = static_cast<PointCoordinateType>(locale.toDouble(parts[cloudDesc.zNormIndex]));

			cloudDesc.cloud->addNorm(N);
		}

		//加入颜色的操作
		........
		cloudDesc.cloud->addColor(col);

	}
	file.close();
	//文件中点全部读取完毕并关闭文件后最后操作
	if (cloudDesc.cloud)
	{
		...................
		for (size_t j = 0; j < cloudDesc.scalarFields.size(); ++j)
		{
			cloudDesc.scalarFields[j]->resizeSafe(cloudDesc.cloud->size(), true, NAN_VALUE);
			cloudDesc.scalarFields[j]->computeMinAndMax();
		}
		cloudDesc.cloud->setCurrentDisplayedScalarField(0);
		cloudDesc.cloud->showSF(true);

		//如果又标签索引
		cloudDesc.cloud->filterChildren(labels, false, CC_TYPES::LABEL_2D, true);
		.........................
		//关键 
		container.addChild(cloudDesc.cloud);
	}
	
}


}

一大堆的源码细节,实际是简单的几行代码就能完成了,

当然这个分析过程是为更加充分的了解CC的内部的一些详细的实现机制与细节的,

还是有必要的。

四、数据显示的完整代码

这个对于初探CC的功能比较有用,有了上面的深入分析也就明白了ccHObject 对象的创建和填充,实现的完整代码如下:

typedef struct _pointCouldp {
	float x;
	float y;
	float z;
}rxsPointCouldp;



short MainWindow::myPointCouldDataShow(rxsPointCouldp *rpc,unsigned pointNums){

    ccHObject* ceObj = new ccHObject();
    ccPointCloud* loadedCloud = new ccPointCloud("pcrTester");
    if (!loadedCloud)
        return 10;//CC_FERR_NOT_ENOUGH_MEMORY;

    //loadedCloud->showColors(true);
    for(unsigned x=0;x< pointNums;x++){
        float *Pf = (float *)&rpc[x];
        loadedCloud->addPoint(CCVector3::fromArray(Pf));
        //        ccColor::Rgb C;
        //        loadedCloud->addColor(C);
    }
    ceObj->addChild(loadedCloud);
    addToDB(ceObj);
    return 0;
}

以上是不带颜色的基本点云显示。如下随机点云测试下效果:

五、QT的一些使用记录

GridLayout

当只有一个窗体 Widget 会进行铺满效果,否则按栅格布局,横行数列的方式,可以配合水平/垂直弹簧占位 使用

build.ninja  qt通过cmake构建项目时候生成的文件

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值