目录
2. compute could/could distance 分析
一、缘起及环境
主要还是项目需要,同时整个项目已经初见雏形,所以对于接下来的一些功能性需求(非算法)实际上处理起来也就没什么压力了,
但毕竟年纪大了记忆不怎么好,所以主要就是记录一下,谨防遗忘。
之前已经做过了一些相关的分析,从做开始的一步步编译,到构建项目,及CC界面层qCC源码的分析及了解并做初步的魔改,相关参考如下:
CMAKE构建 QT5 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构建项目时候生成的文件