CloudCompare插件編寫三(算法實(shí)現(xiàn))

嘮叨

本文分三篇來介紹一個(gè)完整的CloudComapre插件的編寫教程寺枉,分別是插件框架篇數(shù)據(jù)結(jié)構(gòu)篇胳搞、算法實(shí)現(xiàn)篇卸例。

這是第三篇,算法實(shí)現(xiàn)篇肌毅,你可以根據(jù)本文改成自己的插件筷转,待卿臨幸。

qSAF源碼:Github . qSAF

前文概要

在上回中悬而,我們知道了點(diǎn)云中掃描角度的存儲(chǔ)結(jié)構(gòu)呜舒,下面我們來講qSAF的具體實(shí)現(xiàn)。

UI界面

新建QT設(shè)計(jì)器界面類笨奠,命名為ccSAFDlg袭蝗,在ccSAFDlg.ui文件設(shè)計(jì)簡單的界面。

因?yàn)槲覀冎恍枰粋€(gè)范圍般婆,一個(gè)確認(rèn)取消鍵到腥,所以我把它弄成這樣子:

doubleSpinBox要設(shè)置范圍:0.0090.00,默認(rèn)值分別設(shè)為20.0070.00腺兴。

ccSAFDlg.h

#ifndef CCSAFDLG_H
#define CCSAFDLG_H

#include "ui_SAFDlg.h"
#include <QDialog>

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"

//定義兩個(gè)靜態(tài)閾值左电,并初始化
static double threshold_1 = 20;
static double threshold_2 = 70;

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

    //關(guān)聯(lián)信號(hào)槽
    connect(buttonBox, SIGNAL(accepted()), this, SLOT(saveSettings()));

    //初始化設(shè)置閾值
    doubleSpinBox_1->setValue(threshold_1);
    doubleSpinBox_2->setValue(threshold_2);
}

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

現(xiàn)在界面就做好了。

插件doAction實(shí)現(xiàn)

至于doAction的實(shí)現(xiàn)页响,點(diǎn)云其中的數(shù)據(jù)結(jié)構(gòu)篓足,可以參考第二篇,數(shù)據(jù)結(jié)構(gòu)篇

簡單地說闰蚕,我們需要:

  1. Scan Angle Rank栈拖,通過getScalarFieldIndexByName()獲得掃描角度在標(biāo)量域中的索引
  2. 用索引,通過getScalarField()獲得掃描角度標(biāo)量域指針
  3. 用指針没陡,通過getValue()獲得每個(gè)點(diǎn)的值
  4. 比較掃描角度值與用戶輸入?yún)^(qū)間的大小涩哟,把合適的值存儲(chǔ)起來
  5. 把合適值封裝成點(diǎn)云實(shí)體
  6. 顯示在界面上

大體的算法思路上是沒有問題的,但是有個(gè)糾結(jié)的地方盼玄,就是是否使用進(jìn)度條贴彼。

實(shí)測SAF處理一個(gè)雷達(dá)文件,

  • 使用進(jìn)度條耗時(shí):129.1s
  • 不用進(jìn)度條耗時(shí):3.5s

這種壓倒性的差距讓我果斷砍掉真·進(jìn)度條埃儿,沒錯(cuò)器仗!我使用假·進(jìn)度條,就是不會(huì)動(dòng)的進(jìn)度條。

這樣短時(shí)間的處理使用假·進(jìn)度條精钮,既不會(huì)降低處理速度威鹿,也不會(huì)降低用戶體驗(yàn)~

下面就是完整代碼,注釋中有真·進(jìn)度條的實(shí)現(xiàn)([進(jìn)度條])轨香,但不推薦使用

void qSAF::doAction()
{
    //當(dāng)插件加載時(shí)忽你,m_app應(yīng)該已經(jīng)被CC初始化了
    assert(m_app);
    if (!m_app)
        return;

    //獲取選擇的實(shí)體
    const ccHObject::Container& selectedEntities = m_app->getSelectedEntities();
    //獲取選擇的實(shí)體數(shù)量
    size_t selNum = selectedEntities.size();
    //確保只選擇一個(gè)實(shí)體
    if (selNum != 1)
    {
        m_app->dispToConsole("[SAF] Select only one cloud!", ccMainAppInterface::ERR_CONSOLE_MESSAGE);
        return;
    }

    ccHObject* ent = selectedEntities[0];
    assert(ent);
    //確保選擇的實(shí)體是POINT_CLOUD類型
    if (!ent || !ent->isA(CC_TYPES::POINT_CLOUD))
    {
        m_app->dispToConsole("[SAF] Select a real point cloud!", ccMainAppInterface::ERR_CONSOLE_MESSAGE);
        return;
    }

    //從選擇的實(shí)體中轉(zhuǎn)換成ccPointCloud*類型
    ccPointCloud* pc = static_cast<ccPointCloud*>(ent);

    //獲取點(diǎn)云的數(shù)量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;
        }

        //存儲(chǔ)閾值
        threshold_1 = safDlg.doubleSpinBox_1->value();
        threshold_2 = safDlg.doubleSpinBox_2->value();
    }

    //顯示進(jìn)度條窗體
    QProgressDialog pDlg;
    pDlg.setWindowTitle("SAF");
    pDlg.setLabelText(QString("Scan Angle Filter\nfrom %1 to %2").arg(threshold_1).arg(threshold_2));
    //[進(jìn)度條]設(shè)置進(jìn)度條總范圍
    //pDlg.setRange(0, count);
    pDlg.setCancelButton(0);
    pDlg.show();
    QApplication::processEvents();
    
    QElapsedTimer timer;
    //計(jì)時(shí)開始
    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;
    }

    //[進(jìn)度條]進(jìn)度條的取消SAF按鈕
    //bool wasCancelled = false;

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

    //[重點(diǎn)]遍歷每個(gè)點(diǎn)的操作
    for(unsigned i = 0; i < count; ++i)
    {
        //獲取每個(gè)點(diǎn)的掃描角度
        scanAngle = pc->getScalarField(scanAngleSFIndex)->getValue(i);

        //取掃描角度的絕對值
        if(scanAngle < 0)
        {
            scanAngle = -scanAngle;
        }

        //如果掃描角度在給定的閾值范圍,則添加它的索引到參考云
        if(threshold_1 <= scanAngle && scanAngle <= threshold_2)
        {
            rangeAnglerc.addPointIndex(i);
        }

//        //[進(jìn)度條]重置進(jìn)度條
//        pDlg.setValue(i);
//        QCoreApplication::processEvents();

//        //[進(jìn)度條]取消SAF處理
//        if (pDlg.wasCanceled())
//        {
//            wasCancelled = true;
//            break;
//        }
    }

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

    //判斷rangeAnglepc是否為空臂容,即所選范圍內(nèi)是否有點(diǎn)
    if(!rangeAnglepc)
    {
        m_app->dispToConsole("[SAF] Failed to extract the range angle subset.", ccMainAppInterface::ERR_CONSOLE_MESSAGE);
        return;
    }
    //計(jì)算SAF后點(diǎn)數(shù)所占的百分比和SAF過程所花的時(shí)間
    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);

    //關(guān)閉進(jìn)度條
    pDlg.close();
    QApplication::processEvents();

//      //[進(jìn)度條]取消SAF    
//    if (wasCancelled)
//    {
//        m_app->dispToConsole("[SAF] SAF was cancelled", ccMainAppInterface::STD_CONSOLE_MESSAGE);
//        return;
//    }

    //隱藏原始點(diǎn)云
    pc->setEnabled(false);

    //添加新的一組DB實(shí)體
    ccHObject* cloudContainer = new ccHObject(pc->getName() + QString("_saf"));

    //設(shè)置新點(diǎn)云并添加到實(shí)體
    rangeAnglepc->setVisible(true);
    rangeAnglepc->setName("SAF Point Cloud");
    cloudContainer->addChild(rangeAnglepc);

    //添加實(shí)體到DB樹
    m_app->addToDB(cloudContainer);

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

效果

結(jié)語

經(jīng)過了三篇的學(xué)習(xí)科雳,終于實(shí)現(xiàn)了個(gè)完整的插件。

回顧我們學(xué)習(xí)的路線:插件框架 -> 數(shù)據(jù)結(jié)構(gòu) -> 算法實(shí)現(xiàn)

我們不僅從中學(xué)會(huì)了CC插件的編寫脓杉,也學(xué)到了QT的pro文件編寫炸渡、QT界面設(shè)計(jì)、CC運(yùn)作流程丽已、點(diǎn)云數(shù)據(jù)結(jié)構(gòu)等。

而我在學(xué)習(xí)這個(gè)插件編寫的過程收獲更多买决,因?yàn)槲沂强创a兩個(gè)月沛婴,寫代碼兩小時(shí),Debug兩天(差不多啦不要糾結(jié)為什么222

看代碼的過程是非常痛苦的督赤,CC里面大量的模板編程思想嘁灯,接口設(shè)計(jì)思想,還有去他繼承誰爸爸的爸爸……

但是期間確實(shí)學(xué)到很多躲舌,以此作為分享丑婿,望共勉!


我的博客:https://blog.huihut.com/
轉(zhuǎn)載請注明出處:http://blog.huihut.com/2017/04/27/CloudCompareSAFPlugin_3_Algorithm/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末没卸,一起剝皮案震驚了整個(gè)濱河市羹奉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌约计,老刑警劉巖诀拭,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異煤蚌,居然都是意外死亡耕挨,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進(jìn)店門尉桩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來筒占,“玉大人,你說我怎么就攤上這事蜘犁『采唬” “怎么了?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵沽瘦,是天一觀的道長革骨。 經(jīng)常有香客問我农尖,道長,這世上最難降的妖魔是什么良哲? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任盛卡,我火速辦了婚禮,結(jié)果婚禮上筑凫,老公的妹妹穿的比我還像新娘滑沧。我一直安慰自己,他們只是感情好巍实,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布滓技。 她就那樣靜靜地躺著,像睡著了一般棚潦。 火紅的嫁衣襯著肌膚如雪令漂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天丸边,我揣著相機(jī)與錄音叠必,去河邊找鬼。 笑死妹窖,一個(gè)胖子當(dāng)著我的面吹牛纬朝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播骄呼,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼共苛,長吁一口氣:“原來是場噩夢啊……” “哼耘戚!你這毒婦竟也來了催首?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤薯鼠,失蹤者是張志新(化名)和其女友劉穎绕德,沒想到半個(gè)月后患膛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡耻蛇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年踪蹬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片臣咖。...
    茶點(diǎn)故事閱讀 40,742評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡跃捣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出夺蛇,到底是詐尸還是另有隱情疚漆,我是刑警寧澤,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站娶聘,受9級(jí)特大地震影響闻镶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜丸升,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一铆农、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧狡耻,春花似錦墩剖、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至沼头,卻和暖如春爷绘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背进倍。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工揉阎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人背捌。 一個(gè)月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像洞斯,于是被迫代替她去往敵國和親毡庆。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,747評論 2 361

推薦閱讀更多精彩內(nèi)容