CloudCompare插件编写三(算法实现)

唠叨

本文分三篇来介绍一个完整的CloudComapre插件的编写教程,分别是插件框架篇、数据结构篇、算法实现篇。

这是第三篇,算法实现篇,你可以根据本文改成自己的插件,待卿临幸。

特别注意:本文的CloudCompare源码构建的是Qt工程并使用Qt Creator开发,并不是Visual Studio。

qSAF源码:Github . qSAF

前文概要

在上回中,我们知道了点云中扫描角度的存储结构,下面我们来讲qSAF的具体实现。

UI界面

新建QT设计器界面类,命名为ccSAFDlg,在ccSAFDlg.ui文件设计简单的界面。

因为我们只需要一个范围,一个确认取消键,所以我把它弄成这样子:

CloudCompare插件编写三(算法实现)_第1张图片

doubleSpinBox要设置范围:0.0090.00,默认值分别设为20.0070.00

ccSAFDlg.h

#ifndef CCSAFDLG_H
#define CCSAFDLG_H

#include "ui_SAFDlg.h"
#include 

namespace Ui {
class ccSAFDlg;
}

class ccSAFDlg : public QDialog, public Ui::ccSAFDlg
{
	Q_OBJECT

public:
	explicit ccSAFDlg(QWidget *parent = 0);

protected slots:

	//! Saves (temporarily) the dialog paramters on acceptation
	void saveSettings();
};

#endif // CCSAFDLG_H

ccSAFDlg.cpp

#include "ccSAFDlg.h"

//定义两个静态阈值,并初始化
static double threshold_1 = 20;
static double threshold_2 = 70;

ccSAFDlg::ccSAFDlg(QWidget *parent) : QDialog(parent), Ui::ccSAFDlg()
{
	setupUi(this);

	//关联信号槽
	connect(buttonBox, SIGNAL(accepted()), this, SLOT(saveSettings()));

	//初始化设置阈值
	doubleSpinBox_1->setValue(threshold_1);
	doubleSpinBox_2->setValue(threshold_2);
}

void ccSAFDlg::saveSettings()
{
	//OK后重新赋值
	threshold_1 = doubleSpinBox_1->value();
	threshold_2 = doubleSpinBox_2->value();
}

现在界面就做好了。

插件doAction实现

至于doAction的实现,点云其中的数据结构,可以参考第二篇,数据结构篇

简单地说,我们需要:

  1. Scan Angle Rank,通过getScalarFieldIndexByName()获得扫描角度在标量域中的索引
  2. 用索引,通过getScalarField()获得扫描角度标量域指针
  3. 用指针,通过getValue()获得每个点的值
  4. 比较扫描角度值与用户输入区间的大小,把合适的值存储起来
  5. 把合适值封装成点云实体
  6. 显示在界面上

大体的算法思路上是没有问题的,但是有个纠结的地方,就是是否使用进度条。

实测SAF处理一个雷达文件,

  • 使用进度条耗时:129.1s
  • 不用进度条耗时:3.5s

这种压倒性的差距让我果断砍掉真·进度条,没错!我使用假·进度条,就是不会动的进度条。

这样短时间的处理使用假·进度条,既不会降低处理速度,也不会降低用户体验~

下面就是完整代码,注释中有真·进度条的实现([进度条]),但不推荐使用

void qSAF::doAction()
{
	//当插件加载时,m_app应该已经被CC初始化了
	assert(m_app);
	if (!m_app)
		return;

	//获取选择的实体
	const ccHObject::Container& selectedEntities = m_app->getSelectedEntities();
	//获取选择的实体数量
	size_t selNum = selectedEntities.size();
	//确保只选择一个实体
	if (selNum != 1)
	{
    	m_app->dispToConsole("[SAF] Select only one cloud!", ccMainAppInterface::ERR_CONSOLE_MESSAGE);
    	return;
	}

	ccHObject* ent = selectedEntities[0];
	assert(ent);
	//确保选择的实体是POINT_CLOUD类型
	if (!ent || !ent->isA(CC_TYPES::POINT_CLOUD))
	{
    	m_app->dispToConsole("[SAF] Select a real point cloud!", ccMainAppInterface::ERR_CONSOLE_MESSAGE);
    	return;
	}

	//从选择的实体中转换成ccPointCloud*类型
	ccPointCloud* pc = static_cast(ent);

	//获取点云的数量m_count
	unsigned count = pc->size();

	//初始化阈值变量
	static double threshold_1 = 20;
	static double threshold_2 = 70;
	double threshold_temp = 0;

	//显示插件ui窗体
	{
    	ccSAFDlg safDlg(m_app->getMainWindow());
    	safDlg.doubleSpinBox_1->setValue(threshold_1);
    	safDlg.doubleSpinBox_2->setValue(threshold_2);

    	if(!safDlg.exec())
    	{
        	return;
    	}

    	//存储阈值
    	threshold_1 = safDlg.doubleSpinBox_1->value();
    	threshold_2 = safDlg.doubleSpinBox_2->value();
	}

	//显示进度条窗体
	QProgressDialog pDlg;
	pDlg.setWindowTitle("SAF");
	pDlg.setLabelText(QString("Scan Angle Filter\nfrom %1 to %2").arg(threshold_1).arg(threshold_2));
	//[进度条]设置进度条总范围
	//pDlg.setRange(0, count);
	pDlg.setCancelButton(0);
	pDlg.show();
	QApplication::processEvents();
	
	QElapsedTimer timer;
	//计时开始
	timer.start();

	ScalarType scanAngle;

	CCLib::ReferenceCloud rangeAnglerc(pc);

	//确保 threshold_1 小于 threshold_2
	if(threshold_1 > threshold_2)
	{
    	threshold_temp = threshold_1;
    	threshold_1 = threshold_2;
    	threshold_2 = threshold_temp;
	}

	//[进度条]进度条的取消SAF按钮
	//bool wasCancelled = false;

	//获取 Scan Angle Rank 的索引
	int scanAngleSFIndex = pc->getScalarFieldIndexByName("Scan Angle Rank");

	//[重点]遍历每个点的操作
	for(unsigned i = 0; i < count; ++i)
	{
    	//获取每个点的扫描角度
    	scanAngle = pc->getScalarField(scanAngleSFIndex)->getValue(i);

    	//取扫描角度的绝对值
    	if(scanAngle < 0)
    	{
        	scanAngle = -scanAngle;
    	}

    	//如果扫描角度在给定的阈值范围,则添加它的索引到参考云
    	if(threshold_1 <= scanAngle && scanAngle <= threshold_2)
    	{
        	rangeAnglerc.addPointIndex(i);
    	}

//        //[进度条]重置进度条
//        pDlg.setValue(i);
//        QCoreApplication::processEvents();

//        //[进度条]取消SAF处理
//        if (pDlg.wasCanceled())
//        {
//            wasCancelled = true;
//            break;
//        }
	}

	//把 ReferenceCloud 类型克隆成 ccPointCloud 类型
	ccPointCloud* rangeAnglepc = pc->partialClone(&rangeAnglerc);

	//判断rangeAnglepc是否为空,即所选范围内是否有点
	if(!rangeAnglepc)
	{
    	m_app->dispToConsole("[SAF] Failed to extract the range angle subset.", ccMainAppInterface::ERR_CONSOLE_MESSAGE);
    	return;
	}
	//计算SAF后点数所占的百分比和SAF过程所花的时间
	m_app->dispToConsole(QString("[SAF] %1% of scan angle points are filtered").arg((rangeAnglerc.size() * 100.0) / count, 0, 'f', 2), ccMainAppInterface::STD_CONSOLE_MESSAGE);
	m_app->dispToConsole(QString("[SAF] Timing: %1 s.").arg(timer.elapsed() / 1000.0, 0, 'f', 1), ccMainAppInterface::STD_CONSOLE_MESSAGE);

	//关闭进度条
	pDlg.close();
	QApplication::processEvents();

//		//[进度条]取消SAF	
//    if (wasCancelled)
//    {
//        m_app->dispToConsole("[SAF] SAF was cancelled", ccMainAppInterface::STD_CONSOLE_MESSAGE);
//        return;
//    }

	//隐藏原始点云
	pc->setEnabled(false);

	//添加新的一组DB实体
	ccHObject* cloudContainer = new ccHObject(pc->getName() + QString("_saf"));

	//设置新点云并添加到实体
	rangeAnglepc->setVisible(true);
	rangeAnglepc->setName("SAF Point Cloud");
	cloudContainer->addChild(rangeAnglepc);

	//添加实体到DB树
	m_app->addToDB(cloudContainer);

	//刷新
	m_app->refreshAll();
}

效果

CloudCompare插件编写三(算法实现)_第2张图片

结语

经过了三篇的学习,终于实现了个完整的插件。

回顾我们学习的路线:插件框架 -> 数据结构 -> 算法实现

我们不仅从中学会了CC插件的编写,也学到了QT的pro文件编写、QT界面设计、CC运作流程、点云数据结构等。

而我在学习这个插件编写的过程收获更多,因为我是看代码两个月,写代码两小时,Debug两天(差不多啦不要纠结为什么222

看代码的过程是非常痛苦的,CC里面大量的模板编程思想,接口设计思想,还有去他继承谁爸爸的爸爸……

但是期间确实学到很多,以此作为分享,望共勉!

你可能感兴趣的:(Qt,CloudCompa)