Pcl開發的應用中,當需要用戶對點雲做一些操作的時候,需要從屏幕拾取點雲中某點的三維座標。關於如何運用PCL實現這部分功能,很多介紹都是基於控制檯程序的,調用寫在main函數裏,但是大部分時候,我們需要把這些事情封裝在類中,這時候有一些小小的差別。
這次學習主要是記錄了對回調機制的理解,封裝pcl的選點的回調函數爲類的成員函數,完成拾取屏幕座標點的功能,也記錄下完整的類和調用部分的代碼。
一、關鍵函數(中間函數)
在理解代碼之前,要先明確三個概念:中間函數、回調函數、起始函數。
回調函數最好理解,是作爲參數被傳入的、後又被調用的函數。中間函數,是需要回調函數作爲參數的那個函數,在第三方庫中應用回調,通常是庫函數去擔任中間函數的角色。在傳入一個回調函數之前,中間函數是不完整的。換句話說,程序可以在運行時,通過登記不同的回調函數,來決定、改變中間函數的行爲,因此程序變得靈活。最後是起始函數,起始函數和回調函數是屬於同一層級的函數,是中間函數的調用者,雖然這個函數可能就一兩行,但是理解起始函數的作用對理解回調機制也很重要。
這三個部分共同作用,實現回調,有了高層(起始函數)調用底層(中間函數),底層再回過頭來調用高層(回調函數)的過程,應該就是"回調"的含義。
(一)點雲的點選功能
A.中間函數:registerPointPickingCallback
函數第二個參數涉及的類:pcl::visualization::PointPickingEvent,是我們寫回調函數的時候第一個參數的類類型。
(二)點雲的框選功能:
A.中間函數:registerAreaPickingCallback()
函數第二個參數涉及的類:pcl::visualization::AreaPickingEvent:,是我們寫回調函數的時候第一個參數的類類型。
B.bool pcl::visualization::AreaPickingEvent::getPointsIndices ( std::vector< int > & indices ) const
利用此函數可以獲取視窗中選擇的點雲數據的索引,根據索引又可以獲得確定的離散點數據。
二、封裝在類中
差別主要是registerKeyboardCallback函數的調用。在大部分的例子中,都是在控制檯程序的main函數中直接調用,只有兩個參數,非類中調用的函數原型;
registerKeyboardCallback (
void (*callback) (const pcl::visualization::KeyboardEvent&, void*),
void* cookie = NULL)
其中,KeyboardEvent是我們自定義的函數,在這裏它是一個回調函數。也就是是一個通過函數指針來調用的函數。需要把函數的指針(地址)作爲參數傳遞給另一個函數,用這個指針被用來調用其所指向的函數時。KeyboardEvent在裏面包含了我們在點擊、框選等動作時的處理聲明。
當keyboard_callback函數是類的一部分時,需要指定類的實例,以便編譯器能夠確定要使用keyboard_callback函數的實例。這裏調用的函數的參數傳遞有了差別,多了第二個參數 T& instance:調用這個中間函數的類的實例,可以傳this指針。
registerKeyboardCallback (
void (T::*callback) (const pcl::visualization::KeyboardEvent&, void*),
T& instance,
void* cookie = NULL)
三、完整代碼
1、.h
#pragma once
#include "stdafx.h"
#include "utility.h"
#include <fstream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl/visualization/cloud_viewer.h>
#include <iostream>
#include <pcl/io/io.h>
#include <pcl/io/ply_io.h>
#include <iostream>
#include <pcl/console/parse.h>
using namespace pcl;
typedef pcl::PointXYZ PointT;
typedef pcl::PointCloud<PointT> PointCloudT;
class PtPicking
{
public:
PtPicking(std::string filename);
void reset();
~PtPicking();
void keyboardEventOccurred(const visualization::KeyboardEvent &event, void* junk);
void areaPicking(); //框選點雲、
void pointPicking(); //單點選取
void ptActicPicking(); //屏幕選點
pcl::PointXYZ randomPoint();
void spin();
protected:
boost::mutex cloud_mutex;
int num = 0;
boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer;
pcl::PointCloud<pcl::PointXYZ>::Ptr baseCloud; //加載的原始點雲
pcl::PointCloud<pcl::PointXYZ>::Ptr clicked_points_3d; //被選中的點雲,可以用reset充值
void areaPicking_callback(const pcl::visualization::AreaPickingEvent & event, void * args);
void pointPicking_callback(const pcl::visualization::PointPickingEvent & event, void * args);
void PtActivePick_callback(const pcl::visualization::PointPickingEvent & event, void * args);
};
2、.cpp
#pragma once
#include "stdafx.h"
#include "PtPicking.h"
PtPicking::~PtPicking()
{
}
PtPicking::PtPicking(std::string filename) {
clicked_points_3d.reset(new pcl::PointCloud<PointT>);
baseCloud.reset(new pcl::PointCloud<PointT>());
if (pcl::io::loadPCDFile(filename, *baseCloud))
{
std::cerr << "ERROR: Cannot open file " << filename << "! Aborting..." << std::endl;
return;
}
reset();
};
void PtPicking:: reset()
{
// Create cloud
clicked_points_3d.reset(new pcl::PointCloud<PointT>);
// Create viewer
viewer.reset(new pcl::visualization::PCLVisualizer("viewer"));
viewer->addCoordinateSystem(1);
viewer->addPointCloud(baseCloud, "cloud");
viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "cloud");
}
void PtPicking::keyboardEventOccurred(const visualization::KeyboardEvent &event, void* junk) {
if (event.getKeySym() == "r" && event.keyDown()) {
baseCloud->push_back(randomPoint());
viewer->updatePointCloud(baseCloud, "cloud");
}
};
//框選事件的調用
void PtPicking::areaPicking()
{
// Register Keyboard Event
//viewer->registerKeyboardCallback(&dummyClass::keyboardEventOccurred, *this);
viewer->registerAreaPickingCallback(&PtPicking::areaPicking_callback, *this);//由於點雲數據寫成了成員變量,所以這裏第三個參數不用傳
std::cout << "press X to strat or ending picking, then press 'Q'..." << std::endl;
}
//點選的調用
void PtPicking::pointPicking()
{
cloud_mutex.lock(); // for not overwriting the point cloud
viewer->registerPointPickingCallback(&PtPicking::pointPicking_callback, *this);
std::cout << "Shift+click on three floor points, then press 'Q'..." << std::endl;
cloud_mutex.unlock();
}
//點選的調用(這個比較靈活,可選屏幕任意位置)
void PtPicking::ptActicPicking()
{
cloud_mutex.lock(); // for not overwriting the point cloud
viewer->registerPointPickingCallback(&PtPicking::PtActivePick_callback, *this);
std::cout << "Shift+click on three floor points, then press 'Q'..." << std::endl;
cloud_mutex.unlock();
}
//框選事件的回調函數,
//款選屏幕上的一部分點雲
//選擇方式:輸入一個x表示開始或者結束。兩次x輸入期間用鼠標左鍵框選點雲,可以多次框選。輸入q則腿粗選擇
void PtPicking::areaPicking_callback(const pcl::visualization::AreaPickingEvent& event, void *args)
{
struct callback_args * data = (struct callback_args *)args;//點雲數據 & 可視化窗口
std::vector<int > indices;
if (event.getPointsIndices(indices) == false)
return;
for (size_t i = 0; i < indices.size(); i++)
{
clicked_points_3d->points.push_back(baseCloud->points.at(indices[i]));
}
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> red(clicked_points_3d, 255, 0, 0);
viewer->removePointCloud("clicked_points");
viewer->addPointCloud(clicked_points_3d, red, "clicked_points");
viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 10, "clicked_points");
for (int i = 0; i < clicked_points_3d->points.size(); i++)
std::cout << clicked_points_3d->points[i].x << std::endl;
std::cout << "clicked_points_3d->points.size()" << clicked_points_3d->points.size() << std::endl;
}
//點選事件的回調函數
//點選屏幕上的點雲
//選擇方式:按住shift,鼠標左鍵點選
void PtPicking::pointPicking_callback(const pcl::visualization::PointPickingEvent& event, void *args)
{
struct callback_args * data = (struct callback_args *)args;//點雲數據 & 可視化窗口
if (event.getPointIndex() == -1)
return;
PointT current_point;
event.getPoint(current_point.x, current_point.y, current_point.z);
clicked_points_3d->points.push_back(current_point);
//Draw clicked points in red:
pcl::visualization::PointCloudColorHandlerCustom<PointT> red(clicked_points_3d, 255, 0, 0);
viewer->removePointCloud("clicked_points");
viewer->addPointCloud(clicked_points_3d, red, "clicked_points");
viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 10, "clicked_points");
std::cout << current_point.x << " " << current_point.y << " " << current_point.z << std::endl;
}
//點選事件的回調函數
//點選事件——點擊屏幕上的任一點(pointPicking_callback()是一定要點擊點雲數據上的點)
//選擇方式:按住shift,鼠標左鍵選擇。鍵盤輸入 Q 則退出選擇
void PtPicking::PtActivePick_callback(const pcl::visualization::PointPickingEvent& event, void *args)
{
std::cout << "Picking event active" << std::endl;
PointT current_point;
if (event.getPointIndex() != -1)
{
float x, y, z;
event.getPoint(current_point.x, current_point.y, current_point.z);
//std::cout << x << ";" << y << ";" << z << std::endl;
clicked_points_3d->points.push_back(current_point);
}
// Draw clicked points in red:
pcl::visualization::PointCloudColorHandlerCustom<PointT> red(clicked_points_3d, 255, 0, 0);
viewer->removePointCloud("clicked_points");
viewer->addPointCloud(clicked_points_3d, red, "clicked_points");
viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 10, "clicked_points");
std::cout << current_point.x << " " << current_point.y << " " << current_point.z << std::endl;
}
pcl::PointXYZ PtPicking::randomPoint() {
pcl::PointXYZ pt;
pt.x = (double)rand() / RAND_MAX * 10 - 5;
pt.y = (double)rand() / RAND_MAX * 10 - 5;
pt.z = (double)rand() / RAND_MAX * 10 - 5;
return pt;
};
void PtPicking::spin() {
viewer->spin();
};
3、調用的方法
調用時需要輸入pcd文件路徑
char filePCD[32];
printf("請輸入pcd文件路徑");
scanf("%s", filePCD);/*輸入文件名*/
PtPicking dc(filePCD);
//dc.pointPicking();
//dc.ptActicPicking();
dc.areaPicking();
dc.spin();