利用PCL庫從點雲數據生成生成深度圖像及關鍵點提取
利用PCL庫從點雲數據生成深度圖像及關鍵點提取
本想利用標準點雲數據庫分割成若干塊,利用標準點雲數據生成深度圖像作爲數據庫用來驗證算法,目前效果不是很好。不過作爲一種特徵點提取方法還是有參考價值。
1.點雲–深度圖像
可視化深度圖像:
- 在3D viewer中以點雲的方式顯示。
- 另一種方式是作爲一幅圖像顯示(以不同的顏色表示不同的深度值)
定義:
- 深度圖像的每個像素點的灰度值可用於表徵場景中某一點距離攝像機的遠近。 直接反應了景物可見表面的幾何形狀。
- 深度圖像經過座標轉換可以計算爲點雲數據,點雲數據也可以轉換爲深度圖像。
2.PCL相關函數
- 所在頭文件:#include <pcl/range_image/range_image.h>
- PCL類:RangeImage是一個工具類,用於在特定視角捕捉的3D場景。
- 函數:
template <typename PointCloudType> void
RangeImage::createFromPointCloud (const PointCloudType& point_cloud, float angular_resolution,
float max_angle_width, float max_angle_height,
const Eigen::Affine3f& sensor_pose, RangeImage::CoordinateFrame coordinate_frame,
float noise_level, float min_range, int border_size)
{
createFromPointCloud (point_cloud, angular_resolution, angular_resolution, max_angle_width, max_angle_height,
sensor_pose, coordinate_frame, noise_level, min_range, border_size);
}
參數 | 描述 |
---|---|
point_cloud | 輸入的點雲數據 |
angular_resolution | 圖像中各個像素之間的角差(弧度)。 角差是一次信號與二次信號的相位之差。 |
max_angle_width | 定義傳感器水平邊界的角度(弧度)。 |
max_angle_height | 定義傳感器垂直邊界的角度(弧度)。 |
sensor_pose | 傳感器姿態的變換矩陣 |
coordinate_frame | 座標系統 (默認爲相機座標系 CAMERA_FRAME) |
noise_level | 近鄰點對查詢點距離的影響水平 |
min_range | 最小可視深度 (defaults to 0) |
border_size | 點雲邊界的尺寸大小 (defaults to 0) |
3.例程(導入自己PCD文件有問題,下個例程講到)
/*
NARF
從深度圖像(RangeImage)中提取NARF關鍵點
1. 邊緣提取
對點雲而言,場景的邊緣代表前景物體和背景物體的分界線。
所以,點雲的邊緣又分爲三種:
前景邊緣,背景邊緣,陰影邊緣。
就是點a 和點b 如果在 rangImage 上是相鄰的,然而在三維距離上卻很遠,那麼多半這裏就有邊緣。
在提取關鍵點時,
邊緣應該作爲一個重要的參考依據。
但一定不是唯一的依據。
對於某個物體來說關鍵點應該是表達了某些特徵的點,而不僅僅是邊緣點。
所以在設計關鍵點提取算法時,需要考慮到以下一些因素:
邊緣和曲面結構都要考慮進去;
關鍵點要能重複;
關鍵點最好落在比較穩定的區域,方便提取法線。
圖像的Harris角點算子將圖像的關鍵點定義爲角點。
角點也就是物體邊緣的交點,
harris算子利用角點在兩個方向的灰度協方差矩陣響應都很大,來定義角點。
既然關鍵點在二維圖像中已經被成功定義且使用了,
看來在三維點雲中可以沿用二維圖像的定義
不過今天要講的是另外一種思路,簡單粗暴,
直接把三維的點雲投射成二維的圖像不就好了。
這種投射方法叫做range_image.
*/
#include <iostream>//標準輸入輸出流
#include <boost/thread/thread.hpp>
#include <pcl/range_image/range_image.h>// RangeImage 深度圖像
#include <pcl/io/pcd_io.h>//PCL的PCD格式文件的輸入輸出頭文件
#include <pcl/visualization/range_image_visualizer.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/features/range_image_border_extractor.h>
#include <pcl/keypoints/narf_keypoint.h>//關鍵點檢測
#include <pcl/console/parse.h>//解析 命令行 參數
//定義別名
typedef pcl::PointXYZ PointType;
// --------------------
// -----參數 Parameters-----
// --------------------
//參數 全局變量
float angular_resolution = 0.5f;//角座標分辨率
float support_size = 0.2f;//感興趣點的尺寸(球面的直徑)
pcl::RangeImage::CoordinateFrame coordinate_frame = pcl::RangeImage::CAMERA_FRAME;//座標框架:相機框架(而不是激光框架)
bool setUnseenToMaxRange = false;//是否將所有不可見的點 看作 最大距離
// --------------
// -----打印幫助信息 Help-----
// --------------
//當用戶輸入命令行參數-h,打印幫助信息
void
printUsage (const char* progName)
{
std::cout << "\n\n用法 Usage: "<<progName<<" [options] <scene.pcd>\n\n"
<< "Options:\n"
<< "-------------------------------------------\n"
<< "-r <float> 角度 angular resolution in degrees (default "<<angular_resolution<<")\n"
<< "-c <int> 座標系 coordinate frame (default "<< (int)coordinate_frame<<")\n"
<< "-m Treat all unseen points as maximum range readings\n"
<< "-s <float> support size for the interest points (diameter of the used sphere - "
<< "default "<<support_size<<")\n"
<< "-h this help\n"
<< "\n\n";
}
//void
//setViewerPose (pcl::visualization::PCLVisualizer& viewer, const Eigen::Affine3f& viewer_pose)
//{
//Eigen::Vector3f pos_vector = viewer_pose * Eigen::Vector3f (0, 0, 0);
//Eigen::Vector3f look_at_vector = viewer_pose.rotation () * Eigen::Vector3f (0, 0, 1) + pos_vector;
//Eigen::Vector3f up_vector = viewer_pose.rotation () * Eigen::Vector3f (0, -1, 0);
//viewer.setCameraPosition (pos_vector[0], pos_vector[1], pos_vector[2],
//look_at_vector[0], look_at_vector[1], look_at_vector[2],
//up_vector[0], up_vector[1], up_vector[2]);
//}
// --------------
// -----Main-----
// --------------
int
main (int argc, char** argv)
{
// --------------------------------------
// ----- 解析 命令行 參數 Parse Command Line Arguments-----
// --------------------------------------
if (pcl::console::find_argument (argc, argv, "-h") >= 0)//help參數
{
printUsage (argv[0]);//程序名
return 0;
}
if (pcl::console::find_argument (argc, argv, "-m") >= 0)
{
setUnseenToMaxRange = true;//將所有不可見的點 看作 最大距離
cout << "Setting unseen values in range image to maximum range readings.\n";
}
int tmp_coordinate_frame;//座標框架:相機框架(而不是激光框架)
if (pcl::console::parse (argc, argv, "-c", tmp_coordinate_frame) >= 0)
{
coordinate_frame = pcl::RangeImage::CoordinateFrame (tmp_coordinate_frame);
cout << "Using coordinate frame "<< (int)coordinate_frame<<".\n";
}
// 感興趣點的尺寸(球面的直徑)
if (pcl::console::parse (argc, argv, "-s", support_size) >= 0)
cout << "Setting support size to "<<support_size<<".\n";
// 角座標分辨率
if (pcl::console::parse (argc, argv, "-r", angular_resolution) >= 0)
cout << "Setting angular resolution to "<<angular_resolution<<"deg.\n";
angular_resolution = pcl::deg2rad (angular_resolution);
// ------------------------------------------------------------------
// -----Read pcd file or create example point cloud if not given-----
// ------------------------------------------------------------------
//讀取pcd文件;如果沒有指定文件,就創建樣本點
pcl::PointCloud<PointType>::Ptr point_cloud_ptr(new pcl::PointCloud<PointType>);//點雲對象指針
pcl::PointCloud<PointType>& point_cloud = *point_cloud_ptr;//引用 上面點雲的別名 常亮指針
pcl::PointCloud<pcl::PointWithViewpoint> far_ranges;//帶視角的點雲
Eigen::Affine3f scene_sensor_pose (Eigen::Affine3f::Identity ());//仿射變換
//檢查參數中是否有pcd格式文件名,返回參數向量中的索引號
std::vector<int> pcd_filename_indices = pcl::console::parse_file_extension_argument (argc, argv, "pcd");
if (!pcd_filename_indices.empty())
{
std::string filename = argv[pcd_filename_indices[0]];
if (pcl::io::loadPCDFile (filename, point_cloud) == -1)//如果指定了pcd文件,讀取pcd文件
{
std::cerr << "Was not able to open file \""<<filename<<"\".\n";
printUsage (argv[0]);
return 0;
}
//設置傳感器的姿勢
scene_sensor_pose = Eigen::Affine3f (Eigen::Translation3f (point_cloud.sensor_origin_[0],
point_cloud.sensor_origin_[1],
point_cloud.sensor_origin_[2])) *
Eigen::Affine3f (point_cloud.sensor_orientation_);
//讀取遠距離文件?
std::string far_ranges_filename = pcl::getFilenameWithoutExtension (filename)+"_far_ranges.pcd";
if (pcl::io::loadPCDFile (far_ranges_filename.c_str (), far_ranges) == -1)
std::cout << "Far ranges file \""<<far_ranges_filename<<"\" does not exists.\n";
}
else//沒有指定pcd文件,生成點雲,並填充它
{
setUnseenToMaxRange = true;//將所有不可見的點 看作 最大距離
cout << "\nNo *.pcd file given => Genarating example point cloud.\n\n";
for (float x=-0.5f; x<=0.5f; x+=0.01f)
{
for (float y=-0.5f; y<=0.5f; y+=0.01f)
{
PointType point; point.x = x; point.y = y; point.z = 2.0f - y;
point_cloud.points.push_back (point);//設置點雲中點的座標
}
}
point_cloud.width = (int) point_cloud.points.size ();
point_cloud.height = 1;
}
// -----------------------------------------------
// -----Create RangeImage from the PointCloud-----
// -----------------------------------------------
// 從點雲數據,創建深度圖像
// 直接把三維的點雲投射成二維的圖像
float noise_level = 0.0;
//noise level表示的是容差率,因爲1°X1°的空間內很可能不止一個點,
//noise level = 0則表示去最近點的距離作爲像素值,如果=0.05則表示在最近點及其後5cm範圍內求個平均距離
//minRange表示深度最小值,如果=0則表示取1°X1°的空間內最遠點,近的都忽略
float min_range = 0.0f;
//bordersieze表示圖像周邊點
int border_size = 1;
boost::shared_ptr<pcl::RangeImage> range_image_ptr (new pcl::RangeImage);//創建RangeImage對象(智能指針)
pcl::RangeImage& range_image = *range_image_ptr; //RangeImage的引用
//從點雲創建深度圖像
//rangeImage也是PCL的基本數據結構
//pcl::RangeImage rangeImage;
// 球座標系
//角分辨率
//float angularResolution = (float) ( 1.0f * (M_PI/180.0f)); // 1.0 degree in radians 弧度
//phi可以取360°
// float maxAngleWidth = (float) (360.0f * (M_PI/180.0f)); // 360.0 degree in radians
//a取180°
// float maxAngleHeight = (float) (180.0f * (M_PI/180.0f)); // 180.0 degree in radians
//半圓掃一圈就是整個圖像了
range_image.createFromPointCloud (point_cloud, angular_resolution, pcl::deg2rad (360.0f), pcl::deg2rad (180.0f),
scene_sensor_pose, coordinate_frame, noise_level, min_range, border_size);
range_image.integrateFarRanges (far_ranges);//整合遠距離點雲
if (setUnseenToMaxRange)
range_image.setUnseenToMaxRange ();
// --------------------------------------------
// -----Open 3D viewer and add point cloud-----
// --------------------------------------------
// 3D點雲顯示
pcl::visualization::PCLVisualizer viewer ("3D Viewer");
viewer.setBackgroundColor (1, 1, 1);//背景顏色 白色
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointWithRange> range_image_color_handler (range_image_ptr, 0, 0, 0);
viewer.addPointCloud (range_image_ptr, range_image_color_handler, "range image");//添加點雲
viewer.setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "range image");
//viewer.addCoordinateSystem (1.0f, "global");
//PointCloudColorHandlerCustom<PointType> point_cloud_color_handler (point_cloud_ptr, 150, 150, 150);
//viewer.addPointCloud (point_cloud_ptr, point_cloud_color_handler, "original point cloud");
viewer.initCameraParameters ();
//setViewerPose (viewer, range_image.getTransformationToWorldSystem ());
// --------------------------
// -----Show range image-----
// --------------------------
//顯示深度圖像(平面圖)
pcl::visualization::RangeImageVisualizer range_image_widget ("Range image");
range_image_widget.showRangeImage (range_image);
// --------------------------------
// -----Extract NARF keypoints-----
// --------------------------------
// 提取NARF關鍵點
pcl::RangeImageBorderExtractor range_image_border_extractor;//創建深度圖像的邊界提取器,用於提取NARF關鍵點
pcl::NarfKeypoint narf_keypoint_detector (&range_image_border_extractor);//創建NARF對象
narf_keypoint_detector.setRangeImage (&range_image);//設置點雲對應的深度圖
narf_keypoint_detector.getParameters ().support_size = support_size;// 感興趣點的尺寸(球面的直徑)
//narf_keypoint_detector.getParameters ().add_points_on_straight_edges = true;
//narf_keypoint_detector.getParameters ().distance_for_additional_points = 0.5;
pcl::PointCloud<int> keypoint_indices;//用於存儲關鍵點的索引
narf_keypoint_detector.compute (keypoint_indices);//計算NARF關鍵
std::cout << "Found找到關鍵點: "<<keypoint_indices.points.size ()<<" key points.\n";
// ----------------------------------------------
// -----Show keypoints in range image widget-----
// ----------------------------------------------
//在range_image_widget中顯示關鍵點
//for (size_t i=0; i<keypoint_indices.points.size (); ++i)
//range_image_widget.markPoint (keypoint_indices.points[i]%range_image.width,
//keypoint_indices.points[i]/range_image.width);
// -------------------------------------
// -----Show keypoints in 3D viewer-----
// -------------------------------------
//在3D圖形窗口中顯示關鍵點
pcl::PointCloud<pcl::PointXYZ>::Ptr keypoints_ptr (new pcl::PointCloud<pcl::PointXYZ>);//創建關鍵點指針
pcl::PointCloud<pcl::PointXYZ>& keypoints = *keypoints_ptr;//引用
keypoints.points.resize (keypoint_indices.points.size ());//初始化大小
for (size_t i=0; i<keypoint_indices.points.size (); ++i)//按照索引獲得 關鍵點
keypoints.points[i].getVector3fMap () = range_image.points[keypoint_indices.points[i]].getVector3fMap ();
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> keypoints_color_handler (keypoints_ptr, 0, 255, 0);
viewer.addPointCloud<pcl::PointXYZ> (keypoints_ptr, keypoints_color_handler, "keypoints");//添加顯示關鍵點
viewer.setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 7, "keypoints");
//--------------------
// -----Main loop-----
//--------------------
while (!viewer.wasStopped ())
{
range_image_widget.spinOnce (); // process GUI events 處理 GUI事件
viewer.spinOnce ();
pcl_sleep(0.01);
}
}
深度圖 結果:
三維信息及關鍵點:
上述是利用例程中創建的樣本點,如如若導入我們自己的PCD點雲文件,則會出錯
原因是例程中使用了參考遠距離文件 "_far_ranges.pcd"
另有: 導入本地PCD文件方法(兩種方法,選一種即可)
- 項目-屬性-調試-命令參數:寫入PCD文件名。
- 在debug文件夾中打開CMD命令框,輸入 **.exe nomslpcd.pcd ,注意中間有個空格,同時PCD文件要在同文件夾下。
4.更改後例程(利用最遠處距離去除參考距離)
#include <iostream> //標準輸入/輸出
#include <boost/thread/thread.hpp> //多線程
#include <pcl/common/common_headers.h>
#include <pcl/range_image/range_image.h> //深度圖有關頭文件
#include <pcl/io/pcd_io.h> //pcd文件輸入/輸出
#include <pcl/visualization/range_image_visualizer.h> //深度圖可視化
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/console/parse.h> //命令行參數解析
#include <pcl/visualization/common/float_image_utils.h>
#include <pcl/io/png_io.h>
typedef pcl::PointXYZ PointType;
//參數 全局
float angular_resolution_x = 0.5f, //角分辨率(單位弧度)
angular_resolution_y = angular_resolution_x;
pcl::RangeImage::CoordinateFrame coordinate_frame = pcl::RangeImage::CAMERA_FRAME; //座標幀(相機幀)
bool live_update = true; //是否根據選擇的視角更新深度圖像
// 打印幫助信息
void printUsage(const char* progName)
{
std::cout << "\n\nUsage: " << progName << " [options] <scene.pcd>\n\n"
<< "Options:\n"
<< "-------------------------------------------\n"
<< "-rx <float> angular resolution in degrees (default " << angular_resolution_x << ")\n"
<< "-ry <float> angular resolution in degrees (default " << angular_resolution_y << ")\n"
<< "-c <int> coordinate frame (default " << (int)coordinate_frame << ")\n"
<< "-l live update - update the range image according to the selected view in the 3D viewer.\n"
<< "-h this help\n"
<< "\n\n";
}
/*
void setViewerPose (pcl::visualization::PCLVisualizer& viewer, const Eigen::Affine3f& viewer_pose)
{
Eigen::Vector3f pos_vector = viewer_pose * Eigen::Vector3f(0, 0, 0);
Eigen::Vector3f look_at_vector = viewer_pose.rotation () * Eigen::Vector3f(0, 0, 1) + pos_vector;
Eigen::Vector3f up_vector = viewer_pose.rotation () * Eigen::Vector3f(0, -1, 0);
viewer.setCameraPosition (pos_vector[0], pos_vector[1], pos_vector[2],
look_at_vector[0], look_at_vector[1], look_at_vector[2],
up_vector[0], up_vector[1], up_vector[2]);
}
*/
// 主函數
int main(int argc, char** argv)
{
//解析命令行參數
if (pcl::console::find_argument(argc, argv, "-h") >= 0)
{
printUsage(argv[0]);
return 0;
}
if (pcl::console::find_argument(argc, argv, "-l") >= 0)
{
live_update = true;
std::cout << "Live update is on.\n";
}
if (pcl::console::parse(argc, argv, "-rx", angular_resolution_x) >= 0)
std::cout << "Setting angular resolution in x-direction to " << angular_resolution_x << "deg.\n";
if (pcl::console::parse(argc, argv, "-ry", angular_resolution_y) >= 0)
std::cout << "Setting angular resolution in y-direction to " << angular_resolution_y << "deg.\n";
int tmp_coordinate_frame;
if (pcl::console::parse(argc, argv, "-c", tmp_coordinate_frame) >= 0)
{
coordinate_frame = pcl::RangeImage::CoordinateFrame(tmp_coordinate_frame);
std::cout << "Using coordinate frame " << (int)coordinate_frame << ".\n";
}
angular_resolution_x = pcl::deg2rad(angular_resolution_x);
angular_resolution_y = pcl::deg2rad(angular_resolution_y);
//讀取pcd文件。如果沒有指定文件,則創建樣本雲點
pcl::PointCloud<PointType>::Ptr point_cloud_ptr(new pcl::PointCloud<PointType>);
pcl::PointCloud<PointType>& point_cloud = *point_cloud_ptr;
Eigen::Affine3f scene_sensor_pose(Eigen::Affine3f::Identity());
std::vector<int> pcd_filename_indices = pcl::console::parse_file_extension_argument(argc, argv, "pcd");
if (!pcd_filename_indices.empty())
{
std::string filename = argv[pcd_filename_indices[0]];
if (pcl::io::loadPCDFile(filename, point_cloud) == -1)
{
std::cout << "Was not able to open file \"" << filename << "\".\n";
printUsage(argv[0]);
return 0;
}
scene_sensor_pose = Eigen::Affine3f(Eigen::Translation3f(point_cloud.sensor_origin_[0],
point_cloud.sensor_origin_[1],
point_cloud.sensor_origin_[2])) *
Eigen::Affine3f(point_cloud.sensor_orientation_);
}
else
{
std::cout << "\nNo *.pcd file given => Genarating example point cloud.\n\n";
for (float x = -0.5f; x <= 0.5f; x += 0.01f)
{
for (float y = -0.5f; y <= 0.5f; y += 0.01f)
{
PointType point; point.x = x; point.y = y; point.z = 2.0f - y;
point_cloud.points.push_back(point);
}
}
point_cloud.width = (int)point_cloud.points.size(); point_cloud.height = 1;
}
//從點雲創建出深度圖
// 直接把三維的點雲投射成二維的圖像
float noise_level = 0.0;
float min_range = 0.0f;
int border_size = 1;
boost::shared_ptr<pcl::RangeImage> range_image_ptr(new pcl::RangeImage); //深度圖指針
pcl::RangeImage& range_image = *range_image_ptr; //引用
range_image.createFromPointCloud(point_cloud, angular_resolution_x,
pcl::deg2rad(360.0f), pcl::deg2rad(180.0f),
scene_sensor_pose, coordinate_frame, noise_level, min_range, border_size); //從點雲創建出深度圖
//打開一個3D圖形窗口,並添加點雲數據
pcl::visualization::PCLVisualizer viewer("3D Viewer");
viewer.setBackgroundColor(1, 1, 1); //背景
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointWithRange> range_image_color_handler(range_image_ptr, 0, 0, 0);
viewer.addPointCloud(range_image_ptr, range_image_color_handler, "range image");
viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "range image");
//viewer.addCoordinateSystem (1.0f, "global");
//PointCloudColorHandlerCustom<PointType> point_cloud_color_handler (point_cloud_ptr, 150, 150, 150);
//viewer.addPointCloud (point_cloud_ptr, point_cloud_color_handler, "original point cloud");
viewer.initCameraParameters();
//setViewerPose(viewer, range_image.getTransformationToWorldSystem ()); //PCL 1.6 出錯
//以圖像的形式顯示深度圖像,深度值作爲顏色顯示
pcl::visualization::RangeImageVisualizer range_image_widget("Range image");
range_image_widget.showRangeImage(range_image);
float* ranges = range_image.getRangesArray();
unsigned char* rgb_image = pcl::visualization::FloatImageUtils::getVisualImage(ranges, range_image.width, range_image.height);
pcl::io::saveRgbPNGFile("saveRangeImageRGB.png", rgb_image, range_image.width, range_image.height); //saveRgbPNGFile
//主循環
while (!viewer.wasStopped())
{
range_image_widget.spinOnce();
viewer.spinOnce();
pcl_sleep(0.01);
//if (live_update) //根據3D顯示,實時更新2D圖像
//{
// scene_sensor_pose = viewer.getViewerPose(); //獲取觀測姿勢
// range_image.createFromPointCloud(point_cloud, angular_resolution_x,
// pcl::deg2rad(360.0f), pcl::deg2rad(180.0f),
// scene_sensor_pose, pcl::RangeImage::LASER_FRAME, noise_level, min_range, border_size); //重新生成新的深度圖
// range_image_widget.showRangeImage(range_image); //重新顯示
// unsigned char* rgb_image = pcl::visualization::FloatImageUtils::getVisualImage(ranges, range_image.width, range_image.height);
// pcl::io::saveRgbPNGFile("saveRangeImageRGB.png", rgb_image, range_image.width, range_image.height); //saveRgbPNGFile
//}
}
}
實驗結果:
效果不好,估計是視角或者參考點沒有調好。
同時除了pcl可以生成深度圖( 原理是根據相機姿態的透視投影),根據生成深度圖的原理一般就是透視投影或者正交投影,我們也可以利用opengl或者libigl庫來實現。