
SLAM 三维视觉的点云处理 概率机器人 多视图几何
我的博客即将入驻“云栖社区”,诚邀技术同仁一同入驻。
接着上一篇的介绍继续 关于在使用readHeader函数读取点云数据头的类型的代码(Read a point cloud data header from a PCD file.) pcl::PCLPointCloud2 cloud; int version; Eigen::Vector4f origin; Eigen::Quaternionf orientation; pcl::PCDReader r; int type; unsigned int idx; //读取PCD文件的头的基本信息 /*(in)path.string ()文件名 cloud 点云数据集 origin传感器的采集中心点 orientation传感器的方位 version为PCD版本 type数据类型(0 = ASCII,1 =二进制,2 =二进制压缩)(in)idx为在文件内的偏移点云数据*/ r.readHeader (path.string (), cloud, origin, orientation, version, type, idx); 查看PCD文件里的内容(其实对于如何生成这种高纬度的文件呢?) # .PCD v.7 - Point Cloud Data file format 注释掉说明这是关于点云的文件格式 VERSION .7 PCD文件的版本号 FIELDS x y z rgb u v vx vy vz normal_x normal_y normal_z curvature 文件中点云的维度数 SIZE 4 4 4 4 4 4 4 4 4 4 4 4 4 每个维度数据的大小 TYPE F F F F F F F F F F F F F 数据的类型 COUNT 1 1 1 1 1 1 1 1 1 1 1 1 1 WIDTH 3484 点云大小 HEIGHT 1 无序点云 VIEWPOINT 0 0 0 1 0 0 0 视点 POINTS 3484 大小 DATA ascii 数据 -0.0042959 -0.041022 0.97549 7.3757e-39 275 261 0 0 0 -0.15142 0.63581 -0.75685 0.018435 -0.0031521 -0.040989 0.97472 7.0991e-39 276 261 0 0 0 -0.12262 0.63448 -0.76315 0.017282 -0.0042959 -0.03988 0.97549 5.9927e-39 299 261 0 0 0 -0.15385 0.62475 -0.76552 0.017079 -0.0020133 -0.03988 0.97549 5.3473e-39 347 261 0 0 0 -0.11114 0.62014 -0.77658 0.015706 -0.00087171 -0.039294 0.9751 5.6239e-39 277.5 261.5 0 0 0 -0.089597 0.61557 -0.78297 0.015285 另外一种PCD文件的比如VFH的PCD文件 # .PCD v.6 - Point Cloud Data file format FIELDS vfh SIZE 4 TYPE F COUNT 308 WIDTH 1 HEIGHT 1 POINTS 1 DATA ascii 0 0 0 0 0 0 0 0 0.086133 0.31582 ........................................................ 那么接下来我们就可以使用PCL给定的数据集,以及FLANN的库是实现对点云的识别,方法是按照(1)的思路来做的 首先我们是假设已经有了数据集,以及相应每个数据集的VFH全局表述子的PCD文件,这样我们就可以使用(1)中的思路把数据集训练并保存到数中,方便之后我们再输入给定的点云的VFH的PCD文件进行查找 那么其实这里面,我们如果是自己生成数据集,并对每个数据生成对应的VFH文件就会有点难度,毕竟这是对采集到的数据,对于一些无关点云需要剔除, 然后对有用的有价值的点云数据进行聚类,以及各个角度的点云聚类,然后对聚类的对象生成对应的VFH的特征PCD文件,这就是大致思路, 那么我们来看一下源代码是如何读取并训练数据源的,并生成可用于FLANN使用的文件,并存在磁盘中 源代码分析如下 #include <pcl/point_types.h> //点云的类型 #include <pcl/point_cloud.h> #include <pcl/console/parse.h> #include <pcl/console/print.h> #include <pcl/io/pcd_io.h> #include <boost/filesystem.hpp> #include <flann/flann.h> #include <flann/io/hdf5.h> #include <fstream> //pair的成员有两个first second两个,很明显第一个vfh_model.first就是std::string //vfh_model.second就是存入的float的变量 typedef std::pair<std::string, std::vector<float> > vfh_model;//用于存储VFH模型的容器 /** \brief Loads an n-D histogram file as a VFH signature 以VFH作为特征直方图 * \param path the input file name 输入的文件的名称 * \param vfh the resultant VFH model //VFH的模型 */ bool loadHist (const boost::filesystem::path &path, vfh_model &vfh) { int vfh_idx; // Load the file as a PCD try { pcl::PCLPointCloud2 cloud; int version; Eigen::Vector4f origin; //中心float的向量 Eigen::Quaternionf orientation; //方向 pcl::PCDReader r; int type; unsigned int idx; r.readHeader (path.string (), cloud, origin, orientation, version, type, idx); vfh_idx = pcl::getFieldIndex (cloud, "vfh"); if (vfh_idx == -1) return (false); if ((int)cloud.width * cloud.height != 1) //点的数目不为0 return (false); } catch (const pcl::InvalidConversionException&) //抛出异常 { return (false); } // Treat the VFH signature as a single Point Cloud把相应的VFH特征代表单个点云 pcl::PointCloud <pcl::VFHSignature308> point; //申明VFH 的点云 pcl::io::loadPCDFile (path.string (), point); vfh.second.resize (308); //因为VFH有308个数据 std::vector <pcl::PCLPointField> fields; pcl::getFieldIndex (point, "vfh", fields); for (size_t i = 0; i < fields[vfh_idx].count; ++i) { vfh.second[i] = point.points[0].histogram[i]; //每个点的直方图 } vfh.first = path.string (); return (true); } /** \brief Load a set of VFH features that will act as the model (training data) //以VFH特征作为模型的训练数据集 * \param argc the number of arguments (pass from main ()) //输入参数的个数 * \param argv the actual command line arguments (pass from main ()) * \param extension the file extension containing the VFH features 文件名的后缀 * \param models the resultant vector of histogram models //特征模型的直方图向量 */ void loadFeatureModels (const boost::filesystem::path &base_dir, const std::string &extension, std::vector<vfh_model> &models) { if (!boost::filesystem::exists (base_dir) && !boost::filesystem::is_directory (base_dir)) return; for (boost::filesystem::directory_iterator it (base_dir); it != boost::filesystem::directory_iterator (); ++it) //对文件下每一个VFH的PCD文件计数 { if (boost::filesystem::is_directory (it->status ())) { std::stringstream ss; //输入 ss << it->path (); pcl::console::print_highlight ("Loading %s (%lu models loaded so far).\n", ss.str ().c_str (), (unsigned long)models.size ()); loadFeatureModels (it->path (), extension, models); } if (boost::filesystem::is_regular_file (it->status ()) && boost::filesystem::extension (it->path ()) == extension) { vfh_model m; if (loadHist (base_dir / it->path ().filename (), m)) models.push_back (m); //装进容器中 } } } int main (int argc, char** argv) { if (argc < 2) //对输入命令行的解析 { PCL_ERROR ("Need at least two parameters! Syntax is: %s [model_directory] [options]\n", argv[0]); return (-1); } std::string extension (".pcd"); transform (extension.begin (), extension.end (), extension.begin (), (int(*)(int))tolower); std::string kdtree_idx_file_name = "kdtree.idx"; std::string training_data_h5_file_name = "training_data.h5"; std::string training_data_list_file_name = "training_data.list"; std::vector<vfh_model> models; //VFH的模型 // Load the model histograms 载入模型的直方图 loadFeatureModels (argv[1], extension, models); pcl::console::print_highlight ("Loaded %d VFH models. Creating training data %s/%s.\n", (int)models.size (), training_data_h5_file_name.c_str (), training_data_list_file_name.c_str ()); // Convert data into FLANN format 把数据转为FLANN格式 flann::Matrix<float> data (new float[models.size () * models[0].second.size ()], models.size (), models[0].second.size ()); for (size_t i = 0; i < data.rows; ++i) for (size_t j = 0; j < data.cols; ++j) data[i][j] = models[i].second[j]; // Save data to disk (list of models)保存数据集到本地 flann::save_to_file (data, training_data_h5_file_name, "training_data"); std::ofstream fs; fs.open (training_data_list_file_name.c_str ()); //打开训练数据集的文件 for (size_t i = 0; i < models.size (); ++i) fs << models[i].first << "\n"; fs.close (); // Build the tree index and save it to disk 建立树索引并保存 pcl::console::print_error ("Building the kdtree index (%s) for %d elements...\n", kdtree_idx_file_name.c_str (), (int)data.rows); flann::Index<flann::ChiSquareDistance<float> > index (data, flann::LinearIndexParams ()); //flann::Index<flann::ChiSquareDistance<float> > index (data, flann::KDTreeIndexParams (4)); index.buildIndex (); index.save (kdtree_idx_file_name); delete[] data.ptr (); return (0); } 这里面就很明显的生成了两个可用于FLANN进行搜索匹配的文件,以及模型的名称的列表,就是会生成以下三个文件 kdtree.idx(这个是kdtree模型的索引) training_data.h5(用于FLANN库中的一种高效的文件格式,上一章有介绍), training_data.list(这是训练数据集的列表) (2)那么对于已经生成好的点云的数据集,我们就需要使用写一个程序来实现给定一个点云的VFH的PCD文件来寻找这个点云所在位置并且是什么角度拍照的结果,闲话少说明,直接就上程序 #include <pcl/point_types.h> #include <pcl/point_cloud.h> #include <pcl/common/common.h> #include <pcl/common/transforms.h> #include <pcl/visualization/pcl_visualizer.h> #include <pcl/console/parse.h> #include <pcl/console/print.h> #include <pcl/io/pcd_io.h> #include <iostream> #include <flann/flann.h> #include <flann/io/hdf5.h> #include <boost/filesystem.hpp> typedef std::pair<std::string, std::vector<float> > vfh_model; /** \brief Loads an n-D histogram file as a VFH signature * \param path the input file name * \param vfh the resultant VFH model */ bool loadHist (const boost::filesystem::path &path, vfh_model &vfh) { int vfh_idx; // Load the file as a PCD try { pcl::PCLPointCloud2 cloud; int version; Eigen::Vector4f origin; Eigen::Quaternionf orientation; pcl::PCDReader r; int type; unsigned int idx; //读取PCD文件的头的基本信息 /*(in)path.string ()文件名 cloud 点云数据集 origin传感器的采集中心点 orientation传感器的方位 version为PCD版本 type数据类型(0 = ASCII,1 =二进制,2 =二进制压缩)(in)idx为在文件内的偏移点云数据*/ r.readHeader (path.string (), cloud, origin, orientation, version, type, idx); vfh_idx = pcl::getFieldIndex (cloud, "vfh"); if (vfh_idx == -1) return (false); if ((int)cloud.width * cloud.height != 1) return (false); } catch (const pcl::InvalidConversionException&) { return (false); } // Treat the VFH signature as a single Point Cloud pcl::PointCloud <pcl::VFHSignature308> point; pcl::io::loadPCDFile (path.string (), point); vfh.second.resize (308); std::vector <pcl::PCLPointField> fields; getFieldIndex (point, "vfh", fields); for (size_t i = 0; i < fields[vfh_idx].count; ++i) { vfh.second[i] = point.points[0].histogram[i]; } vfh.first = path.string (); return (true); } /** \brief Search for the closest k neighbors搜索最近的K邻域 * \param index the tree 树 的索引 * \param model the query model //给定的模型 * \param k the number of neighbors to search for * \param indices the resultant neighbor indices * \param distances the resultant neighbor distances */ inline void nearestKSearch (flann::Index<flann::ChiSquareDistance<float> > &index, const vfh_model &model, int k, flann::Matrix<int> &indices, flann::Matrix<float> &distances) { // Query point 给定的点云 flann::Matrix<float> p = flann::Matrix<float>(new float[model.second.size ()], 1, model.second.size ()); memcpy (&p.ptr ()[0], &model.second[0], p.cols * p.rows * sizeof (float)); indices = flann::Matrix<int>(new int[k], 1, k); distances = flann::Matrix<float>(new float[k], 1, k); index.knnSearch (p, indices, distances, k, flann::SearchParams (512)); delete[] p.ptr (); } /** \brief Load the list of file model names from an ASCII file 载入模型文件名 * \param models the resultant list of model name * \param filename the input file name */ bool loadFileList (std::vector<vfh_model> &models, const std::string &filename) { ifstream fs; fs.open (filename.c_str ()); if (!fs.is_open () || fs.fail ()) return (false); std::string line; while (!fs.eof ()) { getline (fs, line); if (line.empty ()) continue; vfh_model m; m.first = line; models.push_back (m); } fs.close (); return (true); } int main (int argc, char** argv) { int k = 6; double thresh = DBL_MAX; // No threshold, disabled by default if (argc < 2) { pcl::console::print_error ("Need at least three parameters! Syntax is: %s <query_vfh_model.pcd> [options] {kdtree.idx} {training_data.h5} {training_data.list}\n", argv[0]); pcl::console::print_info (" where [options] are: -k = number of nearest neighbors to search for in the tree (default: "); pcl::console::print_value ("%d", k); pcl::console::print_info (")\n"); pcl::console::print_info (" -thresh = maximum distance threshold for a model to be considered VALID (default: "); pcl::console::print_value ("%f", thresh); pcl::console::print_info (")\n\n"); return (-1); } std::string extension (".pcd"); transform (extension.begin (), extension.end (), extension.begin (), (int(*)(int))tolower); // Load the test histogram 载入测试的直方图 std::vector<int> pcd_indices = pcl::console::parse_file_extension_argument (argc, argv, ".pcd"); vfh_model histogram; if (!loadHist (argv[pcd_indices.at (0)], histogram)) { pcl::console::print_error ("Cannot load test file %s\n", argv[pcd_indices.at (0)]); return (-1); } pcl::console::parse_argument (argc, argv, "-thresh", thresh); // Search for the k closest matches 设置K邻域的个数 pcl::console::parse_argument (argc, argv, "-k", k); pcl::console::print_highlight ("Using "); pcl::console::print_value ("%d", k); pcl::console::print_info (" nearest neighbors.\n"); std::string kdtree_idx_file_name = "kdtree.idx"; std::string training_data_h5_file_name = "training_data.h5"; std::string training_data_list_file_name = "training_data.list"; std::vector<vfh_model> models; flann::Matrix<int> k_indices; //索引 flann::Matrix<float> k_distances; //距离 flann::Matrix<float> data; // Check if the data has already been saved to disk if (!boost::filesystem::exists ("training_data.h5") || !boost::filesystem::exists ("training_data.list")) { pcl::console::print_error ("Could not find training data models files %s and %s!\n", training_data_h5_file_name.c_str (), training_data_list_file_name.c_str ()); return (-1); } else { loadFileList (models, training_data_list_file_name); //载入模型的文件名 flann::load_from_file (data, training_data_h5_file_name, "training_data"); pcl::console::print_highlight ("Training data found. Loaded %d VFH models from %s/%s.\n", (int)data.rows, training_data_h5_file_name.c_str (), training_data_list_file_name.c_str ()); } // Check if the tree index has already been saved to disk if (!boost::filesystem::exists (kdtree_idx_file_name)) { pcl::console::print_error ("Could not find kd-tree index in file %s!", kdtree_idx_file_name.c_str ()); return (-1); } else { flann::Index<flann::ChiSquareDistance<float> > index (data, flann::SavedIndexParams ("kdtree.idx")); index.buildIndex (); nearestKSearch (index, histogram, k, k_indices, k_distances); //搜索K邻域 } // Output the results on screen pcl::console::print_highlight ("The closest %d neighbors for %s are:\n", k, argv[pcd_indices[0]]); for (int i = 0; i < k; ++i) pcl::console::print_info (" %d - %s (%d) with a distance of: %f\n", i, models.at (k_indices[0][i]).first.c_str (), k_indices[0][i], k_distances[0][i]); // Load the results可视化结果 pcl::visualization::PCLVisualizer p (argc, argv, "VFH Cluster Classifier"); int y_s = (int)floor (sqrt ((double)k)); int x_s = y_s + (int)ceil ((k / (double)y_s) - y_s); double x_step = (double)(1 / (double)x_s); double y_step = (double)(1 / (double)y_s); pcl::console::print_highlight ("Preparing to load "); pcl::console::print_value ("%d", k); pcl::console::print_info (" files ("); pcl::console::print_value ("%d", x_s); pcl::console::print_info ("x"); pcl::console::print_value ("%d", y_s); pcl::console::print_info (" / "); pcl::console::print_value ("%f", x_step); pcl::console::print_info ("x"); pcl::console::print_value ("%f", y_step); pcl::console::print_info (")\n"); int viewport = 0, l = 0, m = 0; for (int i = 0; i < k; ++i) { std::string cloud_name = models.at (k_indices[0][i]).first; boost::replace_last (cloud_name, "_vfh", ""); p.createViewPort (l * x_step, m * y_step, (l + 1) * x_step, (m + 1) * y_step, viewport); l++; if (l >= x_s) { l = 0; m++; } pcl::PCLPointCloud2 cloud; pcl::console::print_highlight (stderr, "Loading "); pcl::console::print_value (stderr, "%s ", cloud_name.c_str ()); if (pcl::io::loadPCDFile (cloud_name, cloud) == -1) break; // Convert from blob to PointCloud pcl::PointCloud<pcl::PointXYZ> cloud_xyz; pcl::fromPCLPointCloud2 (cloud, cloud_xyz); if (cloud_xyz.points.size () == 0) break; pcl::console::print_info ("[done, "); pcl::console::print_value ("%d", (int)cloud_xyz.points.size ()); pcl::console::print_info (" points]\n"); pcl::console::print_info ("Available dimensions: "); pcl::console::print_value ("%s\n", pcl::getFieldsList (cloud).c_str ()); // Demean the cloud Eigen::Vector4f centroid; pcl::compute3DCentroid (cloud_xyz, centroid);//Compute the 3D (X-Y-Z) centroid of a set of points and return it as a 3D vector. pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_xyz_demean (new pcl::PointCloud<pcl::PointXYZ>); pcl::demeanPointCloud<pcl::PointXYZ> (cloud_xyz, centroid, *cloud_xyz_demean); // Add to renderer* p.addPointCloud (cloud_xyz_demean, cloud_name, viewport); // Check if the model found is within our inlier tolerance std::stringstream ss; ss << k_distances[0][i]; if (k_distances[0][i] > thresh) { p.addText (ss.str (), 20, 30, 1, 0, 0, ss.str (), viewport); // display the text with red // Create a red line pcl::PointXYZ min_p, max_p; pcl::getMinMax3D (*cloud_xyz_demean, min_p, max_p); std::stringstream line_name; line_name << "line_" << i; p.addLine (min_p, max_p, 1, 0, 0, line_name.str (), viewport); p.setShapeRenderingProperties (pcl::visualization::PCL_VISUALIZER_LINE_WIDTH, 5, line_name.str (), viewport); } else p.addText (ss.str (), 20, 30, 0, 1, 0, ss.str (), viewport); // Increase the font size for the score* p.setShapeRenderingProperties (pcl::visualization::PCL_VISUALIZER_FONT_SIZE, 18, ss.str (), viewport); // Add the cluster name p.addText (cloud_name, 20, 10, cloud_name, viewport); } // Add coordianate systems to all viewports p.addCoordinateSystem (0.1, "global", 0); p.spin (); return (0); } 这里面就涉及到FLANN的库的函数的使用,执行的结果 打印的结果,这里面会显示最接近的六个数据集,并且计算这六个最近点云与给定点云之间的“距离”,这也是衡量两者之间的相似度的大小 可视化的结果 很明显左下角就是我们给定的数据点云,而且运行查找的速度非常快~ 好了就这样了 关注微信公众号,欢迎大家的无私分享
FLANN库全称是Fast Library for Approximate Nearest Neighbors,它是目前最完整的(近似)最近邻开源库。不但实现了一系列查找算法,还包含了一种自动选取最快算法的机制,在一个度量空间X给定一组点P=p1,p2,…,pn,这些点必须通过以下方式进行预处理,给第一个新的查询点q属于X,快速在P中找到距离q最近的点,即最近邻搜索问题。最近邻搜索的问题是在很多应用领域是一个重大问题,如图像识别、数据压缩、模式识别和分类.在高维空间中解决这个问题似乎是一个非常难以执行任务,没有算法明显优于标准的蛮力搜索。因此越来越多的人把兴趣点转向执行近似最近邻搜索的一类算法,这些方法在很多实际应用和大多数案例中被证明是足够好的近似,比精确搜索算法快很大的数量级 库的地址:www.cs.ubc.ca/research/flann/ 很显然在PCL 搜索最近邻也是属于高维度近邻搜索,所以需要用到这样的库来寻找匹配的,比如我们如果想通过使用PCL点云的数据来识别出物体到底是什么,那么很明显是需要一个数据录的,这个数据库的训练该怎么来实现呢?因为我对机器学习了解的比较少,我想通过机器学习的方法来识别是最好的,也是今后可以做的工作,目前来看,我认为的方法就是通过对大量的点云进行训练并提取他们的特征点,比如VFH,然后把大量点云训练存储成八叉树,以便于很好的搜索和匹配,而且在PCL 中使用八叉树是很常见的一种存储结构,也是方便搜索的,然后通过计算我们想要识别物体的点云 的VFH,通过FLANN算法寻找到最接近的,这就是整体的思路 主要流程 使用SURF,SIFT特征提取关键点(在PCL中同样适用) 计算特征描述子(比如VFH) 使用FLANN匹配器进行描述子向量匹配 那么有兴趣可以去仔细看看论文啊,我等渣渣真是多年以来对论文没什么感觉 介绍几种FLANN库中的API接口,如何去使用它,然后通过具体的实例去实现这样一个匹配 (1)flann::Index这是FLANN最近邻指数类。该类用于抽象的不同类型的最近邻搜索索引。距离函数类模板用于计算两个特征空间之间的距离。 namespace flann { template<typename Distance> class Index { typedef typename Distance::ElementType ElementType; typedef typename Distance::ResultType DistanceType; public: Index(const IndexParams& params, Distance distance = Distance() ); Index(const Matrix<ElementType>& points, const IndexParams& params, Distance distance = Distance() ); ~Index(); void buildIndex(); void buildIndex(const Matrix<ElementType>& points); void addPoints(const Matrix<ElementType>& points, float rebuild_threshold = 2); void removePoint(size_t point_id); ElementType* getPoint(size_t point_id); int knnSearch(const Matrix<ElementType>& queries, Matrix<int>& indices, Matrix<DistanceType>& dists, size_t knn, const SearchParams& params); int knnSearch(const Matrix<ElementType>& queries, std::vector< std::vector<int> >& indices, std::vector<std::vector<DistanceType> >& dists, size_t knn, const SearchParams& params); int radiusSearch(const Matrix<ElementType>& queries, Matrix<int>& indices, Matrix<DistanceType>& dists, float radius, const SearchParams& params); int radiusSearch(const Matrix<ElementType>& queries, std::vector< std::vector<int> >& indices, std::vector<std::vector<DistanceType> >& dists, float radius, const SearchParams& params); void save(std::string filename); int veclen() const; int size() const; IndexParams getParameters() const; flann_algorithm_t getType() const; }; } (2)flann::Index::knnSearch 对一组点查询点,该方法执行K最近邻搜索。该方法有两个实现,一个携带预开辟空间的flann::Matrix对象接收返回的找到邻居的索引号和到其距离;另一个是携带std::vector<std::vector>根据需要自动重新调整大小。 参数解释: queries: 承载查询点的矩阵,矩阵大小是:查询点数*纬数; indices: 将承载所有被找到的K最近邻的索引号( 预开辟的大小应该至少是查询点数*knn); dists: 将承载到所有被找到的K最近邻的距离只(预开辟大小应该至少是查询点数*knn); knn: 要找的最近邻的个数; params: 搜索参数。承载搜索时要使用的参数的结构体,结构体类型是SearchParameters。 int Index::knnSearch(const Matrix<ElementType>& queries, Matrix<int>& indices, Matrix<DistanceType>& dists, size_t knn, const SearchParams& params); int Index::knnSearch(const Matrix<ElementType>& queries, std::vector< std::vector<int> >& indices, std::vector<std::vector<DistanceType> >& dists, size_t knn, const SearchParams& params); 在这里就不再一一翻译了 /** Sets the log level used for all flann functions (unless specified in FLANNParameters for each call Params: level = verbosity level */ FLANN_EXPORT void flann_log_verbosity(int level); 设置FLANN距离的类型 /** * Sets the distance type to use throughout FLANN. * If distance type specified is MINKOWSKI, the second argument * specifies which order the minkowski distance should have. */ FLANN_EXPORT void flann_set_distance_type(enum flann_distance_t distance_type, int order); 建立和返回索引的函数 /** Builds and returns an index. It uses autotuning if the target_precision field of index_params is between 0 and 1, or the parameters specified if it's -1. Params: dataset = pointer to a data set stored in row major order rows = number of rows (features) in the dataset cols = number of columns in the dataset (feature dimensionality) speedup = speedup over linear search, estimated if using autotuning, output parameter index_params = index related parameters flann_params = generic flann parameters Returns: the newly created index or a number <0 for error */ FLANN_EXPORT flann_index_t flann_build_index(float* dataset, int rows, int cols, float* speedup, struct FLANNParameters* flann_params); 保存索引到本地磁盘(仅仅是索引被保存) /** * Saves the index to a file. Only the index is saved into the file, the dataset corresponding to the index is not saved. * * @param index_id The index that should be saved * @param filename The filename the index should be saved to 文件名 * @return Returns 0 on success, negative value on error. 返回0则保存成功 */ FLANN_EXPORT int flann_save_index(flann_index_t index_id, char* filename); 从磁盘中载入索引文件 /** * Loads an index from a file. * * @param filename File to load the index from. * @param dataset The dataset corresponding to the index. * @param rows Dataset tors * @param cols Dataset columns * @return */ FLANN_EXPORT flann_index_t flann_load_index(char* filename, float* dataset, int rows, int cols); 建立一个索引或者说指定一个索引来寻找最近邻域 /** Builds an index and uses it to find nearest neighbors. Params: dataset = pointer to a data set stored in row major order rows = number of rows (features) in the dataset cols = number of columns in the dataset (feature dimensionality) testset = pointer to a query set stored in row major order trows = number of rows (features) in the query dataset (same dimensionality as features in the dataset) indices = pointer to matrix for the indices of the nearest neighbors of the testset features in the dataset (must have trows number of rows and nn number of columns) nn = how many nearest neighbors to return flann_params = generic flann parameters Returns: zero or -1 for error */ FLANN_EXPORT int flann_find_nearest_neighbors(float* dataset, int rows, int cols, float* testset, int trows, int* indices, float* dists, int nn, struct FLANNParameters* flann_params); 对提供的索引来寻找最近邻 /** Searches for nearest neighbors using the index provided Params: index_id = the index (constructed previously using flann_build_index). testset = pointer to a query set stored in row major order trows = number of rows (features) in the query dataset (same dimensionality as features in the dataset) indices = pointer to matrix for the indices of the nearest neighbors of the testset features in the dataset (must have trows number of rows and nn number of columns) dists = pointer to matrix for the distances of the nearest neighbors of the testset features in the dataset (must have trows number of rows and 1 column) nn = how many nearest neighbors to return flann_params = generic flann parameters Returns: zero or a number <0 for error */ FLANN_EXPORT int flann_find_nearest_neighbors_index(flann_index_t index_id, float* testset, int trows, int* indices, float* dists, int nn, struct FLANNParameters* flann_params); 半径搜索的方法在已经建立的索引文件中寻找 /** * Performs an radius search using an already constructed index. * * In case of radius search, instead of always returning a predetermined * number of nearest neighbours (for example the 10 nearest neighbours), the * search will return all the neighbours found within a search radius * of the query point. * * The check parameter in the FLANNParameters below sets the level of approximation * for the search by only visiting "checks" number of features in the index * (the same way as for the KNN search). A lower value for checks will give * a higher search speedup at the cost of potentially not returning all the * neighbours in the specified radius. */ FLANN_EXPORT int flann_radius_search(flann_index_t index_ptr, /* the index */ float* query, /* query point */ int* indices, /* array for storing the indices found (will be modified) */ float* dists, /* similar, but for storing distances */ int max_nn, /* size of arrays indices and dists */ float radius, /* search radius (squared radius for euclidian metric) */ struct FLANNParameters* flann_params); 释放内存 /** Deletes an index and releases the memory used by it. Params: index_id = the index (constructed previously using flann_build_index). flann_params = generic flann parameters Returns: zero or a number <0 for error */ FLANN_EXPORT int flann_free_index(flann_index_t index_id, struct FLANNParameters* flann_params); 利用特征聚类 /** Clusters the features in the dataset using a hierarchical kmeans clustering approach. This is significantly faster than using a flat kmeans clustering for a large number of clusters. Params: dataset = pointer to a data set stored in row major order rows = number of rows (features) in the dataset cols = number of columns in the dataset (feature dimensionality) clusters = number of cluster to compute result = memory buffer where the output cluster centers are storred index_params = used to specify the kmeans tree parameters (branching factor, max number of iterations to use) flann_params = generic flann parameters Returns: number of clusters computed or a number <0 for error. This number can be different than the number of clusters requested, due to the way hierarchical clusters are computed. The number of clusters returned will be the highest number of the form (branch_size-1)*K+1 smaller than the number of clusters requested. */ FLANN_EXPORT int flann_compute_cluster_centers(float* dataset, int rows, int cols, int clusters, float* result, struct FLANNParameters* flann_params); 基本的介绍,可以查看相应的手册 #include <flann/flann.h>#include <flann/io/hdf5.h> 其中还涉及到另外一种文件的数据结构的使用,一般都会包含两个头文件,第一个头文件是关于FLANN算法实现的声明的函数集,第二个头文件是关于数据结构的一种存储的方式,hdf5是用于存储和分发科学数据的一种自我描述、多对象文件格式,HDF 被设计为: 自述性:对于一个HDF 文件里的每一个数据对象,有关于该数据的综合信息(元数据)。在没有任何外部信息的情况下,HDF 允许应用程序解释HDF文件的结构和内容。 通用性:许多数据类型都可以被嵌入在一个HDF文件里。例如,通过使用合适的HDF 数据结构,符号、数字和图形数据可以同时存储在一个HDF 文件里。 灵活性:HDF允许用户把相关的数据对象组合在一起,放到一个分层结构中,向数据对象添加描述和标签。它还允许用户把科学数据放到多个HDF 文件里。 扩展性:HDF极易容纳将来新增加的数据模式,容易与其他标准格式兼容。 跨平台性:HDF 是一个与平台无关的文件格式。HDF 文件无需任何转换就可以在不同平台上使用 最好的办法是把HDF 文件看成为一本有表格内容的多章节书。HDF 文件是“数据书”,其中每章都包含一个不同类型的数据内容。正如书籍用一个目录表列出它的章节一样,HDF文件用“data index”(数据索引)列出其数据内容 HDF 文件结构包括一个file id(文件号)、至少一个 data descriptor (数据描述符)、没有或多个 data element(数据内容)数据内容。 以上内容是涉及到关于对PCL中物体的识别的基础知识。 有兴趣者关注微信公众号,更欢迎大家投稿分享,只要是关于点云的都可以分享
很多使用在windows环境下编译和使用PCL,这样让我想试试,所以就迫不得已的放弃使用Ubuntu环境,但是我还是建议使用Ubuntu系统,毕竟在Ubuntu下几条命令就搞定了,为了迎合在window使用PCL开发kinect,今天就试着在vS下配置和使用PCL,习惯了一边安装一边记录,首先安装VS2017,直接就是百度的界面提示所安装的VS2017 (1)下载PCL-1.8.1-AllInOne-msvc2017-win32.exe pcl-1.8.1-pdb-msvc2017-win32.zip 网址在: https://github.com/PointCloudLibrary/pcl/releases 下载适当的版本,这里有32位和64位的,是要看你的VS2013的编译器是几位的,并不是操作系统,我的操作系统是32位,我的VS2017的编译器是32位,所以我选择32位的版本。 双击PCL-1.8.1-AllInOne-msvc2017-win32.exe 直接点击下一步,到如下界面选择添加路径 之后下一步你可以自己选择,安装路径,由于C盘比较多所以我选择安装在D盘之中,之后就是别动一直点击下一步即可 我是将C盘改为D盘下了(忘了截图)接下来你可以卡到安装的三方库都在 之后会等待一段时间安装,接下来安装第三方的库函数的时候记住要在自己选定的安装路径下安装 比如我是安装在D盘下,所以当提示安装openni的时候我们需要给 opneni更改到自己选定的安装PCL的第三方库安装的路径下 如图:过程中可能会提示其他的库安装 你都可以选择安装 那么之后就会完成所有的安装 拷贝与你安装PCL版本对应的PDB压缩包解压后的PDB文件,到你PCL安装路径下的bin文件夹,就是里面有pcl开头的dll的那个文件夹 (此时一种草泥马在崩腾一转眼没保存 很多东西都没有 好垃圾啊) 此时我们可以查看PCL的路径已经添加到我的电脑的环境变量中 以上是关于PCL的路径在安装的过程中就已经设置好了,之前安装的第三方的依赖项的路径还么有添加进去路径所以需要 环境变量配置: %PCL_ROOT%\bin;%PCL_ROOT%\3rdParty\FLANN\bin;%PCL_ROOT%\3rdParty\VTK\bin;%PCL_ROOT%\Qhull\bin;%PCL_ROOT%\3rdParty\OpenNI2\Tools添加到系统变量Path下。 一般路径设置完需要对电脑重启使得路径生效。 现在PCL已经安装好了,我们可以开始配置VS2017开发环境,新建一个空项目 右击项目“属性”设置包含目录 选择VC++目录,选择包含目录添加包含的文件 设置库目录 以上就是设置完全的PCL以及第三方库的所有的步骤,但是在windows使用PCL并不是那么简单,太多太多的初学者由于关注了微信公众号都会问关于在 windows下遇到的各种问题,但是我想说,很多错误都是万变不离其中,那就是环境变量的配置问题,所以当其中遇到的问题我们就要到工程文件下的属性在寻找是不是我们什么配置错误下图的属性文件 一下是罗列的关于在我遇到的一些问题总结 (1) 在Visual Studio2017使用Boost库的时候,出现如下错误: error C4996: 'std::copy::_Unchecked_iterators::_Deprecate': Call to 'std::copy' with parameters that may be unsafe - this call relies on the caller to check that the passed values are correct. To disable this warning, use -D_SCL_SECURE_NO_WARNINGS. See documentation on how to use Visual C++ 'Checked Iterators' 解决办法 在工程属性—>C/C++—>命令行—>其他选项 中添加: -D_SCL_SECURE_NO_WARNINGS 以上,错误解除 (2) 提取文件夹中.lib文件名到文本中 分别粘贴复制静态链接库名到文本甚是麻烦,故通过批处理来完成 例如要获取目录中D:\PCL 1.8.1\3rdParty\VTK\lib下的所有静态链接库文件名并存储至文本.txt,方法如下: 1、win+r 2、输入:cmd回车 3、输入:cd /d D:\PCL 1.8.1\3rdParty\VTK\lib 回车 4、输入:dir /b *.lib *>0.txt 回车 (3)如果编译通过,但是在可视化后出现一下的提示错误,是说明了VTK的初始化错误,也就是VTK的组件出错了 Generic Warning: In C:\location\VTK6.0.0\Rendering\Core\vtkPolyDataMapper.cxx, line 27 Error: no override found for 'vtkPolyDataMapper'. 解决办法便是在主程序中添加三行代码 #include <vtkAutoInit.h> VTK_MODULE_INIT(vtkRenderingOpenGL); VTK_MODULE_INIT(vtkInteractionStyle); 问题即可解决,那么对于出现的调试的信息比如关于说找不到很多的PDB文件的,不影响结果, 代码如下 #include <pcl/visualization/cloud_viewer.h> #include <iostream> #include <pcl/io/io.h> #include <pcl/io/pcd_io.h> #include <vtkAutoInit.h> VTK_MODULE_INIT(vtkRenderingOpenGL); VTK_MODULE_INIT(vtkInteractionStyle); int user_data; void viewerOneOff(pcl::visualization::PCLVisualizer& viewer) { viewer.setBackgroundColor(1.0, 0.5, 1.0); pcl::PointXYZ o; o.x = 1.0; o.y = 0; o.z = 0; viewer.addSphere(o, 0.25, "sphere", 0); std::cout << "i only run once" << std::endl; } void viewerPsycho(pcl::visualization::PCLVisualizer& viewer) { static unsigned count = 0; std::stringstream ss; ss << "Once per viewer loop: " << count++; viewer.removeShape("text", 0); viewer.addText(ss.str(), 200, 300, "text", 0); //FIXME: possible race condition here: user_data++; } int main() { pcl::PointCloud<pcl::PointXYZRGBA>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZRGBA>); pcl::io::loadPCDFile("bunny.pcd", *cloud); pcl::visualization::CloudViewer viewer("Cloud Viewer"); //blocks until the cloud is actually rendered viewer.showCloud(cloud); //use the following functions to get access to the underlying more advanced/powerful //PCLVisualizer //This will only get called once viewer.runOnVisualizationThreadOnce(viewerOneOff); //This will get called once per visualization iteration viewer.runOnVisualizationThread(viewerPsycho); while (!viewer.wasStopped()) { //you can also do cool processing here //FIXME: Note that this is running in a separate thread from viewerPsycho //and you should guard against race conditions yourself... user_data++; } return 0; } 实验结果的可视化如下 对于如果没有PCD文件的同学,可以自己新建一个.txt格式的文档将如下的数据复制到文件中,最后将文件的后缀改为.pcd文件,并将.pcd放在我们新建的工程文件在,这样的我们的可执行的文件才能找到该文件,并读取可显示,这个数据是bunny.pcd文件的数据 # .PCD v.5 - Point Cloud Data file format VERSION .5 FIELDS x y z SIZE 4 4 4 TYPE F F F COUNT 1 1 1 WIDTH 397 HEIGHT 1 POINTS 397 DATA ascii 0.0054216 0.11349 0.040749 -0.0017447 0.11425 0.041273 -0.010661 0.11338 0.040916 0.026422 0.11499 0.032623 0.024545 0.12284 0.024255 0.034137 0.11316 0.02507 0.02886 0.11773 0.027037 0.02675 0.12234 0.017605 0.03575 0.1123 0.019109 0.015982 0.12307 0.031279 0.0079813 0.12438 0.032798 0.018101 0.11674 0.035493 0.0086687 0.11758 0.037538 0.01808 0.12536 0.026132 0.0080861 0.12866 0.02619 0.02275 0.12146 0.029671 -0.0018689 0.12456 0.033184 -0.011168 0.12376 0.032519 -0.0020063 0.11937 0.038104 -0.01232 0.11816 0.037427 -0.0016659 0.12879 0.026782 -0.011971 0.12723 0.026219 0.016484 0.12828 0.01928 0.0070921 0.13103 0.018415 0.0014615 0.13134 0.017095 -0.013821 0.12886 0.019265 -0.01725 0.11202 0.040077 -0.074556 0.13415 0.051046 -0.065971 0.14396 0.04109 -0.071925 0.14545 0.043266 -0.06551 0.13624 0.042195 -0.071112 0.13767 0.047518 -0.079528 0.13416 0.051194 -0.080421 0.14428 0.042793 -0.082672 0.1378 0.046806 -0.08813 0.13514 0.042222 -0.066325 0.12347 0.050729 -0.072399 0.12662 0.052364 -0.066091 0.11973 0.050881 -0.072012 0.11811 0.052295 -0.062433 0.12627 0.043831 -0.068326 0.12998 0.048875 -0.063094 0.11811 0.044399 -0.071301 0.11322 0.04841 -0.080515 0.12741 0.052034 -0.078179 0.1191 0.051116 -0.085216 0.12609 0.049001 -0.089538 0.12621 0.044589 -0.082659 0.11661 0.04797 -0.089536 0.11784 0.04457 -0.0565 0.15248 0.030132 -0.055517 0.15313 0.026915 -0.03625 0.17198 0.00017688 -0.03775 0.17198 0.00022189 -0.03625 0.16935 0.00051958 -0.033176 0.15711 0.0018682 -0.051913 0.1545 0.011273 -0.041707 0.16642 0.0030522 -0.049468 0.16414 0.0041988 -0.041892 0.15669 0.0054879 -0.051224 0.15878 0.0080283 -0.062417 0.15317 0.033161 -0.07167 0.15319 0.033701 -0.062543 0.15524 0.027405 -0.07211 0.1555 0.027645 -0.078663 0.15269 0.032268 -0.081569 0.15374 0.026085 -0.08725 0.1523 0.022135 -0.05725 0.15568 0.010325 -0.057888 0.1575 0.0073225 -0.0885 0.15223 0.019215 -0.056129 0.14616 0.03085 -0.054705 0.13555 0.032127 -0.054144 0.14714 0.026275 -0.046625 0.13234 0.021909 -0.05139 0.13694 0.025787 -0.018278 0.12238 0.030773 -0.021656 0.11643 0.035209 -0.031921 0.11566 0.032851 -0.021348 0.12421 0.024562 -0.03241 0.12349 0.023293 -0.024869 0.12094 0.028745 -0.031747 0.12039 0.028229 -0.052912 0.12686 0.034968 -0.041672 0.11564 0.032998 -0.052037 0.1168 0.034582 -0.042495 0.12488 0.024082 -0.047946 0.12736 0.028108 -0.042421 0.12035 0.028633 -0.047661 0.12024 0.028871 -0.035964 0.1513 0.0005395 -0.050598 0.1474 0.013881 -0.046375 0.13293 0.018289 -0.049125 0.13856 0.016269 -0.042976 0.14915 0.0054003 -0.047965 0.14659 0.0086783 -0.022926 0.1263 0.018077 -0.031583 0.1259 0.017804 -0.041733 0.12796 0.01665 -0.061482 0.14698 0.036168 -0.071729 0.15026 0.038328 -0.060526 0.1368 0.035999 -0.082619 0.14823 0.035955 -0.087824 0.14449 0.033779 -0.089 0.13828 0.037774 -0.085662 0.15095 0.028208 -0.089601 0.14725 0.025869 -0.090681 0.13748 0.02369 -0.058722 0.12924 0.038992 -0.060075 0.11512 0.037685 -0.091812 0.12767 0.038703 -0.091727 0.11657 0.039619 -0.093164 0.12721 0.025211 -0.093938 0.12067 0.024399 -0.091583 0.14522 0.01986 -0.090929 0.13667 0.019817 -0.093094 0.11635 0.018959 0.024948 0.10286 0.041418 0.0336 0.092627 0.040463 0.02742 0.096386 0.043312 0.03392 0.086911 0.041034 0.028156 0.086837 0.045084 0.03381 0.078604 0.040854 0.028125 0.076874 0.045059 0.0145 0.093279 0.05088 0.0074817 0.09473 0.052315 0.017407 0.10535 0.043139 0.0079536 0.10633 0.042968 0.018511 0.097194 0.047253 0.0086436 0.099323 0.048079 -0.0020197 0.095698 0.053906 -0.011446 0.095169 0.053862 -0.001875 0.10691 0.043455 -0.011875 0.10688 0.043019 -0.0017622 0.10071 0.046648 -0.012498 0.10008 0.045916 0.016381 0.085894 0.051642 0.0081167 0.08691 0.055228 0.017644 0.076955 0.052372 0.008125 0.076853 0.055536 0.020575 0.088169 0.049006 0.022445 0.075721 0.049563 -0.0017931 0.086849 0.056843 -0.011943 0.086771 0.057009 -0.0019567 0.076863 0.057803 -0.011875 0.076964 0.057022 0.03325 0.067541 0.040033 0.028149 0.066829 0.042953 0.026761 0.057829 0.042588 0.023571 0.04746 0.040428 0.015832 0.067418 0.051639 0.0080431 0.066902 0.055006 0.013984 0.058886 0.050416 0.0080973 0.056888 0.05295 0.020566 0.065958 0.0483 0.018594 0.056539 0.047879 0.012875 0.052652 0.049689 -0.0017852 0.066712 0.056503 -0.011785 0.066885 0.055015 -0.001875 0.056597 0.05441 -0.01184 0.057054 0.052714 -0.015688 0.052469 0.049615 0.0066154 0.04993 0.051259 0.018088 0.046655 0.043321 0.008841 0.045437 0.046623 0.017688 0.039719 0.043084 0.008125 0.039516 0.045374 -0.0016111 0.049844 0.05172 -0.01245 0.046773 0.050903 -0.013851 0.039778 0.051036 -0.0020294 0.044874 0.047587 -0.011653 0.04686 0.048661 -0.0018611 0.039606 0.047339 -0.0091545 0.03958 0.049415 0.043661 0.094028 0.02252 0.034642 0.10473 0.031831 0.028343 0.1072 0.036339 0.036339 0.096552 0.034843 0.031733 0.099372 0.038505 0.036998 0.10668 0.026781 0.032875 0.11108 0.02959 0.040938 0.097132 0.026663 0.044153 0.086466 0.024241 0.05375 0.072221 0.020429 0.04516 0.076574 0.023594 0.038036 0.086663 0.035459 0.037861 0.076625 0.035658 0.042216 0.087237 0.028254 0.042355 0.076747 0.02858 0.043875 0.096228 0.015269 0.044375 0.096797 0.0086445 0.039545 0.1061 0.017655 0.042313 0.10009 0.017237 0.045406 0.087417 0.015604 0.055118 0.072639 0.017944 0.048722 0.07376 0.017434 0.045917 0.086298 0.0094211 0.019433 0.1096 0.039063 0.01097 0.11058 0.039648 0.046657 0.057153 0.031337 0.056079 0.066335 0.024122 0.048168 0.06701 0.026298 0.056055 0.057253 0.024902 0.051163 0.056662 0.029137 0.036914 0.067032 0.036122 0.033 0.06472 0.039903 0.038004 0.056507 0.033119 0.030629 0.054915 0.038484 0.041875 0.066383 0.028357 0.041434 0.06088 0.029632 0.044921 0.049904 0.031243 0.054635 0.050167 0.022044 0.04828 0.04737 0.025845 0.037973 0.048347 0.031456 0.028053 0.047061 0.035991 0.025595 0.040346 0.03415 0.038455 0.043509 0.028278 0.032031 0.043278 0.029253 0.036581 0.040335 0.025144 0.03019 0.039321 0.026847 0.059333 0.067891 0.017361 0.0465 0.071452 0.01971 0.059562 0.057747 0.01834 0.055636 0.049199 0.019173 0.0505 0.045064 0.019181 0.023 0.047803 0.039776 0.022389 0.03886 0.038795 -0.019545 0.0939 0.052205 -0.021462 0.10618 0.042059 -0.031027 0.10395 0.041228 -0.022521 0.097723 0.045194 -0.031858 0.097026 0.043878 -0.043262 0.10412 0.040891 -0.052154 0.10404 0.040972 -0.041875 0.096944 0.042424 -0.051919 0.096967 0.043563 -0.021489 0.086672 0.054767 -0.027 0.083087 0.050284 -0.02107 0.077249 0.054365 -0.026011 0.089634 0.048981 -0.031893 0.087035 0.044169 -0.025625 0.074892 0.047102 -0.03197 0.0769 0.042177 -0.041824 0.086954 0.043295 -0.051825 0.086844 0.044933 -0.041918 0.076728 0.042564 -0.051849 0.076877 0.042992 -0.061339 0.10393 0.041164 -0.072672 0.10976 0.044294 -0.061784 0.096825 0.043327 -0.070058 0.096203 0.041397 -0.080439 0.11091 0.044343 -0.061927 0.086724 0.04452 -0.070344 0.087352 0.041908 -0.06141 0.077489 0.042178 -0.068579 0.080144 0.041024 -0.019045 0.067732 0.052388 -0.017742 0.058909 0.050809 -0.023548 0.066382 0.045226 -0.03399 0.067795 0.040929 -0.02169 0.056549 0.045164 -0.036111 0.060706 0.040407 -0.041231 0.066951 0.041392 -0.048588 0.070956 0.040357 -0.0403 0.059465 0.040446 -0.02192 0.044965 0.052258 -0.029187 0.043585 0.051088 -0.021919 0.039826 0.053521 -0.030331 0.039749 0.052133 -0.021998 0.049847 0.046725 -0.031911 0.046848 0.045187 -0.035276 0.039753 0.047529 -0.042016 0.044823 0.041594 -0.05194 0.044707 0.043498 -0.041928 0.039327 0.043582 -0.051857 0.039252 0.046212 -0.059453 0.04424 0.042862 -0.060765 0.039087 0.044363 -0.024273 0.11038 0.039129 -0.032379 0.10878 0.037952 -0.041152 0.10853 0.037969 -0.051698 0.10906 0.038258 -0.062091 0.10877 0.038274 -0.071655 0.10596 0.037516 -0.074634 0.097746 0.038347 -0.07912 0.10508 0.032308 -0.080203 0.096758 0.033592 -0.08378 0.10568 0.025985 -0.087292 0.10314 0.020825 -0.08521 0.097079 0.02781 -0.088082 0.096456 0.022985 -0.07516 0.08604 0.038816 -0.064577 0.073455 0.03897 -0.072279 0.076416 0.036413 -0.076375 0.072563 0.02873 -0.080031 0.087076 0.03429 -0.078919 0.079371 0.032477 -0.084834 0.086686 0.026974 -0.087891 0.089233 0.022611 -0.081048 0.077169 0.025829 -0.086393 0.10784 0.018635 -0.087672 0.10492 0.017264 -0.089333 0.098483 0.01761 -0.086375 0.083067 0.018607 -0.089179 0.089186 0.018947 -0.082879 0.076109 0.017794 -0.0825 0.074674 0.0071175 -0.026437 0.064141 0.039321 -0.030035 0.06613 0.038942 -0.026131 0.056531 0.038882 -0.031664 0.056657 0.037742 -0.045716 0.064541 0.039166 -0.051959 0.066869 0.036733 -0.042557 0.055545 0.039026 -0.049406 0.056892 0.034344 -0.0555 0.062391 0.029498 -0.05375 0.058574 0.026313 -0.03406 0.050137 0.038577 -0.041741 0.04959 0.03929 -0.050975 0.049435 0.036965 -0.053 0.051065 0.029209 -0.054145 0.054568 0.012257 -0.055848 0.05417 0.0083272 -0.054844 0.049295 0.011462 -0.05615 0.050619 0.0092929 -0.061451 0.068257 0.035376 -0.069725 0.069958 0.032788 -0.062823 0.063322 0.026886 -0.071037 0.066787 0.025228 -0.060857 0.060568 0.022643 -0.067 0.061558 0.020109 -0.0782 0.071279 0.021032 -0.062116 0.045145 0.037802 -0.065473 0.039513 0.037964 -0.06725 0.03742 0.033413 -0.072702 0.065008 0.018701 -0.06145 0.059165 0.018731 -0.0675 0.061479 0.019221 -0.057411 0.054114 0.0038257 -0.079222 0.070654 0.017735 -0.062473 0.04468 0.01111 -0.06725 0.042258 0.010414 -0.066389 0.040515 0.01316 -0.068359 0.038502 0.011958 -0.061381 0.04748 0.007607 -0.068559 0.043549 0.0081576 -0.070929 0.03983 0.0085888 -0.016625 0.18375 -0.019735 -0.015198 0.17471 -0.018868 -0.015944 0.16264 -0.0091037 -0.015977 0.1607 -0.0088072 -0.013251 0.16708 -0.015264 -0.014292 0.16098 -0.011252 -0.013986 0.184 -0.023739 -0.011633 0.17699 -0.023349 -0.0091029 0.16988 -0.021457 -0.025562 0.18273 -0.0096247 -0.02725 0.18254 -0.0094384 -0.025736 0.17948 -0.0089653 -0.031216 0.17589 -0.0051154 -0.020399 0.1845 -0.014943 -0.021339 0.17645 -0.014566 -0.027125 0.17234 -0.010156 -0.03939 0.1733 -0.0023575 -0.022876 0.16406 -0.0078103 -0.031597 0.16651 -0.0049292 -0.0226 0.15912 -0.003799 -0.030372 0.15767 -0.0012672 -0.021158 0.16849 -0.012383 -0.027 0.1712 -0.01022 -0.041719 0.16813 -0.00074958 -0.04825 0.16748 -0.00015191 -0.03725 0.16147 -7.2628e-05 -0.066429 0.15783 -0.0085673 -0.071284 0.15839 -0.005998 -0.065979 0.16288 -0.017792 -0.071623 0.16384 -0.01576 -0.066068 0.16051 -0.013567 -0.073307 0.16049 -0.011832 -0.077 0.16204 -0.019241 -0.077179 0.15851 -0.01495 -0.073691 0.17286 -0.037944 -0.07755 0.17221 -0.039175 -0.065921 0.16586 -0.025022 -0.072095 0.16784 -0.024725 -0.066 0.16808 -0.030916 -0.073448 0.17051 -0.032045 -0.07777 0.16434 -0.025938 -0.077893 0.16039 -0.021299 -0.078211 0.169 -0.034566 -0.034667 0.15131 -0.00071029 -0.066117 0.17353 -0.047453 -0.071986 0.17612 -0.045384 -0.06925 0.182 -0.055026 -0.064992 0.17802 -0.054645 -0.069935 0.17983 -0.051988 -0.07793 0.17516 -0.0444 以上就是我在window下配置使用PCL的过程中的总结,有问题可以关注微信公众号,加入微信交流群一起交流分享
TF库的目的是实现系统中任一个点在所有坐标系之间的坐标变换,也就是说,只要给定一个坐标系下的一个点的坐标,就能获得这个点在其他坐标系的坐标. 使用tf功能包,a. 监听tf变换: 接收并缓存系统中发布的所有参考系变换,并从中查询所需要的参考系变换。 b.广播 tf变换: 向系统中广播参考系之间的坐标变换关系。系统中更可能会存在多个不同部分的tf变换广播,每个广播都可以直接将参考系变换关系直接插入tf树中,不需要再进行同步。 首先介绍关于TF的API的一些数据结构: 基本的数据类型有(Quaternion, Vector, Point, Pose, Transform) 这其中Quaternion 是表示四元数,vector3是一个3*1 的向量,point是一个表示一个点坐标,Pose是位姿(位姿是包括坐标以及方向) Transform是一个转换的模版 tf::Stamped <T> 是一种包含了除了Transform的其他几种基本的数据结构的一种数据结构: template <typename T> //模版结构可以是tf::Pose tf:Point 这些基本的结构 class Stamped : public T{ public: ros::Time stamp_; //记录时间 std::string frame_id_; //ID Stamped() :frame_id_ ("NO_ID_STAMPED_DEFAULT_CONSTRUCTION"){}; //Default constructor used only for preallocation Stamped(const T& input, const ros::Time& timestamp, const std::string & frame_id); void setData(const T& input); }; tf::StampedTransform TF::stampedtransform是TF的一种特殊情况:它需要frame_id和stamp以及child_frame_id。 /** \brief The Stamped Transform datatype used by tf */ class StampedTransform : public tf::Transform { public: ros::Time stamp_; ///< The timestamp associated with this transform 时间戳 std::string frame_id_; ///< The frame_id of the coordinate frame in which this transform is defined 定义转换坐标框架的frameID std::string child_frame_id_; ///< The frame_id of the coordinate frame this transform defines 的坐标系变换定义的id StampedTransform(const tf::Transform& input, const ros::Time& timestamp, const std::string & frame_id, const std::string & child_frame_id): tf::Transform (input), stamp_ ( timestamp ), frame_id_ (frame_id), child_frame_id_(child_frame_id){ }; /** \brief Default constructor only to be used for preallocation */ StampedTransform() { }; /** \brief Set the inherited Traonsform data */ void setData(const tf::Transform& input){*static_cast<tf::Transform*>(this) = input;}; }; 举个例子 在机器人的定位领域有蒙特卡罗定位(AMCL)的算法,这个算法是根据给定的地图,结合粒子滤波获取最佳定位点Mp,这个定位点是相对于地图map上的坐标,也就是base_link(也就是机器人的基坐标)相对map上的坐标。我们知道 odom 的原点是机器人启动时刻的位置,它在map上的位置或转换矩阵是未知的。但是AMCL可以根据最佳粒子的位置推算出 odom->map(就是说通过最佳粒子推算出机器人在地图的中的位置)的tf转换信息并发布到 tf主题上。因为base_link->odom的tf转换信息是每时每刻都在发布的,所以它是已知的 ,所以这里有个这样的tf关系 map->base_link(就是地图中机器人的位置 是根据最佳粒子推算的) base_link->odom(这是现实中机器人的位姿可以说是里程计的信息) 可以理解:机器人的里程计的信息 = 当前地图中的机器人的的位置 减去 地图中机器人的起点位置。 转为公式可以写成 :map->odom = map->base_link - base_link->odom 或者写为: base_link->odom = map->base_link - map->odom (这样更容易理解) 提示:首先我们可以先了解关于PRY这三个概念关于pitch yaw roll的博客 http://blog.csdn.net/yuzhongchun/article/details/22749521) pitch是围绕X轴旋转,也叫做俯仰角, yaw是围绕Y轴旋转,也叫偏航角, roll是围绕Z轴旋转,也叫翻滚角 1. ROS_DEBUG("New pose: %6.3f %6.3f %6.3f", 2. hyps[max_weight_hyp].pf_pose_mean.v[0], 3. hyps[max_weight_hyp].pf_pose_mean.v[1], 4. hyps[max_weight_hyp].pf_pose_mean.v[2]); 5. // hyps[max_weight_hyp].pf_pose_mean.v[0], [1], [2] 就代表了Mp 也就是机器人的位姿那么位姿的格式是(x,y,theta)最后一个是yaw偏航角, 6. // subtracting base to odom from map to base and send map to odom instead 7. tf::Stamped<tf::Pose> odom_to_map; 8. try 9. { 10. tf::Transform tmp_tf(tf::createQuaternionFromYaw(hyps[max_weight_hyp].pf_pose_mean.v[2]), tf::Vector3(hyps[max_weight_hyp].pf_pose_mean.v[0], 11. hyps[max_weight_hyp].pf_pose_mean.v[1], 12. 0.0)); 13. // tmp_tf = 从map原点看base_link的位置 为yaw生成四元数,最后的0.0是(x,y,z)的Z的值为0 因为这是在二维平面中。 14. tf::Stamped<tf::Pose> tmp_tf_stamped (tmp_tf.inverse(), 15. laser_scan->header.stamp, 16. base_frame_id_); 17. //tmp_tf.inverse() = 以为Mp为坐标的原点,地图map原点相对于Mp的位置,就是在base_link坐标系下map 原点的位置 18. this->tf_->transformPose(odom_frame_id_, 19. tmp_tf_stamped, 20. odom_to_map); 21. //进行 base_link坐标系下的点转换到odom坐标系,也就是把map原点转换到odom坐标系下,等于从odom原点看map原点的位置。放到latest_tf_变量里面 22. } 23. catch(tf::TransformException) 24. { 25. ROS_DEBUG("Failed to subtract base to odom transform"); 26. return; 27. } TF命令行工具 (1) tf_monitor工具的功能是打印tf树中的所有参考系信息,通过输入参数来查看指定参考系之间的信息 用法: rosrun tf tf_monitor tf_monitor <source_frame> <target_target> 监视一个特定的转换 For example, to monitor the transform from /base_footprint to /odom: (2) tf_echo工具的功能是查看指定参考系之间的变换关系。命令的格式: tf_echo <source_frame> <target_frame> (3)static_transform_publisher工具的功能是发布两个参考系之间的静态坐标变换,两个参考系一般不发生相对位置变化 (4)view_frames 是可视化的调试工具,可以生成pdf文件,来显示整棵tf树的信息。用法:rosrun tf view_frames 具体可以查看http://wiki.ros.org/tf/ 博客园的编辑界面真实难以编辑啊 受不鸟了 所以还是不写了 转向CSDN了
我用的是ubuntu14.04LTS,ROS 版本是indigo,kinect v2,我是用双系统装的ubuntu,关于怎么使用安装kinect2的安装以及使用的都在github上有着详细的说明 Kinect2 开源驱动:libfreenect2 kinect2--> ros的bridge:iai_kinect2 安装 安装libfreenect2,这个libfreenect2据说是它的驱动包,希望是稳定的,放在home目录下; git clone https://github.com/OpenKinect/libfreenect2.git 安装好libfreenect2的依赖项 sudo apt-get install build-essential cmake pkg-config libturbojpeg libjpeg-turbo8-dev mesa-common-dev freeglut3-dev libxrandr-dev libxi-dev 安装libusb sudo apt-add-repository ppa:floe/libusb sudo apt-get update sudo apt-get install libusb-1.0-0-dev 安装GLFW3 cd libfreenect2/depends sh install_ubuntu.sh sudo dpkg -i libglfw3*_3.0.4-1_*.deb 不要用sudo apt-get install安装glfw3,但可以用sudo apt-get remove libglfw3-dev卸载它。 所有以包形式安装的包都可以sudo apt-get remove来卸载它 接着编译库 cd .. mkdir build && cd build cmake .. make sudo make install 这时候插上kinect的数据线 运行lsusb,如果下面出现Bus 002 Device 003: ID 045e:02c4 Microsoft Corp. Bus 002 Device 002: ID 045e:02d9 Microsoft Corp. 就说明连上了。 运行 ./bin/Protonect 如果提示no device connected,是因为你的虚拟机默认设置是接usb2.0的口,而kinect v2要接usb3.0的口,所以要做出更改,但是用虚拟机装的ubuntu是连不上kinect的。 如果是要重装或是重新编译libfreenect2,则 cd sudo rm -rf /usr/local/include/libfreenect2 cd /usr/local/lib sudo rm -rf libfreenect2.so libfreenect2.so.0.2 libfreenect2.so.0.2.0 cd OpenNI2/Drivers sudo rm -rf libfreenect2-openni2.so libfreenect2-openni2.so.0 ROS接口安装 对于已经安装了Ros Indigo的Ubuntu14.04来说,使用下面的命令 cd ~/catkin_ws/src/ git clone https://github.com/code-iai/iai_kinect2.git cd iai_kinect2 rosdep install -r --from-paths . cd ~/catkin_ws catkin_make -DCMAKE_BUILD_TYPE="Release" rospack profile rosdep install -r --from-paths .这一步可能会报错,但这个报错是正常的,不用管,不会影响最终结果。 接下来可以测试了。 新打开一个终端,运行 cd ~/catkin_ws/ catkin_make source ./devel/setup.bash cd src roslaunch kinect2_bridge kinect2_bridge.launch 获取Kinect数据 再新打开一个终端,运行 rosrun kinect2_viewer kinect2_viewer 就可以正常显示图像了 一般在使用前 ,我们都会矫正,就是所谓的计算相机的内外参数,一般使用的方法都是张正友标定法的理论,理论知识就不在赘述,其实当时看都是能看懂的一旦让自己完全从头推理就懵逼了 首先呢我们的矫正步骤就是,我们得有标定模板,这如果你之前下载了iai_kinect2在kinect2_calibration文件下是有标定模板的 你可以随意打印一个模板等着后期的标定使用 那么我就按照github的教程教你如果去矫正kinect2 以下 就是github给出的具体的步骤,对于其中的参数我们一般按照要求给就OK了,对于kienct 2的标定并不需要说把红外发射器给遮住 然后使用红外LED去矫正的,这是针对kinect1的,之前我矫正过kinect1 确实是需要把红外发射装置给遮住,然后才能矫正深度信息, Detailed steps: If you haven't already, start the kinect2_bridge with a low number of frames per second (to make it easy on your CPU): rosrun kinect2_bridge kinect2_bridge _fps_limit:=2 (这一步就是要启动ROS的kienct 2的驱动货都kienct2的原始数据) create a directory for your calibration data files, for example: mkdir ~/kinect_cal_data; cd ~/kinect_cal_data(这是为了创建一个文件,存储我们用于矫正的照片) Record images for the color camera: rosrun kinect2_calibration kinect2_calibration chess5x7x0.03 record color (这命令就是开始矫正深度相机的RGB相机,对于后面的参数我们可以知道chess5x7x0.03(是棋盘的大小 可以用尺子量一下的) record(记录保存下下) color) Calibrate the intrinsics: rosrun kinect2_calibration kinect2_calibration chess5x7x0.03 calibrate color Record images for the ir camera: rosrun kinect2_calibration kinect2_calibration chess5x7x0.03 record ir Calibrate the intrinsics of the ir camera: rosrun kinect2_calibration kinect2_calibration chess5x7x0.03 calibrate ir Record images on both cameras synchronized: rosrun kinect2_calibration kinect2_calibration chess5x7x0.03 record sync Calibrate the extrinsics: rosrun kinect2_calibration kinect2_calibration chess5x7x0.03 calibrate sync Calibrate the depth measurements: rosrun kinect2_calibration kinect2_calibration chess5x7x0.03 calibrate depth Find out the serial number of your kinect2 by looking at the first lines printed out by the kinect2_bridge. The line looks like this: device serial: 012526541941 Create the calibration results directory in kinect2_bridge/data/$serial: roscd kinect2_bridge/data; mkdir 012526541941 Copy the following files from your calibration directory (~/kinect_cal_data) into the directory you just created: calib_color.yaml calib_depth.yaml calib_ir.yaml calib_pose.yaml Restart the kinect2_bridge and be amazed at the better data. 还要好多就不在写了 通常遇到最多的就是这种问题,但是这个问题并不影响我们的矫正 [ERROR] Tried to advertise a service that is already advertised in this node [/kinect2_calib_1458721837266890808/compressed/set_parameters] [ERROR] Tried to advertise a service that is already advertised in this node [/kinect2_calib_1458721837266890808/compressed/set_parameters] 真正影响我们的是我们输入的参数 比如ROS的开发包给例子是使用棋盘格6*8的棋盘,正方形的边长为0.03米 但是我们的棋盘可能并并不是那么小的,这时候要使用我们的 棋盘的话就要注意给的参数了。比如用的是长有9个,款有7个正方形的标定棋盘,这时候我们的参数是6*8*0.05,参数一定要给准确,不然会出现错误的 啊、 而且就是在同一个文件下使用命令,
至于ROS的系统,之前就是安装好的,如果有疑问的可以参考官网的安装教程,按照指令一步一步的操作,http://wiki.ros.org/cn/indigo/Installation/Ubuntu (1)添加 sources.list sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list' (2) 添加 keys sudo apt-key adv --keyserver hkp://pool.sks-keyservers.net --recv-key 421C365BD9FF1F717815A3895523BAEEB01FA116 (3)安装 首先,确保你的Debian软件包索引是最新的: sudo apt-get update 桌面完整版安装:(推荐) 包含ROS、rqt、rviz、通用机器人函数库、2D/3D仿真器、导航以及2D/3D感知功能。 sudo apt-get install ros-indigo-desktop-full (4)初始化 rosdep 在开始使用ROS之前你还需要初始化rosdep。rosdep可以方便在你需要编译某些源码的时候为其安装一些系统依赖,同时也是某些ROS核心功能组件所必需用到的工具。 sudo rosdep init rosdep update (5) 环境设置 如果每次打开一个新的终端时ROS环境变量都能够自动配置好(即添加到bash会话中),那将会方便得多: echo "source /opt/ros/indigo/setup.bash" >> ~/.bashrc source ~/.bashrc一般情况下我们会建立自己的ROS工作空间需要在配置环境变量的.bashrc文件里配置一下,比如我的工作空间建立的是catkin_ws那么我的环境配置文件的最小面添加了两行了source /opt/ros/indigo/setup.bashsource /home/(你的主机名)/catkin_ws/devel/setup.bash 如果你安装有多个ROS版本, ~/.bashrc 必须只能 source 你当前使用版本所对应的 setup.bash。 如果你只想改变当前终端下的环境变量,你可以执行命令: source /opt/ros/indigo/setup.bash 过程中可能会遇到各种问题,比如依赖项的问题,对于这种问题,一般我们就直接命令行:sudo apt-get install lib(提示你需要的依赖项的名称)-dev 后面建立工作空间等问题就不在赘述 那我们来搞halcon 的安装吧。 安装完成后我会写一个使用halcon库的ROS下的一个例子 (1)首先到halcon官网下载软件,http://www.mvtec.com/download/halcon,当然首先要注册一下,选择我们需要安装的halcon的版本以及我们电脑的系统的环境,如图 (似乎只能安装在64位的系统,)之后我们就可以选点击“Download Full version”下载完整版 ,之后我们就可以安装了,但是我们知道halcon是用于商业的软件,我们反正无论如何都是需要许可文件的,所以先找一下对应的破解文件,这个可能需要花一点的时间 一般在http://www.ihalcon.com/read.php?tid=56 这个网站找一下,一般能找到破解一个月为有效期的破解文件,之后就可以安装了 这两个压缩包就是安装的源文件以及破解文件 那么我们首先解压安装文件 右键点击halcon-13.0.1.1-linux.tar.gz-> 选择Extract; 在压缩包所在文件夹空白处 终端输入: tar zxvf halcon-13.0.1.1-linux.tar.gz 解压出来即可 (2)我们就可以安装进入解压后的文件来安装执行安装命令 sudo sh install-linux.sh 会遇到一些提示的消息,基本上每个版本都是这样的,当然是选择Y继续啊 阅读许可协议,要注意大多数软件是我们选择了y就会自动执行下去,但是halcon是要我们一直按着enter键来阅读许可文件 当然不能一直按着,一直按到提示,如果按过了,就需要重新开始,哈哈哈,所以记得手下留情 那我们选择Y之后的效果就会提示您安装halcon的具体位置在/opt/halcon下 那我个人觉的还是不要更改安装路径,毕竟真的很麻烦,所以直接enter过去 提示你这个文件是不存在的是否需要新建啊?当然是y啊 之后就是一些选项比如安装的类型,选择3 full参考的语言是English 之后就安装啦 ,没有什么意外的话就会直接安装成功 然后就需要为Ubuntu14.04设置环境了,我们知道在Ubuntu下环境变量的配置文件是 .bashrc 所以执行命令 cd ~ (回到home文件下) sudo gedit ~/.bashrc 便可以打开文件 那么我们在文件的最下面添加halcon的环境变量的设置 # Sample shell script for HALCON environment settings # (sh syntax) # If you are using the Bourne shell source this file with the following # command: # source .profile_halcon HALCONARCH=x64-linux; export HALCONARCH HALCONROOT=/opt/halcon; export HALCONROOT HALCONEXAMPLES=${HALCONROOT}/examples; export HALCONEXAMPLES HALCONIMAGES=${HALCONROOT}/examples/images; export HALCONIMAGES PATH=${HALCONROOT}/bin/${HALCONARCH}:${HALCONROOT}/FLEXlm/${HALCONARCH}:${PATH} export PATH if [ ${LD_LIBRARY_PATH} ] ; then LD_LIBRARY_PATH=${HALCONROOT}/lib/${HALCONARCH}:${LD_LIBRARY_PATH} export LD_LIBRARY_PATH else LD_LIBRARY_PATH=${HALCONROOT}/lib/${HALCONARCH}; export LD_LIBRARY_PATH fi if [ "x${FLEXID_LIBRARY_PATH}" = "x" ]; then FLEXID_LIBRARY_PATH="${HALCONROOT}/FLEXlm/${HALCONARCH}/flexid9:/usr/lib" export FLEXID_LIBRARY_PATH fi 如图,最好我们把这一段添加halcon环境变量的文件给备份一下,因为有时候我们其实不需要在这文件下添加,用到的时候就添加,用不到就去掉,避免出现编译其他 文件的时候出错,因为我在编译其他的文件的时候就提示错误了,只需要咱把这个添加的内容去掉就可以了,当然需要我们需要使用halcon的环境这个是肯定要添加的, 保存好文件后需要source ~/.bashrc一下,使得环境变量设置后生效 那么到此处我们是安装成功了这个halcon软件了,但是还有许可文件啊 真是很蛋疼的东西 解压下载的文件后,发现这个有13.01版本的许可文件license_support_halcon13_2017_08.dat 直接提出到home 文件下 然后我们将这个文件给移动或者复制到 /opt/halcon/license文件下 一般是需要获得root的所以使用sudo cp (许可文件) /opt/halcon/license/ 修改许可证文件权限 sudo chmod 777 /opt/halcon/license/license_support_halcon13_2017_08.dat 这样我们就顺利完成了在Ubuntu环境下安装halcon软件,终端输入: hdevelop 之前说过如果我们在编译其他文件的过程中由于提示消息说有错误,而且是因为我们在~/.bashrc文件中添加了halcon的环境出现的错误 我们只好把添加的文档去掉,去掉之后可能就再在终端输入hdevelop就提示没有这个命令 没关系我们可以直接到 /opt/halcon/bin/x64_linux/双击hdevelop也是可以运行的 运行的hdeveop界面如下 那么其实我对这个软件的使用也是很少的也是第一次使用,但是我们可以随便打开一个例子来试一下 打开find_surface_model.hdev文件,按F5查看单步运行的效果 哈哈 说明安装成功了吧 所以我们就可以尽情的享受halcon 了,这用起来可比opencv爽多了,好多工业应用领域的例子 那么下一篇文章,会教你如果结合ROS操作系统调用halcon来读取一张照片并显示出来, 看完我的文章,对您的ubuntu下学习halcon有帮助的话还请您可以关注我的微信公众号,点赞支持,或者扫描我的支付宝二维码,为了注入动力 如果您觉得看完有所收获,欢迎扫一扫,可以资助一分,几分money,不在乎多少(我也是跟网上的大神们学的),不想挣钱娶媳妇的程序员不是好程序员(同时我也看看到底有没有人认可),谢谢
那我们无论在虚拟机还是在双系统的Ubuntu环境下都是一样的安装过程 我们使用快捷键“ctrl+alt+T”,来打开一个命令窗口如下图 比如我们在命令窗口下输入ls 我们会看到在主目录下的所有文件 下图是我插上u盘的界面,点击确定,有时候插上U盘反应比较慢, 如果U盘不显示你可以点击“虚拟机”查看是否有U盘的出现 或者点击右下角硬盘标注 在左侧显示U盘已经读取的标志是如图 在Ubuntu系统系统下安装PCL等库可以借鉴的网址 http://www.pclcn.org/study/shownews.php?lang=cn&id=32 http://www.linuxdiyf.com/linux/24123.html http://blog.csdn.net/wishchin/article/details/39754165 你先可以按照我的步骤慢慢来,千万不要着急(有点耐心才可以) 安装一些必要的环境工具以及使用PCL的必要的依赖项 在命令窗口输入 sudo apt-get update sudo apt-get install build-essential 会有如下的提示输入y即可 等待 sudo apt-get install cmake 如上(安装编译共军) sudo apt-get install qtcreator 安装qt(这个是一个打开工程文件的工具) sudo apt-get install libflann-* 安装FLANN库 sudo apt-get install libboost1.55-* 安装boost库 sudo apt-get install libqt4-dev 安装qt4的库 sudo apt-get remove libvtk5.8-* 安装VTK sudo apt-get install libeigen3-dev 安装EIGEN sudo apt-get install vtk5-qt4-* 安装qt4的vtk的插件 sudo apt-get install libqhull-dev (或者直接一步把上面的install后面的集中到一起安装 sudo apt-getinstall cmake libpcl-* ibflann-* libboost1.55-* libqt4-devlibvtk5.8-* libeigen3-dev vtk5-qt4* libqhull-dev ) 那么这些依赖项都安装成功了,就可以安装pcl了 这里有两种安装方式 (1)命令行直接安装 sudoadd-apt-repository ppa:v-launchpad-jochen-sprickerhof-de/pcl sudoapt-get update sudoapt-get install libpcl-all 或者sudo apt-get install libpcl-1.7-all-dev (2)源码安装 下载源码:https://github.com/PointCloudLibrary/pcl cd pcl (进入解压后下载的源码文件中) mkdir build cd build cmake -DCMAKE_BUILD_TYPE=Release .. make -j2 sudo make -j2 install 如果安装不上你可以看提示,需要安装哪些东西(有时候就是依赖项的版本不符合所以就把对应的依赖项安装上就可以了,这个过程你可以看提示的内容来安装) sudo apt-getinstall libboost1.54-dev sudo apt-getinstall libboost-system1.54-dev 那么这些都安装好了我给了一个关于qt与PCL的粒子,你可以去CSDN下载来测试编译检查你的pcl环境是否已经安装成功 下载的地址:http://download.csdn.net/detail/u013019296/9843606 然后把我们的工程文件复制到我们的Ubuntu的文件中,在这里我只是复制到home 主目录下 一个简单的程序,你可以先编译这个然后检测是否编译得过,在运行查看是否可以通过 那么现在我们已经复制过来了 现在就开始编译它步骤: (1) 打开命令窗口(Ctrl+alt+T) (2) ls (查看文件时候在目录下)有我给你的文件 (3)cd qt_love_cloud 进入文件 (4)然后新建一个编译的文件:mkdir build (5)进入编译文件:cd build (6)然后生成编译链接:cmake.. (..前面有一个空格的哦)cmake ..之后文件下会生成一些文件你可以查看一次,命令窗口的显示如下 (7)之后就是编译:make 等待 一般如果前面的依赖项都安装成功的话 不会出错的你可以使用命令ls查看时候有一个如下图的文件生成 她就是在ubuntu系统下生成的可执行文件,那么我们可以执行二进制文件 在该目录下执行 ./qt_cloud 你可以点击操作实现对随机点云的着色。 你也可以打开点云数据,注意到文件下的 Data文件下就是我准备的文件 你可以打开试一下 这就说明QT 与pcl结合的粒子, 累死我了 有兴趣者可以关注微信公众号,评论与我交流,或者分享你的想法,也可以点赞支持,分享给更多的人,谢谢
安装虚拟机 (1) 下载VMware安装(自己百度一下,会有很多可供下载的) (2) 安装方式: 双击,一路点击next,不用更改安装路径(当然你也可以更改),选择安装“典型”即可 接着就有安装成功的提示的页面 然后双击桌面的安装的软件 进入后的界面 选择“创建新的虚拟机” 点击“下一步” 在下一个界面里选择“安装程序光盘映像文件 选择一个ubuntu的镜像文件在这里我的是ubuntu-14.04.1-desktop-i386 , 下载地址:链接:http://pan.baidu.com/s/1kV3bjU3 密码:tr1q 比如我的安装的结果如下 点击下一步,需要你设置你的用户名和密码 这里你可以自己设置,比如我的设置就是用户名为pcl 密码就是123,接着点击下一步 这个界面你可以更改虚拟机的名称和安装的位置,你也可以使用默认的位置 点击下一步后是最为关键的一步,就是要设置最大磁盘的大小在这里面你可以使用默认的,也可以使用更大的一点 我建议使用大一点的磁盘空间 然后点击下一步,在点击完成 安装的过程中如果有提示,就直接点击确定即可 如果提示其他内容就是可以不用管他 如果要求下载设呢么VMware tool 你可以下载也可以不用下载 (我建议不下载影响) 然后就可以等待虚拟机安装完成即可 在这里你可能要等上一万年,可以出去厕所,谈个恋爱什么的,等待虚拟机的安装成功 完成之后,虚拟机会自动启动,在这个过程中,我们尽量少开几个程序,少操作电脑,以免出现不必要,或者不知道原因的错误 以下就是ubuntu系统在虚拟机的启动的界面 我们输入之前设置的密码123,回车键即可进入系统 进入系统后他有可能提示你,系统不是最新版本的,你是否需要升级? 我们不选升级选择“don’t upgrade” 之后我们就可以在win系统下使用linux系统了 (注意一定要注意在ubuntu系统下与win 环境下的快捷键有可能是重复的) 所以我们在使用ubuntu系统是,一定要用鼠标点击一下Ubuntu的桌面 我们使用快捷键“ctrl+alt+T”,来打开一个命令窗口如下图 这就是第一步关于在windows系统下安装虚拟机的步骤,这就是大致粗略的步骤,当然啊,对于VMware你可以自行下载安装,对于Ubuntu的镜像文件! 后面将会如何安装PCL以及在QT的使用,等等问题,这也是最近帮别人做小东西记录的过程,其中难免有错误,有发现的可以评论指出, (尽量不要私信,评论也是可以看到会及时回复的,谢谢) 有感兴趣可以关注微信公众号,如果你也觉得对你有帮助,你可以点赞或者支付宝以表示支持,让我们更有动力更新, 同时也鼓励和感谢能够把公众号推荐给更多的小伙伴,或者把公众号发到与此相关的qq群里,谢谢
(3)上两篇介绍了关于欧几里德分割,条件分割,最小分割法等等还有之前就有用RANSAC法的分割方法,这一篇是关于区域生成的分割法, 区 域生长的基本 思想是: 将具有相似性的像素集合起来构成区域。首先对每个需要分割的区域找出一个种子像素作为生长的起点,然后将种子像素周围邻域中与种子有相同或相似性质的像素 (根据事先确定的生长或相似准则来确定)合并到种子像素所在的区域中。而新的像素继续作为种子向四周生长,直到再没有满足条件的像素可以包括进来,一个区 域就生长而成了。 区域生长算法直观感觉上和欧几里德算法相差不大,都是从一个点出发,最终占领整个被分割区域,欧几里德算法是通过距离远近,对于普通点云的区域生长,其可由法线、曲率估计算法获得其法线和曲率值。通过法线和曲率来判断某点是否属于该类。 算法的主要思想是:首先依据点的曲率值对点进行排序,之所以排序是因为,区域生长算法是从曲率最小的点开始生长的,这个点就是初始种子点,初始种子点所在的区域即为最平滑的区域,从最平滑的区域开始生长可减少分割片段的总数,提高效率,设置一空的种子点序列和空的聚类区域,选好初始种子后,将其加入到种子点序列中,并搜索邻域点,对每一个邻域点,比较邻域点的法线与当前种子点的法线之间的夹角,小于平滑阀值的将当前点加入到当前区域,然后检测每一个邻域点的曲率值,小于曲率阀值的加入到种子点序列中,删除当前的种子点,循环执行以上步骤,直到种子序列为空, 其算法可以总结为: 种子周围的临近点和种子点云相比较 法线的方向是否足够相近 曲率是否足够小 如果满足1,2则该点可用做种子点 如果只满足1,则归类而不做种 从某个种子出发,其“子种子”不再出现则一类聚集完成 类的规模既不能太大也不能太小 显然,上述算法是针对小曲率变化面设计的。尤其适合对连续阶梯平面进行分割:比如SLAM算法所获得的建筑走廊。 那么就看一下代码的效果 #include <iostream> #include <vector> #include <pcl/point_types.h> #include <pcl/io/pcd_io.h> #include <pcl/search/search.h> #include <pcl/search/kdtree.h> #include <pcl/features/normal_3d.h> #include <pcl/visualization/cloud_viewer.h> #include <pcl/filters/passthrough.h> #include <pcl/segmentation/region_growing.h> int main (int argc, char** argv) { //点云的类型 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>); //打开点云 if ( pcl::io::loadPCDFile <pcl::PointXYZ> ("region_growing_tutorial.pcd", *cloud) == -1) { std::cout << "Cloud reading failed." << std::endl; return (-1); } //设置搜索的方式或者说是结构 pcl::search::Search<pcl::PointXYZ>::Ptr tree = boost::shared_ptr<pcl::search::Search<pcl::PointXYZ> > (new pcl::search::KdTree<pcl::PointXYZ>); //求法线 pcl::PointCloud <pcl::Normal>::Ptr normals (new pcl::PointCloud <pcl::Normal>); pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> normal_estimator; normal_estimator.setSearchMethod (tree); normal_estimator.setInputCloud (cloud); normal_estimator.setKSearch (50); normal_estimator.compute (*normals); //直通滤波在Z轴的0到1米之间 pcl::IndicesPtr indices (new std::vector <int>); pcl::PassThrough<pcl::PointXYZ> pass; pass.setInputCloud (cloud); pass.setFilterFieldName ("z"); pass.setFilterLimits (0.0, 1.0); pass.filter (*indices); //聚类对象<点,法线> pcl::RegionGrowing<pcl::PointXYZ, pcl::Normal> reg; reg.setMinClusterSize (50); //最小的聚类的点数 reg.setMaxClusterSize (1000000); //最大的 reg.setSearchMethod (tree); //搜索方式 reg.setNumberOfNeighbours (30); //设置搜索的邻域点的个数 reg.setInputCloud (cloud); //输入点 //reg.setIndices (indices); reg.setInputNormals (normals); //输入的法线 reg.setSmoothnessThreshold (3.0 / 180.0 * M_PI); //设置平滑度 reg.setCurvatureThreshold (1.0); //设置曲率的阀值 std::vector <pcl::PointIndices> clusters; reg.extract (clusters); std::cout << "Number of clusters is equal to " << clusters.size () << std::endl; std::cout << "First cluster has " << clusters[0].indices.size () << " points." << endl; std::cout << "These are the indices of the points of the initial" << std::endl << "cloud that belong to the first cluster:" << std::endl; int counter = 0; while (counter < clusters[0].indices.size ()) { std::cout << clusters[0].indices[counter] << ", "; counter++; if (counter % 10 == 0) std::cout << std::endl; } std::cout << std::endl; //可视化聚类的结果 pcl::PointCloud <pcl::PointXYZRGB>::Ptr colored_cloud = reg.getColoredCloud (); pcl::visualization::CloudViewer viewer ("Cluster viewer"); viewer.showCloud(colored_cloud); while (!viewer.wasStopped ()) { } return (0); } 看一下结果 原始点云 区域生成后的点云 (4)基于颜色的区域生长分割法 除了普通点云之外,还有一种特殊的点云,成为RGB点云。显而易见,这种点云除了结构信息之外,还存在颜色信息。将物体通过颜色分类,是人类在辨认果实的 过程中进化出的能力,颜色信息可以很好的将复杂场景中的特殊物体分割出来。比如Xbox Kinect就可以轻松的捕捉颜色点云。基于颜色的区域生长分割原理上和基于曲率,法线的分割方法是一致的。只不过比较目标换成了颜色,去掉了点云规模上 限的限制。可以认为,同一个颜色且挨得近,是一类的可能性很大,不需要上限来限制。所以这种方式比较适合用于室内场景分割。尤其是复杂室内场景,颜色分割 可以轻松的将连续的场景点云变成不同的物体。哪怕是高低不平的地面,没法用采样一致分割器抽掉,颜色分割算法同样能完成分割任务。 算法分为两步: (1)分割,当前种子点和领域点之间色差小于色差阀值的视为一个聚类 (2)合并,聚类之间的色差小于色差阀值和并为一个聚类,且当前聚类中点的数量小于聚类点数量的与最近的聚类合并在一起 查看代码 #include <iostream> #include <vector> #include <pcl/point_types.h> #include <pcl/io/pcd_io.h> #include <pcl/search/search.h> #include <pcl/search/kdtree.h> #include <pcl/visualization/cloud_viewer.h> #include <pcl/filters/passthrough.h> #include <pcl/segmentation/region_growing_rgb.h> int main (int argc, char** argv) { pcl::search::Search <pcl::PointXYZRGB>::Ptr tree = boost::shared_ptr<pcl::search::Search<pcl::PointXYZRGB> > (new pcl::search::KdTree<pcl::PointXYZRGB>); pcl::PointCloud <pcl::PointXYZRGB>::Ptr cloud (new pcl::PointCloud <pcl::PointXYZRGB>); if ( pcl::io::loadPCDFile <pcl::PointXYZRGB> ("region_growing_rgb_tutorial.pcd", *cloud) == -1 ) { std::cout << "Cloud reading failed." << std::endl; return (-1); } //存储点云的容器 pcl::IndicesPtr indices (new std::vector <int>); //滤波 pcl::PassThrough<pcl::PointXYZRGB> pass; pass.setInputCloud (cloud); pass.setFilterFieldName ("z"); pass.setFilterLimits (0.0, 1.0); pass.filter (*indices); //基于颜色的区域生成的对象 pcl::RegionGrowingRGB<pcl::PointXYZRGB> reg; reg.setInputCloud (cloud); reg.setIndices (indices); //点云的索引 reg.setSearchMethod (tree); reg.setDistanceThreshold (10); //距离的阀值 reg.setPointColorThreshold (6); //点与点之间颜色容差 reg.setRegionColorThreshold (5); //区域之间容差 reg.setMinClusterSize (600); //设置聚类的大小 std::vector <pcl::PointIndices> clusters; reg.extract (clusters); pcl::PointCloud <pcl::PointXYZRGB>::Ptr colored_cloud = reg.getColoredCloud (); pcl::visualization::CloudViewer viewer ("Cluster viewer"); viewer.showCloud (colored_cloud); while (!viewer.wasStopped ()) { boost::this_thread::sleep (boost::posix_time::microseconds (100)); } return (0); } 恩 就这样实际应用就是调参数, ------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------ 版权所有,转载请注明出处
(2)关于上一篇博文中提到的欧几里德分割法称之为标准的距离分离,当然接下来介绍其他的与之相关的延伸出来的聚类的方法,我称之为条件欧几里德聚类法,(是我的个人理解),这个条件的设置是可以由我们自定义的,因为除了距离检查,聚类的点还需要满足一个特殊的自定义的要求,就是以第一个点为标准作为种子点,候选其周边的点作为它的对比或者比较的对象,如果满足条件就加入到聚类的对象中,至于到底怎么翻译我也蒙了,只能这样理解了 主要的缺点:该算法没有初始化种子系统,没有过度分割或者分割不足的控制,还有就是从主循环运算中调用条件函数时,效率比较低, 看一下代码吧,至于到底怎么理解各个有个人的理解吧 #include <pcl/io/pcd_io.h> #include <pcl/segmentation/conditional_euclidean_clustering.h> #include <iostream> //如果此函数返回true,则将添加候选点到种子点的簇类中。 bool customCondition(const pcl::PointXYZ& seedPoint, const pcl::PointXYZ& candidatePoint, float squaredDistance) { // 在这里你可以添加你自定义的条件 if (candidatePoint.y < seedPoint.y) return false; return true; } int main(int argc, char** argv) { pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>); if (pcl::io::loadPCDFile<pcl::PointXYZ>(argv[1], *cloud) != 0) { return -1; } // 申明一个条件聚类的对象 pcl::ConditionalEuclideanClustering<pcl::PointXYZ> clustering; clustering.setClusterTolerance(0.02); clustering.setMinClusterSize(100); clustering.setMaxClusterSize(25000); clustering.setInputCloud(cloud); // 设置要检查每对点的函数。 clustering.setConditionFunction(&customCondition); std::vector<pcl::PointIndices> clusters; clustering.segment(clusters); // 对于每一个聚类结果 int currentClusterNum = 1; for (std::vector<pcl::PointIndices>::const_iterator i = clusters.begin(); i != clusters.end(); ++i) { // ...add all its points to a new cloud... pcl::PointCloud<pcl::PointXYZ>::Ptr cluster(new pcl::PointCloud<pcl::PointXYZ>); for (std::vector<int>::const_iterator point = i->indices.begin(); point != i->indices.end(); point++) cluster->points.push_back(cloud->points[*point]); cluster->width = cluster->points.size(); cluster->height = 1; cluster->is_dense = true; // ...and save it to disk. if (cluster->points.size() <= 0) break; std::cout << "Cluster " << currentClusterNum << " has " << cluster->points.size() << " points." << std::endl; std::string fileName = "cluster" + boost::to_string(currentClusterNum) + ".pcd"; pcl::io::savePCDFileASCII(fileName, *cluster); currentClusterNum++; } } 上面执行的条件是检查候选点的Y坐标是否小于种子的Y坐标,没有什么实际意义。所以我就再查看结果了。 那么同时我暂时也用不到,如果有想法的时候再回来研究吧 (2)最小分割算法 该算法是将一幅点云图像分割为两部分:前景点云(目标物体)和背景物体(剩余部分) 关于该算法的论文的地址:http://gfx.cs.princeton.edu/pubs/Golovinskiy_2009_MBS/paper_small.pdf The Min-Cut (minimum cut) algorithm最小割算法是图论中的一个概念,其作用是以某种方式,将两个点分开,当然这两个点中间可能是通过无数的点再相连的。如图 如果要分开最左边的点和最右边的点,红绿两种割法都是可行的,但是红线跨过了三条线,绿线只跨过了两条。单从跨线数量上来论可以得出绿线这种切割方法更优 的结论。但假设线上有不同的权值,那么最优切割则和权值有关了。当你给出了点之间的 “图” ,以及连线的权值时,最小割算法就能按照要求把图分开。 所以那么怎么来理解点云的图呢? 显而易见,切割有两个非常重要的因素,第一个是获得点与点之间的拓扑关系,这种拓扑关系就是生成一张 “图”。第二个是给图中的连线赋予合适的权值。只要这两个要素合适,最小割算法就会正确的分割出想要的结果。点云是分开的点。只要把点云中所有的点连起来就可以了。连接算法如下: 找到每个点临近的n个点 将这n个点和父点连接 找到距离最小的两个块(A块中某点与B块中某点距离最小),并连接 重复3,直至只剩一个块 经过上面的步骤现在已经有了点云的“图”,只要给图附上合适的权值,就满足了最小分割的前提条件。物体分割比如图像分割给人一个直观印象就是属于该物体的点,应该相互之间不会太远。也就是说,可以用点与点之间的欧式距离来构造权值。所有线的权值可映射为线长的函数。 我们知道这种分割是需要指定对象的,也就是我们指定聚类的中心点(center)以及聚类的半径(radius),当然我们指定了中心点和聚类的半径,那么就要被保护起来,保护的方法就是增加它的权值 接下来我们就来看看代码 #include <pcl/io/pcd_io.h> #include <pcl/search/kdtree.h> #include <pcl/features/normal_3d.h> #include <pcl/segmentation/region_growing.h> #include <pcl/segmentation/min_cut_segmentation.h> #include <iostream> #include <pcl/segmentation/region_growing_rgb.h> int main(int argc, char** argv) { //申明点云的类型 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>); // 法线 pcl::PointCloud<pcl::Normal>::Ptr normals(new pcl::PointCloud<pcl::Normal>); if (pcl::io::loadPCDFile<pcl::PointXYZ>(argv[1], *cloud) != 0) { return -1; } // 申明一个Min-cut的聚类对象 pcl::MinCutSegmentation<pcl::PointXYZ> clustering; clustering.setInputCloud(cloud); //设置输入 //创建一个点云,列出所知道的所有属于对象的点 // (前景点)在这里设置聚类对象的中心点(想想是不是可以可以使用鼠标直接选择聚类中心点的方法呢?) pcl::PointCloud<pcl::PointXYZ>::Ptr foregroundPoints(new pcl::PointCloud<pcl::PointXYZ>()); pcl::PointXYZ point; point.x = 100.0; point.y = 100.0; point.z = 100.0; foregroundPoints->points.push_back(point); clustering.setForegroundPoints(foregroundPoints); //设置聚类对象的前景点 //设置sigma,它影响计算平滑度的成本。它的设置取决于点云之间的间隔(分辨率) clustering.setSigma(0.02); // 设置聚类对象的半径. clustering.setRadius(0.01); //设置需要搜索的临近点的个数,增加这个也就是要增加边界处图的个数 clustering.setNumberOfNeighbours(20); //设置前景点的权重(也就是排除在聚类对象中的点,它是点云之间线的权重,) clustering.setSourceWeight(0.6); std::vector <pcl::PointIndices> clusters; clustering.extract(clusters); std::cout << "Maximum flow is " << clustering.getMaxFlow() << "." << std::endl; int currentClusterNum = 1; for (std::vector<pcl::PointIndices>::const_iterator i = clusters.begin(); i != clusters.end(); ++i) { //设置聚类后点云的属性 pcl::PointCloud<pcl::PointXYZ>::Ptr cluster(new pcl::PointCloud<pcl::PointXYZ>); for (std::vector<int>::const_iterator point = i->indices.begin(); point != i->indices.end(); point++) cluster->points.push_back(cloud->points[*point]); cluster->width = cluster->points.size(); cluster->height = 1; cluster->is_dense = true; //保存聚类的结果 if (cluster->points.size() <= 0) break; std::cout << "Cluster " << currentClusterNum << " has " << cluster->points.size() << " points." << std::endl; std::string fileName = "cluster" + boost::to_string(currentClusterNum) + ".pcd"; pcl::io::savePCDFileASCII(fileName, *cluster); currentClusterNum++; } } 看一下实际运行的最小分割法的结果 原始的点云 最小分割法的结果 对于实际应用中我们应该设置正确的参数这是最为关键的!
基于欧式距离的分割和基于区域生长的分割本质上都是用区分邻里关系远近来完成的。由于点云数据提供了更高维度的数据,故有很多信息可以提取获得。欧几里得算法使用邻居之间距离作为判定标准,而区域生长算法则利用了法线,曲率,颜色等信息来判断点云是否应该聚成一类。 (1)欧几里德算法 具体的实现方法大致是: 找到空间中某点p10,有kdTree找到离他最近的n个点,判断这n个点到p的距离。将距离小于阈值r的点p12,p13,p14....放在类Q里 在 Q\p10 里找到一点p12,重复1 在 Q\p10,p12 找到一点,重复1,找到p22,p23,p24....全部放进Q里 当 Q 再也不能有新点加入了,则完成搜索了 因为点云总是连成片的,很少有什么东西会浮在空中来区分。但是如果结合此算法可以应用很多东东。比如 半径滤波删除离群点 采样一致找到桌面或者除去滤波 当然,一旦桌面被剔除,桌上的物体就自然成了一个个的浮空点云团。就能够直接用欧几里德算法进行分割了,这样就可以提取出我们想要识别的东西 在这里我们就可以使用提取平面,利用聚类的方法平面去掉再显示剩下的所有聚类的结果,在这里也就是有关注我的微信公众号的小伙伴向我请教,说虽然都把平面和各种非平面提取出来了,但是怎么把非平面的聚类对象可视化出来呢? 哈哈,刚开始我也以为没有例程实现这样的可视化,也许比较难吧,但是仔细一想,提取出来的聚类的对象都是单独的显示在相对与源文件不变的位置所以我们直接相加就应该可以实现阿~所以废话没多说我就直接写程序,的确可视化的结果就是我想要的结果 那么我们看一下我的代码吧 #include <pcl/ModelCoefficients.h> #include <pcl/point_types.h> #include <pcl/io/pcd_io.h> #include <pcl/filters/extract_indices.h> #include <pcl/filters/voxel_grid.h> #include <pcl/features/normal_3d.h> #include <pcl/kdtree/kdtree.h> #include <pcl/sample_consensus/method_types.h> #include <pcl/sample_consensus/model_types.h> #include <pcl/segmentation/sac_segmentation.h> #include <pcl/segmentation/extract_clusters.h> /****************************************************************************** 打开点云数据,并对点云进行滤波重采样预处理,然后采用平面分割模型对点云进行分割处理 提取出点云中所有在平面上的点集,并将其存盘 ******************************************************************************/ int main (int argc, char** argv) { // 读取文件 pcl::PCDReader reader; pcl::PointCloud<pcl::PointXYZ>::Ptr add_cloud(new pcl::PointCloud<pcl::PointXYZ>); pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>), cloud_f (new pcl::PointCloud<pcl::PointXYZ>); reader.read ("table_scene_lms400.pcd", *cloud); std::cout << "PointCloud before filtering has: " << cloud->points.size () << " data points." << std::endl; //* // 下采样,体素叶子大小为0.01 pcl::VoxelGrid<pcl::PointXYZ> vg; pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered (new pcl::PointCloud<pcl::PointXYZ>); vg.setInputCloud (cloud); vg.setLeafSize (0.01f, 0.01f, 0.01f); vg.filter (*cloud_filtered); std::cout << "PointCloud after filtering has: " << cloud_filtered->points.size () << " data points." << std::endl; //* //创建平面模型分割的对象并设置参数 pcl::SACSegmentation<pcl::PointXYZ> seg; pcl::PointIndices::Ptr inliers (new pcl::PointIndices); //设置聚类的内点索引 pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients);//平面模型的因子 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_plane (new pcl::PointCloud<pcl::PointXYZ> ()); pcl::PCDWriter writer; seg.setOptimizeCoefficients (true); seg.setModelType (pcl::SACMODEL_PLANE); //分割模型 seg.setMethodType (pcl::SAC_RANSAC); //随机参数估计方法 seg.setMaxIterations (100); //最大的迭代的次数 seg.setDistanceThreshold (0.02); //设置阀值 int i=0, nr_points = (int) cloud_filtered->points.size ();//剩余点云的数量 while (cloud_filtered->points.size () > 0.3 * nr_points) { // 从剩余点云中再分割出最大的平面分量 (因为我们要处理的点云的数据是两个平面的存在的) seg.setInputCloud (cloud_filtered); seg.segment (*inliers, *coefficients); if (inliers->indices.size () == 0) //如果内点的数量已经等于0,就说明没有 { std::cout << "Could not estimate a planar model for the given dataset." << std::endl; break; } // 从输入的点云中提取平面模型的内点 pcl::ExtractIndices<pcl::PointXYZ> extract; extract.setInputCloud (cloud_filtered); extract.setIndices (inliers); //提取内点的索引并存储在其中 extract.setNegative (false); // 得到与平面表面相关联的点云数据 extract.filter (*cloud_plane); std::cout << "PointCloud representing the planar component: " << cloud_plane->points.size () << " data points." << std::endl; // // 移去平面局内点,提取剩余点云 extract.setNegative (true); extract.filter (*cloud_f); *cloud_filtered = *cloud_f; } // 创建用于提取搜索方法的kdtree树对象 pcl::search::KdTree<pcl::PointXYZ>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZ>); tree->setInputCloud (cloud_filtered); std::vector<pcl::PointIndices> cluster_indices; pcl::EuclideanClusterExtraction<pcl::PointXYZ> ec; //欧式聚类对象 ec.setClusterTolerance (0.02); // 设置近邻搜索的搜索半径为2cm ec.setMinClusterSize (100); //设置一个聚类需要的最少的点数目为100 ec.setMaxClusterSize (25000); //设置一个聚类需要的最大点数目为25000 ec.setSearchMethod (tree); //设置点云的搜索机制 ec.setInputCloud (cloud_filtered); ec.extract (cluster_indices); //从点云中提取聚类,并将点云索引保存在cluster_indices中 //迭代访问点云索引cluster_indices,直到分割处所有聚类 int j = 0; for (std::vector<pcl::PointIndices>::const_iterator it = cluster_indices.begin (); it != cluster_indices.end (); ++it) { //迭代容器中的点云的索引,并且分开保存索引的点云 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_cluster (new pcl::PointCloud<pcl::PointXYZ>); for (std::vector<int>::const_iterator pit = it->indices.begin (); pit != it->indices.end (); ++pit) //设置保存点云的属性问题 cloud_cluster->points.push_back (cloud_filtered->points[*pit]); //* cloud_cluster->width = cloud_cluster->points.size (); cloud_cluster->height = 1; cloud_cluster->is_dense = true; std::cout << "PointCloud representing the Cluster: " << cloud_cluster->points.size () << " data points." << std::endl; std::stringstream ss; ss << "cloud_cluster_" << j << ".pcd"; writer.write<pcl::PointXYZ> (ss.str (), *cloud_cluster, false); //* //————————————以上就是实现所有的聚类的步骤,并且保存了————————————————————————————// //以下就是我为了回答网友提问解决可视化除了平面以后的可视化的代码也就两行 j++; *add_cloud+=*cloud_cluster; pcl::io::savePCDFileASCII("add_cloud.pcd",*add_cloud); } return (0); } 编译生成可执行文件后结果如下 那么我们查看以下源文件可视化的结果 再可视化我们聚类后除了平面的可视化的结果,从中可以看出效果还是很明显的。 当然总结一下,我们在实际应用的过程中可能没那么轻松,因为我们要根据实际的点云的大小来设置相关的参数,如果参数错误就不太能实现现在的效果。 所以对实际应用中参数的设置是需要经验的吧,下一期会介绍其他的分割方法 有兴趣这关注微信公众号,加入我们与更多的人交流,同时也欢迎更多的来自网友的分享
记录关于我们运行roslaunch openni_launch openni.launch 命令时生成的话题以及这些话题的数据类型便于后期的处理,只有知道它们的数据结构,才能很好的对数据进行处理,我们观察到使用rostopic list的所有话题的列表,当然其中也有一些不经常使用的话题类型,比如下面这些话题是我们经常使用的/camera/depth/image/camera/depth/image_raw/camera/depth/points/camera/ir/image_raw/camera/rgb/image_color/camera/rgb/image_raw 发布的话题: image_raw (sensor_msgs/Image) : 未处理的原始图像 使用命令查看sensor_msgs/Image的数据 camera_info (sensor_msgs/CameraInfo):包含了相机标定配置以及相关数据 介绍几个ROS节点运行的几种工具。他们的作用是ROS格式点云或包与点云数据(PCD)文件格式之间的相互转换。 (1)bag_to_pcd 用法:rosrun pcl_ros bag_to_pcd <input_file.bag> <topic> <output_directory> 读取一个包文件,保存所有ROS点云消息在指定的PCD文件中。 (2)convert_pcd_to_image 用法:rosrun pcl_ros convert_pcd_to_image <cloud.pcd> 加载一个PCD文件,将其作为ROS图像消息每秒中发布五次。 (3) convert_pointcloud_to_image 用法:rosrun pcl_ros convert_pointcloud_to_image input:=/my_cloud output:=/my_image 查看图像:rosrun image_view image_view image:=/my_image 订阅一个ROS的点云的话题并以图像的信息发布出去。(4)pcd_to_pointcloud 用法:rosrun pcl_ros pcd_to_pointcloud <file.pcd> [ <interval> ] <file.pcd> is the (required) file name to read. <interval> is the (optional) number of seconds to sleep between messages. If <interval> is zero or not specified the message is published once. 加载一个PCD文件,发布一次或多次作为ROS点云消息(5)pointcloud_to_pcd 例如: rosrun pcl_ros pointcloud_to_pcd input:=/velodyne/pointcloud2 订阅一个ROS的话题和保存为点云PCD文件。每个消息被保存到一个单独的文件,名称是由一个可自定义的前缀参数,ROS时间的消息,和以PCD扩展的文件。 那么我们使用一个简单的例子来实现在ROS中进行平面的分割,同时注意到使用的数据转换的使用 /************************************************************************** 关于使用pcl/PointCloud<T>的举例应用。这一类型的数据格式是PCL库中定义的一种数据格式 这里面使用了两次数据转换从 sensor_msgs/PointCloud2 到 pcl/PointCloud<T> 和 从 pcl::ModelCoefficients 到 pcl_msgs::ModelCoefficients. ************************************************************************/ #include <iostream> //ROS #include <ros/ros.h> // PCL specific includes #include <sensor_msgs/PointCloud2.h> #include <pcl_conversions/pcl_conversions.h> #include <pcl/ros/conversions.h> #include <pcl/point_cloud.h> #include <pcl/point_types.h> #include <pcl/io/pcd_io.h> //关于平面分割的头文件 #include <pcl/sample_consensus/model_types.h> //分割模型的头文件 #include <pcl/sample_consensus/method_types.h> //采样一致性的方法 #include <pcl/segmentation/sac_segmentation.h> //ransac分割法 ros::Publisher pub; void cloud_cb (const sensor_msgs::PointCloud2ConstPtr& input) { // 将点云格式为sensor_msgs/PointCloud2 格式转为 pcl/PointCloud pcl::PointCloud<pcl::PointXYZ> cloud; pcl::fromROSMsg (*input, cloud); //关键的一句数据的转换 pcl::ModelCoefficients coefficients; //申明模型的参数 pcl::PointIndices inliers; //申明存储模型的内点的索引 // 创建一个分割方法 pcl::SACSegmentation<pcl::PointXYZ> seg; // 这一句可以选择最优化参数的因子 seg.setOptimizeCoefficients (true); // 以下都是强制性的需要设置的 seg.setModelType (pcl::SACMODEL_PLANE); //平面模型 seg.setMethodType (pcl::SAC_RANSAC); //分割平面模型所使用的分割方法 seg.setDistanceThreshold (0.01); //设置最小的阀值距离 seg.setInputCloud (cloud.makeShared ()); //设置输入的点云 seg.segment (inliers, coefficients); //cloud.makeShared() 创建一个 boost shared_ptr // pcl_msgs::fromROSMsg(const sensor_msgs::PointCloud2 &, pcl::PointCloud<T>&); //pcl::io::savePCDFileASCII("test_pcd.pcd",cloud); // 把提取出来的内点形成的平面模型的参数发布出去 pcl_msgs::ModelCoefficients ros_coefficients; pcl_conversions::fromPCL(coefficients, ros_coefficients); pub.publish (ros_coefficients); } int main (int argc, char** argv) { // Initialize ROS ros::init (argc, argv, "my_pcl_tutorial"); ros::NodeHandle nh; // Create a ROS subscriber for the input point cloud ros::Subscriber sub = nh.subscribe ("input", 1, cloud_cb); // Create a ROS publisher for the output model coefficients pub = nh.advertise<pcl_msgs::ModelCoefficients> ("output", 1); // Spin ros::spin (); } 在这里我们的input就是要订阅的话题/camera/depth/points 我们在rosrun 的时候注明input:=/camera/depth/points的这样就可以使用kienct发布的点云数据,同时你也可以指定点云的数据
超体聚类是一种图像的分割方法。 超体(supervoxel)是一种集合,集合的元素是“体”。与体素滤波器中的体类似,其本质是一个个的小方块。与大部分的分割手段不同,超体聚 类的目的并不是分割出某种特定物体,超体是对点云实施过分割(over segmentation),将场景点云化成很多小块,并研究每个小块之间的关系。这种将更小单元合并的分割思路已经出现了有些年份了,在图像分割中,像 素聚类形成超像素,以超像素关系来理解图像已经广为研究。本质上这种方法是对局部的一种总结,纹理,材质,颜色类似的部分会被自动的分割成一块,有利于后 续识别工作。比如对人的识别,如果能将头发,面部,四肢,躯干分开,则能更好的对各种姿态,性别的人进行识别。 点云和图像不一样,其不存在像素邻接关系。所以,超体聚类之前,必须以八叉树对点云进行划分,获得不同点团之间的邻接关系。与图像相似点云的邻接关系也有很多,如面邻接,线邻接,点邻接。 超体聚类实际上是一种特殊的区域生长算法,和无限制的生长不同,超体聚类首先需要规律的布置区域生长“晶核”。晶核在空间中实际上是均匀分布的,并指定晶核距离(Rseed)。再指定粒子距离(Rvoxel)。再指定最小晶粒(MOV),过小的晶粒需要融入最近的大晶粒。 这些基本参数在接下来的参数中会有设置 #include <pcl/console/parse.h> #include <pcl/point_cloud.h> #include <pcl/point_types.h> #include <pcl/io/pcd_io.h> #include <pcl/visualization/pcl_visualizer.h> #include <pcl/segmentation/supervoxel_clustering.h> //VTK include needed for drawing graph lines #include <vtkPolyLine.h> // 数据类型 typedef pcl::PointXYZRGBA PointT; typedef pcl::PointCloud<PointT> PointCloudT; typedef pcl::PointNormal PointNT; typedef pcl::PointCloud<PointNT> PointNCloudT; typedef pcl::PointXYZL PointLT; typedef pcl::PointCloud<PointLT> PointLCloudT; //可视化 void addSupervoxelConnectionsToViewer (PointT &supervoxel_center, PointCloudT &adjacent_supervoxel_centers, std::string supervoxel_name, boost::shared_ptr<pcl::visualization::PCLVisualizer> & viewer); int main (int argc, char ** argv) { //解析命令行 if (argc < 2) { pcl::console::print_error ("Syntax is: %s <pcd-file> \n " "--NT Dsables the single cloud transform \n" "-v <voxel resolution>\n-s <seed resolution>\n" "-c <color weight> \n-z <spatial weight> \n" "-n <normal_weight>\n", argv[0]); return (1); } //打开点云 PointCloudT::Ptr cloud = boost::shared_ptr <PointCloudT> (new PointCloudT ()); pcl::console::print_highlight ("Loading point cloud...\n"); if (pcl::io::loadPCDFile<PointT> (argv[1], *cloud)) { pcl::console::print_error ("Error loading cloud file!\n"); return (1); } bool disable_transform = pcl::console::find_switch (argc, argv, "--NT"); float voxel_resolution = 0.008f; //分辨率 bool voxel_res_specified = pcl::console::find_switch (argc, argv, "-v"); if (voxel_res_specified) pcl::console::parse (argc, argv, "-v", voxel_resolution); float seed_resolution = 0.1f; bool seed_res_specified = pcl::console::find_switch (argc, argv, "-s"); if (seed_res_specified) pcl::console::parse (argc, argv, "-s", seed_resolution); float color_importance = 0.2f; if (pcl::console::find_switch (argc, argv, "-c")) pcl::console::parse (argc, argv, "-c", color_importance); float spatial_importance = 0.4f; if (pcl::console::find_switch (argc, argv, "-z")) pcl::console::parse (argc, argv, "-z", spatial_importance); float normal_importance = 1.0f; if (pcl::console::find_switch (argc, argv, "-n")) pcl::console::parse (argc, argv, "-n", normal_importance); //如何使用SupervoxelClustering函数 pcl::SupervoxelClustering<PointT> super (voxel_resolution, seed_resolution); if (disable_transform)//如果设置的是参数--NT 就用默认的参数 super.setUseSingleCameraTransform (false); super.setInputCloud (cloud); super.setColorImportance (color_importance); //0.2f super.setSpatialImportance (spatial_importance); //0.4f super.setNormalImportance (normal_importance); //1.0f std::map <uint32_t, pcl::Supervoxel<PointT>::Ptr > supervoxel_clusters; pcl::console::print_highlight ("Extracting supervoxels!\n"); super.extract (supervoxel_clusters); pcl::console::print_info ("Found %d supervoxels\n", supervoxel_clusters.size ()); boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer (new pcl::visualization::PCLVisualizer ("3D Viewer")); viewer->setBackgroundColor (0, 0, 0); PointCloudT::Ptr voxel_centroid_cloud = super.getVoxelCentroidCloud ();//获得体素中心的点云 viewer->addPointCloud (voxel_centroid_cloud, "voxel centroids"); viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE,2.0, "voxel centroids"); //渲染点云 viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_OPACITY,0.95, "voxel centroids"); PointLCloudT::Ptr labeled_voxel_cloud = super.getLabeledVoxelCloud (); viewer->addPointCloud (labeled_voxel_cloud, "labeled voxels"); viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_OPACITY,0.8, "labeled voxels"); PointNCloudT::Ptr sv_normal_cloud = super.makeSupervoxelNormalCloud (supervoxel_clusters); //We have this disabled so graph is easy to see, uncomment to see supervoxel normals //viewer->addPointCloudNormals<PointNormal> (sv_normal_cloud,1,0.05f, "supervoxel_normals"); pcl::console::print_highlight ("Getting supervoxel adjacency\n"); std::multimap<uint32_t, uint32_t> supervoxel_adjacency; super.getSupervoxelAdjacency (supervoxel_adjacency); //To make a graph of the supervoxel adjacency, we need to iterate through the supervoxel adjacency multimap //为了使整个超体形成衣服图,我们需要遍历超体的每个临近的个体 std::multimap<uint32_t,uint32_t>::iterator label_itr = supervoxel_adjacency.begin (); for ( ; label_itr != supervoxel_adjacency.end (); ) { //First get the label uint32_t supervoxel_label = label_itr->first; //Now get the supervoxel corresponding to the label pcl::Supervoxel<PointT>::Ptr supervoxel = supervoxel_clusters.at (supervoxel_label); //Now we need to iterate through the adjacent supervoxels and make a point cloud of them PointCloudT adjacent_supervoxel_centers; std::multimap<uint32_t,uint32_t>::iterator adjacent_itr = supervoxel_adjacency.equal_range (supervoxel_label).first; for ( ; adjacent_itr!=supervoxel_adjacency.equal_range (supervoxel_label).second; ++adjacent_itr) { pcl::Supervoxel<PointT>::Ptr neighbor_supervoxel = supervoxel_clusters.at (adjacent_itr->second); adjacent_supervoxel_centers.push_back (neighbor_supervoxel->centroid_); } //Now we make a name for this polygon std::stringstream ss; ss << "supervoxel_" << supervoxel_label; //This function is shown below, but is beyond the scope of this tutorial - basically it just generates a "star" polygon mesh from the points given //从给定的点云中生成一个星型的多边形, addSupervoxelConnectionsToViewer (supervoxel->centroid_, adjacent_supervoxel_centers, ss.str (), viewer); //Move iterator forward to next label label_itr = supervoxel_adjacency.upper_bound (supervoxel_label); } while (!viewer->wasStopped ()) { viewer->spinOnce (100); } return (0); } //VTK可视化构成的聚类图 void addSupervoxelConnectionsToViewer (PointT &supervoxel_center, PointCloudT &adjacent_supervoxel_centers, std::string supervoxel_name, boost::shared_ptr<pcl::visualization::PCLVisualizer> & viewer) { vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New (); vtkSmartPointer<vtkCellArray> cells = vtkSmartPointer<vtkCellArray>::New (); vtkSmartPointer<vtkPolyLine> polyLine = vtkSmartPointer<vtkPolyLine>::New (); //Iterate through all adjacent points, and add a center point to adjacent point pair PointCloudT::iterator adjacent_itr = adjacent_supervoxel_centers.begin (); for ( ; adjacent_itr != adjacent_supervoxel_centers.end (); ++adjacent_itr) { points->InsertNextPoint (supervoxel_center.data); points->InsertNextPoint (adjacent_itr->data); } // Create a polydata to store everything in vtkSmartPointer<vtkPolyData> polyData = vtkSmartPointer<vtkPolyData>::New (); // Add the points to the dataset polyData->SetPoints (points); polyLine->GetPointIds ()->SetNumberOfIds(points->GetNumberOfPoints ()); for(unsigned int i = 0; i < points->GetNumberOfPoints (); i++) polyLine->GetPointIds ()->SetId (i,i); cells->InsertNextCell (polyLine); // Add the lines to the dataset polyData->SetLines (cells); viewer->addModelFromPolyData (polyData,supervoxel_name); } 可执行文件生成后的图像显示如下 当然也可以自己设定参数生成自己想要的效果。同时在不同的场景中,使用的参数是十分重要的, 只是先了解超体的概念,如果想应用到实际的应用中,还需要很多其他的知识 ,所以这里只是基本的学习 有兴趣这关注我的微信公众号
来自微信公众号的分享 我刚刚开始接触PCL,懂的东西也很少,所以总是出现各种各样的问题,每次遇见问题的时候要查找各种各样的资料,很费时间。所以,今天我把我遇见的常见问题分享给大家,讲解的步骤尽量详细,让和我一样基础差的小伙伴能尽快进入到PCL点云库的学习中,希望能和大家进步。 运行环境:PCL-1.8.0-AllInOne-msvc2013-win64,是64位的,VS2013英文版。 问题1:如何获取PCD文件。之前有小伙伴问我如何获取pcd文件,我知道这是很基础的问题,但是新手常常会问这个问题,也包括我自己学习刚刚开始学习的时候。通常有两种方法, way1:一种是通过cloudcompare软件进行转换,该软件可以到它的官网下载,这对于新手比较直接。 Way2:写代码自己转。 问题2:错误提示为1.IntelliSense: cannot open source file "pcl/io/pcd_io.h" c:\visual 等如下图所示,要检查一下自己的是不是把编译平台已经更改为64位的。 解决方法: 步骤1: 步骤2: 问题3:报错类似的问题如 Error 3 error C4996: 'std::_Uninitialized_copy0':Function call with parameters that may be unsafe - this call relies on thecaller to check that the passed values are correct. To disable this warning,use -D_SCL_SECURE_NO_WARNINGS.See documentation on how to use Visual C++ 'Checked Iterators' C:\Program Files (x86)\Microsoft VisualStudio 12.0\VC\include\xmemory 348 解决方法: 步骤1:打开属性表; 步骤2:将_SCL_SECURE_NO_WARNINGS添加在预处理器定义里如如所示 注意:若上述提示的错误信息是C4996:’fopen’*******_CRT_SECURE_NO_WARNINGS********的问题,按照上述步骤将_CRT_SECURE_NO_WARNINGS添加到预处理器定义中即可。 问题4:编译的时候遇到如下错误提示 error C4996: 'pcl::SAC_SAMPLE_SIZE': Thismap is deprecated and is kept only to prevent breaking existing user code. Startingfrom PCL 1.8.0 model sample size is a protected member of theSampleConsensusModel class. 这是程序生命周期检查出现的问题。 解决方法: 打开项目属性页>C/C++>常规>SDL检查(设置为否)。 问题5:编译的时候遇到如下错误提示 error C1128: number of sections exceededobject file format limit : compile with /bigobj 解决方法: 右键项目,properties(属性)-> Configuration Properties(配置属性) -> C/C++-> Command Line(命令行) -> Additional options(其他选项),然后加上 /bigobj属性,确定,然后重新编译即可。 在这里非常感谢这位同学,这样分享总结,我十分感动啊,我的初衷就是希望大家可以这样分享,给初学者提供一点建议。相互学习进步。 所以建议在学习一段时间之后,能写一点总结分享大家 感兴趣者扫描二维码关注微信公众号,后台可直接私信我
image_encodings.cpp文件是关于图像编码模式的源文件,其中规定了RGB的图像以及深度图的编码模式 该编码文件image_encodings.cpp所依赖的头文件图 命令空间 sensor_msgs::image_encodings 下的函数 Functions int bitDepth (const std::string &encoding) bool hasAlpha (const std::string &encoding) bool isBayer (const std::string &encoding) bool isColor (const std::string &encoding) bool isMono (const std::string &encoding) int numChannels (const std::string &encoding) Variables const std::string BGR8 = "bgr8" const std::string MONO16 = "mono16" const std::string MONO8 = "mono8" const std::string TYPE_16SC1 = "16SC1" const std::string TYPE_16UC1 = "16UC1" const std::string TYPE_32FC1 = "32FC1" const std::string TYPE_32SC1 = "32SC1" const std::string TYPE_64FC1 = "64FC1" const std::string TYPE_8SC1 = "8SC1" const std::string TYPE_8UC1 = "8UC1" 那么关于深度图的编码的方式 有如下:TYPE_8UC1 TYPE_64FC1 等等// B = bits (8, 16, 32,64), T = type (U, S, F)#define CHECK_BIT_DEPTH(B, T) if (encoding == TYPE_##B##T##C1 || encoding == TYPE_##B##T##C2 || encoding == TYPE_##B##T##C3 || encoding == TYPE_##B##T##C4) return B; 比如使用这样编码方式,对kinect获得的深度进行显示,程序如下 #include <ros/ros.h> //ros 的头文件 #include <image_transport/image_transport.h> //image_transport #include <cv_bridge/cv_bridge.h> //cv_bridge #include <sensor_msgs/image_encodings.h> //图像编码格式 #include <opencv2/imgproc/imgproc.hpp> //图像处理 #include <opencv2/highgui/highgui.hpp> //opencv GUI static const std::string OPENCV_WINDOW = "Image window"; //申明一个GUI 的显示的字符串 class ImageConverter //申明一个图像转换的类 { ros::NodeHandle nh_; //实例化一个节点 image_transport::ImageTransport it_; image_transport::Subscriber image_sub_; //订阅节点 image_transport::Publisher image_pub_; //发布节点 public: ImageConverter() : it_(nh_) { // Subscrive to input video feed and publish output video feed image_sub_ = it_.subscribe("/camera/depth/image_raw", 1, &ImageConverter::imageCb, this); image_pub_ = it_.advertise("/image_converter/output_video", 1); cv::namedWindow(OPENCV_WINDOW); } ~ImageConverter() { cv::destroyWindow(OPENCV_WINDOW); } void imageCb(const sensor_msgs::ImageConstPtr& msg) //回调函数 { cv_bridge::CvImagePtr cv_ptr; //申明一个CvImagePtr try { cv_ptr = cv_bridge::toCvCopy(msg, sensor_msgs::image_encodings::TYPE_32FC1); } catch (cv_bridge::Exception& e) { ROS_ERROR("cv_bridge exception: %s", e.what()); return; } //转化为opencv的格式之后就可以对图像进行操作了 // Draw an example circle on the video stream if (cv_ptr->image.rows > 60 && cv_ptr->image.cols > 60) cv::circle(cv_ptr->image, cv::Point(50, 50), 10, CV_RGB(255,0,0)); //画圆 // Update GUI Window cv::imshow(OPENCV_WINDOW, cv_ptr->image); cv::waitKey(3); // Output modified video stream image_pub_.publish(cv_ptr->toImageMsg()); } }; int main(int argc, char** argv) { ros::init(argc, argv, "image_converter"); ImageConverter ic; ros::spin(); return 0; } 最主要的就是编码的方式的正确即可实现深度图的显示 从中我们可以看得处深度图使用cv_bridge进行转换与RGB图之间的转换为OPENCV可处理的结构基本上类似,但是最重要的就是编码的模式的正确,所以这是非常关键的为了使用深度图与RGB的图生成点云,所以我们需要对深度图使用正确的编码模式,具体的代码我就不再展示了,那么我们可以看一下,对于不同的编码模式生成点云之间的区别看起来就好像断层了一样,但是如果配合正确的编码的模式效果就不是这样的了,所以在使用cv_bridge的时候选择正确的编码模式是非常重要的,暂时就更新到这里了,如果有问题可以直接评论,或者关注微信公众号,或者加入QQ交流群与更多的人交流
关于输入一个具体的物体的点云,从场景中找出与该物体点云相匹配的,这种方法可以用来抓取指定的物体等等,具体的代码的解释如下,需要用到的一些基础的知识,在之前的博客中都有提及,其中用到的一些方法可以翻阅前面的博客,当然有问题可以关注公众号,与众多爱好者一起交流 具体的代码实现 #include <pcl/io/pcd_io.h> #include <pcl/point_cloud.h> //点云类型头文件 #include <pcl/correspondence.h> //对应表示两个实体之间的匹配(例如,点,描述符等)。 #include <pcl/features/normal_3d_omp.h> //法线 #include <pcl/features/shot_omp.h> //描述子 #include <pcl/features/board.h> #include <pcl/filters/uniform_sampling.h> //均匀采样 #include <pcl/recognition/cg/hough_3d.h> //hough算子 #include <pcl/recognition/cg/geometric_consistency.h> //几何一致性 #include <pcl/visualization/pcl_visualizer.h> //可视化 #include <pcl/kdtree/kdtree_flann.h> //配准方法 #include <pcl/kdtree/impl/kdtree_flann.hpp> // #include <pcl/common/transforms.h> //转换矩阵 #include <pcl/console/parse.h> typedef pcl::PointXYZRGBA PointType; //PointXYZRGBA数据结构 typedef pcl::Normal NormalType; //法线结构 typedef pcl::ReferenceFrame RFType; //参考帧 typedef pcl::SHOT352 DescriptorType; //SHOT特征的数据结构 std::string model_filename_; //模型的文件名 std::string scene_filename_; //Algorithm params bool show_keypoints_ (false); bool show_correspondences_ (false); bool use_cloud_resolution_ (false); bool use_hough_ (true); float model_ss_ (0.01f); float scene_ss_ (0.03f); float rf_rad_ (0.015f); float descr_rad_ (0.02f); float cg_size_ (0.01f); float cg_thresh_ (5.0f); void showHelp (char *filename) { std::cout << std::endl; std::cout << "***************************************************************************" << std::endl; std::cout << "* *" << std::endl; std::cout << "* Correspondence Grouping Tutorial - Usage Guide *" << std::endl; std::cout << "* *" << std::endl; std::cout << "***************************************************************************" << std::endl << std::endl; std::cout << "Usage: " << filename << " model_filename.pcd scene_filename.pcd [Options]" << std::endl << std::endl; std::cout << "Options:" << std::endl; std::cout << " -h: Show this help." << std::endl; std::cout << " -k: Show used keypoints." << std::endl; std::cout << " -c: Show used correspondences." << std::endl; std::cout << " -r: Compute the model cloud resolution and multiply" << std::endl; std::cout << " each radius given by that value." << std::endl; std::cout << " --algorithm (Hough|GC): Clustering algorithm used (default Hough)." << std::endl; std::cout << " --model_ss val: Model uniform sampling radius (default 0.01)" << std::endl; std::cout << " --scene_ss val: Scene uniform sampling radius (default 0.03)" << std::endl; std::cout << " --rf_rad val: Reference frame radius (default 0.015)" << std::endl; std::cout << " --descr_rad val: Descriptor radius (default 0.02)" << std::endl; std::cout << " --cg_size val: Cluster size (default 0.01)" << std::endl; std::cout << " --cg_thresh val: Clustering threshold (default 5)" << std::endl << std::endl; } void parseCommandLine (int argc, char *argv[]) { //Show help if (pcl::console::find_switch (argc, argv, "-h")) { showHelp (argv[0]); exit (0); } //Model & scene filenames std::vector<int> filenames; filenames = pcl::console::parse_file_extension_argument (argc, argv, ".pcd"); if (filenames.size () != 2) { std::cout << "Filenames missing.\n"; showHelp (argv[0]); exit (-1); } model_filename_ = argv[filenames[0]]; scene_filename_ = argv[filenames[1]]; //Program behavior if (pcl::console::find_switch (argc, argv, "-k")) { show_keypoints_ = true; } if (pcl::console::find_switch (argc, argv, "-c")) { show_correspondences_ = true; } if (pcl::console::find_switch (argc, argv, "-r")) //计算点云的分辨率和多样性 { use_cloud_resolution_ = true; } std::string used_algorithm; if (pcl::console::parse_argument (argc, argv, "--algorithm", used_algorithm) != -1) { if (used_algorithm.compare ("Hough") == 0) { use_hough_ = true; }else if (used_algorithm.compare ("GC") == 0) { use_hough_ = false; } else { std::cout << "Wrong algorithm name.\n"; showHelp (argv[0]); exit (-1); } } //General parameters pcl::console::parse_argument (argc, argv, "--model_ss", model_ss_); pcl::console::parse_argument (argc, argv, "--scene_ss", scene_ss_); pcl::console::parse_argument (argc, argv, "--rf_rad", rf_rad_); pcl::console::parse_argument (argc, argv, "--descr_rad", descr_rad_); pcl::console::parse_argument (argc, argv, "--cg_size", cg_size_); pcl::console::parse_argument (argc, argv, "--cg_thresh", cg_thresh_); } double computeCloudResolution (const pcl::PointCloud<PointType>::ConstPtr &cloud)//计算点云分辨率 { double res = 0.0; int n_points = 0; int nres; std::vector<int> indices (2); std::vector<float> sqr_distances (2); pcl::search::KdTree<PointType> tree; tree.setInputCloud (cloud); //设置输入点云 for (size_t i = 0; i < cloud->size (); ++i) { if (! pcl_isfinite ((*cloud)[i].x)) { continue; } //Considering the second neighbor since the first is the point itself. //运算第二个临近点,因为第一个点是它本身 nres = tree.nearestKSearch (i, 2, indices, sqr_distances);//return :number of neighbors found if (nres == 2) { res += sqrt (sqr_distances[1]); ++n_points; } } if (n_points != 0) { res /= n_points; } return res; } int main (int argc, char *argv[]) { parseCommandLine (argc, argv); pcl::PointCloud<PointType>::Ptr model (new pcl::PointCloud<PointType> ()); //模型点云 pcl::PointCloud<PointType>::Ptr model_keypoints (new pcl::PointCloud<PointType> ());//模型角点 pcl::PointCloud<PointType>::Ptr scene (new pcl::PointCloud<PointType> ()); //目标点云 pcl::PointCloud<PointType>::Ptr scene_keypoints (new pcl::PointCloud<PointType> ());//目标角点 pcl::PointCloud<NormalType>::Ptr model_normals (new pcl::PointCloud<NormalType> ()); //法线 pcl::PointCloud<NormalType>::Ptr scene_normals (new pcl::PointCloud<NormalType> ()); pcl::PointCloud<DescriptorType>::Ptr model_descriptors (new pcl::PointCloud<DescriptorType> ()); //描述子 pcl::PointCloud<DescriptorType>::Ptr scene_descriptors (new pcl::PointCloud<DescriptorType> ()); //载入文件 if (pcl::io::loadPCDFile (model_filename_, *model) < 0) { std::cout << "Error loading model cloud." << std::endl; showHelp (argv[0]); return (-1); } if (pcl::io::loadPCDFile (scene_filename_, *scene) < 0) { std::cout << "Error loading scene cloud." << std::endl; showHelp (argv[0]); return (-1); } // 设置分辨率 if (use_cloud_resolution_) { float resolution = static_cast<float> (computeCloudResolution (model)); if (resolution != 0.0f) { model_ss_ *= resolution; scene_ss_ *= resolution; rf_rad_ *= resolution; descr_rad_ *= resolution; cg_size_ *= resolution; } std::cout << "Model resolution: " << resolution << std::endl; std::cout << "Model sampling size: " << model_ss_ << std::endl; std::cout << "Scene sampling size: " << scene_ss_ << std::endl; std::cout << "LRF support radius: " << rf_rad_ << std::endl; std::cout << "SHOT descriptor radius: " << descr_rad_ << std::endl; std::cout << "Clustering bin size: " << cg_size_ << std::endl << std::endl; } //计算法线 normalestimationomp估计局部表面特性在每个三个特点,分别表面的法向量和曲率,平行,使用OpenMP标准。//初始化调度程序并设置要使用的线程数(默认为0)。 pcl::NormalEstimationOMP<PointType, NormalType> norm_est; norm_est.setKSearch (10); //设置K邻域搜索阀值为10个点 norm_est.setInputCloud (model); //设置输入点云 norm_est.compute (*model_normals); //计算点云法线 norm_est.setInputCloud (scene); norm_est.compute (*scene_normals); //均匀采样点云并提取关键点 pcl::UniformSampling<PointType> uniform_sampling; uniform_sampling.setInputCloud (model); //输入点云 uniform_sampling.setRadiusSearch (model_ss_); //设置半径 uniform_sampling.filter (*model_keypoints); //滤波 std::cout << "Model total points: " << model->size () << "; Selected Keypoints: " << model_keypoints->size () << std::endl; uniform_sampling.setInputCloud (scene); uniform_sampling.setRadiusSearch (scene_ss_); uniform_sampling.filter (*scene_keypoints); std::cout << "Scene total points: " << scene->size () << "; Selected Keypoints: " << scene_keypoints->size () << std::endl; //为关键点计算描述子 pcl::SHOTEstimationOMP<PointType, NormalType, DescriptorType> descr_est; descr_est.setRadiusSearch (descr_rad_); //设置搜索半径 descr_est.setInputCloud (model_keypoints); //输入模型的关键点 descr_est.setInputNormals (model_normals); //输入模型的法线 descr_est.setSearchSurface (model); //输入的点云 descr_est.compute (*model_descriptors); //计算描述子 descr_est.setInputCloud (scene_keypoints); //同理 descr_est.setInputNormals (scene_normals); descr_est.setSearchSurface (scene); descr_est.compute (*scene_descriptors); // 使用Kdtree找出 Model-Scene 匹配点 pcl::CorrespondencesPtr model_scene_corrs (new pcl::Correspondences ()); pcl::KdTreeFLANN<DescriptorType> match_search; //设置配准的方法 match_search.setInputCloud (model_descriptors); //输入模板点云的描述子 //每一个场景的关键点描述子都要找到模板中匹配的关键点描述子并将其添加到对应的匹配向量中。 for (size_t i = 0; i < scene_descriptors->size (); ++i) { std::vector<int> neigh_indices (1); //设置最近邻点的索引 std::vector<float> neigh_sqr_dists (1); //申明最近邻平方距离值 if (!pcl_isfinite (scene_descriptors->at (i).descriptor[0])) //忽略 NaNs点 { continue; } int found_neighs = match_search.nearestKSearch (scene_descriptors->at (i), 1, neigh_indices, neigh_sqr_dists); //scene_descriptors->at (i)是给定点云 1是临近点个数 ,neigh_indices临近点的索引 neigh_sqr_dists是与临近点的索引 if(found_neighs == 1 && neigh_sqr_dists[0] < 0.25f) // 仅当描述子与临近点的平方距离小于0.25(描述子与临近的距离在一般在0到1之间)才添加匹配 { //neigh_indices[0]给定点, i 是配准数 neigh_sqr_dists[0]与临近点的平方距离 pcl::Correspondence corr (neigh_indices[0], static_cast<int> (i), neigh_sqr_dists[0]); model_scene_corrs->push_back (corr); //把配准的点存储在容器中 } } std::cout << "Correspondences found: " << model_scene_corrs->size () << std::endl; // 实际的配准方法的实现 std::vector<Eigen::Matrix4f, Eigen::aligned_allocator<Eigen::Matrix4f> > rototranslations; std::vector<pcl::Correspondences> clustered_corrs; // 使用 Hough3D算法寻找匹配点 if (use_hough_) { // // Compute (Keypoints) Reference Frames only for Hough //计算参考帧的Hough(也就是关键点) pcl::PointCloud<RFType>::Ptr model_rf (new pcl::PointCloud<RFType> ()); pcl::PointCloud<RFType>::Ptr scene_rf (new pcl::PointCloud<RFType> ()); //特征估计的方法(点云,法线,参考帧) pcl::BOARDLocalReferenceFrameEstimation<PointType, NormalType, RFType> rf_est; rf_est.setFindHoles (true); rf_est.setRadiusSearch (rf_rad_); //设置搜索半径 rf_est.setInputCloud (model_keypoints); //模型关键点 rf_est.setInputNormals (model_normals); //模型法线 rf_est.setSearchSurface (model); //模型 rf_est.compute (*model_rf); //模型的参考帧 rf_est.setInputCloud (scene_keypoints); //同理 rf_est.setInputNormals (scene_normals); rf_est.setSearchSurface (scene); rf_est.compute (*scene_rf); // Clustering聚类的方法 //对输入点与的聚类,以区分不同的实例的场景中的模型 pcl::Hough3DGrouping<PointType, PointType, RFType, RFType> clusterer; clusterer.setHoughBinSize (cg_size_);//霍夫空间设置每个bin的大小 clusterer.setHoughThreshold (cg_thresh_);//阀值 clusterer.setUseInterpolation (true); clusterer.setUseDistanceWeight (false); clusterer.setInputCloud (model_keypoints); clusterer.setInputRf (model_rf); //设置输入的参考帧 clusterer.setSceneCloud (scene_keypoints); clusterer.setSceneRf (scene_rf); clusterer.setModelSceneCorrespondences (model_scene_corrs);//model_scene_corrs存储配准的点 //clusterer.cluster (clustered_corrs);辨认出聚类的对象 clusterer.recognize (rototranslations, clustered_corrs); } else // Using GeometricConsistency 或者使用几何一致性性质 { pcl::GeometricConsistencyGrouping<PointType, PointType> gc_clusterer; gc_clusterer.setGCSize (cg_size_); //设置几何一致性的大小 gc_clusterer.setGCThreshold (cg_thresh_); //阀值 gc_clusterer.setInputCloud (model_keypoints); gc_clusterer.setSceneCloud (scene_keypoints); gc_clusterer.setModelSceneCorrespondences (model_scene_corrs); //gc_clusterer.cluster (clustered_corrs); gc_clusterer.recognize (rototranslations, clustered_corrs); } //输出的结果 找出输入模型是否在场景中出现 std::cout << "Model instances found: " << rototranslations.size () << std::endl; for (size_t i = 0; i < rototranslations.size (); ++i) { std::cout << "\n Instance " << i + 1 << ":" << std::endl; std::cout << " Correspondences belonging to this instance: " << clustered_corrs[i].size () << std::endl; // 打印处相对于输入模型的旋转矩阵与平移矩阵 Eigen::Matrix3f rotation = rototranslations[i].block<3,3>(0, 0); Eigen::Vector3f translation = rototranslations[i].block<3,1>(0, 3); printf ("\n"); printf (" | %6.3f %6.3f %6.3f | \n", rotation (0,0), rotation (0,1), rotation (0,2)); printf (" R = | %6.3f %6.3f %6.3f | \n", rotation (1,0), rotation (1,1), rotation (1,2)); printf (" | %6.3f %6.3f %6.3f | \n", rotation (2,0), rotation (2,1), rotation (2,2)); printf ("\n"); printf (" t = < %0.3f, %0.3f, %0.3f >\n", translation (0), translation (1), translation (2)); } //可视化 pcl::visualization::PCLVisualizer viewer ("Correspondence Grouping"); viewer.addPointCloud (scene, "scene_cloud");//可视化场景点云 pcl::PointCloud<PointType>::Ptr off_scene_model (new pcl::PointCloud<PointType> ()); pcl::PointCloud<PointType>::Ptr off_scene_model_keypoints (new pcl::PointCloud<PointType> ()); if (show_correspondences_ || show_keypoints_) //可视化配准点 { // We are translating the model so that it doesn't end in the middle of the scene representation //就是要对输入的模型进行旋转与平移,使其在可视化界面的中间位置 pcl::transformPointCloud (*model, *off_scene_model, Eigen::Vector3f (-1,0,0), Eigen::Quaternionf (1, 0, 0, 0)); pcl::transformPointCloud (*model_keypoints, *off_scene_model_keypoints, Eigen::Vector3f (-1,0,0), Eigen::Quaternionf (1, 0, 0, 0)); //因为模型的位置变化了,所以要对特征点进行变化 pcl::visualization::PointCloudColorHandlerCustom<PointType> off_scene_model_color_handler (off_scene_model, 255, 255, 128); //对模型点云上颜色 viewer.addPointCloud (off_scene_model, off_scene_model_color_handler, "off_scene_model"); } if (show_keypoints_) //可视化关键点 { pcl::visualization::PointCloudColorHandlerCustom<PointType> scene_keypoints_color_handler (scene_keypoints, 0, 0, 255); //对场景中的点云上为绿色 viewer.addPointCloud (scene_keypoints, scene_keypoints_color_handler, "scene_keypoints"); viewer.setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 5, "scene_keypoints"); pcl::visualization::PointCloudColorHandlerCustom<PointType> off_scene_model_keypoints_color_handler (off_scene_model_keypoints, 0, 0, 255); viewer.addPointCloud (off_scene_model_keypoints, off_scene_model_keypoints_color_handler, "off_scene_model_keypoints"); viewer.setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 5, "off_scene_model_keypoints"); } for (size_t i = 0; i < rototranslations.size (); ++i) { pcl::PointCloud<PointType>::Ptr rotated_model (new pcl::PointCloud<PointType> ()); pcl::transformPointCloud (*model, *rotated_model, rototranslations[i]);//把model转化为rotated_model // <Eigen::Matrix4f, Eigen::aligned_allocator<Eigen::Matrix4f> > rototranslations是射影变换矩阵 std::stringstream ss_cloud; ss_cloud << "instance" << i; pcl::visualization::PointCloudColorHandlerCustom<PointType> rotated_model_color_handler (rotated_model, 255, 0, 0); viewer.addPointCloud (rotated_model, rotated_model_color_handler, ss_cloud.str ()); if (show_correspondences_) //显示配准连接 { for (size_t j = 0; j < clustered_corrs[i].size (); ++j) { std::stringstream ss_line; ss_line << "correspondence_line" << i << "_" << j; PointType& model_point = off_scene_model_keypoints->at (clustered_corrs[i][j].index_query); PointType& scene_point = scene_keypoints->at (clustered_corrs[i][j].index_match); // We are drawing a line for each pair of clustered correspondences found between the model and the scene viewer.addLine<PointType, PointType> (model_point, scene_point, 0, 255, 0, ss_line.str ()); } } } while (!viewer.wasStopped ()) { viewer.spinOnce (); } return (0); } 可视化特征角点 使用Hough配准的角点的方法 有兴趣者可以扫描二维码关注公众号,加QQ交流群 一起分享你的学习方法, 如果有好多想法和工程可以一起学习,并且欢迎您一起投稿分享给更多的人,大家一起进步!!
(1) 关于pcl::PCLPointCloud2::Ptr和pcl::PointCloud<pcl::PointXYZ>两中数据结构的区别 pcl::PointXYZ::PointXYZ ( float_x, float_y, float_z ) 区别: struct PCLPointCloud2 { PCLPointCloud2 () : header (), height (0), width (0), fields (), is_bigendian (false), point_step (0), row_step (0), data (), is_dense (false) { #if defined(BOOST_BIG_ENDIAN) is_bigendian = true; #elif defined(BOOST_LITTLE_ENDIAN) is_bigendian = false; #else #error "unable to determine system endianness" #endif } ::pcl::PCLHeader header; pcl::uint32_t height; pcl::uint32_t width; std::vector< ::pcl::PCLPointField> fields; pcl::uint8_t is_bigendian; pcl::uint32_t point_step; pcl::uint32_t row_step; std::vector<pcl::uint8_t> data; pcl::uint8_t is_dense; public: typedef boost::shared_ptr< ::pcl::PCLPointCloud2> Ptr; typedef boost::shared_ptr< ::pcl::PCLPointCloud2 const> ConstPtr; }; // struct PCLPointCloud2 那么要实现它们之间的数据转换, 举个例子 pcl::PCLPointCloud2::Ptr cloud_blob (new pcl::PCLPointCloud2), cloud_filtered_blob (new pcl::PCLPointCloud2);//申明滤波前后的点云 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered (new pcl::PointCloud<pcl::PointXYZ>), cloud_p (new pcl::PointCloud<pcl::PointXYZ>), cloud_f (new pcl::PointCloud<pcl::PointXYZ>); // 读取PCD文件 pcl::PCDReader reader; reader.read ("table_scene_lms400.pcd", *cloud_blob); //统计滤波前的点云个数 std::cerr << "PointCloud before filtering: " << cloud_blob->width * cloud_blob->height << " data points." << std::endl; // 创建体素栅格下采样: 下采样的大小为1cm pcl::VoxelGrid<pcl::PCLPointCloud2> sor; //体素栅格下采样对象 sor.setInputCloud (cloud_blob); //原始点云 sor.setLeafSize (0.01f, 0.01f, 0.01f); // 设置采样体素大小 sor.filter (*cloud_filtered_blob); //保存 // 转换为模板点云 pcl::fromPCLPointCloud2 (*cloud_filtered_blob, *cloud_filtered); std::cerr << "PointCloud after filtering: " << cloud_filtered->width * cloud_filtered->height << " data points." << std::endl; // 保存下采样后的点云 pcl::PCDWriter writer; writer.write<pcl::PointXYZ> ("table_scene_lms400_downsampled.pcd", *cloud_filtered, false); 程序中红色部分就是一句实现两者之间的数据转化的我们可以看出 cloud_filtered_blob 声明的数据格式为pcl::PCLPointCloud2::Ptr cloud_filtered_blob (new pcl::PCLPointCloud2); cloud_filtered 申明的数据格式 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered (new pcl::PointCloud<pcl::PointXYZ>) 那么依照这种的命名风格我们可以查看到更多的关于的数据格式之间的转换的类的成员 (1) void pcl::fromPCLPointCloud(const pcl:PCLPointCloud2 & msg pcl::PointCloud<PointT> & cloud const MsgFieldMap & filed_map ) 函数使用field_map实现将一个pcl::pointcloud2二进制数据blob到PCL::PointCloud<pointT>对象 使用 PCLPointCloud2 (PCLPointCloud2, PointCloud<T>)生成自己的 MsgFieldMap MsgFieldMap field_map; createMapping<PointT> (msg.fields, field_map); (2) void pcl::fromPCLPointCloud2(const pcl::PCLPointCloud & msg pcl::PointCloud<pointT> &cloud ) 把pcl::PCLPointCloud数据格式的点云转化为pcl::PointCloud<pointT>格式 (3) void pcl::fromROSMsg(const pcl:PCLPointCloud2 & msg pcl::PointCloud<PointT> & cloud const MsgFieldMap & filed_map ) (4) void pcl::fromROSMsg(const pcl:PCLPointCloud2 & msg pcl::PointCloud<PointT> & cloud ) 在使用fromROSMsg是一种在ROS 下的一种数据转化的作用,我们举个例子实现订阅使用kinect发布 /camera/depth/points 从程序中我们可以看到如何使用该函数实现数据的转换。并且我在程序中添加了如果使用PCL的库实现在ROS下调用并且可视化, /************************************************ 关于如何使用PCL在ROS 中,实现简单的数据转化 时间:2017.3.31 ****************************************************/ #include <ros/ros.h> // PCL specific includes #include <sensor_msgs/PointCloud2.h> #include <pcl_conversions/pcl_conversions.h> #include <pcl/point_cloud.h> #include <pcl/point_types.h> #include <pcl/visualization/pcl_visualizer.h> #include <boost/thread/thread.hpp> #include <pcl/visualization/cloud_viewer.h> ros::Publisher pub; pcl::visualization::CloudViewer viewer("Cloud Viewer"); void cloud_cb (const sensor_msgs::PointCloud2ConstPtr& input) { // 创建一个输出的数据格式 sensor_msgs::PointCloud2 output; //ROS中点云的数据格式 //对数据进行处理 pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZRGB>); output = *input; pcl::fromROSMsg(output,*cloud); //blocks until the cloud is actually rendered viewer.showCloud(cloud); pub.publish (output); } int main (int argc, char** argv) { // Initialize ROS ros::init (argc, argv, "my_pcl_tutorial"); ros::NodeHandle nh; // Create a ROS subscriber for the input point cloud ros::Subscriber sub = nh.subscribe ("input", 1, cloud_cb); ros::Rate loop_rate(100); // Create a ROS publisher for the output point cloud pub = nh.advertise<sensor_msgs::PointCloud2> ("output", 1); // Spin ros::spin (); /* while (!viewer.wasStopped ()) { } */ } 那么对于这一段小程序实现了从发布的节点中转化为可以使用PCL的可视化函数实现可视化,并不一定要用RVIZ来实现,所以我们分析以下其中的步骤,在订阅话题的回调函数中, void cloud_cb (const sensor_msgs::PointCloud2ConstPtr& input) //这里面设置了一个数据类型为sensor_msgs::PointCloud2ConstPtr& input形参{ sensor_msgs::PointCloud2 output; //ROS中点云的数据格式(或者说是发布话题点云的数据类型) pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZRGB>); //对数据转换后存储的类型 output = *input; pcl::fromROSMsg(output,*cloud); //最重要的一步骤实现从ROS到PCL中的数据的转化,同时也可以直接使用PCL库实现可视化 viewer.showCloud(cloud); //PCL库的可视化 pub.publish (output); //那么原来的output的类型仍然是sensor_msgs::PointCloud2,可以通过RVIZ来可视化} 那么也可以使用 pcl::PCDWriter writer; writer.write<pcl::PointXYZ> ("ros_to_PCL.pcd", *cloud, false); 这一段代码来实现保存的作用。那么见到那看一下可视化的结果 使用pcl_viewer 可视化保存的PCD文件 可能写的比较乱,但是有用到关于PCL中点云数据类型的转换以及可视化等功能可以参考,同时欢迎有兴趣者扫描下方二维码或者QQ群 与我交流并且投稿,大家一起学习,共同进步与分享
在ROS中点云的数据类型 在ROS中表示点云的数据结构有: sensor_msgs::PointCloud sensor_msgs::PointCloud2 pcl::PointCloud<T> 关于PCL在ros的数据的结构,具体的介绍可查 看 wiki.ros.org/pcl/Overview 关于sensor_msgs::PointCloud2 和 pcl::PointCloud<T>之间的转换使用pcl::fromROSMsg 和 pcl::toROSMsg sensor_msgs::PointCloud 和 sensor_msgs::PointCloud2之间的转换 使用sensor_msgs::convertPointCloud2ToPointCloud 和sensor_msgs::convertPointCloudToPointCloud2. 那么如何在ROS中使用PCL呢?(1)在建立的包下的CMakeLists.txt文件下添加依赖项 在package.xml文件里添加: <build_depend>libpcl-all-dev</build_depend> <run_depend>libpcl-all</run_depend> 在src文件夹下新建.cpp文件 #include <ros/ros.h> // PCL specific includes #include <sensor_msgs/PointCloud2.h> #include <pcl_conversions/pcl_conversions.h> #include <pcl/point_cloud.h> #include <pcl/point_types.h> ros::Publisher pub; void cloud_cb (const sensor_msgs::PointCloud2ConstPtr& input) { // Create a container for the data. sensor_msgs::PointCloud2 output; // Do data processing here... output = *input; // Publish the data. pub.publish (output); } int main (int argc, char** argv) { // Initialize ROS ros::init (argc, argv, "my_pcl_tutorial"); ros::NodeHandle nh; ros::Subscriber sub = nh.subscribe ("input", 1, cloud_cb); pub = nh.advertise<sensor_msgs::PointCloud2> ("output", 1); // Spin ros::spin (); } 在 CMakeLists.txt 文件中添加: add_executable(example src/example.cpp) target_link_libraries(example ${catkin_LIBRARIES}) catkin_make之后生成可执行文件,运行以下命令 roslaunch openni_launch openni.launch 这是打开kinect发布的命令 $ rosrun ros_slam example input:=/camera/depth/points //运行我们生成的文件 运行RVIZ可视化以下,添加了程序发布的点云的话题既可以显示。同时也可以使用PCL自带的显示的函数可视化(这里不再一一赘述) $ rosrun rviz rviz在RVIZ中显示的点云的数据格式sensor_msgs::PointCloud2; 那么如果我们想实现对获取的点云的数据的滤波的处理,这里就是进行一个简单的体素网格采样的实验 同样在src文件夹下新建.cpp文件,然后我们的程序如下。也就是要在回调函数中实现对获取的点云的滤波的处理,但是我们要特别注意每个程序中的点云的数据格式以及我们是如何使用函数实现对ROS与PCL 的转化的。 程序如下 /*********************************************************** 关于使用sensor_msgs/PointCloud2, ***********************************************************/ #include <ros/ros.h> // PCL 的相关的头文件 #include <sensor_msgs/PointCloud2.h> #include <pcl_conversions/pcl_conversions.h> #include <pcl/point_cloud.h> #include <pcl/point_types.h> //滤波的头文件 #include <pcl/filters/voxel_grid.h> //申明发布器 ros::Publisher pub; //回调函数 void cloud_cb (const sensor_msgs::PointCloud2ConstPtr& input) //特别注意的是这里面形参的数据格式 { // 声明存储原始数据与滤波后的数据的点云的 格式 pcl::PCLPointCloud2* cloud = new pcl::PCLPointCloud2; //原始的点云的数据格式 pcl::PCLPointCloud2ConstPtr cloudPtr(cloud); pcl::PCLPointCloud2 cloud_filtered; //存储滤波后的数据格式 // 转化为PCL中的点云的数据格式 pcl_conversions::toPCL(*input, *cloud); // 进行一个滤波处理 pcl::VoxelGrid<pcl::PCLPointCloud2> sor; //实例化滤波 sor.setInputCloud (cloudPtr); //设置输入的滤波 sor.setLeafSize (0.1, 0.1, 0.1); //设置体素网格的大小 sor.filter (cloud_filtered); //存储滤波后的点云 // 再将滤波后的点云的数据格式转换为ROS 下的数据格式发布出去 sensor_msgs::PointCloud2 output; //声明的输出的点云的格式 pcl_conversions::fromPCL(cloud_filtered, output); //第一个参数是输入,后面的是输出 //发布命令 pub.publish (output); } int main (int argc, char** argv) { // 初始化 ROS节点 ros::init (argc, argv, "my_pcl_tutorial"); ros::NodeHandle nh; //声明节点的名称 // 为接受点云数据创建一个订阅节点 ros::Subscriber sub = nh.subscribe ("input", 1, cloud_cb); //创建ROS的发布节点 pub = nh.advertise<sensor_msgs::PointCloud2> ("output", 1); // 回调 ros::spin (); } 看一下结果如图,这是在RVIZ中显示的结果,当然也可以使用PCL库实现可视化(注意我们在rviz中显示的点云的数据格式都是sensor_msgs::PointCloud2 要区别pcl::PCLPointCloud2 这是PCL点云库中定义的一种的数据格式,在RVIZ中不可显示,) /**************************************************************************关于使用pcl/PointCloud<T>的举例应用。这一类型的数据格式是PCL库中定义的一种数据格式这里面使用了两次数据转换从 sensor_msgs/PointCloud2 到 pcl/PointCloud<T> pcl::ModelCoefficients 到 pcl_msgs::ModelCoefficients. 代码 #include <ros/ros.h> // PCL specific includes #include <sensor_msgs/PointCloud2.h> #include <pcl_conversions/pcl_conversions.h> #include <pcl/ros/conversions.h> #include <pcl/point_cloud.h> #include <pcl/point_types.h> //关于平面分割的头文件 #include <pcl/sample_consensus/model_types.h> //分割模型的头文件 #include <pcl/sample_consensus/method_types.h> //采样一致性的方法 #include <pcl/segmentation/sac_segmentation.h> //ransac分割法 ros::Publisher pub; void cloud_cb (const sensor_msgs::PointCloud2ConstPtr& input) { // 将点云格式为sensor_msgs/PointCloud2 格式转为 pcl/PointCloud pcl::PointCloud<pcl::PointXYZ> cloud; pcl::fromROSMsg (*input, cloud); //关键的一句数据的转换 pcl::ModelCoefficients coefficients; //申明模型的参数 pcl::PointIndices inliers; //申明存储模型的内点的索引 // 创建一个分割方法 pcl::SACSegmentation<pcl::PointXYZ> seg; // 这一句可以选择最优化参数的因子 seg.setOptimizeCoefficients (true); // 以下都是强制性的需要设置的 seg.setModelType (pcl::SACMODEL_PLANE); //平面模型 seg.setMethodType (pcl::SAC_RANSAC); //分割平面模型所使用的分割方法 seg.setDistanceThreshold (0.01); //设置最小的阀值距离 seg.setInputCloud (cloud.makeShared ()); //设置输入的点云 seg.segment (inliers, coefficients); //cloud.makeShared() 创建一个 boost shared_ptr // 把提取出来的内点形成的平面模型的参数发布出去 pcl_msgs::ModelCoefficients ros_coefficients; pcl_conversions::fromPCL(coefficients, ros_coefficients); pub.publish (ros_coefficients); } int main (int argc, char** argv) { // Initialize ROS ros::init (argc, argv, "my_pcl_tutorial"); ros::NodeHandle nh; // Create a ROS subscriber for the input point cloud ros::Subscriber sub = nh.subscribe ("input", 1, cloud_cb); // Create a ROS publisher for the output model coefficients pub = nh.advertise<pcl_msgs::ModelCoefficients> ("output", 1); // Spin ros::spin (); } 提取点云中平面的参数并且发布出去 PCL对ROS的接口的总结 比如: pcl::toROSMsg(*cloud,output); 实现的功能是将pcl里面的pcl::PointCloud<pcl::PointXYZ> cloud 转换成ros里面的sensor_msgs::PointCloud2 output 这个类型。 PCL对ROS的接口提供PCL数据结构的转换,通过通过ROS提供的以消息为基础的转换系统系统。这有一系列的转换函数提供用来转换原始的PCL数据类型成消息型。一些最有用常用的的message类型列举在下面。 std_msgs:Header:这不是真的消息类型,但是用在Ros消息里面的每一个部分。它包含了消息被发送的时间和序列号和框名。PCL等于pcl::Header类型 sensor_msgs::PointCloud2:这是最重要的类型。这个消息通常是用来转换pcl::PointCloud类型的,pcl::PCLPointCloud2这个类型也很重要,因为前面版本的可能被废除。 pcl_msgs::PointIndices:这个类型存储属于点云里面的点的下标,在pcl里面等于pcl::PointIndices pcl_msgs::PolygonMesh这个类型包括消息需要描述多边形网眼,就是顶点和边,在pcl里面等于pcl::PolygonMesh pcl_msgs::Vertices:这个类型包含了一系列的顶点作为一个数组的下标,来描述一个多边形。在pcl里面等于pcl::Vertices pcl_msgs::ModelCoefficients:这存储了一个模型的不同的系数,比如描述一个平面需要4个系数。在PCL里面等于pcl::ModelCoefficients 上面的数据可以从PCL转成ROS里面的PCL。所有的函数有一个类似的特征,意味着一旦我们知道这样去转换一个类型,我们就能学会转换其他的类型。下面的函数是在pcl_conversions命名空间里面提供的函数 下面的函数是在pcl_conversions命名空间里面提供的函数 void copyImageMetaData (const sensor_msgs::Image &image, pcl::PCLImage &pcl_image) void copyPCLImageMetaData (const pcl::PCLImage &pcl_image, sensor_msgs::Image &image) void copyPCLPointCloud2MetaData (const pcl::PCLPointCloud2 &pcl_pc2, sensor_msgs::PointCloud2 &pc2) void copyPointCloud2MetaData (const sensor_msgs::PointCloud2 &pc2, pcl::PCLPointCloud2 &pcl_pc2) void fromPCL (const pcl::PCLImage &pcl_image, sensor_msgs::Image &image) void fromPCL (const std::vector< pcl::PCLPointField > &pcl_pfs, std::vector< sensor_msgs::PointField > &pfs) void fromPCL (const pcl::PCLPointCloud2 &pcl_pc2, sensor_msgs::PointCloud2 &pc2) void moveFromPCL (pcl::PCLImage &pcl_image, sensor_msgs::Image &image) void moveFromPCL (pcl::PCLPointCloud2 &pcl_pc2, sensor_msgs::PointCloud2 &pc2) void moveToPCL (sensor_msgs::Image &image, pcl::PCLImage &pcl_image) void moveToPCL (sensor_msgs::PointCloud2 &pc2, pcl::PCLPointCloud2 &pcl_pc2) void moveToPCL (pcl_msgs::ModelCoefficients &mc, pcl::ModelCoefficients &pcl_mc) void toPCL (const sensor_msgs::Image &image, pcl::PCLImage &pcl_image) void toPCL (const sensor_msgs::PointCloud2 &pc2, pcl::PCLPointCloud2 &pcl_pc2) 总结出来就是 void fromPCL(const <PCL Type> &, <ROS Message type> &);void moveFromPCL(<PCL Type> &, <ROS Message type> &);void toPCL(const <ROS Message type> &, <PCL Type> &);void moveToPCL(<ROS Message type> &, <PCL Type> &); PCL类型必须被替换成先前指定的PCL类型和ROS里面相应的类型。sensor_msgs::PointCloud2有一个特定的函数集去执行转换 void toROSMsg(const pcl::PointCloud<T> &, sensor_msgs::PointCloud2 &); 转换为ROS的点云sensor_msgs::PointCloud2类型void fromROSMsg(const sensor_msgs::PointCloud2 &, pcl::PointCloud<T>&); 转为PCL中的pcl::PointCloud<T>类型void moveFromROSMsg(sensor_msgs::PointCloud2 &, pcl::PointCloud<T> &); 转换为pcl::PointCloud<T> 类型 ************** 有兴趣者可以关注微信公众号或者加入QQ群中
最近想使用OpenCV 和ROS实现点云的拼接,实现三维重建,那么在学习了kinect的基本的使用方法以后我们知道,直接使用ROS 的包即可得到点云,深度图,rgb图等信息, roslaunch openni_launch openni.launch(深度图彩色图,还有点云都获取了) rosrun openni_camera openni_node (深度图与彩色图) 那么实现点云的拼接就需要使用cv_bridge把ROS 的数据格式转为Opencv可以使用的数据格式。即是一个提供ROS和OpenCV库提供之间的接口的开发包。 (1) 将ROS图像信息转换为OpenCV图像cvbridge定义了一个opencv图像cvimage的类型、包含了编码和ROS的信息头。cvimage包含准确的信息sensor_msgs /image,因此我们可以将两种数据类型进行转换。cvimage类格式: namespace cv_bridge { class CvImage { public: std_msgs::Header header; std::string encoding; cv::Mat image; }; typedef boost::shared_ptr<CvImage> CvImagePtr; typedef boost::shared_ptr<CvImage const> CvImageConstPtr; } 当要把一个ROS 的sensor_msgs /image 信息转化为一cvimage,cvbridge有两个不同的使用情况: (1)如果要修改某一位的数据。我们必须复制ROS信息数据的作为副本。 (2)不修改数据。可以安全地共享的ROS信息,而不是复制的数据。 cvbridge转换为CvImage提供了以下功能: // Case 1: Always copy, returning a mutable CvImage CvImagePtr toCvCopy(const sensor_msgs::ImageConstPtr& source, const std::string& encoding = std::string()); CvImagePtr toCvCopy(const sensor_msgs::Image& source, const std::string& encoding = std::string()); // Case 2: Share if possible, returning a const CvImage CvImageConstPtr toCvShare(const sensor_msgs::ImageConstPtr& source, const std::string& encoding = std::string()); CvImageConstPtr toCvShare(const sensor_msgs::Image& source, const boost::shared_ptr<void const>& tracked_object, const std::string& encoding = std::string()); 输入是图像消息指针,以及可选的编码参数。编码是指cvimage的类型。 tocvcopy复制从ROS消息的图像数据,可以自由修改返回的cvimage。即使当源和目的编码匹配。 tocvshare将避免复制,在ROS的消息数据把指针返回的 cv::mat,,如果源和目的编码匹配。只要你保持一份返回的cvimage,ROS的消息数据将不会释放。如果编码不匹配时,它将分配一个新的缓冲区,执行转换。将不得修改返回的cvimage,因为它可能与ROS的图像信息共享数据,这反过来又可能与其他回调函数共享。注:对tocvshare二次过载更方便,转换当一个指针指向其他信息类型(如stereo_msgs / disparityimage)包含一个sensor_msgs /image。 如果没有编码(或更确切地说,空字符串),则目标图像编码将与图像消息编码相同。在这种情况下tocvshare保证不复制图像数据。图像编码可以是以下任何一个opencv图像编码: 8UC[1-4] 8SC[1-4] 16UC[1-4] 16SC[1-4] 32SC[1-4] 32FC[1-4] 64FC[1-4] 介绍集中cvbridge 中常见的数据编码的形式,cv_bridge可以有选择的对颜色和深度信息进行转化。为了使用指定的特征编码,就有下面集中的编码形式: mono8: CV_8UC1, 灰度图像mono16: CV_16UC1,16位灰度图像bgr8: CV_8UC3,带有颜色信息并且颜色的顺序是BGR顺序rgb8: CV_8UC3,带有颜色信息并且颜色的顺序是RGB顺序bgra8: CV_8UC4, BGR的彩色图像,并且带alpha通道rgba8: CV_8UC4,CV,RGB彩色图像,并且带alpha通道注:这其中mono8和bgr8两种图像编码格式是大多数OpenCV的编码格式。 Finally, CvBridge will recognize Bayer pattern encodings as having OpenCV type 8UC1 (8-bit unsigned, one channel). It will not perform conversions to or from Bayer pattern; in a typical ROS system, this is done instead by image_proc. CvBridge recognizes the following Bayer encodings: bayer_rggb8 bayer_bggr8 bayer_gbrg8 bayer_grbg8 1.把Opencv图像转换为ROS图像格式 To convert a CvImage into a ROS image message, use one the toImageMsg() member function: class CvImage { sensor_msgs::ImagePtr toImageMsg() const; // Overload mainly intended for aggregate messages that contain // a sensor_msgs::Image as a member. void toImageMsg(sensor_msgs::Image& ros_image) const; }; 举个例子 首先要在创建的包CMakeLists.txt里添加依赖包。分别如下: sensor_msgs cv_bridge roscpp std_msgs image_transport 在src文件下建立一个cv_ros_beginner.cpp文件文件内容如下 #include <ros/ros.h> //ros 的头文件 #include <image_transport/image_transport.h> //image_transport #include <cv_bridge/cv_bridge.h> //cv_bridge #include <sensor_msgs/image_encodings.h> //图像编码格式 #include <opencv2/imgproc/imgproc.hpp> //图像处理 #include <opencv2/highgui/highgui.hpp> //opencv GUI static const std::string OPENCV_WINDOW = "Image window"; //申明一个GUI 的显示的字符串 class ImageConverter //申明一个图像转换的类 { ros::NodeHandle nh_; //实例化一个节点 image_transport::ImageTransport it_; image_transport::Subscriber image_sub_; //订阅节点 image_transport::Publisher image_pub_; //发布节点 public: ImageConverter() : it_(nh_) { // Subscrive to input video feed and publish output video feed image_sub_ = it_.subscribe("/rgb/image_raw", 1, &ImageConverter::imageCb, this); image_pub_ = it_.advertise("/image_converter/output_video", 1); cv::namedWindow(OPENCV_WINDOW); } ~ImageConverter() { cv::destroyWindow(OPENCV_WINDOW); } void imageCb(const sensor_msgs::ImageConstPtr& msg) //回调函数 { cv_bridge::CvImagePtr cv_ptr; //申明一个CvImagePtr try { cv_ptr = cv_bridge::toCvCopy(msg, sensor_msgs::image_encodings::BGR8); } catch (cv_bridge::Exception& e) { ROS_ERROR("cv_bridge exception: %s", e.what()); return; } //转化为opencv的格式之后就可以对图像进行操作了 // Draw an example circle on the video stream if (cv_ptr->image.rows > 60 && cv_ptr->image.cols > 60) cv::circle(cv_ptr->image, cv::Point(50, 50), 10, CV_RGB(255,0,0)); //画圆 // Update GUI Window cv::imshow(OPENCV_WINDOW, cv_ptr->image); cv::waitKey(3); // Output modified video stream image_pub_.publish(cv_ptr->toImageMsg()); } }; int main(int argc, char** argv) { ros::init(argc, argv, "image_converter"); ImageConverter ic; ros::spin(); return 0; } 然后在CMakeLists.txt添加, add_executable(cv_ros_beginner src/cv_ros_beginner.cpp) target_link_libraries(cv_ros_beginner ${catkin_LIBRARIES} ) 返回catin_ws 下catkin_make即可生成可执行文件 那么我们在查看结果的时候就是要把 image_sub_ = it_.subscribe("/rgb/image_raw", 1, &ImageConverter::imageCb, this); 把订阅的节点换成之前我们需要订阅的节点即可实现 在订阅的图像上画一个小圆圈,并且可视化出来。 (小技巧记录:如果你的工作空间下包很多,每次都使用catkin_make的话效率十分低下,因为这种编译方法会编译工作空间下的所有的包,特别是我们在调试程序是会经常修改CMakeLists.txt文件里的内容,这样每次都会要编译整个工作空间,那么所以我们可以使用ROS的catkin_Make的功能编译一个或者多个包,具体的命令是:catkin_make -DCATKIN_WHITELIST_PACKAGES=" 你的包名"),例如:catkin_make -DCATKIN_WHITELIST_PACKAGES="ros_slam" 如果需要编译两个或者多个只需要中间加分号即可catkin_make -DCATKIN_WHITELIST_PACKAGES="ros_slam;cv_bridge", 只是搬运以下官网的程序,当然你可以实现更多的处理 那么有关于ROS以及PCL的使用的兴趣者扫描二维码关注一起与我交流分享
点云分割是根据空间,几何和纹理等特征对点云进行划分,使得同一划分内的点云拥有相似的特征,点云的有效分割往往是许多应用的前提,例如逆向工作,CAD领域对零件的不同扫描表面进行分割,然后才能更好的进行空洞修复曲面重建,特征描述和提取,进而进行基于3D内容的检索,组合重用等。 案例分析 用一组点云数据做简单的平面的分割: planar_segmentation.cpp #include <iostream> #include <pcl/ModelCoefficients.h> #include <pcl/io/pcd_io.h> #include <pcl/point_types.h> #include <pcl/sample_consensus/method_types.h> //随机参数估计方法头文件 #include <pcl/sample_consensus/model_types.h> //模型定义头文件 #include <pcl/segmentation/sac_segmentation.h> //基于采样一致性分割的类的头文件 int main (int argc, char** argv) { pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>); // 填充点云 cloud->width = 15; cloud->height = 1; cloud->points.resize (cloud->width * cloud->height); // 生成数据,采用随机数填充点云的x,y坐标,都处于z为1的平面上 for (size_t i = 0; i < cloud->points.size (); ++i) { cloud->points[i].x = 1024 * rand () / (RAND_MAX + 1.0f); cloud->points[i].y = 1024 * rand () / (RAND_MAX + 1.0f); cloud->points[i].z = 1.0; } // 设置几个局外点,即重新设置几个点的z值,使其偏离z为1的平面 cloud->points[0].z = 2.0; cloud->points[3].z = -2.0; cloud->points[6].z = 4.0; std::cerr << "Point cloud data: " << cloud->points.size () << " points" << std::endl; //打印 for (size_t i = 0; i < cloud->points.size (); ++i) std::cerr << " " << cloud->points[i].x << " " << cloud->points[i].y << " " << cloud->points[i].z << std::endl; //创建分割时所需要的模型系数对象,coefficients及存储内点的点索引集合对象inliers pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients); pcl::PointIndices::Ptr inliers (new pcl::PointIndices); // 创建分割对象 pcl::SACSegmentation<pcl::PointXYZ> seg; // 可选择配置,设置模型系数需要优化 seg.setOptimizeCoefficients (true); // 必要的配置,设置分割的模型类型,所用的随机参数估计方法,距离阀值,输入点云 seg.setModelType (pcl::SACMODEL_PLANE); //设置模型类型 seg.setMethodType (pcl::SAC_RANSAC); //设置随机采样一致性方法类型 seg.setDistanceThreshold (0.01); //设定距离阀值,距离阀值决定了点被认为是局内点是必须满足的条件 //表示点到估计模型的距离最大值, seg.setInputCloud (cloud); //引发分割实现,存储分割结果到点几何inliers及存储平面模型的系数coefficients seg.segment (*inliers, *coefficients); if (inliers->indices.size () == 0) { PCL_ERROR ("Could not estimate a planar model for the given dataset."); return (-1); } //打印出平面模型 std::cerr << "Model coefficients: " << coefficients->values[0] << " " << coefficients->values[1] << " " << coefficients->values[2] << " " << coefficients->values[3] << std::endl; std::cerr << "Model inliers: " << inliers->indices.size () << std::endl; for (size_t i = 0; i < inliers->indices.size (); ++i) std::cerr << inliers->indices[i] << " " << cloud->points[inliers->indices[i]].x << " " << cloud->points[inliers->indices[i]].y << " " << cloud->points[inliers->indices[i]].z << std::endl; return (0); } 结果如下:开始打印的数据为手动添加的点云数据,并非都处于z为1的平面上,通过分割对象的处理后提取所有内点,即过滤掉z不等于1的点集 (2)实现圆柱体模型的分割:采用随机采样一致性估计从带有噪声的点云中提取一个圆柱体模型。 新建文件cylinder_segmentation.cpp #include <pcl/ModelCoefficients.h> #include <pcl/io/pcd_io.h> #include <pcl/point_types.h> #include <pcl/filters/extract_indices.h> #include <pcl/filters/passthrough.h> #include <pcl/features/normal_3d.h> #include <pcl/sample_consensus/method_types.h> #include <pcl/sample_consensus/model_types.h> #include <pcl/segmentation/sac_segmentation.h> typedef pcl::PointXYZ PointT; int main (int argc, char** argv) { // All the objects needed pcl::PCDReader reader; //PCD文件读取对象 pcl::PassThrough<PointT> pass; //直通滤波对象 pcl::NormalEstimation<PointT, pcl::Normal> ne; //法线估计对象 pcl::SACSegmentationFromNormals<PointT, pcl::Normal> seg; //分割对象 pcl::PCDWriter writer; //PCD文件读取对象 pcl::ExtractIndices<PointT> extract; //点提取对象 pcl::ExtractIndices<pcl::Normal> extract_normals; ///点提取对象 pcl::search::KdTree<PointT>::Ptr tree (new pcl::search::KdTree<PointT> ()); // Datasets pcl::PointCloud<PointT>::Ptr cloud (new pcl::PointCloud<PointT>); pcl::PointCloud<PointT>::Ptr cloud_filtered (new pcl::PointCloud<PointT>); pcl::PointCloud<pcl::Normal>::Ptr cloud_normals (new pcl::PointCloud<pcl::Normal>); pcl::PointCloud<PointT>::Ptr cloud_filtered2 (new pcl::PointCloud<PointT>); pcl::PointCloud<pcl::Normal>::Ptr cloud_normals2 (new pcl::PointCloud<pcl::Normal>); pcl::ModelCoefficients::Ptr coefficients_plane (new pcl::ModelCoefficients), coefficients_cylinder (new pcl::ModelCoefficients); pcl::PointIndices::Ptr inliers_plane (new pcl::PointIndices), inliers_cylinder (new pcl::PointIndices); // Read in the cloud data reader.read ("table_scene_mug_stereo_textured.pcd", *cloud); std::cerr << "PointCloud has: " << cloud->points.size () << " data points." << std::endl; // 直通滤波,将Z轴不在(0,1.5)范围的点过滤掉,将剩余的点存储到cloud_filtered对象中 pass.setInputCloud (cloud); pass.setFilterFieldName ("z"); pass.setFilterLimits (0, 1.5); pass.filter (*cloud_filtered); std::cerr << "PointCloud after filtering has: " << cloud_filtered->points.size () << " data points." << std::endl; // 过滤后的点云进行法线估计,为后续进行基于法线的分割准备数据 ne.setSearchMethod (tree); ne.setInputCloud (cloud_filtered); ne.setKSearch (50); ne.compute (*cloud_normals); // Create the segmentation object for the planar model and set all the parameters seg.setOptimizeCoefficients (true); seg.setModelType (pcl::SACMODEL_NORMAL_PLANE); seg.setNormalDistanceWeight (0.1); seg.setMethodType (pcl::SAC_RANSAC); seg.setMaxIterations (100); seg.setDistanceThreshold (0.03); seg.setInputCloud (cloud_filtered); seg.setInputNormals (cloud_normals); //获取平面模型的系数和处在平面的内点 seg.segment (*inliers_plane, *coefficients_plane); std::cerr << "Plane coefficients: " << *coefficients_plane << std::endl; // 从点云中抽取分割的处在平面上的点集 extract.setInputCloud (cloud_filtered); extract.setIndices (inliers_plane); extract.setNegative (false); // 存储分割得到的平面上的点到点云文件 pcl::PointCloud<PointT>::Ptr cloud_plane (new pcl::PointCloud<PointT> ()); extract.filter (*cloud_plane); std::cerr << "PointCloud representing the planar component: " << cloud_plane->points.size () << " data points." << std::endl; writer.write ("table_scene_mug_stereo_textured_plane.pcd", *cloud_plane, false); // Remove the planar inliers, extract the rest extract.setNegative (true); extract.filter (*cloud_filtered2); extract_normals.setNegative (true); extract_normals.setInputCloud (cloud_normals); extract_normals.setIndices (inliers_plane); extract_normals.filter (*cloud_normals2); // Create the segmentation object for cylinder segmentation and set all the parameters seg.setOptimizeCoefficients (true); //设置对估计模型优化 seg.setModelType (pcl::SACMODEL_CYLINDER); //设置分割模型为圆柱形 seg.setMethodType (pcl::SAC_RANSAC); //参数估计方法 seg.setNormalDistanceWeight (0.1); //设置表面法线权重系数 seg.setMaxIterations (10000); //设置迭代的最大次数10000 seg.setDistanceThreshold (0.05); //设置内点到模型的距离允许最大值 seg.setRadiusLimits (0, 0.1); //设置估计出的圆柱模型的半径的范围 seg.setInputCloud (cloud_filtered2); seg.setInputNormals (cloud_normals2); // Obtain the cylinder inliers and coefficients seg.segment (*inliers_cylinder, *coefficients_cylinder); std::cerr << "Cylinder coefficients: " << *coefficients_cylinder << std::endl; // Write the cylinder inliers to disk extract.setInputCloud (cloud_filtered2); extract.setIndices (inliers_cylinder); extract.setNegative (false); pcl::PointCloud<PointT>::Ptr cloud_cylinder (new pcl::PointCloud<PointT> ()); extract.filter (*cloud_cylinder); if (cloud_cylinder->points.empty ()) std::cerr << "Can't find the cylindrical component." << std::endl; else { std::cerr << "PointCloud representing the cylindrical component: " << cloud_cylinder->points.size () << " data points." << std::endl; writer.write ("table_scene_mug_stereo_textured_cylinder.pcd", *cloud_cylinder, false); } return (0); } 试验打印的结果如下 原始点云可视化的结果.三维场景中有平面,杯子,和其他物体 产生分割以后的平面和圆柱点云,查看的结果如下 (3)PCL中实现欧式聚类提取。对三维点云组成的场景进行分割 #include <pcl/ModelCoefficients.h> #include <pcl/point_types.h> #include <pcl/io/pcd_io.h> #include <pcl/filters/extract_indices.h> #include <pcl/filters/voxel_grid.h> #include <pcl/features/normal_3d.h> #include <pcl/kdtree/kdtree.h> #include <pcl/sample_consensus/method_types.h> #include <pcl/sample_consensus/model_types.h> #include <pcl/segmentation/sac_segmentation.h> #include <pcl/segmentation/extract_clusters.h> /****************************************************************************** 打开点云数据,并对点云进行滤波重采样预处理,然后采用平面分割模型对点云进行分割处理 提取出点云中所有在平面上的点集,并将其存盘 ******************************************************************************/ int main (int argc, char** argv) { // Read in the cloud data pcl::PCDReader reader; pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>), cloud_f (new pcl::PointCloud<pcl::PointXYZ>); reader.read ("table_scene_lms400.pcd", *cloud); std::cout << "PointCloud before filtering has: " << cloud->points.size () << " data points." << std::endl; //* // Create the filtering object: downsample the dataset using a leaf size of 1cm pcl::VoxelGrid<pcl::PointXYZ> vg; pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered (new pcl::PointCloud<pcl::PointXYZ>); vg.setInputCloud (cloud); vg.setLeafSize (0.01f, 0.01f, 0.01f); vg.filter (*cloud_filtered); std::cout << "PointCloud after filtering has: " << cloud_filtered->points.size () << " data points." << std::endl; //* //创建平面模型分割的对象并设置参数 pcl::SACSegmentation<pcl::PointXYZ> seg; pcl::PointIndices::Ptr inliers (new pcl::PointIndices); pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients); pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_plane (new pcl::PointCloud<pcl::PointXYZ> ()); pcl::PCDWriter writer; seg.setOptimizeCoefficients (true); seg.setModelType (pcl::SACMODEL_PLANE); //分割模型 seg.setMethodType (pcl::SAC_RANSAC); //随机参数估计方法 seg.setMaxIterations (100); //最大的迭代的次数 seg.setDistanceThreshold (0.02); //设置阀值 int i=0, nr_points = (int) cloud_filtered->points.size (); while (cloud_filtered->points.size () > 0.3 * nr_points) { // Segment the largest planar component from the remaining cloud seg.setInputCloud (cloud_filtered); seg.segment (*inliers, *coefficients); if (inliers->indices.size () == 0) { std::cout << "Could not estimate a planar model for the given dataset." << std::endl; break; } pcl::ExtractIndices<pcl::PointXYZ> extract; extract.setInputCloud (cloud_filtered); extract.setIndices (inliers); extract.setNegative (false); // Get the points associated with the planar surface extract.filter (*cloud_plane); std::cout << "PointCloud representing the planar component: " << cloud_plane->points.size () << " data points." << std::endl; // // 移去平面局内点,提取剩余点云 extract.setNegative (true); extract.filter (*cloud_f); *cloud_filtered = *cloud_f; } // Creating the KdTree object for the search method of the extraction pcl::search::KdTree<pcl::PointXYZ>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZ>); tree->setInputCloud (cloud_filtered); std::vector<pcl::PointIndices> cluster_indices; pcl::EuclideanClusterExtraction<pcl::PointXYZ> ec; //欧式聚类对象 ec.setClusterTolerance (0.02); // 设置近邻搜索的搜索半径为2cm ec.setMinClusterSize (100); //设置一个聚类需要的最少的点数目为100 ec.setMaxClusterSize (25000); //设置一个聚类需要的最大点数目为25000 ec.setSearchMethod (tree); //设置点云的搜索机制 ec.setInputCloud (cloud_filtered); ec.extract (cluster_indices); //从点云中提取聚类,并将点云索引保存在cluster_indices中 //迭代访问点云索引cluster_indices,直到分割处所有聚类 int j = 0; for (std::vector<pcl::PointIndices>::const_iterator it = cluster_indices.begin (); it != cluster_indices.end (); ++it) { pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_cluster (new pcl::PointCloud<pcl::PointXYZ>); for (std::vector<int>::const_iterator pit = it->indices.begin (); pit != it->indices.end (); ++pit) cloud_cluster->points.push_back (cloud_filtered->points[*pit]); //* cloud_cluster->width = cloud_cluster->points.size (); cloud_cluster->height = 1; cloud_cluster->is_dense = true; std::cout << "PointCloud representing the Cluster: " << cloud_cluster->points.size () << " data points." << std::endl; std::stringstream ss; ss << "cloud_cluster_" << j << ".pcd"; writer.write<pcl::PointXYZ> (ss.str (), *cloud_cluster, false); //* j++; } return (0); } 运行结果: 不再一一查看可视化的结果 为了更切合实际的应用我会在这些基本的程序的基础上,进行与实际结合的实例,因为这些都是官方给的实例,我是首先学习一下,至少过一面,这样在后期结合实际应用的过程中会更加容易一点。(因为我也是一边学习,然后回头再在基础上进行更修) 同时有很多在我的微信公众号上的同学后台与我交流,有时候不能即时回复敬请谅解,(之前,就有一个不知道哪个学校的关注后就一直问我问题,告诉它基本的案例,还要我告诉他怎么实现,本人不才,我也是入门者阿,) 请扫面二维码关注
关于点云的分割算是我想做的机械臂抓取中十分重要的俄一部分,所以首先学习如果使用点云库处理我用kinect获取的点云的数据,本例程也是我自己慢慢修改程序并结合官方API 的解说实现的,其中有很多细节如果直接更改源程序,可能会因为数据类型,或者头文件等各种原因编译不过,会导致我们比较难得找出其中的错误,首先我们看一下我自己设定的一个场景,然后我用kinect获取数据 观察到kinect获取的原始图像的,然后使用简单的滤波,把在其中的NANS点移除,因为很多的算法要求不能出现NANS点,我们可以看见这里面有充电宝,墨水,乒乓球,一双筷子,下面是两张纸,上面分别贴了两道黑色的胶带,我们首先就可以做一个提取原始点云的平面的实验,那么如果提取点云中平面,之前有一些基本的实例,使用平面分割法 程序如下 #include <iostream> #include <pcl/ModelCoefficients.h> #include <pcl/io/pcd_io.h> #include <pcl/point_types.h> #include <pcl/filters/voxel_grid.h> #include <pcl/features/normal_3d.h> #include <pcl/kdtree/kdtree.h> #include <pcl/sample_consensus/method_types.h> #include <pcl/sample_consensus/model_types.h> #include <pcl/segmentation/sac_segmentation.h> #include <pcl/console/parse.h> #include <pcl/filters/extract_indices.h> #include <pcl/sample_consensus/ransac.h> #include <pcl/sample_consensus/sac_model_plane.h> #include <pcl/sample_consensus/sac_model_sphere.h> #include <pcl/visualization/pcl_visualizer.h> #include <boost/thread/thread.hpp> int main (int argc, char** argv) { // 读取文件 pcl::PCDReader reader; pcl::PointCloud<pcl::PointXYZRGBA>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZRGBA>), cloud_f (new pcl::PointCloud<pcl::PointXYZRGBA>); pcl::PointCloud<pcl::PointXYZRGBA>::Ptr final (new pcl::PointCloud<pcl::PointXYZRGBA>); reader.read ("out0.pcd", *cloud); std::cout << "PointCloud before filtering has: " << cloud->points.size () << " data points." << std::endl; //* // 下采样,体素叶子大小为0.01 pcl::VoxelGrid<pcl::PointXYZRGBA> vg; pcl::PointCloud<pcl::PointXYZRGBA>::Ptr cloud_filtered (new pcl::PointCloud<pcl::PointXYZRGBA>); vg.setInputCloud (cloud); vg.setLeafSize (0.01f, 0.01f, 0.01f); vg.filter (*cloud_filtered); std::cout << "PointCloud after filtering has: " << cloud_filtered->points.size () << " data points." << std::endl; //* pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients); pcl::PointIndices::Ptr inliers (new pcl::PointIndices); // Create the segmentation object pcl::SACSegmentation<pcl::PointXYZRGBA> seg; // Optional seg.setOptimizeCoefficients (true); // Mandatory seg.setModelType (pcl::SACMODEL_PLANE); // seg.setModelType (pcl::SACMODEL_LINE ); seg.setMethodType (pcl::SAC_RANSAC); seg.setDistanceThreshold (0.01); seg.setInputCloud (cloud_filtered); seg.segment (*inliers, *coefficients); if (inliers->indices.size () == 0) { PCL_ERROR ("Could not estimate a planar model for the given dataset."); return (-1); } std::cerr << "Model coefficients: " << coefficients->values[0] << " " << coefficients->values[1] << " " << coefficients->values[2] << " " << coefficients->values[3] <<std::endl; return (0); } 运行生成的可执行文件会输出平面模型的参数 平面模型的参数 此图是采样后的点云图 也可以在这个程序中直接实现平面的提取,但是为了更好的说明,我是将获取平面参数与平面提取给分成两个程序实现,程序如下 #include <iostream> #include <pcl/io/pcd_io.h> #include <pcl/point_types.h> #include <pcl/ModelCoefficients.h> #include <pcl/filters/project_inliers.h> #include <pcl/filters/extract_indices.h> #include <pcl/filters/voxel_grid.h> #include <pcl/visualization/pcl_visualizer.h> #include <boost/thread/thread.hpp> boost::shared_ptr<pcl::visualization::PCLVisualizer> simpleVis (pcl::PointCloud<pcl::PointXYZ>::ConstPtr cloud) { boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer (new pcl::visualization::PCLVisualizer ("3D Viewer")); viewer->setBackgroundColor (0, 0, 0); viewer->addPointCloud<pcl::PointXYZ> (cloud, "project_inliners cloud"); viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "sample cloud"); //viewer->addCoordinateSystem (1.0, "global"); viewer->initCameraParameters (); return (viewer); } int main (int argc, char** argv) { // 读取文件 pcl::PCDReader reader; pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>), cloud_f (new pcl::PointCloud<pcl::PointXYZ>); pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_projected (new pcl::PointCloud<pcl::PointXYZ>); pcl::PointCloud<pcl::PointXYZ>::Ptr final (new pcl::PointCloud<pcl::PointXYZ>); reader.read ("out0.pcd", *cloud); std::cout << "PointCloud before filtering has: " << cloud->points.size () << " data points." << std::endl; //* // 下采样,体素叶子大小为0.01 pcl::VoxelGrid<pcl::PointXYZ> vg; pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered (new pcl::PointCloud<pcl::PointXYZ>); vg.setInputCloud (cloud); vg.setLeafSize (0.01f, 0.01f, 0.01f); vg.filter (*cloud_filtered); std::cout << "PointCloud after filtering has: " << cloud_filtered->points.size () << " data points." << std::endl; //* // Create a set of planar coefficients with X=Y= pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients ()); coefficients->values.resize (4); coefficients->values[0] = 0.140101; coefficients->values[1] = 0.126715; coefficients->values[2] = 0.981995; coefficients->values[3] = -0.702224; // Create the filtering object pcl::ProjectInliers<pcl::PointXYZ> proj; proj.setModelType (pcl::SACMODEL_PLANE); proj.setInputCloud (cloud_filtered); proj.setModelCoefficients (coefficients); proj.filter (*cloud_projected); boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer; viewer = simpleVis(cloud_projected); while (!viewer->wasStopped ()) { viewer->spinOnce (100); boost::this_thread::sleep (boost::posix_time::microseconds (100000)); } return (0); } 执行结果就如下 提取了平面,**********************8 微信公众号号可扫描二维码一起共同学习交流
首先我们知道Hog特征结合SVM分类器已经被广泛应用于图像识别中,尤其在行人检测中获得了极大的成功,HOG+SVM进行行人检测的方法是法国研究人员Dalal在2005的CVPR上提出的,而如今虽然有很多行人检测算法不断提出,但基本都是以HOG+SVM的思路为主,那么PCL中也是利用这一思想来进行行人的检测, 总体思路: 1、提取正负样本hog特征 2、投入svm分类器训练,得到model 3、由model生成检测子 4、利用检测子检测负样本,得到hardexample 5、提取hardexample的hog特征并结合第一步中的特征一起投入训练,得到最终检测子。 首先整合一下理论知识 HOG特征: 方向梯度直方图(Histogram of Oriented Gradient, HOG)特征是一种在计算机视觉和图像处理中用来进行物体检测的特征描述子。它通过计算和统计图像局部区域的梯度方向直方图来构成特征。 具体的是实现方法:首先将图像分成小的连通区域,我们把它叫细胞单元。然后采集细胞单元中各像素点的梯度的或边缘的方向直方图。最后把这些直方图组合起来就可以构成特征描述器。 提高性能: 把这些局部直方图在图像的更大的范围内(我们把它叫区间或block)进行对比度归一化(contrast-normalized),所采用的方法是:先计算各直方图在这个区间(block)中的密度,然后根据这个密度对区间中的各个细胞单元做归一化。通过这个归一化后,能对光照变化和阴影获得更好的效果。 优点:与其他的特征描述方法相比,HOG有很多优点。首先,由于HOG是 在图像的局部方格单元上操作,所以它对图像几何的和光学的形变都能保持很好的不变性,这两种形变只会出现在更大的空间领域上。其次,在粗的空域抽样、精细 的方向抽样以及较强的局部光学归一化等条件下,只要行人大体上能够保持直立的姿势,可以容许行人有一些细微的肢体动作,这些细微的动作可以被忽略而不影响 检测效果。因此HOG特征是特别适合于做图像中的人体检测的。 HOG特征提取算法的实现过程: 1)HOG特征提取方法就是将一个image(你要检测的目标或者扫描窗口)灰度化(x,y,z的三维图像); 2)采用Gamma校正法对输入图像进行颜色空间的标准化(归一化);目的是调节图像的对比度,降低图像局部的阴影和光照变化所造成的影响,同时可以抑制噪音的干扰; 3)计算图像每个像素的梯度(包括大小和方向);主要是为了捕获轮廓信息,同时进一步弱化光照的干扰。 4)将图像划分成小cells(例如6*6像素/cell); 5)统计每个cell的梯度直方图(不同梯度的个数),即可形成每个cell的descriptor; 6)将每几个cell组成一个block(例如3*3个cell/block),一个block内所有cell的特征descriptor串联起来便得到该block的HOG特征descriptor。 7)将图像image内的所有block的HOG特征descriptor串联起来就可以得到该image(你要检测的目标)的HOG特征descriptor了。这个就是最终的可供分类使用的特征向量了。 关于SVM可以参看www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/ml/introduction_to_svm/introduction_to_svm.html (2)那么使用PCL 检测行人该如何使用呢?目标: detecting people walking on the ground plane 输入:RGBXYZ pointcloud ground coeficients输出:people clusters流程图: 步骤 detected on point cloud (1)groud plane estimation and removal (2)Euclidean clustering of remaining points (3)people vertically splitted int more clusters sub-clustering procedure (4)candidates pruning based on height from the ground plane (5) merging of clusters close in groud plane coordinates (6)Subdivision of big clusters by means of head detection HOG+SVM evaluation (7)candidate clusters are extened until the ground for achieving robustness to lower limbs occlusion (8)HOG detector applied to image patches that are projection of 3Dclusters onto the RGB imagepcl::people代码的构架 GroundBasedPeopleDetectionAPP<PointT> ________________|_____________________ | | | PersonClassifier<pcl::RGB> PersonCluster<PointT> headBasedSubclustering<pointT> | | HOG detector HeightMap2D<PointT> 代码解析 #include <pcl/console/parse.h> //命令行解析头文件 #include <pcl/point_types.h> //点云数据类型 #include <pcl/visualization/pcl_visualizer.h> //可视化头文件 #include <pcl/io/openni_grabber.h> //kinect抓取的头文件 #include <pcl/sample_consensus/sac_model_plane.h> //sac_model_plane随机采样一致形平面的模板 #include <pcl/people/ground_based_people_detection_app.h> #include <pcl/common/time.h> typedef pcl::PointXYZRGBA PointT; typedef pcl::PointCloud<PointT> PointCloudT; // PCL viewer // pcl::visualization::PCLVisualizer viewer("PCL Viewer"); //可视化 // Mutex: // boost::mutex cloud_mutex; enum { COLS = 640, ROWS = 480 }; //窗口大小 int print_help() { cout << "*******************************************************" << std::endl; cout << "Ground based people detection app options:" << std::endl; cout << " --help <show_this_help>" << std::endl; cout << " --svm <path_to_svm_file>" << std::endl; cout << " --conf <minimum_HOG_confidence (default = -1.5)>" << std::endl; cout << " --min_h <minimum_person_height (default = 1.3)>" << std::endl; cout << " --max_h <maximum_person_height (default = 2.3)>" << std::endl; cout << " --sample <sampling_factor (default = 1)>" << std::endl; cout << "*******************************************************" << std::endl; return 0; } void cloud_cb_ (const PointCloudT::ConstPtr &callback_cloud, PointCloudT::Ptr& cloud, bool* new_cloud_available_flag) { cloud_mutex.lock (); // for not overwriting the point cloud from another thread锁住进程防止从其他进程写入点云 *cloud = *callback_cloud; //形参点云赋值 *new_cloud_available_flag = true; //标志位赋值为真 cloud_mutex.unlock (); //解锁进程 } //申明结构体用于传递参数给回调函数 struct callback_args{ // structure used to pass arguments to the callback function PointCloudT::Ptr clicked_points_3d; //按键三次 pcl::visualization::PCLVisualizer::Ptr viewerPtr; //可视化 }; void pp_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); data->clicked_points_3d->points.push_back(current_point); // Draw clicked points in red: pcl::visualization::PointCloudColorHandlerCustom<PointT> red (data->clicked_points_3d, 255, 0, 0); data->viewerPtr->removePointCloud("clicked_points"); data->viewerPtr->addPointCloud(data->clicked_points_3d, red, "clicked_points"); data->viewerPtr->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 10, "clicked_points"); std::cout << current_point.x << " " << current_point.y << " " << current_point.z << std::endl; } int main (int argc, char** argv) { if(pcl::console::find_switch (argc, argv, "--help") || pcl::console::find_switch (argc, argv, "-h")) return print_help(); // Algorithm parameters:算法参数 std::string svm_filename = "/home/salm/myopencv/yl_pcl/people/data/trainedLinearSVMForPeopleDetectionWithHOG.yaml"; float min_confidence = -1.5; float min_width = 0.1; float max_width = 8.0; float min_height = 1.3; float max_height = 2.3; float voxel_size = 0.06; //体素大小 float sampling_factor = 1; //采样因子 Eigen::Matrix3f rgb_intrinsics_matrix; rgb_intrinsics_matrix << 525, 0.0, 319.5, 0.0, 525, 239.5, 0.0, 0.0, 1.0; // Kinect RGB camera intrinsics相机的内参 // Read if some parameters are passed from command line: pcl::console::parse_argument (argc, argv, "--svm", svm_filename); pcl::console::parse_argument (argc, argv, "--conf", min_confidence); pcl::console::parse_argument (argc, argv, "--min_h", min_height); pcl::console::parse_argument (argc, argv, "--max_h", max_height); pcl::console::parse_argument (argc, argv, "--sample", sampling_factor); // Read Kinect live stream: 读取kienct的数据流 PointCloudT::Ptr cloud (new PointCloudT); bool new_cloud_available_flag = false; pcl::Grabber* interface = new pcl::OpenNIGrabber(); boost::function<void (const pcl::PointCloud<pcl::PointXYZRGBA>::ConstPtr&)> f = boost::bind (&cloud_cb_, _1, cloud, &new_cloud_available_flag); interface->registerCallback (f); interface->start (); // Wait for the first frame:等待第一帧数据流 while(!new_cloud_available_flag) boost::this_thread::sleep(boost::posix_time::milliseconds(1)); new_cloud_available_flag = false; cloud_mutex.lock (); // for not overwriting the point cloud锁住进程 // Display pointcloud:显示点云 pcl::visualization::PointCloudColorHandlerRGBField<PointT> rgb(cloud); viewer.addPointCloud<PointT> (cloud, rgb, "input_cloud"); viewer.setCameraPosition(0,0,-2,0,-1,0,0); //设置相机的位置 // Add point picking callback to viewer: struct callback_args cb_args; PointCloudT::Ptr clicked_points_3d (new PointCloudT); cb_args.clicked_points_3d = clicked_points_3d; cb_args.viewerPtr = pcl::visualization::PCLVisualizer::Ptr(&viewer); viewer.registerPointPickingCallback (pp_callback, (void*)&cb_args); std::cout << "Shift+click on three floor points, then press 'Q'..." << std::endl; // Spin until 'Q' is pressed: viewer.spin(); std::cout << "done." << std::endl; cloud_mutex.unlock (); // Ground plane estimation: Eigen::VectorXf ground_coeffs; ground_coeffs.resize(4); std::vector<int> clicked_points_indices; for (unsigned int i = 0; i < clicked_points_3d->points.size(); i++) clicked_points_indices.push_back(i); pcl::SampleConsensusModelPlane<PointT> model_plane(clicked_points_3d); model_plane.computeModelCoefficients(clicked_points_indices,ground_coeffs); std::cout << "Ground plane: " << ground_coeffs(0) << " " << ground_coeffs(1) << " " << ground_coeffs(2) << " " << ground_coeffs(3) << std::endl; // Initialize new viewer:可视化初始化 pcl::visualization::PCLVisualizer viewer("PCL Viewer"); // viewer initialization viewer.setCameraPosition(0,0,-2,0,-1,0,0); // Create classifier for people detection:创建分类器 pcl::people::PersonClassifier<pcl::RGB> person_classifier; person_classifier.loadSVMFromFile(svm_filename); // load trained SVM // People detection app initialization: 检测初始化 pcl::people::GroundBasedPeopleDetectionApp<PointT> people_detector; // people detection object people_detector.setVoxelSize(voxel_size); // set the voxel size people_detector.setIntrinsics(rgb_intrinsics_matrix); // set RGB camera intrinsic parameters people_detector.setClassifier(person_classifier); // set person classifier //people_detector.setPersonClusterLimits(min_height, max_height, min_width, max_width); people_detector.setSamplingFactor(sampling_factor); // set a downsampling factor to the point cloud (for increasing speed) // people_detector.setSensorPortraitOrientation(true); // set sensor orientation to vertical // For timing: static unsigned count = 0; static double last = pcl::getTime (); // Main loop: while (!viewer.wasStopped()) { if (new_cloud_available_flag && cloud_mutex.try_lock ()) // if a new cloud is available { new_cloud_available_flag = false; // Perform people detection on the new cloud:显示检测到的新点云 std::vector<pcl::people::PersonCluster<PointT> > clusters; // vector containing persons clusters people_detector.setInputCloud(cloud); people_detector.setGround(ground_coeffs); // set floor coefficients people_detector.compute(clusters); // perform people detection ground_coeffs = people_detector.getGround(); // get updated floor coefficients // Draw cloud and people bounding boxes in the viewer: viewer.removeAllPointClouds(); viewer.removeAllShapes(); pcl::visualization::PointCloudColorHandlerRGBField<PointT> rgb(cloud); viewer.addPointCloud<PointT> (cloud, rgb, "input_cloud"); unsigned int k = 0; for(std::vector<pcl::people::PersonCluster<PointT> >::iterator it = clusters.begin(); it != clusters.end(); ++it) { if(it->getPersonConfidence() > min_confidence) // draw only people with confidence above a threshold { // draw theoretical person bounding box in the PCL viewer: it->drawTBoundingBox(viewer, k); k++; } } std::cout << k << " people found" << std::endl; viewer.spinOnce(); // Display average framerate: if (++count == 30) { double now = pcl::getTime (); std::cout << "Average framerate: " << double(count)/double(now - last) << " Hz" << std::endl; count = 0; last = now; } cloud_mutex.unlock (); } } 但是我们知道这需要其他的头文件配合。那么其他程序就不再一一贴出来。PCL源码里也有,但是如果你直接编译是编译不过去的,所以需要我们自己写一个CMakeLists.txt文件来编译。 暂时就到这里了。。。。。。 微信公众号号可扫描二维码一起共同学习交流
平面的法线是垂直于它的单位向量。在点云的表面的法线被定义为垂直于与点云表面相切的平面的向量。表面法线也可以计算点云中一点的法线,被认为是一种十分重要的性质。常常在被使用在很多计算机视觉的应用里面,比如可以用来推出光源的位置,通过阴影与其他视觉影响,表面法线的问题可以近似化解为切面的问题,这个切面的问题又会变成最小二乘法拟合平面的问题 解决表面法线估计的问题可以最终化简为对一个协方差矩阵的特征向量和特征值的分析(或者也叫PCA-Principal Component Analysis 主成分分析),这个协方差矩阵是由查询点的最近邻产生的。对于每个点Pi,我们假设协方差矩阵C如下: 法线提供了关于曲面的曲率信息,这是它的优势。许多的PCL的算法需要我们提供输入点云的法线。为了估计它们,代码分析如下 #include <pcl/io/pcd_io.h> #include <pcl/features/normal_3d.h> #include <boost/thread/thread.hpp> #include <pcl/visualization/pcl_visualizer.h> int main(int argc,char**argv) { //创建点云对象 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>); //创建法线的对象 pcl::PointCloud<pcl::Normal>::Ptr normals(new pcl::PointCloud<pcl::Normal>); //读取PCD文件 if(pcl::io::loadPCDFile<pcl::PointXYZ>(argv[1],*cloud) !=0) { return -1; } //创建法线估计的对象 pcl::NormalEstimation<pcl::PointXYZ,pcl::Normal> normalEstimation; normalEstimation.setInputCloud(cloud); //对于每一个点都用半径为3cm的近邻搜索方式 normalEstimation.setRadiusSearch(0.03); //Kd_tree是一种数据结构便于管理点云以及搜索点云,法线估计对象会使用这种结构来找到哦啊最近邻点 pcl::search::KdTree<pcl::PointXYZ>::Ptr kdtree(new pcl::search::KdTree<pcl::PointXYZ>); normalEstimation.setSearchMethod(kdtree); //计算法线 normalEstimation.compute(*normals); //可视化 boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer(new pcl::visualization::PCLVisualizer("Normals")); viewer->addPointCloud<pcl::PointXYZ>(cloud,"cloud"); while(!viewer->wasStopped()) { viewer->spinOnce(100); boost::this_thread::sleep(boost::posix_time::microseconds(1000000)); } } 试验结果就是运行命令,这里就随便输入一个PCD 文件 可能看不处什么效果********************* (2)图像积分 积分图像是对有序点云的发现的估计的一种方法。该算法把点云作为一个深度图像,并创建一定的矩形区域来计算法线,考虑到相邻像素关系,而无需建立树形查询结构。因此,它是非常有效的。 #include <pcl/io/pcd_io.h> #include <pcl/features/integral_image_normal.h> #include <boost/thread/thread.hpp> #include <pcl/visualization/pcl_visualizer.h> int main(int argc, char** argv) { // 点云数据对象 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>); // 法线对象 pcl::PointCloud<pcl::Normal>::Ptr normals(new pcl::PointCloud<pcl::Normal>); // 读取文件 if (pcl::io::loadPCDFile<pcl::PointXYZ>(argv[1], *cloud) != 0) { return -1; } // 法线估计对象 pcl::IntegralImageNormalEstimation<pcl::PointXYZ, pcl::Normal> normalEstimation; normalEstimation.setInputCloud(cloud); // 法线估计方法: COVARIANCE_MATRIX, AVERAGE_DEPTH_CHANGE, SIMPLE_3D_GRADIENT. normalEstimation.setNormalEstimationMethod(normalEstimation.AVERAGE_3D_GRADIENT); //设置深度变化的阀值 normalEstimation.setMaxDepthChangeFactor(0.02f); // 设置计算法线的区域 normalEstimation.setNormalSmoothingSize(10.0f); // 计算 normalEstimation.compute(*normals); // 可视化 boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer(new pcl::visualization::PCLVisualizer("Normals")); viewer->addPointCloud<pcl::PointXYZ>(cloud, "cloud"); viewer->addPointCloudNormals<pcl::PointXYZ, pcl::Normal>(cloud, normals, 20, 0.03, "normals"); while (!viewer->wasStopped()) { viewer->spinOnce(100); boost::this_thread::sleep(boost::posix_time::microseconds(100000)); } } 结果可视化 具体官方的网址查看pointclouds.org/documentation/tutorials/normal_estimation_using_integral_images.php 大神请忽略!!!! 微信公众号号可扫描二维码一起共同学习交流
PCL提供节约一点云的值为一个PNG图像文件的可能方案。显然,这只能用有序的点云来完成,因为生成的图像的行和列将与点云的对应完全一致。例如,如果你从一个传感器Kinect或Xtion的点云,你可以用这个来检索640x480 RGB图像匹配的点云。 就是将点云文件PCD保存成PNG文件,程序如下 #include <pcl/io/pcd_io.h> #include <pcl/io/png_io.h> int main(int argc, char** argv) { // 创建点云对象 pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZRGB>); // 读取点云文件 if (pcl::io::loadPCDFile<pcl::PointXYZRGB>(argv[1], *cloud) != 0) { return -1; } // 保存图片,(必须为有序点云) pcl::io::savePNGFile("output.png", *cloud, "rgb"); } 那么这里的实验结果是根据我之前使用的用kinect获得的点云数据,他的点云可视化效果如下 保存为PNG的结果为 如果省略参数,函数将默认保存RGB域。 (2)计算点云重心 点云的重心是一个点坐标,计算出云中所有点的平均值。你可以说它是“质量中心”,它对于某些算法有多种用途。如果你想计算一个聚集的物体的实际重心,记住,传感器没有检索到从相机中相反的一面,就像被前面板遮挡的背面,或者里面的。只有面对相机表面的一部分。 #include <pcl/io/pcd_io.h> #include <pcl/common/centroid.h> #include <iostream> int main(int argc, char** argv) { // 创建点云的对象 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>); // 读取点云 if (pcl::io::loadPCDFile<pcl::PointXYZ>(argv[1], *cloud) != 0) { return -1; } // 创建存储点云重心的对象 Eigen::Vector4f centroid; pcl::compute3DCentroid(*cloud, centroid); std::cout << "The XYZ coordinates of the centroid are: (" << centroid[0] << ", " << centroid[1] << ", " << centroid[2] << ")." << std::endl; } 这样就可以计算出点云的XYZ三个轴的重心的坐标值 简单的程序演示,大神请忽略, 微信公众号号可扫描二维码一起共同学习交流
前不久,谷歌开源的Draco关于点云的编码与压缩的源码,Draco 由谷歌 Chrome 媒体团队设计,旨在大幅加速 3D 数据的编码、传输和解码。因为研发团队的 Chrome 背景,这个开源算法的首要应用对象是浏览器。但既然谷歌把它开源,现在全世界的开发者可以去探索 Draco 在其他场景的应用,比如说非网页端。目前,谷歌提供了它的两个版本: JavaScript 和 C++。 Draco 可以被用来压缩 mesh 和点云数据。它还支持压缩点( compressing points),连接信息,纹理协调,颜色信息,法线( normals)以及其他与几何相关的通用属性。下面是谷歌官方发布的 Draco Mesh 文件压缩率,可以看出,它大幅优于 ZIP。 Draco 的算法既支持有损模式,也支持无损。这给普通消费者带来的好处还不太明显——毕竟大多数人对分辨率并没有强迫症,有损压缩带来的轻微画质改变完全在承受范 围之内。但对专业应用和科研应用的意义就很大了。这就好比 JPEG 之于 PNG。后者用于游戏和 VR 应用完全没有问题,降低的画质很难用肉眼察觉。但在科研点云数据中,任何信息都不能丢失。 如何使用draco? github上简介:Draco is a library for compressing and decompressing 3D geometric meshes and point clouds. It is intended to improve the storage and transmission of 3D graphics. (1)下载源码 git clone https://github.com/google/draco.git (2) cd dracs mkdir build cmake .. 如果提示你cmake的版本太低了,则需要升级cmake ,命令如下 sudo add-apt-repository ppa:george-edison55/cmake-3.x sudo apt-get update 如果你从来没装过cmake 则sudo apt-get install cmake(经过测试 即使装过还是用这个命令直接升级比较好) 如果你装过cmake 则sudo apt-get upgrade 安装完后,用命令:cmake --version 查看当前的cmake版本,可以看到现在cmake的版本为3.2.2 之后就可以编译通过,make 一下傻等片刻就安装成功 那么就可以使用查看一下效果,为了可视化以下,所以还是使用强大的PCL库来对比以下,在PCL库中读取PLY文件也是有对应的函数的 class pcl::PCDReader()与class pcl::FLYReader() 分别是PCD,和FLY文件格式的接口的实现 class pcl::PCDWriter()与class pcl::FLYWriter()分别是对PCD,FLY文件写入接口的实现 查看发现它们的其他函数基本都是一样的,就是把PCD 换成FLY即可 所以我们就可以使用draco来编码FLY格式的点云数据,然后在解码生成FLY文件,再在点云库中可视化一下,并且查看编码前后编码后文件的大小,可以看看编码的效率 首先我们查看原始的文件以及它的大小3M 编码 生成了out.drc文件并查看文件大小113.KB的大小 并可视化原始点云 然后为了在可视化编码后再解码的效果,所以再查看解压后的大小为431.5KB 可视化的效果和之前的是一样的,同时为了查看有没有丢失点数,打印出来看一下,发现点云数目没有变化都是35947 data points 总结以下,原来的文本是3M,编码后是113.KB文件,文件很小,压缩的效率非常高,为了再可视化,所以再解压,可视化的效果与原点云效果是一样的, 原点云 解码后的点云 可视化的代码 #include <iostream> //标准C++库中的输入输出的头文件 #include <pcl/io/pcd_io.h> //PCD读写类相关的头文件 #include <pcl/point_types.h> //PCL中支持的点类型的头文件 #include <pcl/io/ply_io.h> #include <boost/thread/thread.hpp> #include <pcl/visualization/pcl_visualizer.h> int main (int argc, char** argv) { //创建一个PointCloud<pcl::PointXYZ> boost共享指针并进行实例化 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>); //打开点云文件 if (pcl::io::loadPLYFile<pcl::PointXYZ> ("bun_zipper.ply", *cloud) == -1) { PCL_ERROR ("Couldn't read file bun_zipper.ply \n"); return (-1); } std::cout << "Loaded " << cloud->width * cloud->height << " data points from bun_zipper.ply with the following fields: " << std::endl; boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer(new pcl::visualization::PCLVisualizer("cloud")); viewer->addPointCloud<pcl::PointXYZ>(cloud,"cloud"); while(!viewer->wasStopped()) { viewer->spinOnce(100); boost::this_thread::sleep(boost::posix_time::microseconds(1000000)); } return (0); } 如有问题可以与我交流,同时欢迎指正,谢谢 微信公众号号可扫描二维码一起共同学习交流
(1)正态分布变换进行配准(normal Distributions Transform) 介绍关于如何使用正态分布算法来确定两个大型点云之间的刚体变换,正态分布变换算法是一个配准算法,它应用于三维点的统计模型,使用标准最优化技术来确定两个点云间的最优匹配,因为其在配准的过程中不利用对应点的特征计算和匹配,所以时间比其他方法比较快, 对于代码的解析 /* 使用正态分布变换进行配准的实验 。其中room_scan1.pcd room_scan2.pcd这些点云包含同一房间360不同视角的扫描数据 */ #include <iostream> #include <pcl/io/pcd_io.h> #include <pcl/point_types.h> #include <pcl/registration/ndt.h> //NDT(正态分布)配准类头文件 #include <pcl/filters/approximate_voxel_grid.h> //滤波类头文件 (使用体素网格过滤器处理的效果比较好) #include <pcl/visualization/pcl_visualizer.h> #include <boost/thread/thread.hpp> int main (int argc, char** argv) { // 加载房间的第一次扫描点云数据作为目标 pcl::PointCloud<pcl::PointXYZ>::Ptr target_cloud (new pcl::PointCloud<pcl::PointXYZ>); if (pcl::io::loadPCDFile<pcl::PointXYZ> ("room_scan1.pcd", *target_cloud) == -1) { PCL_ERROR ("Couldn't read file room_scan1.pcd \n"); return (-1); } std::cout << "Loaded " << target_cloud->size () << " data points from room_scan1.pcd" << std::endl; // 加载从新视角得到的第二次扫描点云数据作为源点云 pcl::PointCloud<pcl::PointXYZ>::Ptr input_cloud (new pcl::PointCloud<pcl::PointXYZ>); if (pcl::io::loadPCDFile<pcl::PointXYZ> ("room_scan2.pcd", *input_cloud) == -1) { PCL_ERROR ("Couldn't read file room_scan2.pcd \n"); return (-1); } std::cout << "Loaded " << input_cloud->size () << " data points from room_scan2.pcd" << std::endl; //以上的代码加载了两个PCD文件得到共享指针,后续配准是完成对源点云到目标点云的参考坐标系的变换矩阵的估计,得到第二组点云变换到第一组点云坐标系下的变换矩阵 // 将输入的扫描点云数据过滤到原始尺寸的10%以提高匹配的速度,只对源点云进行滤波,减少其数据量,而目标点云不需要滤波处理 //因为在NDT算法中在目标点云对应的体素网格数据结构的统计计算不使用单个点,而是使用包含在每个体素单元格中的点的统计数据 pcl::PointCloud<pcl::PointXYZ>::Ptr filtered_cloud (new pcl::PointCloud<pcl::PointXYZ>); pcl::ApproximateVoxelGrid<pcl::PointXYZ> approximate_voxel_filter; approximate_voxel_filter.setLeafSize (0.2, 0.2, 0.2); approximate_voxel_filter.setInputCloud (input_cloud); approximate_voxel_filter.filter (*filtered_cloud); std::cout << "Filtered cloud contains " << filtered_cloud->size () << " data points from room_scan2.pcd" << std::endl; // 初始化正态分布(NDT)对象 pcl::NormalDistributionsTransform<pcl::PointXYZ, pcl::PointXYZ> ndt; // 根据输入数据的尺度设置NDT相关参数 ndt.setTransformationEpsilon (0.01); //为终止条件设置最小转换差异 ndt.setStepSize (0.1); //为more-thuente线搜索设置最大步长 ndt.setResolution (1.0); //设置NDT网格网格结构的分辨率(voxelgridcovariance) //以上参数在使用房间尺寸比例下运算比较好,但是如果需要处理例如一个咖啡杯子的扫描之类更小的物体,需要对参数进行很大程度的缩小 //设置匹配迭代的最大次数,这个参数控制程序运行的最大迭代次数,一般来说这个限制值之前优化程序会在epsilon变换阀值下终止 //添加最大迭代次数限制能够增加程序的鲁棒性阻止了它在错误的方向上运行时间过长 ndt.setMaximumIterations (35); ndt.setInputSource (filtered_cloud); //源点云 // Setting point cloud to be aligned to. ndt.setInputTarget (target_cloud); //目标点云 // 设置使用机器人测距法得到的粗略初始变换矩阵结果 Eigen::AngleAxisf init_rotation (0.6931, Eigen::Vector3f::UnitZ ()); Eigen::Translation3f init_translation (1.79387, 0.720047, 0); Eigen::Matrix4f init_guess = (init_translation * init_rotation).matrix (); // 计算需要的刚体变换以便将输入的源点云匹配到目标点云 pcl::PointCloud<pcl::PointXYZ>::Ptr output_cloud (new pcl::PointCloud<pcl::PointXYZ>); ndt.align (*output_cloud, init_guess); //这个地方的output_cloud不能作为最终的源点云变换,因为上面对点云进行了滤波处理 std::cout << "Normal Distributions Transform has converged:" << ndt.hasConverged () << " score: " << ndt.getFitnessScore () << std::endl; // 使用创建的变换对为过滤的输入点云进行变换 pcl::transformPointCloud (*input_cloud, *output_cloud, ndt.getFinalTransformation ()); // 保存转换后的源点云作为最终的变换输出 pcl::io::savePCDFileASCII ("room_scan2_transformed.pcd", *output_cloud); // 初始化点云可视化对象 boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer_final (new pcl::visualization::PCLVisualizer ("3D Viewer")); viewer_final->setBackgroundColor (0, 0, 0); //设置背景颜色为黑色 // 对目标点云着色可视化 (red). pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> target_color (target_cloud, 255, 0, 0); viewer_final->addPointCloud<pcl::PointXYZ> (target_cloud, target_color, "target cloud"); viewer_final->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "target cloud"); // 对转换后的源点云着色 (green)可视化. pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> output_color (output_cloud, 0, 255, 0); viewer_final->addPointCloud<pcl::PointXYZ> (output_cloud, output_color, "output cloud"); viewer_final->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "output cloud"); // 启动可视化 viewer_final->addCoordinateSystem (1.0); //显示XYZ指示轴 viewer_final->initCameraParameters (); //初始化摄像头参数 // 等待直到可视化窗口关闭 while (!viewer_final->wasStopped ()) { viewer_final->spinOnce (100); boost::this_thread::sleep (boost::posix_time::microseconds (100000)); } return (0); } 编译完成运行的结果: (2)本实验将学习如何编写一个交互式ICP可视化的程序。该程序将加载点云并对其进行刚性变换。之后,使用ICP算法将变换后的点云与原来的点云对齐。每次用户按下“空格”,进行ICP迭代,刷新可视化界面。 在这里原始例程使用的是PLY格式的文件,可以找一个PLY格式的文件进行实验,也可以使用格式转换文件 把PCD 文件转为PLY文件 #include <iostream> #include <string> #include <pcl/io/ply_io.h> //PLY相关头文件 #include <pcl/point_types.h> // #include <pcl/registration/icp.h> #include <pcl/visualization/pcl_visualizer.h> #include <pcl/console/time.h> typedef pcl::PointXYZ PointT; typedef pcl::PointCloud<PointT> PointCloudT; //定义点云的格式 bool next_iteration = false; void print4x4Matrix (const Eigen::Matrix4d & matrix) //打印旋转矩阵和平移矩阵 { printf ("Rotation matrix :\n"); printf (" | %6.3f %6.3f %6.3f | \n", matrix (0, 0), matrix (0, 1), matrix (0, 2)); printf ("R = | %6.3f %6.3f %6.3f | \n", matrix (1, 0), matrix (1, 1), matrix (1, 2)); printf (" | %6.3f %6.3f %6.3f | \n", matrix (2, 0), matrix (2, 1), matrix (2, 2)); printf ("Translation vector :\n"); printf ("t = < %6.3f, %6.3f, %6.3f >\n\n", matrix (0, 3), matrix (1, 3), matrix (2, 3)); } void keyboardEventOccurred (const pcl::visualization::KeyboardEvent& event, void* nothing) { //使用空格键来增加迭代次数,并更新显示 if (event.getKeySym () == "space" && event.keyDown ()) next_iteration = true; } int main (int argc, char* argv[]) { // 申明点云将要使用的 PointCloudT::Ptr cloud_in (new PointCloudT); // 原始点云 PointCloudT::Ptr cloud_tr (new PointCloudT); // 转换后的点云 PointCloudT::Ptr cloud_icp (new PointCloudT); // ICP 输出点云 // 检查程序输入命令的合法性 if (argc < 2) //如果只有一个命令说明没有指定目标点云,所以会提示用法 { printf ("Usage :\n"); printf ("\t\t%s file.ply number_of_ICP_iterations\n", argv[0]); PCL_ERROR ("Provide one ply file.\n"); return (-1); } int iterations = 1; // 默认的ICP迭代次数 if (argc > 2) { //如果命令的有两个以上。说明用户是将迭代次数作为传递参数 iterations = atoi (argv[2]); //传递参数的格式转化为int型 if (iterations < 1) //同时不能设置迭代次数为1 { PCL_ERROR ("Number of initial iterations must be >= 1\n"); return (-1); } } pcl::console::TicToc time; //申明时间记录 time.tic (); //time.tic开始 time.toc结束时间 if (pcl::io::loadPLYFile (argv[1], *cloud_in) < 0) { PCL_ERROR ("Error loading cloud %s.\n", argv[1]); return (-1); } std::cout << "\nLoaded file " << argv[1] << " (" << cloud_in->size () << " points) in " << time.toc () << " ms\n" << std::endl; //定义旋转矩阵和平移向量Matrix4d是为4*4的矩阵 Eigen::Matrix4d transformation_matrix = Eigen::Matrix4d::Identity (); //初始化 // 旋转矩阵的定义可以参考 ( https://en.wikipedia.org/wiki/Rotation_matrix) double theta = M_PI / 8; // 旋转的角度用弧度的表示方法 transformation_matrix (0, 0) = cos (theta); transformation_matrix (0, 1) = -sin (theta); transformation_matrix (1, 0) = sin (theta); transformation_matrix (1, 1) = cos (theta); // Z轴的平移向量 (0.4 meters) transformation_matrix (2, 3) = 0.4; //打印转换矩阵 std::cout << "Applying this rigid transformation to: cloud_in -> cloud_icp" << std::endl; print4x4Matrix (transformation_matrix); // 执行点云转换 pcl::transformPointCloud (*cloud_in, *cloud_icp, transformation_matrix); *cloud_tr = *cloud_icp; // 备份cloud_icp赋值给cloud_tr为后期使用 // 迭代最近点算法 time.tic (); //时间 pcl::IterativeClosestPoint<PointT, PointT> icp; icp.setMaximumIterations (iterations); //设置最大迭代次数iterations=true icp.setInputSource (cloud_icp); //设置输入的点云 icp.setInputTarget (cloud_in); //目标点云 icp.align (*cloud_icp); //匹配后源点云 icp.setMaximumIterations (1); // 设置为1以便下次调用 std::cout << "Applied " << iterations << " ICP iteration(s) in " << time.toc () << " ms" << std::endl; if (icp.hasConverged ())//icp.hasConverged ()=1(true)输出变换矩阵的适合性评估 { std::cout << "\nICP has converged, score is " << icp.getFitnessScore () << std::endl; std::cout << "\nICP transformation " << iterations << " : cloud_icp -> cloud_in" << std::endl; transformation_matrix = icp.getFinalTransformation ().cast<double>(); print4x4Matrix (transformation_matrix); } else { PCL_ERROR ("\nICP has not converged.\n"); return (-1); } // 可视化ICP的过程与结果 pcl::visualization::PCLVisualizer viewer ("ICP demo"); // 创建两个观察视点 int v1 (0); int v2 (1); viewer.createViewPort (0.0, 0.0, 0.5, 1.0, v1); viewer.createViewPort (0.5, 0.0, 1.0, 1.0, v2); // 定义显示的颜色信息 float bckgr_gray_level = 0.0; // Black float txt_gray_lvl = 1.0 - bckgr_gray_level; // 原始的点云设置为白色的 pcl::visualization::PointCloudColorHandlerCustom<PointT> cloud_in_color_h (cloud_in, (int) 255 * txt_gray_lvl, (int) 255 * txt_gray_lvl, (int) 255 * txt_gray_lvl); viewer.addPointCloud (cloud_in, cloud_in_color_h, "cloud_in_v1", v1);//设置原始的点云都是显示为白色 viewer.addPointCloud (cloud_in, cloud_in_color_h, "cloud_in_v2", v2); // 转换后的点云显示为绿色 pcl::visualization::PointCloudColorHandlerCustom<PointT> cloud_tr_color_h (cloud_tr, 20, 180, 20); viewer.addPointCloud (cloud_tr, cloud_tr_color_h, "cloud_tr_v1", v1); // ICP配准后的点云为红色 pcl::visualization::PointCloudColorHandlerCustom<PointT> cloud_icp_color_h (cloud_icp, 180, 20, 20); viewer.addPointCloud (cloud_icp, cloud_icp_color_h, "cloud_icp_v2", v2); // 加入文本的描述在各自的视口界面 //在指定视口viewport=v1添加字符串“white 。。。”其中"icp_info_1"是添加字符串的ID标志,(10,15)为坐标16为字符大小 后面分别是RGB值 viewer.addText ("White: Original point cloud\nGreen: Matrix transformed point cloud", 10, 15, 16, txt_gray_lvl, txt_gray_lvl, txt_gray_lvl, "icp_info_1", v1); viewer.addText ("White: Original point cloud\nRed: ICP aligned point cloud", 10, 15, 16, txt_gray_lvl, txt_gray_lvl, txt_gray_lvl, "icp_info_2", v2); std::stringstream ss; ss << iterations; //输入的迭代的次数 std::string iterations_cnt = "ICP iterations = " + ss.str (); viewer.addText (iterations_cnt, 10, 60, 16, txt_gray_lvl, txt_gray_lvl, txt_gray_lvl, "iterations_cnt", v2); // 设置背景颜色 viewer.setBackgroundColor (bckgr_gray_level, bckgr_gray_level, bckgr_gray_level, v1); viewer.setBackgroundColor (bckgr_gray_level, bckgr_gray_level, bckgr_gray_level, v2); // 设置相机的坐标和方向 viewer.setCameraPosition (-3.68332, 2.94092, 5.71266, 0.289847, 0.921947, -0.256907, 0); viewer.setSize (1280, 1024); // 可视化窗口的大小 // 注册按键回调函数 viewer.registerKeyboardCallback (&keyboardEventOccurred, (void*) NULL); // 显示 while (!viewer.wasStopped ()) { viewer.spinOnce (); //按下空格键的函数 if (next_iteration) { // 最近点迭代算法 time.tic (); icp.align (*cloud_icp); std::cout << "Applied 1 ICP iteration in " << time.toc () << " ms" << std::endl; if (icp.hasConverged ()) { printf ("\033[11A"); // Go up 11 lines in terminal output. printf ("\nICP has converged, score is %+.0e\n", icp.getFitnessScore ()); std::cout << "\nICP transformation " << ++iterations << " : cloud_icp -> cloud_in" << std::endl; transformation_matrix *= icp.getFinalTransformation ().cast<double>(); // WARNING /!\ This is not accurate! print4x4Matrix (transformation_matrix); // 打印矩阵变换 ss.str (""); ss << iterations; std::string iterations_cnt = "ICP iterations = " + ss.str (); viewer.updateText (iterations_cnt, 10, 60, 16, txt_gray_lvl, txt_gray_lvl, txt_gray_lvl, "iterations_cnt"); viewer.updatePointCloud (cloud_icp, cloud_icp_color_h, "cloud_icp_v2"); } else { PCL_ERROR ("\nICP has not converged.\n"); return (-1); } } next_iteration = false; } return (0); } 生成可执行文件后结果 窗口输出的基本信息 刚开始迭代第一次的结果 按空格键不断迭代的结果 完毕,如有错误请与我联系交流,谢谢,大牛请忽略 微信公众号号可扫描二维码一起共同学习交流
在逆向工程,计算机视觉,文物数字化等领域中,由于点云的不完整,旋转错位,平移错位等,使得要得到的完整的点云就需要对局部点云进行配准,为了得到被测物体的完整数据模型,需要确定一个合适的坐标系,将从各个视角得到的点集合并到统一的坐标系下形成一个完整的点云,然后就可以方便进行可视化的操作,这就是点云数据的配准。点云的配准有手动配准依赖仪器的配准,和自动配准,点云的自动配准技术是通过一定的算法或者统计学规律利用计算机计算两块点云之间错位,从而达到两块点云自动配准的效果,其实质就是把不同的坐标系中测得到的数据点云进行坐标系的变换,以得到整体的数据模型,问题的关键是如何让得到坐标变换的参数R(旋转矩阵)和T(平移向量),使得两视角下测得的三维数据经坐标变换后的距离最小,,目前配准算法按照过程可以分为整体配准和局部配准,。PCL中有单独的配准模块,实现了配准相关的基础数据结构,和经典的配准算法如ICP。 PCL中实现配准算法以及相关的概念 两两配准的简介:一对点云数据集的配准问题是两两配准(pairwise registration 或 pair-wise registration).通常通过应用一个估计得到的表示平移和选装的4*4缸体变换矩阵来使得一个点云的数据集精确的与另一个点云数据集(目标数据集)进行完美的配准 具体的实现步骤: (1)首先从两个数据集中按照同样的关键点选取的标准,提取关键点 (2)对选择所有的关键点分别计算其特征描述子 (3)结合特征描述子在两个数据集中的坐标位置,以两者之间的特征和位置的相似度为基础,来估算它们的对应关系,初步的估计对应点对。 (4)假设数据是有噪声,出去对配准有影响的错误的对应点对 (5)利用剩余的正确的对应关系来估算刚体变换,完整配准。 对应估计(correspondences estimation):假设我们已经得到由来给你此扫描的点云数据获得的两组特征向量,在此基础基础上,我们必须找到,相似特征再确定数据的重叠部分,然后才能进行配准,根据特征的类型PCL使用不同的方法来搜索特征之间的对应关系 使用点匹配时,使用点的XYZ的坐标作为特征值,针对有序点云和无序点云数据的不同的处理策略: (1)穷举配准(brute force matching) (2)kd——数最近邻查询(FLANN) (3)在有序点云数据的图像空间中查找 (4)在无序点云数据的索引空间中查找 (3)对应关系的去除(correspondence rejection) 由于噪声的影响,通常并不是所有估计的对应关系都是正确的,由于错误的对应关系对于最终的刚体变换矩阵的估算会产生负面的影响,所以必须去除它们,可以采用随机采样一致性估计,或者其他方法剔除错误的对应关系,最终使用对应关系数量只使用一定比例的对应关系,这样既能提高变换矩阵的估计京都也可以提高配准点的速度。 (4)变换矩阵的估算(transormation estimation) 估算对应矩阵的步骤如下 1. 在对应关系的基础上评估一些错误的度量标准 2.在摄像机位姿(运动估算)和最小化错误度量标准下估算一个刚体变换 3.优化点的结构 4使用刚体变换把源旋转/平移到与目标所在的同一坐标系下,用所有点,点的一个子集或者关键点运算一个内部的ICP循环 5,进行迭代,直到符合收敛性判断标准为止。 (5)迭代最近点算法(Iterative CLosest Point简称ICP算法) ICP算法对待拼接的2片点云,首先根据一定的准则确立对应点集P与Q,其中对应点对的个数,然后通过最小乘法迭代计算最优的坐标变换,即旋转矩阵R和平移矢量t,使得误差函数最小,ICP处理流程分为四个主要的步骤: 1. 对原始点云数据进行采样 2.确定初始对应点集 3,去除错误对应点对 4.坐标变换的求解 PCL类的相关的介绍 class pcl::CorrespondenceGrouping< PointModelT, PointSceneT > Abstract base class for Correspondence Grouping algorithms class pcl::GeometricConsistencyGrouping< PointModelT, PointSceneT > Class implementing a 3D correspondence grouping enforcing geometric consistency among feature correspondences. class pcl::recognition::HoughSpace3D HoughSpace3D is a 3D voting space. class pcl::Hough3DGrouping< PointModelT, PointSceneT, PointModelRfT, PointSceneRfT > Class implementing a 3D correspondence grouping algorithm that can deal with multiple instances of a model template found into a given scene. class pcl::CRHAlignment< PointT, nbins_ > CRHAlignment uses two Camera Roll Histograms (CRH) to find the roll rotation that aligns both views. class pcl::recognition::ObjRecRANSAC::Output This is an output item of the ObjRecRANSAC::recognize() method. More... class pcl::recognition::ObjRecRANSAC::OrientedPointPair class pcl::recognition::ObjRecRANSAC::HypothesisCreator class pcl::recognition::ObjRecRANSAC This is a RANSAC-based 3D object recognition method. class pcl::recognition::ORROctree::Node::Data class pcl::recognition::ORROctree::Node class pcl::recognition::ORROctree That's a very specialized and simple octree class. class pcl::recognition::RotationSpace This is a class for a discrete representation of the rotation space based on the axis-angle representation. 实例分析: (1)如何使用迭代最近点算法:在代码中使用ICP迭代最近点算法,程序随机生成一个点与作为源点云,并将其沿x轴平移后作为目标点云,然后利用ICP估计源到目标的刚体变换橘子,中间对所有信息都打印出来 新建文件 iterative_closest_point.cpp #include <iostream> //标准输入输出头文件 #include <pcl/io/pcd_io.h> //I/O操作头文件 #include <pcl/point_types.h> //点类型定义头文件 #include <pcl/registration/icp.h> //ICP配准类相关头文件 int main (int argc, char** argv) { //创建两个pcl::PointCloud<pcl::PointXYZ>共享指针,并初始化它们 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_in (new pcl::PointCloud<pcl::PointXYZ>); pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_out (new pcl::PointCloud<pcl::PointXYZ>); // 随机填充点云 cloud_in->width = 5; //设置点云宽度 cloud_in->height = 1; //设置点云为无序点 cloud_in->is_dense = false; cloud_in->points.resize (cloud_in->width * cloud_in->height); for (size_t i = 0; i < cloud_in->points.size (); ++i) { cloud_in->points[i].x = 1024 * rand () / (RAND_MAX + 1.0f); cloud_in->points[i].y = 1024 * rand () / (RAND_MAX + 1.0f); cloud_in->points[i].z = 1024 * rand () / (RAND_MAX + 1.0f); } std::cout << "Saved " << cloud_in->points.size () << " data points to input:"//打印处点云总数 << std::endl; for (size_t i = 0; i < cloud_in->points.size (); ++i) std::cout << " " << //打印处实际坐标 cloud_in->points[i].x << " " << cloud_in->points[i].y << " " << cloud_in->points[i].z << std::endl; *cloud_out = *cloud_in; std::cout << "size:" << cloud_out->points.size() << std::endl; //实现一个简单的点云刚体变换,以构造目标点云 for (size_t i = 0; i < cloud_in->points.size (); ++i) cloud_out->points[i].x = cloud_in->points[i].x + 0.7f; std::cout << "Transformed " << cloud_in->points.size () << " data points:" << std::endl; for (size_t i = 0; i < cloud_out->points.size (); ++i) //打印构造出来的目标点云 std::cout << " " << cloud_out->points[i].x << " " << cloud_out->points[i].y << " " << cloud_out->points[i].z << std::endl; pcl::IterativeClosestPoint<pcl::PointXYZ, pcl::PointXYZ> icp; //创建IterativeClosestPoint的对象 icp.setInputCloud(cloud_in); //cloud_in设置为点云的源点 icp.setInputTarget(cloud_out); //cloud_out设置为与cloud_in对应的匹配目标 pcl::PointCloud<pcl::PointXYZ> Final; //存储经过配准变换点云后的点云 icp.align(Final); //打印配准相关输入信息 std::cout << "has converged:" << icp.hasConverged() << " score: " << icp.getFitnessScore() << std::endl; std::cout << icp.getFinalTransformation() << std::endl; return (0); } 编译运行的结果 可以有试验结果看得出变换后的点云只是在x轴的值增加了固定的值0.7,然后由这目标点云与源点云计算出它的旋转与平移,明显可以看出最后一行的x值为0.7 同时,我们可以自己更改程序,来观察不同的实验结果。 对于两幅图像通过ICP求它的变换: 刚开始,如果直接通过通过kinect 得到数据运行会出现如下的错误,是因为该ICP 算法不能处理含有NaNs的点云数据,所以需要通过移除这些点,才能作为ICP算法的输入 错误的提示 所以我们必须通过之前所学的代码把其中的无效的点云去除,然后作为输入,由于是我自己获得的点云数据,数据没有预处理,其中输入的两个点云后ICP后的结果及可视化为 (2)如何逐步匹配多幅点云 本实例是使用迭代最近点算法,逐步实现地对一系列点云进行两两匹配,他的思想是对所有的点云进行变换,使得都与第一个点云统一坐标系中,在每个连贯的有重叠的点云之间找出最佳的变换,并积累这些变换到全部的点云,能够进行ICP算法的点云需要粗略的预匹配(比如在一个机器人的量距内或者在地图的框架内),并且一个点云与另一个点云需要有重叠的部分。 新建文件pairwise_incremental_registration.cpp #include <boost/make_shared.hpp> //boost指针相关头文件 #include <pcl/point_types.h> //点类型定义头文件 #include <pcl/point_cloud.h> //点云类定义头文件 #include <pcl/point_representation.h> //点表示相关的头文件 #include <pcl/io/pcd_io.h> //PCD文件打开存储类头文件 #include <pcl/filters/voxel_grid.h> //用于体素网格化的滤波类头文件 #include <pcl/filters/filter.h> //滤波相关头文件 #include <pcl/features/normal_3d.h> //法线特征头文件 #include <pcl/registration/icp.h> //ICP类相关头文件 #include <pcl/registration/icp_nl.h> //非线性ICP 相关头文件 #include <pcl/registration/transforms.h> //变换矩阵类头文件 #include <pcl/visualization/pcl_visualizer.h> //可视化类头文件 using pcl::visualization::PointCloudColorHandlerGenericField; using pcl::visualization::PointCloudColorHandlerCustom; //定义 typedef pcl::PointXYZ PointT; typedef pcl::PointCloud<PointT> PointCloud; //申明pcl::PointXYZ数据 typedef pcl::PointNormal PointNormalT; typedef pcl::PointCloud<PointNormalT> PointCloudWithNormals; // 申明一个全局可视化对象变量,定义左右视点分别显示配准前和配准后的结果点云 pcl::visualization::PCLVisualizer *p; //创建可视化对象 int vp_1, vp_2; //定义存储 左 右视点的ID //申明一个结构体方便对点云以文件名和点云对象进行成对处理和管理点云,处理过程中可以同时接受多个点云文件的输入 struct PCD { PointCloud::Ptr cloud; //点云共享指针 std::string f_name; //文件名称 PCD() : cloud (new PointCloud) {}; }; struct PCDComparator //文件比较处理 { bool operator () (const PCD& p1, const PCD& p2) { return (p1.f_name < p2.f_name); } }; // 以< x, y, z, curvature >形式定义一个新的点表示 class MyPointRepresentation : public pcl::PointRepresentation <PointNormalT> { using pcl::PointRepresentation<PointNormalT>::nr_dimensions_; public: MyPointRepresentation () { nr_dimensions_ = 4; //定义点的维度 } // 重载copyToFloatArray方法将点转化为四维数组 virtual void copyToFloatArray (const PointNormalT &p, float * out) const { // < x, y, z, curvature > out[0] = p.x; out[1] = p.y; out[2] = p.z; out[3] = p.curvature; } }; /** \左视图用来显示未匹配的源和目标点云*/ void showCloudsLeft(const PointCloud::Ptr cloud_target, const PointCloud::Ptr cloud_source) { p->removePointCloud ("vp1_target"); p->removePointCloud ("vp1_source"); PointCloudColorHandlerCustom<PointT> tgt_h (cloud_target, 0, 255, 0); PointCloudColorHandlerCustom<PointT> src_h (cloud_source, 255, 0, 0); p->addPointCloud (cloud_target, tgt_h, "vp1_target", vp_1); p->addPointCloud (cloud_source, src_h, "vp1_source", vp_1); PCL_INFO ("Press q to begin the registration.\n"); p-> spin(); } /** \右边显示配准后的源和目标点云*/ void showCloudsRight(const PointCloudWithNormals::Ptr cloud_target, const PointCloudWithNormals::Ptr cloud_source) { p->removePointCloud ("source"); p->removePointCloud ("target"); PointCloudColorHandlerGenericField<PointNormalT> tgt_color_handler (cloud_target, "curvature"); if (!tgt_color_handler.isCapable ()) PCL_WARN ("Cannot create curvature color handler!"); PointCloudColorHandlerGenericField<PointNormalT> src_color_handler (cloud_source, "curvature"); if (!src_color_handler.isCapable ()) PCL_WARN ("Cannot create curvature color handler!"); p->addPointCloud (cloud_target, tgt_color_handler, "target", vp_2); p->addPointCloud (cloud_source, src_color_handler, "source", vp_2); p->spinOnce(); } //////////////////////////////////////////////////////////////////////////////// /** \brief Load a set of PCD files that we want to register together * \param argc the number of arguments (pass from main ()) * \param argv the actual command line arguments (pass from main ()) * \param models the resultant vector of point cloud datasets */ void loadData (int argc, char **argv, std::vector<PCD, Eigen::aligned_allocator<PCD> > &models) { std::string extension (".pcd"); // 第一个参数是命令本身,所以要从第二个参数开始解析 for (int i = 1; i < argc; i++) { std::string fname = std::string (argv[i]); // PCD文件名至少为5个字符大小字符串(因为后缀名.pcd就已经占了四个字符位置) if (fname.size () <= extension.size ()) continue; std::transform (fname.begin (), fname.end (), fname.begin (), (int(*)(int))tolower); //检查参数是否为一个pcd后缀的文件 if (fname.compare (fname.size () - extension.size (), extension.size (), extension) == 0) { //加载点云并保存在总体的点云列表中 PCD m; m.f_name = argv[i]; pcl::io::loadPCDFile (argv[i], *m.cloud); //从点云中移除NAN点也就是无效点 std::vector<int> indices; pcl::removeNaNFromPointCloud(*m.cloud,*m.cloud, indices); models.push_back (m); } } } //////////////////////////////////////////////////////////////////////////////// /** \brief Align a pair of PointCloud datasets and return the result * \param cloud_src the source PointCloud * \param cloud_tgt the target PointCloud * \param output the resultant aligned source PointCloud * \param final_transform the resultant transform between source and target *////实现匹配,其中参数有输入一组需要配准的点云,以及是否需要进行下采样,其他参数输出配准后的点云以及变换矩阵void pairAlign (const PointCloud::Ptr cloud_src, const PointCloud::Ptr cloud_tgt, PointCloud::Ptr output, Eigen::Matrix4f &final_transform, bool downsample = false) { // // Downsample for consistency and speed // \note enable this for large datasets PointCloud::Ptr src (new PointCloud); //存储滤波后的源点云 PointCloud::Ptr tgt (new PointCloud); //存储滤波后的目标点云 pcl::VoxelGrid<PointT> grid; /////滤波处理对象 if (downsample) { grid.setLeafSize (0.05, 0.05, 0.05); //设置滤波时采用的体素大小 grid.setInputCloud (cloud_src); grid.filter (*src); grid.setInputCloud (cloud_tgt); grid.filter (*tgt); } else { src = cloud_src; tgt = cloud_tgt; } // 计算表面的法向量和曲率 PointCloudWithNormals::Ptr points_with_normals_src (new PointCloudWithNormals); PointCloudWithNormals::Ptr points_with_normals_tgt (new PointCloudWithNormals); pcl::NormalEstimation<PointT, PointNormalT> norm_est; //点云法线估计对象 pcl::search::KdTree<pcl::PointXYZ>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZ> ()); norm_est.setSearchMethod (tree); norm_est.setKSearch (30); norm_est.setInputCloud (src); norm_est.compute (*points_with_normals_src); pcl::copyPointCloud (*src, *points_with_normals_src); norm_est.setInputCloud (tgt); norm_est.compute (*points_with_normals_tgt); pcl::copyPointCloud (*tgt, *points_with_normals_tgt); // // Instantiate our custom point representation (defined above) ... MyPointRepresentation point_representation; // ... and weight the 'curvature' dimension so that it is balanced against x, y, and z float alpha[4] = {1.0, 1.0, 1.0, 1.0}; point_representation.setRescaleValues (alpha); // // 配准 pcl::IterativeClosestPointNonLinear<PointNormalT, PointNormalT> reg; // 配准对象 reg.setTransformationEpsilon (1e-6); ///设置收敛判断条件,越小精度越大,收敛也越慢 // Set the maximum distance between two correspondences (src<->tgt) to 10cm大于此值的点对不考虑 // Note: adjust this based on the size of your datasets reg.setMaxCorrespondenceDistance (0.1); // 设置点表示 reg.setPointRepresentation (boost::make_shared<const MyPointRepresentation> (point_representation)); reg.setInputSource (points_with_normals_src); // 设置源点云 reg.setInputTarget (points_with_normals_tgt); // 设置目标点云 // // Run the same optimization in a loop and visualize the results Eigen::Matrix4f Ti = Eigen::Matrix4f::Identity (), prev, targetToSource; PointCloudWithNormals::Ptr reg_result = points_with_normals_src; reg.setMaximumIterations (2);////设置最大的迭代次数,即每迭代两次就认为收敛,停止内部迭代 for (int i = 0; i < 30; ++i) ////手动迭代,每手动迭代一次,在配准结果视口对迭代的最新结果进行刷新显示 { PCL_INFO ("Iteration Nr. %d.\n", i); // 存储点云以便可视化 points_with_normals_src = reg_result; // Estimate reg.setInputSource (points_with_normals_src); reg.align (*reg_result); //accumulate transformation between each Iteration Ti = reg.getFinalTransformation () * Ti; //if the difference between this transformation and the previous one //is smaller than the threshold, refine the process by reducing //the maximal correspondence distance if (fabs ((reg.getLastIncrementalTransformation () - prev).sum ()) < reg.getTransformationEpsilon ()) reg.setMaxCorrespondenceDistance (reg.getMaxCorrespondenceDistance () - 0.001); prev = reg.getLastIncrementalTransformation (); // visualize current state showCloudsRight(points_with_normals_tgt, points_with_normals_src); } // // Get the transformation from target to source targetToSource = Ti.inverse();//deidao // // Transform target back in source frame pcl::transformPointCloud (*cloud_tgt, *output, targetToSource); p->removePointCloud ("source"); p->removePointCloud ("target"); PointCloudColorHandlerCustom<PointT> cloud_tgt_h (output, 0, 255, 0); PointCloudColorHandlerCustom<PointT> cloud_src_h (cloud_src, 255, 0, 0); p->addPointCloud (output, cloud_tgt_h, "target", vp_2); p->addPointCloud (cloud_src, cloud_src_h, "source", vp_2); PCL_INFO ("Press q to continue the registration.\n"); p->spin (); p->removePointCloud ("source"); p->removePointCloud ("target"); //add the source to the transformed target *output += *cloud_src; final_transform = targetToSource; } int main (int argc, char** argv) { // 存储管理所有打开的点云 std::vector<PCD, Eigen::aligned_allocator<PCD> > data; loadData (argc, argv, data); // 加载所有点云到data // 检查输入 if (data.empty ()) { PCL_ERROR ("Syntax is: %s <source.pcd> <target.pcd> [*]", argv[0]); PCL_ERROR ("[*] - multiple files can be added. The registration results of (i, i+1) will be registered against (i+2), etc"); return (-1); } PCL_INFO ("Loaded %d datasets.", (int)data.size ()); // 创建PCL可视化对象 p = new pcl::visualization::PCLVisualizer (argc, argv, "Pairwise Incremental Registration example"); p->createViewPort (0.0, 0, 0.5, 1.0, vp_1); //用左半窗口创建视口vp_1 p->createViewPort (0.5, 0, 1.0, 1.0, vp_2); //用右半窗口创建视口vp_2 PointCloud::Ptr result (new PointCloud), source, target; Eigen::Matrix4f GlobalTransform = Eigen::Matrix4f::Identity (), pairTransform; for (size_t i = 1; i < data.size (); ++i) //循环处理所有点云 { source = data[i-1].cloud; //连续配准 target = data[i].cloud; // 相邻两组点云 showCloudsLeft(source, target); //可视化为配准的源和目标点云 //调用子函数完成一组点云的配准,temp返回配准后两组点云在第一组点云坐标下的点云 PointCloud::Ptr temp (new PointCloud); PCL_INFO ("Aligning %s (%d) with %s (%d).\n", data[i-1].f_name.c_str (), source->points.size (), data[i].f_name.c_str (), target->points.size ()); // pairTransform返回从目标点云target到source的变换矩阵 pairAlign (source, target, temp, pairTransform, true); //把当前两两配准后的点云temp转化到全局坐标系下返回result pcl::transformPointCloud (*temp, *result, GlobalTransform); //用当前的两组点云之间的变换更新全局变换 GlobalTransform = GlobalTransform * pairTransform; //保存转换到第一个点云坐标下的当前配准后的两组点云result到文件i.pcd std::stringstream ss; ss << i << ".pcd"; pcl::io::savePCDFile (ss.str (), *result, true); } } /* ]--- */ 运行执行可执行文件 ./pairwise_incremental_registration frame_00000.pcd capture0001.pcd capture0002.pcd capture0004.pcd capture0005.pcd 如果观察不到结果,就按键R来重设摄像头,调整角度可以观察到有红绿两组点云显示在窗口的左边,红色为源点云,将看到上面的类似结果,命令行提示需要执行配准按下Q,按下后可以发现左边的窗口不断的调整点云,其实是配准过程中的迭代中间结果的输出,在迭代次数小于设定的次数之前,右边会不断刷新最新的配准结果,直到收敛,迭代次数30次完成整个匹配的过程,再次按下Q后会看到存储的1.pcd文件,此文件为第一个和第二个点云配准后与第一个输入点云在同一个坐标系下的点云。 微信公众号号可扫描二维码一起共同学习交流 未完待续************************************8
对点云的操作可以直接应用变换矩阵,即旋转,平移,尺度,3D的变换就是要使用4*4 的矩阵,例如: 等等模型 在这里直接使用程序开实现一个点云的旋转,新建文件matrix.cpp #include <iostream> #include <pcl/io/pcd_io.h> #include <pcl/io/ply_io.h> #include <pcl/point_cloud.h> #include <pcl/console/parse.h> #include <pcl/common/transforms.h> #include <pcl/visualization/pcl_visualizer.h> // 命令行的帮助提示 void showHelp(char * program_name) { std::cout << std::endl; std::cout << "Usage: " << program_name << " cloud_filename.[pcd|ply]" << std::endl; std::cout << "-h: Show this help." << std::endl; } int main (int argc, char** argv) { if (pcl::console::find_switch (argc, argv, "-h") || pcl::console::find_switch (argc, argv, "--help")) { showHelp (argv[0]); return 0; } // 读取文件 std::vector<int> filenames; bool file_is_pcd = false; filenames = pcl::console::parse_file_extension_argument (argc, argv, ".ply"); if (filenames.size () != 1) { filenames = pcl::console::parse_file_extension_argument (argc, argv, ".pcd"); if (filenames.size () != 1) { showHelp (argv[0]); return -1; } else { file_is_pcd = true; } } //载入文件 pcl::PointCloud<pcl::PointXYZ>::Ptr source_cloud (new pcl::PointCloud<pcl::PointXYZ> ()); if (file_is_pcd) { if (pcl::io::loadPCDFile (argv[filenames[0]], *source_cloud) < 0) { std::cout << "Error loading point cloud " << argv[filenames[0]] << std::endl << std::endl; showHelp (argv[0]); return -1; } } else { if (pcl::io::loadPLYFile (argv[filenames[0]], *source_cloud) < 0) { std::cout << "Error loading point cloud " << argv[filenames[0]] << std::endl << std::endl; showHelp (argv[0]); return -1; } } /* Reminder: how transformation matrices work : |-------> This column is the translation | 1 0 0 x | \ | 0 1 0 y | }-> The identity 3x3 matrix (no rotation) on the left | 0 0 1 z | / | 0 0 0 1 | -> We do not use this line (and it has to stay 0,0,0,1) METHOD #1: Using a Matrix4f This is the "manual" method, perfect to understand but error prone ! */ Eigen::Matrix4f transform_1 = Eigen::Matrix4f::Identity(); // Define a rotation matrix 定义旋转的角度 再有角度计算出旋转矩阵 float theta = M_PI/4; // The angle of rotation in radians transform_1 (0,0) = cos (theta); transform_1 (0,1) = -sin(theta); transform_1 (1,0) = sin (theta); transform_1 (1,1) = cos (theta); // (row, column) // Define a translation of 2.5 meters on the x axis. transform_1 (0,3) = 2.5;//意思就是在第一行第四个元素的值为2.5,也就是在x轴的平移为2.5 // Print the transformation 打印出这个变换矩阵 printf ("Method #1: using a Matrix4f\n"); std::cout << transform_1 << std::endl; /* METHOD #2: Using a Affine3f 第二种方案 This method is easier and less error prone 更简单的方案 */ Eigen::Affine3f transform_2 = Eigen::Affine3f::Identity(); // Define a translation of 2.5 meters on the x axis. transform_2.translation() << 2.5, 0.0, 0.0; // The same rotation matrix as before; theta radians arround Z axis transform_2.rotate (Eigen::AngleAxisf (theta, Eigen::Vector3f::UnitZ())); // Print the transformation printf ("\nMethod #2: using an Affine3f\n"); std::cout << transform_2.matrix() << std::endl; // Executing the transformation pcl::PointCloud<pcl::PointXYZ>::Ptr transformed_cloud (new pcl::PointCloud<pcl::PointXYZ> ()); // 你可以使用 transform_1 或者 transform_2;效果都是一样的 pcl::transformPointCloud (*source_cloud, *transformed_cloud, transform_2); // 可视化的 printf( "\nPoint cloud colors : white = original point cloud\n" " red = transformed point cloud\n"); pcl::visualization::PCLVisualizer viewer ("Matrix transformation example"); // 为点云设置RGB的值 pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> source_cloud_color_handler (source_cloud, 255, 255, 255); // We add the point cloud to the viewer and pass the color handler viewer.addPointCloud (source_cloud, source_cloud_color_handler, "original_cloud"); pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> transformed_cloud_color_handler (transformed_cloud, 230, 20, 20); // Red viewer.addPointCloud (transformed_cloud, transformed_cloud_color_handler, "transformed_cloud"); viewer.addCoordinateSystem (1.0, 0); viewer.setBackgroundColor(0.05, 0.05, 0.05, 0); //设置背景颜色 viewer.setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "original_cloud"); viewer.setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "transformed_cloud"); //viewer.setPosition(800, 400); // Setting visualiser window position while (!viewer.wasStopped ()) { // Display the visualiser until 'q' key is pressed viewer.spinOnce (); } return 0; } 编译后我们随便找一个PCD文件查看效果,也可以该程序的参数,查看不同的参数的结果 命令窗口打印的结果 可视化的结果 (2)移除 NaNs: 从传感器获得的点云可能包含几种测量误差和/或不准确。其中之一是在一些点的坐标中存在NaN(不是数)值,正如你在下面的文件中看到的那样: # .PCD v0.7 - Point Cloud Data file format VERSION 0.7 FIELDS x y z rgba SIZE 4 4 4 4 TYPE F F F U COUNT 1 1 1 1 WIDTH 640 HEIGHT 480 VIEWPOINT 0 0 0 1 0 0 0 POINTS 307200 DATA ascii nan nan nan 10135463 nan nan nan 10398635 nan nan nan 10070692 nan nan nan 10268071 ... 点云对象的成员函数有称为“is_dense()”,如果所有的点都有效的返回true是为有限值。一个NaNs表明测量传感器距离到该点的距离值是有问题的,可能是因为传感器太近或太远,或者因为表面反射。那么当存在无效点云的NaNs值作为算法的输入的时候,可能会引起很多问题,比如“"Assertion `point_representation_->isValid (point) && "Invalid (NaN, Inf) point coordinates given to radiusSearch!"' failed."”如果发生这样的错误就要移除这些点,那么下面就是为了解决移除无效点的程序 #include <pcl/io/pcd_io.h> #include <pcl/filters/filter.h> #include <iostream> #include <pcl/visualization/cloud_viewer.h> int main(int argc,char** argv) { if(argc !=3) { std::cout <<"\tUsage: "<<argv[0] <<"<input cloud> <output cloud>" <<std::endl; return -1; } //object for string the point cloud pcl::PointCloud<pcl::PointXYZRGBA>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZRGBA>); //read a PCDfile from disk if(pcl::io::loadPCDFile<pcl::PointXYZRGBA>(argv[1],*cloud) !=0) { return -1; } //the mapping tells you to that points of the oldcloud the new ones correspond //but we will not use it std::vector<int> mapping; pcl::removeNaNFromPointCloud(*cloud, *cloud, mapping); //pcl::removeNaNFromPointCloud(*cloud, *cloud, mapping); //save it back pcl::io::savePCDFileASCII(argv[2],*cloud); pcl::visualization::CloudViewer viewer(argv[2]); viewer.showCloud(cloud); while (!viewer.wasStopped()) { // Do nothing but wait. } } 然后可以显示移除NaNs点后的可视图, 这张点云是我自己用kinect 生成的点云,在没有移除NaNs的时候可以先读取以下,显示他的点云数值在命令窗口,你会发现会有很多的NaNs的无效点,经过 移除这些点之后在read一些打印处的结果就不会存在NaNs的无效点,这样在后期的使用算法的时候就不会出现错误了。 这种方法的问题是它不会保持点云仍然是有序点云。所有的点云都存储一个“宽度”和“高度”变量。在无序点云,总数为宽度相同,而高度设置为1。在有序的点云(像从相机拍摄像传感器如Kinect或Xtion的),宽度和高度都相同的像素的图像分辨率传感器的工作。点云分布在深度图像的行中,每一个点对应一个像素。成员函数”isorganized()”如果高度大于1时返回真。由于移除NaNs无效点会改变点云的点的数量,它不再能保持组织与原来的宽高比,所以函数将设置高度1。这不是一个大问题,只有少数的PCL的算法工作明确要求是有序的点云(大多这样情况下会使用在优化上),但你必须考虑其中的影响。 暂时就到这里了。。。。。。 微信公众号号可扫描二维码一起共同学习交流
在测量较小的数据时会产生一些误差,这些误差所造成的不规则数据如果直接拿来曲面重建的话,会使得重建的曲面不光滑或者有漏洞,可以采用对数据重采样来解决这样问题,通过对周围的数据点进行高阶多项式插值来重建表面缺少的部分, (1)用最小二乘法对点云进行平滑处理 新建文件resampling.cpp #include <pcl/point_types.h> #include <pcl/io/pcd_io.h> #include <pcl/kdtree/kdtree_flann.h> //kd-tree搜索对象的类定义的头文件 #include <pcl/surface/mls.h> //最小二乘法平滑处理类定义头文件 int main (int argc, char** argv) { pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ> ()); pcl::io::loadPCDFile ("bun0.pcd", *cloud); // 创建 KD-Tree pcl::search::KdTree<pcl::PointXYZ>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZ>); // Output has the PointNormal type in order to store the normals calculated by MLS pcl::PointCloud<pcl::PointNormal> mls_points; // 定义最小二乘实现的对象mls pcl::MovingLeastSquares<pcl::PointXYZ, pcl::PointNormal> mls; mls.setComputeNormals (true); //设置在最小二乘计算中需要进行法线估计 // Set parameters mls.setInputCloud (cloud); mls.setPolynomialFit (true); mls.setSearchMethod (tree); mls.setSearchRadius (0.03); // Reconstruct mls.process (mls_points); // Save output pcl::io::savePCDFile ("bun0-mls.pcd", mls_points); } 结果对比 (2)在平面模型上提取凸(凹)多边形 本例子先从点云中提取平面模型,再通过该估计的平面模型系数从滤波后的点云投影一组点集形成点云,最后为投影后的点云计算其对应的二维凸多边形 #include <pcl/ModelCoefficients.h> //采样一致性模型相关类头文件 #include <pcl/io/pcd_io.h> #include <pcl/point_types.h> #include <pcl/sample_consensus/method_types.h> #include <pcl/sample_consensus/model_types.h> #include <pcl/filters/passthrough.h> #include <pcl/filters/project_inliers.h> //滤波相关类头文件 #include <pcl/segmentation/sac_segmentation.h> //基于采样一致性分割类定义的头文件 #include <pcl/surface/concave_hull.h> //创建凹多边形类定义头文件 int main (int argc, char** argv) { pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>), cloud_filtered (new pcl::PointCloud<pcl::PointXYZ>), cloud_projected (new pcl::PointCloud<pcl::PointXYZ>); pcl::PCDReader reader; reader.read ("table_scene_mug_stereo_textured.pcd", *cloud); // 建立过滤器消除杂散的NaN pcl::PassThrough<pcl::PointXYZ> pass; pass.setInputCloud (cloud); //设置输入点云 pass.setFilterFieldName ("z"); //设置分割字段为z坐标 pass.setFilterLimits (0, 1.1); //设置分割阀值为(0, 1.1) pass.filter (*cloud_filtered); std::cerr << "PointCloud after filtering has: " << cloud_filtered->points.size () << " data points." << std::endl; pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients); pcl::PointIndices::Ptr inliers (new pcl::PointIndices); //inliers存储分割后的点云 // 创建分割对象 pcl::SACSegmentation<pcl::PointXYZ> seg; // 设置优化系数,该参数为可选参数 seg.setOptimizeCoefficients (true); // Mandatory seg.setModelType (pcl::SACMODEL_PLANE); seg.setMethodType (pcl::SAC_RANSAC); seg.setDistanceThreshold (0.01); seg.setInputCloud (cloud_filtered); seg.segment (*inliers, *coefficients); std::cerr << "PointCloud after segmentation has: " << inliers->indices.size () << " inliers." << std::endl; // Project the model inliers pcl::ProjectInliers<pcl::PointXYZ> proj;//点云投影滤波模型 proj.setModelType (pcl::SACMODEL_PLANE); //设置投影模型 proj.setIndices (inliers); proj.setInputCloud (cloud_filtered); proj.setModelCoefficients (coefficients); //将估计得到的平面coefficients参数设置为投影平面模型系数 proj.filter (*cloud_projected); //得到投影后的点云 std::cerr << "PointCloud after projection has: " << cloud_projected->points.size () << " data points." << std::endl; // 存储提取多边形上的点云 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_hull (new pcl::PointCloud<pcl::PointXYZ>); pcl::ConcaveHull<pcl::PointXYZ> chull; //创建多边形提取对象 chull.setInputCloud (cloud_projected); //设置输入点云为提取后点云 chull.setAlpha (0.1); chull.reconstruct (*cloud_hull); //创建提取创建凹多边形 std::cerr << "Concave hull has: " << cloud_hull->points.size () << " data points." << std::endl; pcl::PCDWriter writer; writer.write ("table_scene_mug_stereo_textured_hull.pcd", *cloud_hull, false); return (0); } 实验结果 (3)无序点云的快速三角化 使用贪婪投影三角化算法对有向点云进行三角化, 具体方法是: (1)先将有向点云投影到某一局部二维坐标平面内 (2)在坐标平面内进行平面内的三角化 (3)根据平面内三位点的拓扑连接关系获得一个三角网格曲面模型. 贪婪投影三角化算法原理: 是处理一系列可以使网格“生长扩大”的点(边缘点)延伸这些点直到所有符合几何正确性和拓扑正确性的点都被连上,该算法可以用来处理来自一个或者多个扫描仪扫描到得到并且有多个连接处的散乱点云但是算法也是有很大的局限性,它更适用于采样点云来自表面连续光滑的曲面且点云的密度变化比较均匀的情况 #include <pcl/point_types.h> #include <pcl/io/pcd_io.h> #include <pcl/kdtree/kdtree_flann.h> #include <pcl/features/normal_3d.h> #include <pcl/surface/gp3.h> //贪婪投影三角化算法 int main (int argc, char** argv) { // 将一个XYZ点类型的PCD文件打开并存储到对象中 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>); pcl::PCLPointCloud2 cloud_blob; pcl::io::loadPCDFile ("bun0.pcd", cloud_blob); pcl::fromPCLPointCloud2 (cloud_blob, *cloud); //* the data should be available in cloud // Normal estimation* pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> n; //法线估计对象 pcl::PointCloud<pcl::Normal>::Ptr normals (new pcl::PointCloud<pcl::Normal>); //存储估计的法线 pcl::search::KdTree<pcl::PointXYZ>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZ>); //定义kd树指针 tree->setInputCloud (cloud); ///用cloud构建tree对象 n.setInputCloud (cloud); n.setSearchMethod (tree); n.setKSearch (20); n.compute (*normals); ////估计法线存储到其中 //* normals should not contain the point normals + surface curvatures // Concatenate the XYZ and normal fields* pcl::PointCloud<pcl::PointNormal>::Ptr cloud_with_normals (new pcl::PointCloud<pcl::PointNormal>); pcl::concatenateFields (*cloud, *normals, *cloud_with_normals); //连接字段 //* cloud_with_normals = cloud + normals //定义搜索树对象 pcl::search::KdTree<pcl::PointNormal>::Ptr tree2 (new pcl::search::KdTree<pcl::PointNormal>); tree2->setInputCloud (cloud_with_normals); //点云构建搜索树 // Initialize objects pcl::GreedyProjectionTriangulation<pcl::PointNormal> gp3; //定义三角化对象 pcl::PolygonMesh triangles; //存储最终三角化的网络模型 // Set the maximum distance between connected points (maximum edge length) gp3.setSearchRadius (0.025); //设置连接点之间的最大距离,(即是三角形最大边长) // 设置各参数值 gp3.setMu (2.5); //设置被样本点搜索其近邻点的最远距离为2.5,为了使用点云密度的变化 gp3.setMaximumNearestNeighbors (100); //设置样本点可搜索的邻域个数 gp3.setMaximumSurfaceAngle(M_PI/4); // 设置某点法线方向偏离样本点法线的最大角度45 gp3.setMinimumAngle(M_PI/18); // 设置三角化后得到的三角形内角的最小的角度为10 gp3.setMaximumAngle(2*M_PI/3); // 设置三角化后得到的三角形内角的最大角度为120 gp3.setNormalConsistency(false); //设置该参数保证法线朝向一致 // Get result gp3.setInputCloud (cloud_with_normals); //设置输入点云为有向点云 gp3.setSearchMethod (tree2); //设置搜索方式 gp3.reconstruct (triangles); //重建提取三角化 // 附加顶点信息 std::vector<int> parts = gp3.getPartIDs(); std::vector<int> states = gp3.getPointStates(); // Finish return (0); } 首先看一下原来的PCD可视化文件 对其进行三角化可视化的结果是 效果还是很明显的阿 微信公众号号可扫描二维码一起共同学习交流
如何从一个深度图像(range image)中提取NARF特征 代码解析narf_feature_extraction.cpp #include <iostream> #include <boost/thread/thread.hpp> #include <pcl/range_image/range_image.h> #include <pcl/io/pcd_io.h> #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/features/narf_descriptor.h> #include <pcl/console/parse.h> typedef pcl::PointXYZ PointType; //参数的设置 float angular_resolution = 0.5f; float support_size = 0.2f; pcl::RangeImage::CoordinateFrame coordinate_frame = pcl::RangeImage::CAMERA_FRAME; bool setUnseenToMaxRange = false; bool rotation_invariant = true; //命令帮助 void printUsage (const char* progName) { std::cout << "\n\nUsage: "<<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 to max range\n" << "-s <float> support size for the interest points (diameter of the used sphere - " "default "<<support_size<<")\n" << "-o <0/1> switch rotational invariant version of the feature on/off" << " (default "<< (int)rotation_invariant<<")\n" << "-h this help\n" << "\n\n"; } void setViewerPose (pcl::visualization::PCLVisualizer& viewer, const Eigen::Affine3f& viewer_pose)//setViewerPose { 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, "-m") >= 0) { setUnseenToMaxRange = true; cout << "Setting unseen values in range image to maximum range readings.\n"; } if (pcl::console::parse (argc, argv, "-o", rotation_invariant) >= 0) cout << "Switching rotation invariant feature version "<< (rotation_invariant ? "on" : "off")<<".\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); //打开一个磁盘中的.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 ()); std::vector<int> pcd_filename_indices = pcl::console::parse_file_extension_argument (argc, argv, "pcd"); if (!pcd_filename_indices.empty ()) //检测是否有far_ranges.pcd { std::string filename = argv[pcd_filename_indices[0]]; if (pcl::io::loadPCDFile (filename, point_cloud) == -1) { 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 { 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; } //从点云中建立生成深度图 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, 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 (); //打开3D viewer并加入点云 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::visualization::RangeImageVisualizer range_image_widget ("Range image"); range_image_widget.showRangeImage (range_image); //提取NARF特征 pcl::RangeImageBorderExtractor range_image_border_extractor; //申明深度图边缘提取器 pcl::NarfKeypoint narf_keypoint_detector; //narf_keypoint_detector为点云对象 narf_keypoint_detector.setRangeImageBorderExtractor (&range_image_border_extractor); narf_keypoint_detector.setRangeImage (&range_image); narf_keypoint_detector.getParameters ().support_size = support_size; //获得特征提取的大小 pcl::PointCloud<int> keypoint_indices; narf_keypoint_detector.compute (keypoint_indices); std::cout << "Found "<<keypoint_indices.points.size ()<<" key points.\n"; // ---------------------------------------------- // -----Show keypoints in 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); //在3Dviewer显示提取的特征信息 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"); //在关键点提取NARF描述子 std::vector<int> keypoint_indices2; keypoint_indices2.resize (keypoint_indices.points.size ()); for (unsigned int i=0; i<keypoint_indices.size (); ++i) // This step is necessary to get the right vector type keypoint_indices2[i]=keypoint_indices.points[i]; ///建立NARF关键点的索引向量,此矢量作为NARF特征计算的输入来使用 pcl::NarfDescriptor narf_descriptor (&range_image, &keypoint_indices2);//创建narf_descriptor对象。并给了此对象输入数据(特征点索引和深度像) narf_descriptor.getParameters ().support_size = support_size;//support_size确定计算描述子时考虑的区域大小 narf_descriptor.getParameters ().rotation_invariant = rotation_invariant; //设置旋转不变的NARF描述子 pcl::PointCloud<pcl::Narf36> narf_descriptors; //创建Narf36的点类型输入点云对象并进行实际计算 narf_descriptor.compute (narf_descriptors); //计算描述子 cout << "Extracted "<<narf_descriptors.size ()<<" descriptors for " //打印输出特征点的数目和提取描述子的数目 <<keypoint_indices.points.size ()<< " keypoints.\n"; //主循环函数 while (!viewer.wasStopped ()) { range_image_widget.spinOnce (); // process GUI events viewer.spinOnce (); pcl_sleep(0.01); } } 编译运行./narf_feature_extraction -m 这将自动生成一个呈矩形的点云,检测的特征点处在角落处,参数-m是必要的,因为矩形周围的区域观测不到,但是属于边界部分,因此系统无法检测到这部分区域的特征点,选项-m将看不到的区域改变到最大范围读取,从而使系统能够使用这些边界区域。 (2)特征描述算子算法基准化分析 使用FeatureEvaluationFramework类对不同的特征描述子算法进行基准测试,基准测试框架可以测试不同种类的特征描述子算法,通过选择输入点云,算法参数,下采样叶子大小,搜索阀值等独立变量来进行测试。 使用FeatureCorrespondenceTest类执行一个单一的“基于特征的对应估计测试”执行以下的操作 1.FeatureCorrespondenceTest类取两个输入点云(源与目标) 它将指定算法和参数,在每个点云中计算特征描述子 2.基于n_D特征空间中的最近邻元素搜索,源点云中的每个特征将和目标点云中对应的特征相对照 3 。对于每一个点,系统将把估计的目标点的三维位置和之前已知的实际位置相比 4 。如果这两个点很接近(取决与决定的阀值)那么对应就成功,否则失败 5 计算并保存成功和失败的总数,以便进一步分析 微信公众号号可扫描二维码一起共同学习交流
点特征直方图(PFH)描述子 正如点特征表示法所示,表面法线和曲率估计是某个点周围的几何特征基本表示法。虽然计算非常快速容易,但是无法获得太多信息,因为它们只使用很少的几个参数值来近似表示一个点的k邻域的几何特征。然而大部分场景中包含许多特征点,这些特征点有相同的或者非常相近的特征值,因此采用点特征表示法,其直接结果就减少了全局的特征信息。那么三维特征描述子中一位成员:点特征直方图(Point Feature Histograms),我们简称为PFH,从PCL实现的角度讨论其实施细节。PFH特征不仅与坐标轴三维数据有关,同时还与表面法线有关。 PFH计算方式通过参数化查询点与邻域点之间的空间差异,并形成一个多维直方图对点的k邻域几何属性进行描述。直方图所在的高维超空间为特征表示提供了一个可度量的信息空间,对点云对应曲面的6维姿态来说它具有不变性,并且在不同的采样密度或邻域的噪音等级下具有鲁棒性。点特征直方图(PFH)表示法是基于点与其k邻域之间的关系以及它们的估计法线,简言之,它考虑估计法线方向之间所有的相互作用,试图捕获最好的样本表面变化情况,以描述样本的几何特征。因此,合成特征超空间取决于每个点的表面法线估计的质量。如图所示,表示的是一个查询点(Pq) 的PFH计算的影响区域,Pq 用红色标注并放在圆球的中间位置,半径为r, (Pq)的所有k邻元素(即与点Pq的距离小于半径r的所有点)全部互相连接在一个网络中。最终的PFH描述子通过计算邻域内所有两点之间关系而得到的直方图,因此存在一个O(k) 的计算复杂性。 查询点 的PFH计算的影响区域 为了计算两点Pi和Pj及与它们对应的法线Ni和Nj之间的相对偏差,在其中的一个点上定义一个固定的局部坐标系,如图2所示。 使用上图中uvw坐标系,法线 和 之间的偏差可以用一组角度来表示,如下所示: d是两点Ps和Pt之间的欧氏距离, 。计算k邻域内的每一对点的四组值,这样就把两点和它们法线相关的12个参数(xyz坐标值和法线信息)减少到4个 为查询点创建最终的PFH表示,所有的四元组将会以某种统计的方式放进直方图中,这个过程首先把每个特征值范围划分为b个子区间,并统计落在每个子区间的点数目,因为四分之三的特征在上述中为法线之间的角度计量,在三角化圆上可以将它们的参数值非常容易地归一到相同的区间内。一个统计的例子是:把每个特征区间划分成等分的相同数目,为此在一个完全关联的空间内创建有 个区间的直方图。在这个空间中,一个直方图中某一区间统计个数的增一对应一个点的四个特征值。如图3所示,就是点云中不同点的点特征直方图表示法的一个例子,在某些情况下,第四个特征量d在通常由机器人捕获的2.5维数据集中的并不重要,因为临近点间的距离从视点开始是递增的,而并非不变的,在扫描中局部点密度影响特征时,实践证明省略d是有益的。 更详细的解释:pointclouds.org/documentation/tutorials/how_features_work.php#rusudissertation 估计PFH特征 点特征直方图(PFH)在PCL中的实现是pcl_features模块的一部分。默认PFH的实现使用5个区间分类(例如:四个特征值中的每个都使用5个区间来统计), 以下代码段将对输入数据集中的所有点估计其对应的PFH特征。 #include <pcl/point_types.h> //点类型头文件 #include <pcl/features/pfh.h> //pfh特征估计类头文件 ...//其他相关操作 pcl::PointCloud<pcl::PointXYZ>::Ptrcloud(new pcl::PointCloud<pcl::PointXYZ>); pcl::PointCloud<pcl::Normal>::Ptrnormals(new pcl::PointCloud<pcl::Normal>()); ...//打开点云文件估计法线等 //创建PFH估计对象pfh,并将输入点云数据集cloud和法线normals传递给它 pcl::PFHEstimation<pcl::PointXYZ,pcl::Normal,pcl::PFHSignature125> pfh; pfh.setInputCloud(cloud); pfh.setInputNormals(normals); //如果点云是类型为PointNormal,则执行pfh.setInputNormals (cloud); //创建一个空的kd树表示法,并把它传递给PFH估计对象。 //基于已给的输入数据集,建立kdtree pcl::KdTreeFLANN<pcl::PointXYZ>::Ptrtree(new pcl::KdTreeFLANN<pcl::PointXYZ>()); pfh.setSearchMethod(tree); //输出数据集 pcl::PointCloud<pcl::PFHSignature125>::Ptrpfhs(new pcl::PointCloud<pcl::PFHSignature125>()); //使用半径在5厘米范围内的所有邻元素。 //注意:此处使用的半径必须要大于估计表面法线时使用的半径!!! pfh.setRadiusSearch(0.05); //计算pfh特征值 pfh.compute(*pfhs); // pfhs->points.size ()应该与input cloud->points.size ()有相同的大小,即每个点都有一个pfh特征向量 PFHEstimation类的实际计算程序内部只执行以下: 对点云P中的每个点p 1.得到p点的最近邻元素 2.对于邻域内的每对点,计算其三个角度特征参数值 3.将所有结果统计到一个输出直方图中 微信公众号号可扫描二维码一起共同学习交流
3D点云特征描述与提取是点云信息处理中最基础也是最关键的一部分,点云的识别。分割,重采样,配准曲面重建等处理大部分算法,都严重依赖特征描述与提取的结果。从尺度上来分,一般分为局部特征的描述和全局特征的描述,例如局部的法线等几何形状特征的描述,全局的拓朴特征的描述,都属于3D点云特征描述与提取的范畴, 特征描述与提取相关的概念与算法 1.3D形状内容描述子(3D shape contexts) 利用描述子建立曲面间的对应点在3D物体识别领域有广发的应用,采用一个向量描述曲面上指定点及邻域的形状特征,通过匹配向量的值来建立不同曲面点的对应关系,此相邻则则称为指定点的俄描述子,经典描述子的3D形状内容描述子结构简单,辨别力强,且对噪声不敏感, 2,旋转图像(spin iamge) 旋转图像最早是由johnson提出的特征描述子,主要用于3D场景中的曲面匹配和模型识别, 3,涉及的算法相关的资料 3D形状内容描述子 https://en.wikipedia.org/wiki/Shape_context www.eecs.berkeley.edu/Research/Projects/CS/vision/shape/belongie-pami02.pdf 还有很多中描述子的理论与算法的研究,不再一一列出来 关于理论的部分有待研究,但是暂时我只是学习会用。 关于PCL中特征描述与提取模块和相关类的介绍 Classes class pcl::ShapeContext3DEstimation< PointInT, PointNT, PointOutT > 实现3D形状内容描述子算法 class pcl::BOARDLocalReferenceFrameEstimation< PointInT, PointNT, PointOutT > 实现局部坐标系估计的方法 特别是处理点云边缘或有孔洞有特殊的处理方式 class pcl::BoundaryEstimation< PointInT, PointNT, PointOutT > 实现估计一组点集是否处于指定点的投影区域的边缘位置 class pcl::CRHEstimation< PointInT, PointNT, PointOutT > 实现摄像头旋转直方图描述子,利用概算法主要进行刚体对象的位姿估计 class pcl::CVFHEstimation< PointInT, PointNT, PointOutT > 实现聚类视点直方图CVFH描述子的计算 主要是针对解决有残缺的点云识别问题 class pcl::ESFEstimation< PointInT, PointOutT > 实现ESF描述子,主要用于实时对三维场景中的点云模型进行分类而提出的 class pcl::Feature< PointInT, PointOutT > 是所有特征相关模块中其他类的基类 class pcl::FeatureWithLocalReferenceFrames< PointInT, PointRFT > 实现FPFH描述子算法主要针对点云配准过程中对应点而提出的 (关于他的类还有很多可以直接去网站自己查看) PCL中描述三维特征相关基础 理论基础 在原始表示形式下,点的定义是用笛卡尔坐标系坐标 x, y, z 相对于一个给定的原点来简单表示的三维映射系统的概念,假定坐标系的原点不随着时间而改变,这里有两个点p1和p2分别在时间t1和t2捕获,有着相同的坐标,对这两个点作比较其实是属于不适定问题(ill—posed problem),因为虽然相对于一些距离测度它们是相等的,但是它们取样于完全不同的表面,因此当把它们和临近的其他环境中点放在一起时,它们表达着完全不同的信息,这是因为在t1和t2之间局部环境有可能发生改变。一些获取设备也许能够提供取样点的额外数据,例如强度或表面反射率等,甚至颜色,然而那并不能完全解决问题,单从两个点之间来 对比仍然是不适定问题。由于各种不同需求需要进行对比以便能够区分曲面空间的分布情况,应用软件要求更好的特征度量方式,因此作为一个单一实体的三维点概念和笛卡尔坐标系被淘汰了,出现了一个新的概念取而代之:局部描述子(locl descriptor)。文献中对这一概念的描述有许多种不同的命名,如:形状描述子(shape descriptors)或几何特征(geometric features),文本中剩余部分都统称为点特征表示。通过包括周围的领域,特征描述子能够表征采样表面的几何 性质,它有助于解决不适定的对比问题,理想情况下相同或相似表面上的点的特征值将非常相似(相对特定度量准则),而不同表面上的点的特征描述子将有明显差异。下面几个条件,通过能否获得相同的局部表面特征值,可以判定点特征表示方式的优劣: (1) 刚体变换-----即三维旋转和三维平移变化 不会影响特征向量F估计,即特征向量具有平移选转不变性。 (2) 改变采样密度-----原则上,一个局部表面小块的采样密度无论是大还是小,都应该有相同的特征向量值,即特征向量具有抗密度干扰性。 (3) 噪声---数据中有轻微噪声的情况下,点特征表示在它的特征向量中必须保持相同或者极其相似的值,即特征向量对点云噪声具有稳定性。 通常,PCL中特征向量利用快速kd-tree查询 ,使用近似法来计算查询点的最近邻元素,通常有两种查询类型:K邻域查询,半径搜索两中方法 法线估计实例 一旦确定邻域以后,查询点的邻域点可以用来估计一个局部特征描述子,它用查询点周围领域点描述采样面的几何特征,描述几何表面图形的一个重要属性,首先是推断它在坐标系中的方位,也就是估计他的法线,表面法线是表面的一个重要的属性,在许多领域都有重要的应用,如果用光源来生成符合视觉效果的渲染等, 代码解析:normal_estimation.cpp (实现对输入点云数据集中的点估计一组表面法线)执行的操作是:对应点云P中每一个点p得到p点最近邻元素,计算p点的表面的法线N,检查N的方向是否指向视点如果不是则翻转。 视点默认坐标是(0,0,0)可使用setViewPoint(float vpx,float vpy,float vpz)来更换 #include <pcl/io/io.h> #include <pcl/io/pcd_io.h> #include <pcl/features/integral_image_normal.h> //法线估计类头文件 #include <pcl/visualization/cloud_viewer.h> #include <pcl/point_types.h> #include <pcl/features/normal_3d.h> int main () { //打开点云代码 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>); pcl::io::loadPCDFile ("table_scene_lms400.pcd", *cloud); //创建法线估计估计向量 pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> ne; ne.setInputCloud (cloud); //创建一个空的KdTree对象,并把它传递给法线估计向量 //基于给出的输入数据集,KdTree将被建立 pcl::search::KdTree<pcl::PointXYZ>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZ> ()); ne.setSearchMethod (tree); //存储输出数据 pcl::PointCloud<pcl::Normal>::Ptr cloud_normals (new pcl::PointCloud<pcl::Normal>); //使用半径在查询点周围3厘米范围内的所有临近元素 ne.setRadiusSearch (0.03); //计算特征值 ne.compute (*cloud_normals); // cloud_normals->points.size ()应该与input cloud_downsampled->points.size ()有相同的尺寸 //可视化 pcl::visualization::PCLVisualizer viewer("PCL Viewer"); viewer.setBackgroundColor (0.0, 0.0, 0.0); viewer.addPointCloudNormals<pcl::PointXYZ,pcl::Normal>(cloud, cloud_normals); while (!viewer.wasStopped ()) { viewer.spinOnce (); } return 0; } 运行结果并执行: (2)估计一个点云的表面法线 表面法线是几何体表面一个十分重要的属性,例如:在进行光照渲染时产生符合可视习惯的效果时需要表面法线的信息才能正常进行,对于一个已经已经知道的几何体表面,根据垂直于点表面的的矢量,因此推推处表面某一点的法线方向比较容易,然而由于我们获取的点云的数据集在真实的物体的表面表现为一组定点的样本,这样就会有两种方法解决: 1 . 使用曲面重建技术,从获取的点云数据中得到采样点对应的曲面,然后从曲面模型中计算出表面法线 2. 直接从点云数据中近似推断表面法线 在确定表面一点法线的问题近似于估计表面的一个相切面法线的问题,因此转换过来就是求一个最小二乘法平面拟合的问题 (3)使用积分图进行法线估计 使用积分图计算一个有序的点云的法线,注意此方法只适用有序点云 代码解析normal_estimation_using_integral_images.cpp #include <pcl/io/io.h> #include <pcl/io/pcd_io.h> #include <pcl/features/integral_image_normal.h> #include <pcl/visualization/cloud_viewer.h> int main () { //打开点云 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>); pcl::io::loadPCDFile ("table_scene_mug_stereo_textured.pcd", *cloud); //创建法线估计向量 pcl::PointCloud<pcl::Normal>::Ptr normals (new pcl::PointCloud<pcl::Normal>); pcl::IntegralImageNormalEstimation<pcl::PointXYZ, pcl::Normal> ne; /**************************************************************************************** 三种法线估计方法 COVARIANCE_MATRIX 模式从具体某个点的局部邻域的协方差矩阵创建9个积分,来计算这个点的法线 AVERAGE_3D_GRADIENT 模式创建6个积分图来计算水平方向和垂直方向的平滑后的三维梯度并使用两个梯度间的向量 积计算法线 AVERAGE_DEPTH——CHANGE 模式只创建了一个单一的积分图,从而平局深度变化计算法线 ********************************************************************************************/ ne.setNormalEstimationMethod (ne.AVERAGE_3D_GRADIENT); //设置法线估计的方式AVERAGE_3D_GRADIENT ne.setMaxDepthChangeFactor(0.02f); //设置深度变化系数 ne.setNormalSmoothingSize(10.0f); //设置法线优化时考虑的邻域的大小 ne.setInputCloud(cloud); //输入的点云 ne.compute(*normals); //计算法线 //可视化 pcl::visualization::PCLVisualizer viewer("PCL Viewer"); //视口的名称 viewer.setBackgroundColor (0.0, 0.0, 0.5); //背景颜色的设置 viewer.addPointCloudNormals<pcl::PointXYZ,pcl::Normal>(cloud, normals); //将法线加入到点云中 while (!viewer.wasStopped ()) { viewer.spinOnce (); } return 0; } 运行结果为: 微信公众号号可扫描二维码一起共同学习交流 未完待续*********************8
(1)点云到深度图与可视化的实现 区分点云与深度图本质的区别 1.深度图像也叫距离影像,是指将从图像采集器到场景中各点的距离(深度)值作为像素值的图像。获取方法有:激光雷达深度成像法、计算机立体视觉成像、坐标测量机法、莫尔条纹法、结构光法。 2.点云:当一束激光照射到物体表面时,所反射的激光会携带方位、距离等信息。若将激光束按照某种轨迹进行扫描,便会边扫描边记录到反射的激光点信息,由 于扫描极为精细,则能够得到大量的激光点,因而就可形成激光点云。点云格式有*.las ;*.pcd; *.txt等。 深度图像经过坐标转换可以计算为点云数据;有规则及必要信息的点云数据可以反算为深度图像 rangeimage是来自传感器一个特定角度拍摄的一个三维场景获取的有规则的有焦距等基本信息的深度图。 深度图像的像素值代表从传感器到物体的距离或者深度值。 RangeImage类的继承于PointCloud主要的功能实现一个特定的视点得到的一个三维场景的深度图像,继承关系为 所以我们知道有规则及必要信息就可以反算为深度图像。那么我们就可以直接创建一个有序的规则的点云,比如一张平面,或者我们直接使用Kinect获取的点云来可视化深度的图,所以首先分析程序中是如果实现的点云到深度图的转变的,(程序的注释是我自己的理解,注释的比较详细) #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> #include <pcl/visualization/range_image_visualizer.h> //深度图可视化的头文件 #include <pcl/visualization/pcl_visualizer.h> //PCL可视化的头文件 #include <pcl/console/parse.h> typedef pcl::PointXYZ PointType; //参数 float angular_resolution_x = 0.5f,//angular_resolution为模拟的深度传感器的角度分辨率,即深度图像中一个像素对应的角度大小 angular_resolution_y = angular_resolution_x; pcl::RangeImage::CoordinateFrame coordinate_frame = pcl::RangeImage::CAMERA_FRAME;//深度图像遵循坐标系统 bool live_update = false; //命令帮助提示 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文件 如果没有输入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 ()); //申明传感器的位置是一个4*4的仿射变换 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深度传感器X方向的角度分辨率 angular_resolution_y深度传感器Y方向的角度分辨率 pcl::deg2rad (360.0f)深度传感器的水平最大采样角度 pcl::deg2rad (180.0f)垂直最大采样角度 scene_sensor_pose设置的模拟传感器的位姿是一个仿射变换矩阵,默认为4*4的单位矩阵变换 coordinate_frame定义按照那种坐标系统的习惯 默认为CAMERA_FRAME noise_level 获取深度图像深度时,邻近点对查询点距离值的影响水平 min_range 设置最小的获取距离,小于最小的获取距离的位置为传感器的盲区 border_size 设置获取深度图像边缘的宽度 默认为0 */ range_image.createFromPointCloud (point_cloud, angular_resolution_x, angular_resolution_y,pcl::deg2rad (360.0f), pcl::deg2rad (180.0f),scene_sensor_pose, coordinate_frame, noise_level, min_range, border_size); //可视化点云 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 (); //range_image.getTransformationToWorldSystem ()的作用是获取从深度图像坐标系统(应该就是传感器的坐标)转换为世界坐标系统的转换矩阵 setViewerPose(viewer, range_image.getTransformationToWorldSystem ()); //设置视点的位置 //可视化深度图 pcl::visualization::RangeImageVisualizer range_image_widget ("Range image"); range_image_widget.showRangeImage (range_image); while (!viewer.wasStopped ()) { range_image_widget.spinOnce (); viewer.spinOnce (); pcl_sleep (0.01); if (live_update) { //如果选择的是——l的参数说明就是要根据自己选择的视点来创建深度图。 // live update - update the range image according to the selected view in the 3D viewer. scene_sensor_pose = viewer.getViewerPose(); range_image.createFromPointCloud (point_cloud, angular_resolution_x, angular_resolution_y, 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); } } } 在代码利解释的十分详细,编译查看结果 没有输入PCD点云文件的结果 输入点云的原始图 输入的结果及其深度图 (2)如何从深度图像中提取边界 从深度图像中提取边界(从前景跨越到背景的位置定义为边界),对于物体边界:这是物体的最外层和阴影边界的可见点集,阴影边界:毗邻与遮挡的背景上的点集,Veil点集,在被遮挡物边界和阴影边界之间的内插点,它们是有激光雷达获取的3D距离数据中的典型数据类型,这三类数据及深度图像的边界如图: 代码解析:从磁盘中读取点云,创建深度图像并使其可视化,提取边界信息很重要的一点就是区分深度图像中当前视点不可见点几何和应该可见但处于传感器获取距离范围之外的点集 ,后者可以标记为典型边界,然而当前视点不可见点则不能成为边界,因此,如果后者的测量值存在,则提供那些超出传感器距离获取范围之外的数据对于边界的提取是非常重要的, 新建文件range_image_border_extraction.cpp: #include <iostream> #include <boost/thread/thread.hpp> #include <pcl/range_image/range_image.h> #include <pcl/io/pcd_io.h> #include <pcl/visualization/range_image_visualizer.h> #include <pcl/visualization/pcl_visualizer.h> #include <pcl/features/range_image_border_extractor.h> #include <pcl/console/parse.h> typedef pcl::PointXYZ PointType; // -------------------- // -----Parameters----- // -------------------- float angular_resolution = 0.5f; pcl::RangeImage::CoordinateFrame coordinate_frame = pcl::RangeImage::CAMERA_FRAME; bool setUnseenToMaxRange = false; // -------------- // -----Help----- // -------------- void printUsage (const char* progName) { std::cout << "\n\nUsage: "<<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 to max range\n" << "-h this help\n" << "\n\n"; } // -------------- // -----Main----- // -------------- int main (int argc, char** argv) { // -------------------------------------- // -----Parse Command Line Arguments----- // -------------------------------------- if (pcl::console::find_argument (argc, argv, "-h") >= 0) { 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, "-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----- // ------------------------------------------------------------------ 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 ()); //传感器的位置 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) //打开文件 { 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_); //仿射变换矩阵 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 { 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; //各种参数的设置 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, 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----- // -------------------------------------------- pcl::visualization::PCLVisualizer viewer ("3D Viewer"); //创建视口 viewer.setBackgroundColor (1, 1, 1); //设置背景颜色 viewer.addCoordinateSystem (1.0f); //设置坐标系 pcl::visualization::PointCloudColorHandlerCustom<PointType> point_cloud_color_handler (point_cloud_ptr, 0, 0, 0); viewer.addPointCloud (point_cloud_ptr, point_cloud_color_handler, "original point cloud"); //添加点云 //PointCloudColorHandlerCustom<pcl::PointWithRange> range_image_color_handler (range_image_ptr, 150, 150, 150); //viewer.addPointCloud (range_image_ptr, range_image_color_handler, "range image"); //viewer.setPointCloudRenderingProperties (PCL_VISUALIZER_POINT_SIZE, 2, "range image"); // ------------------------- // -----Extract borders提取边界的部分----- // ------------------------- pcl::RangeImageBorderExtractor border_extractor (&range_image); pcl::PointCloud<pcl::BorderDescription> border_descriptions; border_extractor.compute (border_descriptions); //提取边界计算描述子 // ------------------------------------------------------- // -----Show points in 3D viewer在3D 视口中显示点云----- // ---------------------------------------------------- pcl::PointCloud<pcl::PointWithRange>::Ptr border_points_ptr(new pcl::PointCloud<pcl::PointWithRange>), //物体边界 veil_points_ptr(new pcl::PointCloud<pcl::PointWithRange>), //veil边界 shadow_points_ptr(new pcl::PointCloud<pcl::PointWithRange>); //阴影边界 pcl::PointCloud<pcl::PointWithRange>& border_points = *border_points_ptr, & veil_points = * veil_points_ptr, & shadow_points = *shadow_points_ptr; for (int y=0; y< (int)range_image.height; ++y) { for (int x=0; x< (int)range_image.width; ++x) { if (border_descriptions.points[y*range_image.width + x].traits[pcl::BORDER_TRAIT__OBSTACLE_BORDER]) border_points.points.push_back (range_image.points[y*range_image.width + x]); if (border_descriptions.points[y*range_image.width + x].traits[pcl::BORDER_TRAIT__VEIL_POINT]) veil_points.points.push_back (range_image.points[y*range_image.width + x]); if (border_descriptions.points[y*range_image.width + x].traits[pcl::BORDER_TRAIT__SHADOW_BORDER]) shadow_points.points.push_back (range_image.points[y*range_image.width + x]); } } pcl::visualization::PointCloudColorHandlerCustom<pcl::PointWithRange> border_points_color_handler (border_points_ptr, 0, 255, 0); viewer.addPointCloud<pcl::PointWithRange> (border_points_ptr, border_points_color_handler, "border points"); viewer.setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 7, "border points"); pcl::visualization::PointCloudColorHandlerCustom<pcl::PointWithRange> veil_points_color_handler (veil_points_ptr, 255, 0, 0); viewer.addPointCloud<pcl::PointWithRange> (veil_points_ptr, veil_points_color_handler, "veil points"); viewer.setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 7, "veil points"); pcl::visualization::PointCloudColorHandlerCustom<pcl::PointWithRange> shadow_points_color_handler (shadow_points_ptr, 0, 255, 255); viewer.addPointCloud<pcl::PointWithRange> (shadow_points_ptr, shadow_points_color_handler, "shadow points"); viewer.setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 7, "shadow points"); //------------------------------------- // -----Show points on range image----- // ------------------------------------ pcl::visualization::RangeImageVisualizer* range_image_borders_widget = NULL; range_image_borders_widget = pcl::visualization::RangeImageVisualizer::getRangeImageBordersWidget (range_image, -std::numeric_limits<float>::infinity (), std::numeric_limits<float>::infinity (), false, border_descriptions, "Range image with borders"); // ------------------------------------- //-------------------- // -----Main loop----- //-------------------- while (!viewer.wasStopped ()) { range_image_borders_widget->spinOnce (); viewer.spinOnce (); pcl_sleep(0.01); } } 编译的运行的结果./range_image_border_extraction -m 这将一个自定生成的,矩形状浮点型点云,有显示结果可以看出检测出的边界用绿色较大的点表示,其他的点用默认的普通的大小点来表示. 因为有人问我为什么使用其他的PCD文件来运行这个程序的时候会提示 Far ranges file far_ranges_filename does not exists 这是因为在深度传感器得带深度图像并可视化图像的时候,我们都知道传感器的测量距离受硬件的限制,所以在这里就是要定义传感器看不到的距离,所以当我们自己使用kinect获取深度图像运行这个程序的时候直接使用命令 ./range_image_border_extraction -m out0.pcd 使用-m的原因是要设置传感器看不见的位置 Setting unseen values in range image to maximum range readings 那么对于我们使用自己使用的PCD文件并且设置 -m后的一个结果比如 忘了宣传我的微信公众号了,所以微信里会有更多的人分享,同时也希望积极关注分享你的学习 未完待续*****************8888888
在计算机视觉领域广泛的使用各种不同的采样一致性参数估计算法用于排除错误的样本,样本不同对应的应用不同,例如剔除错误的配准点对,分割出处在模型上的点集,PCL中以随机采样一致性算法(RANSAC)为核心,同时实现了五种类似与随机采样一致形算法的随机参数估计算法,例如随机采样一致性算法(RANSAC)最大似然一致性算法(MLESAC),最小中值方差一致性算法(LMEDS)等,所有估计参数算法都符合一致性原则。在PCL中设计的采样一致性算法的应用主要就是对点云进行分割,根据设定的不同的几个模型,估计对应的几何参数模型的参数,在一定容许的范围内分割出在模型上的点云。 (1)RANSAC随机采样一致性算法的介绍 RANSAC是“RANdom SAmple Consensus(随机抽样一致)”的缩写。它可以从一组包含“局外点”的观测数据集中,通过迭代方式估计数学模型的参数。它是一种不确定的算法——它有一定的概率得出一个合理的结果;为了提高概率必须提高迭代次数。 数 据分两种:有效数据(inliers)和无效数据(outliers)。偏差不大的数据称为有效数据,偏差大的数据是无效数据。如果有效数据占大多数,无 效数据只是少量时,我们可以通过最小二乘法或类似的方法来确定模型的参数和误差;如果无效数据很多(比如超过了50%的数据都是无效数据),最小二乘法就 失效了,我们需要新的算法 一个简单的例子是从一组观测数据中找出合适的2维直线。假设观测数据中包含局内点和局外点,其中局内点近似的被直线所通过,而局外点远离于直线。简单的最 小二乘法不能找到适应于局内点的直线,原因是最小二乘法尽量去适应包括局外点在内的所有点。相反,RANSAC能得出一个仅仅用局内点计算出模型,并且概 率还足够高。但是,RANSAC并不能保证结果一定正确,为了保证算法有足够高的合理概率,我们必须小心的选择算法的参数。 左图:包含很多局外点的数据集 右图:RANSAC找到的直线(局外点并不影响结果) 概述 RANSAC算法的输入是一组观测数据,一个可以解释或者适应于观测数据的参数化模型,一些可信的参数。 RANSAC通过反复选择数据中的一组随机子集来达成目标。被选取的子集被假设为局内点,并用下述方法进行验证: 1.有一个模型适应于假设的局内点,即所有的未知参数都能从假设的局内点计算得出。 2.用1中得到的模型去测试所有的其它数据,如果某个点适用于估计的模型,认为它也是局内点。 3.如果有足够多的点被归类为假设的局内点,那么估计的模型就足够合理。 4.然后,用所有假设的局内点去重新估计模型,因为它仅仅被初始的假设局内点估计过。 5.最后,通过估计局内点与模型的错误率来评估模型。 算法 伪码形式的算法如下所示: 输入: data —— 一组观测数据 model —— 适应于数据的模型 n —— 适用于模型的最少数据个数 k —— 算法的迭代次数 t —— 用于决定数据是否适应于模型的阀值 d —— 判定模型是否适用于数据集的数据数目 输出: best_model —— 跟数据最匹配的模型参数(如果没有找到好的模型,返回null) best_consensus_set —— 估计出模型的数据点 best_error —— 跟数据相关的估计出的模型错误 iterations = 0 best_model = null best_consensus_set = null best_error = 无穷大 while ( iterations < k ) maybe_inliers = 从数据集中随机选择n个点 maybe_model = 适合于maybe_inliers的模型参数 consensus_set = maybe_inliers for ( 每个数据集中不属于maybe_inliers的点 ) if ( 如果点适合于maybe_model,且错误小于t ) 将点添加到consensus_set if ( consensus_set中的元素数目大于d ) 已经找到了好的模型,现在测试该模型到底有多好 better_model = 适合于consensus_set中所有点的模型参数 this_error = better_model究竟如何适合这些点的度量 if ( this_error < best_error ) 我们发现了比以前好的模型,保存该模型直到更好的模型出现 best_model = better_model best_consensus_set = consensus_set best_error = this_error 增加迭代次数 返回 best_model, best_consensus_set, best_error (2)最小中值法(LMedS) LMedS的做法很简单,就是从样本中随机抽出N个样本子集,使用最大似然(通常是最小二乘)对每个子集计算模型参数和该模型的偏差,记录该模型参 数及子集中所有样本中偏差居中的那个样本的偏差(即Med偏差),最后选取N个样本子集中Med偏差最小的所对应的模型参数作为我们要估计的模型参数。 在PCL中sample_consensus模块支持的几何模型 SACMODEL_PLANE - used to determine plane models. The four coefficients of the plane are its Hessian Normal form: [normal_x normal_y normal_z d] SACMODEL_LINE - used to determine line models. The six coefficients of the line are given by a point on the line and the direction of the line as: [point_on_line.x point_on_line.y point_on_line.z line_direction.x line_direction.y line_direction.z] SACMODEL_CIRCLE2D - used to determine 2D circles in a plane. The circle's three coefficients are given by its center and radius as: [center.x center.y radius] SACMODEL_CIRCLE3D - used to determine 3D circles in a plane. The circle's seven coefficients are given by its center, radius and normal as: [center.x, center.y, center.z, radius, normal.x, normal.y, normal.z] SACMODEL_SPHERE - used to determine sphere models. The four coefficients of the sphere are given by its 3D center and radius as: [center.x center.y center.z radius] SACMODEL_CYLINDER - used to determine cylinder models. The seven coefficients of the cylinder are given by a point on its axis, the axis direction, and a radius, as: [point_on_axis.x point_on_axis.y point_on_axis.z axis_direction.x axis_direction.y axis_direction.z radius] SACMODEL_CONE - used to determine cone models. The seven coefficients of the cone are given by a point of its apex, the axis direction and the opening angle, as: [apex.x, apex.y, apex.z, axis_direction.x, axis_direction.y, axis_direction.z, opening_angle] SACMODEL_TORUS - not implemented yet SACMODEL_PARALLEL_LINE - a model for determining a line parallel with a given axis, within a maximum specified angular deviation. The line coefficients are similar to SACMODEL_LINE . SACMODEL_PERPENDICULAR_PLANE - a model for determining a plane perpendicular to an user-specified axis, within a maximum specified angular deviation. The plane coefficients are similar to SACMODEL_PLANE . SACMODEL_PARALLEL_LINES - not implemented yet SACMODEL_NORMAL_PLANE - a model for determining plane models using an additional constraint: the surface normals at each inlier point has to be parallel to the surface normal of the output plane, within a maximum specified angular deviation. The plane coefficients are similar to SACMODEL_PLANE . SACMODEL_PARALLEL_PLANE - a model for determining a plane parallel to an user-specified axis, within a maximum specified angular deviation. SACMODEL_PLANE . SACMODEL_NORMAL_PARALLEL_PLANE defines a model for 3D plane segmentation using additional surface normal constraints. The plane must lie parallel to a user-specified axis. SACMODEL_NORMAL_PARALLEL_PLANE therefore is equivalent to SACMODEL_NORMAL_PLANE + SACMODEL_PARALLEL_PLANE. The plane coefficients are similar to SACMODEL_PLANE . (2)PCL中Sample_consensus模块及类的介绍 PCL中Sample_consensus库实现了随机采样一致性及其泛化估计算法,例如平面,柱面,等各种常见的几何模型,用不同的估计算法和不同的几何模型自由的结合估算点云中隐含的具体几何模型的系数,实现对点云中所处的几何模型的分割,线,平面,柱面 ,和球面都可以在PCL 库中实现,平面模型经常被用到常见的室内平面的分割提取中, 比如墙,地板,桌面,其他模型常应用到根据几何结构检测识别和分割物体中,一共可以分为两类:一类是针对采样一致性及其泛化函数的实现,一类是几个不同模型的具体实现,例如:平面,直线,圆球等 pcl::SampleConsensusModel< PointT >是随机采样一致性估计算法中不同模型实现的基类,所有的采样一致性估计模型都继承与此类,定义了采样一致性模型的相关的一般接口,具体实现由子类完成,其继承关系: 类成员的介绍 Public Member Functions SampleConsensusModel (const PointCloudConstPtr &cloud, const std::vector< int > &indices, bool random=false) SampleConsensusModel类的构造函数,cloud为输入点云对象的指针,indices为算法使用点云索引向量,如果设置random=true则用当前时间初始化随机化函数的种子否则使用12345作为种子 virtual void getSamples (int &iterations, std::vector< int > &samples) 获取一组随机采样点数据以点云中点的索引方式存储到samples,iterations为迭代次数 virtual bool computeModelCoefficients (const std::vector< int > &samples, Eigen::VectorXf &model_coefficients)=0 纯虚函数检查给定的点云索引样本samples能否一个有效的模型, virtual void optimizeModelCoefficients (const std::vector< int > &inliers, const Eigen::VectorXf &model_coefficients, Eigen::VectorXf &optimized_coefficients)=0 优化初始估计的模型参数,inliers设定的局内点,model_coefficients初始估计的模型的系数,optimized_coefficients优化后的模型系数 virtual void getDistancesToModel (const Eigen::VectorXf &model_coefficients, std::vector< double > &distances)=0 计算点云中所有点到给定模型中model_coefficients的距离,存储到distances virtual void selectWithinDistance (const Eigen::VectorXf &model_coefficients, const double threshold, std::vector< int > &inliers)=0 从点云中选择所有到给定模型model_coefficients的距离小于给定的阀值 threshold 的点为局内点,inliers存储最终输出的局内点 virtual int countWithinDistance (const Eigen::VectorXf &model_coefficients, const double threshold)=0 统计点云到给定模型model_coefficients距离小于阀值的点的个数 virtual void projectPoints (const std::vector< int > &inliers, const Eigen::VectorXf &model_coefficients, PointCloud &projected_points, bool copy_data_fields=true)=0 将局内点inliner投影到model_coefficients上创建一组新的点云projected_points, (太多了 太耽误时间了)************************** (2)pcl::SampleConsensus< T > 是采样一致性算法的基类 Public Member Functions SampleConsensus (const SampleConsensusModelPtr &model, double threshold, bool random=false) 其中model设置随机采样性算法使用的模型,threshold 阀值 void setSampleConsensusModel (const SampleConsensusModelPtr &model) Set the Sample Consensus model to use. void setDistanceThreshold (double threshold) Set the distance to model threshold. double getDistanceThreshold () Get the distance to model threshold, as set by the user. void setMaxIterations (int max_iterations) Set the maximum number of iterations. int getMaxIterations () Get the maximum number of iterations, as set by the user. void setProbability (double probability) Set the desired probability of choosing at least one sample free from outliers. double getProbability () Obtain the probability of choosing at least one sample free from outliers, as set by the user. virtual bool computeModel (int debug_verbosity_level=0)=0 Compute the actual model. virtual bool refineModel (const double sigma=3.0, const unsigned int max_iterations=1000) Refine the model found. void getRandomSamples (const boost::shared_ptr< std::vector< int > > &indices, size_t nr_samples, std::set< int > &indices_subset) Get a set of randomly selected indices. void getModel (std::vector< int > &model) (3)pcl::RandomizedMEstimatorSampleConsensus< PointT > 实现了RANSAC算法,RANSAC算法适用与处理数据点中局内点比例比较大的情况,科快速的进行局外点的剔除。 class pcl::SampleConsensusModelCircle2D< PointT >实现采样一致性 计算二位平面圆周模型 class pcl::SampleConsensusModelCone< PointT, PointNT > 实现采样一致性计算的三维椎体模型 太多了 class pcl::LeastMedianSquares< PointT > LeastMedianSquares represents an implementation of the LMedS (Least Median of Squares) algorithm. class pcl::MaximumLikelihoodSampleConsensus< PointT > MaximumLikelihoodSampleConsensus represents an implementation of the MLESAC (Maximum Likelihood Estimator SAmple Consensus) algorithm, as described in: "MLESAC: A new robust estimator with application to estimating image geometry", P.H.S. class pcl::MEstimatorSampleConsensus< PointT > MEstimatorSampleConsensus represents an implementation of the MSAC (M-estimator SAmple Consensus) algorithm, as described in: "MLESAC: A new robust estimator with application to estimating image geometry", P.H.S. class pcl::ProgressiveSampleConsensus< PointT > RandomSampleConsensus represents an implementation of the RANSAC (RAndom SAmple Consensus) algorithm, as described in: "Matching with PROSAC – Progressive Sample Consensus", Chum, O. class pcl::RandomSampleConsensus< PointT > RandomSampleConsensus represents an implementation of the RANSAC (RAndom SAmple Consensus) algorithm, as described in: "Random Sample Consensus: A Paradigm for Model Fitting with Applications to Image Analysis and Automated Cartography", Martin A. class pcl::RandomizedMEstimatorSampleConsensus< PointT > RandomizedMEstimatorSampleConsensus represents an implementation of the RMSAC (Randomized M-estimator SAmple Consensus) algorithm, which basically adds a Td,d test (see RandomizedRandomSampleConsensus) to an MSAC estimator (see MEstimatorSampleConsensus). class pcl::RandomizedRandomSampleConsensus< PointT > RandomizedRandomSampleConsensus represents an implementation of the RRANSAC (Randomized RAndom SAmple Consensus), as described in "Randomized RANSAC with Td,d test", O. class pcl::SampleConsensusModelNormalParallelPlane< PointT, PointNT > SampleConsensusModelNormalParallelPlane defines a model for 3D plane segmentation using additional surface normal constraints. class pcl::SampleConsensusModelNormalSphere< PointT, PointNT > SampleConsensusModelNormalSphere defines a model for 3D sphere segmentation using additional surface normal constraints. class pcl::SampleConsensusModelParallelLine< PointT > SampleConsensusModelParallelLine defines a model for 3D line segmentation using additional angular constraints. 代码实例 random_sample_consensus.cpp #include <iostream> #include <pcl/console/parse.h> #include <pcl/filters/extract_indices.h> #include <pcl/io/pcd_io.h> #include <pcl/point_types.h> #include <pcl/sample_consensus/ransac.h> #include <pcl/sample_consensus/sac_model_plane.h> #include <pcl/sample_consensus/sac_model_sphere.h> #include <pcl/visualization/pcl_visualizer.h> #include <boost/thread/thread.hpp> boost::shared_ptr<pcl::visualization::PCLVisualizer> simpleVis (pcl::PointCloud<pcl::PointXYZ>::ConstPtr cloud) { // -------------------------------------------- // -----Open 3D viewer and add point cloud----- // -------------------------------------------- boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer (new pcl::visualization::PCLVisualizer ("3D Viewer")); viewer->setBackgroundColor (0, 0, 0); viewer->addPointCloud<pcl::PointXYZ> (cloud, "sample cloud"); viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "sample cloud"); //viewer->addCoordinateSystem (1.0, "global"); viewer->initCameraParameters (); return (viewer); } /****************************************************************************************************************** 对点云进行初始化,并对其中一个点云填充点云数据作为处理前的的原始点云,其中大部分点云数据是基于设定的圆球和平面模型计算 而得到的坐标值作为局内点,有1/5的点云数据是被随机放置的组委局外点。 *****************************************************************************************************************/ int main(int argc, char** argv) { // 初始化点云对象 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>); //存储源点云 pcl::PointCloud<pcl::PointXYZ>::Ptr final (new pcl::PointCloud<pcl::PointXYZ>); //存储提取的局内点 // 填充点云数据 cloud->width = 500; //填充点云数目 cloud->height = 1; //无序点云 cloud->is_dense = false; cloud->points.resize (cloud->width * cloud->height); for (size_t i = 0; i < cloud->points.size (); ++i) { if (pcl::console::find_argument (argc, argv, "-s") >= 0 || pcl::console::find_argument (argc, argv, "-sf") >= 0) { //根据命令行参数用x^2+y^2+Z^2=1设置一部分点云数据,此时点云组成1/4个球体作为内点 cloud->points[i].x = 1024 * rand () / (RAND_MAX + 1.0); cloud->points[i].y = 1024 * rand () / (RAND_MAX + 1.0); if (i % 5 == 0) cloud->points[i].z = 1024 * rand () / (RAND_MAX + 1.0); //此处对应的点为局外点 else if(i % 2 == 0) cloud->points[i].z = sqrt( 1 - (cloud->points[i].x * cloud->points[i].x) - (cloud->points[i].y * cloud->points[i].y)); else cloud->points[i].z = - sqrt( 1 - (cloud->points[i].x * cloud->points[i].x) - (cloud->points[i].y * cloud->points[i].y)); } else { //用x+y+z=1设置一部分点云数据,此时地拿云组成的菱形平面作为内点 cloud->points[i].x = 1024 * rand () / (RAND_MAX + 1.0); cloud->points[i].y = 1024 * rand () / (RAND_MAX + 1.0); if( i % 2 == 0) cloud->points[i].z = 1024 * rand () / (RAND_MAX + 1.0); //对应的局外点 else cloud->points[i].z = -1 * (cloud->points[i].x + cloud->points[i].y); } } std::vector<int> inliers; //存储局内点集合的点的索引的向量 //创建随机采样一致性对象 pcl::SampleConsensusModelSphere<pcl::PointXYZ>::Ptr model_s(new pcl::SampleConsensusModelSphere<pcl::PointXYZ> (cloud)); //针对球模型的对象 pcl::SampleConsensusModelPlane<pcl::PointXYZ>::Ptr model_p (new pcl::SampleConsensusModelPlane<pcl::PointXYZ> (cloud)); //针对平面模型的对象 if(pcl::console::find_argument (argc, argv, "-f") >= 0) { //根据命令行参数,来随机估算对应平面模型,并存储估计的局内点 pcl::RandomSampleConsensus<pcl::PointXYZ> ransac (model_p); ransac.setDistanceThreshold (.01); //与平面距离小于0.01 的点称为局内点考虑 ransac.computeModel(); //执行随机参数估计 ransac.getInliers(inliers); //存储估计所得的局内点 } else if (pcl::console::find_argument (argc, argv, "-sf") >= 0 ) { //根据命令行参数 来随机估算对应的圆球模型,存储估计的内点 pcl::RandomSampleConsensus<pcl::PointXYZ> ransac (model_s); ransac.setDistanceThreshold (.01); ransac.computeModel(); ransac.getInliers(inliers); } // 复制估算模型的所有的局内点到final中 pcl::copyPointCloud<pcl::PointXYZ>(*cloud, inliers, *final); // 创建可视化对象并加入原始点云或者所有的局内点 boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer; if (pcl::console::find_argument (argc, argv, "-f") >= 0 || pcl::console::find_argument (argc, argv, "-sf") >= 0) viewer = simpleVis(final); else viewer = simpleVis(cloud); while (!viewer->wasStopped ()) { viewer->spinOnce (100); boost::this_thread::sleep (boost::posix_time::microseconds (100000)); } return 0; } 运行结果: 在没有任何参数的情况下,三维窗口显示创建的原始点云(含有局内点和局外点),如图所示,很明显这是一个带有噪声的菱形平面,噪声点是立方体,自己要是我们在产生点云是生成的是随机数生在(0,1)范围内。 ./random_sample_consensus ./random_sample_consensus -f ./random_sample_consensus -sf 未完待续****************************************************8
目前深度图像的获取方法有激光雷达深度成像法,计算机立体视觉成像,坐标测量机法,莫尔条纹法,结构光法等等,针对深度图像的研究重点主要集中在以下几个方面,深度图像的分割技术 ,深度图像的边缘检测技术 ,基于不同视点的多幅深度图像的配准技术,基于深度数据的三维重建技术,基于三维深度图像的三维目标识别技术,深度图像的多分辨率建模和几何压缩技术等等,在PCL 中深度图像与点云最主要的区别在于 其近邻的检索方式的不同,并且可以互相转换。 (这一章是我认为非常重要的) 模块RangeImage相关概念以及算法的介绍 深度图像(Depth Images)也被称为距离影像(Range Image),是指将从图像采集器到场景中各点的距离值作为像素值的图像,它直接反应了景物可见表面的几何形状,利用它可以很方便的解决3D目标描述中的许多问题,深度图像经过点云变换可以计算为点云数据,有规则及有必要信息的点云数据可以反算为深度图像数据 不同视角获得深度图像的过程: (1)PCL中的模块RangeImage相关类的介绍 pcl_range_image库中包含两个表达深度图像和对深度图像进行操作的类,其依赖于pcl::common模块,深度图像(距离图像)的像素值代表从传感器到物体的距离以及深度, 深度图像是物体的三维表示形式,一般通过立体照相机或者ToF照相机获取,如果具备照相机的内标定参数,就可以将深度图像转换为点云 1.class pcl::RangeImage RangeImage类继承于PointCloud,主要功能是实现一个特定视点得到一个三维场景的深度图像。其继承关系如下: 类RangeImage的成员有: Public Member Functions template<typename PointCloudType > void createFromPointCloud (const PointCloudType &point_cloud, float angular_resolution=pcl::deg2rad(0.5f), float max_angle_width=pcl::deg2rad(360.0f), float max_angle_height=pcl::deg2rad(180.0f), const Eigen::Affine3f &sensor_pose=Eigen::Affine3f::Identity(), CoordinateFrame coordinate_frame=CAMERA_FRAME, float noise_level=0.0f, float min_range=0.0f, int border_size=0) 从点云创建深度图像,point_cloud为指向创建深度图像所需要的点云的引用,angular_resolution为模拟的深度传感器的角度分辨率,即深度图像中一个像素对应的角度大小,max_angle_width为模拟的深度传感器的水平最大采样角度,max_angle_height为模拟传感器的垂直方向最大采样角度,sensor_pose设置模拟的深度传感器的位姿是一个仿射变换矩阵,默认为4*4的单位矩阵变换,coordinate_frame定义按照那种坐标系统的习惯默认为CAMERA_FRAME,noise_level获取深度图像深度时,近邻点对查询点距离值的影响水平,min_range设置最小的获取距离,小于最小获取距离的位置为传感器的盲区,border_size获得深度图像的边缘的宽度 默认为0 该函数中涉及的角度的单位都是弧度 template<typename PointCloudType > void createFromPointCloudWithKnownSize (const PointCloudType &point_cloud, float angular_resolution, const Eigen::Vector3f &point_cloud_center, float point_cloud_radius, const Eigen::Affine3f &sensor_pose=Eigen::Affine3f::Identity(), CoordinateFrame coordinate_frame=CAMERA_FRAME, float noise_level=0.0f, float min_range=0.0f, int border_size=0) 从点云创建深度图像,其中参数中有关场景大小的提示,提高了获取深度图像时的计算速度。point_cloud为指向创建深度图像所需要的点云的引用,angular_resolution为模拟的深度传感器的角度分辨率,弧度表示,point_cloud_center为点云外接球体的中心,默认为(0,0,0)point_cloud_radius为点云外接球体的半径,sensor_pose设置模拟的深度传感器的位姿是一个仿射变换矩阵,默认为4*4的单位矩阵变换,coordinate_frame定义按照那种坐标系统的习惯默认为CAMERA_FRAME,noise_level获取深度图像深度时,近邻点对查询点距离值的影响距离,以米为单位,min_range设置最小的获取距离,小于最小获取距离的位置为传感器的盲区,border_size获得深度图像的边缘的宽度 默认为0 该函数中涉及的角度的单位都是弧度 template<typename PointCloudTypeWithViewpoints > void createFromPointCloudWithViewpoints (const PointCloudTypeWithViewpoints &point_cloud, float angular_resolution, float max_angle_width, float max_angle_height, CoordinateFrame coordinate_frame=CAMERA_FRAME, float noise_level=0.0f, float min_range=0.0f, int border_size=0) 从点云创建深度图像,点云中包含深度信息,其中,point_cloud为指向创建深度图像所需要的点云的引用,angular_resolution为模拟的深度传感器的角度分辨率,即深度图像中一个像素对应的角度大小,max_angle_width为模拟的深度传感器的水平最大采样角度,max_angle_height为模拟传感器的垂直方向最大采样角度,sensor_pose设置模拟的深度传感器的位姿是一个仿射变换矩阵,默认为4*4的单位矩阵变换,coordinate_frame定义按照那种坐标系统的习惯默认为CAMERA_FRAME,noise_level获取深度图像深度时,近邻点对查询点距离值的影响水平,如果该值比较小,则常用Z-缓冲区中深度平均值作为查询点的深度,min_range设置最小的可视深度,小于最小获取距离的位置为传感器的盲区,border_size获得深度图像的边缘的宽度 默认为0 该函数中涉及的角度的单位都是弧度 void createEmpty (float angular_resolution, const Eigen::Affine3f &sensor_pose=Eigen::Affine3f::Identity(), RangeImage::CoordinateFrame coordinate_frame=CAMERA_FRAME, float angle_width=pcl::deg2rad(360.0f), float angle_height=pcl::deg2rad(180.0f)) 创建一个空的深度图像,以当前视点不可见点填充,其中,angle_width为模拟的深度传感器的水平采样角度,默认为PI*2(360);angle_height垂直方向的采样角度默认为PI(180)*****其他参数同上 template<typename PointCloudType > void integrateFarRanges (const PointCloudType &far_ranges) 将已有的远距离测量结果融合到深度图像中, PCL_EXPORTS void cropImage (int border_size=0, int top=-1, int right=-1, int bottom=-1, int left=-1) 裁剪深度图像到最小尺寸,使这个最小尺寸包含所有点云,其中,board_size设置裁剪后深度图像的边界尺寸, top为裁剪框的边界***********默认都为-1 void setTransformationToRangeImageSystem (const Eigen::Affine3f &to_range_image_system) 设置从深度图像坐标系(传感器的坐标系)转换到世界坐标系的变换矩阵 float getAngularResolution () const 获得深度图像X和Y方向的角分辨率 弧度表示 void setAngularResolution (float angular_resolution) 设置深度图像在X方向和Y方向的新的角分辨率,angular_resolution即每个像素所对应的弧度 void calculate3DPoint (float image_x, float image_y, float range, PointWithRange &point) const 根据深度图像点(X Y)和距离(range)计算返回场景中的3D点的point void calculate3DPoint (float image_x, float image_y, PointWithRange &point) const 根据给定的深度图像点和离该点最近像素上的距离值计算返回场景中的3D 点point (详细的解释请官网查看) (2)class pcl::RangeImagePlanner RangeImagePlanner 来源于最原始的深度图像,但又区别于原始的深度图像,因为该类不使用球类投影方式,而是通过一个平面投影方式进行投影(相机一一般采用这种投影方式),因此对于已有的利用深度传感器获取的深度图像来说比较实用,类的继承关系如下: Public Member Functions PCL_EXPORTS void setDisparityImage (const float *disparity_image, int di_width, int di_height, float focal_length, float base_line, float desired_angular_resolution=-1) 从给定的视差图像中创建图像,其中disparity_image是输入的视差图像,di_width视差图像的宽度di_height视差图像的高度focal_length, 产生视差图像的照相机的焦距,base_line是用于产生视差图像的立体相对的基线长度,desired_angular_resolution预设的角分辨率 每个像素对应的弧度,该值不能大于点云的密度, PCL_EXPORTS void setDepthImage (const float *depth_image, int di_width, int di_height, float di_center_x, float di_center_y, float di_focal_length_x, float di_focal_length_y, float desired_angular_resolution=-1) 从已存在的深度影像中创建深度图像,其中,depth_image是输入的浮点形的深度图像,di_width,深度图像的宽度,di_height图像的高度,di_center_x di_center_y 是照相机投影中心XY的坐标。di_focal_length_x di_focal_length_y是照相机水平 垂直方向上的焦距,desired_angular_resolution预设的角分辨率 每个像素对应的弧度,该值不能大于点云的密度, template<typename PointCloudType > void createFromPointCloudWithFixedSize (const PointCloudType &point_cloud, int di_width, int di_height, float di_center_x, float di_center_y, float di_focal_length_x, float di_focal_length_y, const Eigen::Affine3f &sensor_pose, CoordinateFrame coordinate_frame=CAMERA_FRAME, float noise_level=0.0f, float min_range=0.0f) 从已存在的点云中创建图像,point_cloud为指向创建深度图像所需要的点云对象的引用,di_width视差图像的宽度di_height视差图像的高度 di_center_x di_center_y 是照相机投影中心XY的坐标 di_focal_length_x di_focal_length_y是照相机水平 垂直方向上的焦距 sensor_pose是模拟深度照相机的位姿 coordinate_frame为点云所使用的坐标系 noise_level传感器的水平噪声, virtual void calculate3DPoint (float image_x, float image_y, float range, Eigen::Vector3f &point) const 根据给定图像点和深度图创建3D点,其中image_x iamge_y range 分别为XY 坐标和深度,point为生成的3D点 virtual void getImagePoint (const Eigen::Vector3f &point, float &image_x, float &image_y, float &range) const 从给定的3D点point中计算图像点(X Y)和深度值 等等具体看官网 (3)应用实例 如何从点云创建深度图,如何从点云和给定的传感器的位置来创建深度图像,此程序是生成一个矩形的点云,然后基于该点云创建深度图像 新建文件range_image_creation.cpp: #include <pcl/range_image/range_image.h> //深度图像的头文件 int main (int argc, char** argv) { pcl::PointCloud<pcl::PointXYZ> pointCloud; //定义点云的对象 // 循环产生点云的数据 for (float y=-0.5f; y<=0.5f; y+=0.01f) { for (float z=-0.5f; z<=0.5f; z+=0.01f) { pcl::PointXYZ point; point.x = 2.0f - y; point.y = y; point.z = z; pointCloud.points.push_back(point); //循环添加点数据到点云对象 } } pointCloud.width = (uint32_t) pointCloud.points.size(); pointCloud.height = 1; //设置点云对象的头信息 //实现一个呈矩形形状的点云 // We now want to create a range image from the above point cloud, with a 1deg angular resolution //angular_resolution为模拟的深度传感器的角度分辨率,即深度图像中一个像素对应的角度大小 float angularResolution = (float) ( 1.0f * (M_PI/180.0f)); // 1.0 degree in radians //max_angle_width为模拟的深度传感器的水平最大采样角度, float maxAngleWidth = (float) (360.0f * (M_PI/180.0f)); // 360.0 degree in radians //max_angle_height为模拟传感器的垂直方向最大采样角度 都转为弧度 float maxAngleHeight = (float) (180.0f * (M_PI/180.0f)); // 180.0 degree in radians //传感器的采集位置 Eigen::Affine3f sensorPose = (Eigen::Affine3f)Eigen::Translation3f(0.0f, 0.0f, 0.0f); //深度图像遵循坐标系统 pcl::RangeImage::CoordinateFrame coordinate_frame = pcl::RangeImage::CAMERA_FRAME; float noiseLevel=0.00; //noise_level获取深度图像深度时,近邻点对查询点距离值的影响水平 float minRange = 0.0f; //min_range设置最小的获取距离,小于最小获取距离的位置为传感器的盲区 int borderSize = 1; //border_size获得深度图像的边缘的宽度 pcl::RangeImage rangeImage; rangeImage.createFromPointCloud(pointCloud, angularResolution, maxAngleWidth, maxAngleHeight, sensorPose, coordinate_frame, noiseLevel, minRange, borderSize); std::cout << rangeImage << "\n"; } 显示结果 微信公众号号可扫描二维码一起共同学习交流 未完待续******************************88888
关键点也称为兴趣点,它是2D图像或是3D点云或者曲面模型上,可以通过定义检测标准来获取的具有稳定性,区别性的点集,从技术上来说,关键点的数量相比于原始点云或图像的数据量减小很多,与局部特征描述子结合在一起,组成关键点描述子常用来形成原始数据的表示,而且不失代表性和描述性,从而加快了后续的识别,追踪等对数据的处理了速度,故而,关键点技术成为在2D和3D 信息处理中非常关键的技术 NARF(Normal Aligned Radial Feature)关键点是为了从深度图像中识别物体而提出的,对NARF关键点的提取过程有以下要求: a) 提取的过程考虑边缘以及物体表面变化信息在内; b)在不同视角关键点可以被重复探测; c)关键点所在位置有足够的支持区域,可以计算描述子和进行唯一的估计法向量。 其对应的探测步骤如下: (1) 遍历每个深度图像点,通过寻找在近邻区域有深度变化的位置进行边缘检测。 (2) 遍历每个深度图像点,根据近邻区域的表面变化决定一测度表面变化的系数,及变化的主方向。 (3) 根据step(2)找到的主方向计算兴趣点,表征该方向和其他方向的不同,以及该处表面的变化情况,即该点有多稳定。 (4) 对兴趣值进行平滑滤波。 (5) 进行无最大值压缩找到的最终关键点,即为NARF关键点。 关于NARF的更为具体的描述请查看这篇博客www.cnblogs.com/ironstark/p/5051533.html。 PCL中keypoints模块及类的介绍 (1)class pcl::Keypoint<PointInT,PointOutT> 类keypoint是所有关键点检测相关类的基类,定义基本接口,具体实现由子类来完成,其继承关系时下图: 具体介绍: Public Member Functions virtual void setSearchSurface (const PointCloudInConstPtr &cloud) 设置搜索时所用搜索点云,cloud为指向点云对象的指针引用 void setSearchMethod (const KdTreePtr &tree) 设置内部算法实现时所用的搜索对象,tree为指向kdtree或者octree对应的指针 void setKSearch (int k) 设置K近邻搜索时所用的K参数 void setRadiusSearch (double radius) 设置半径搜索的半径的参数 int searchForNeighbors (int index, double parameter, std::vector< int > &indices, std::vector< float > &distances) const 采用setSearchMethod设置搜索对象,以及setSearchSurface设置搜索点云,进行近邻搜索,返回近邻在点云中的索引向量, indices以及对应的距离向量distance其中为查询点的索引,parameter为搜索时所用的参数半径或者K (2)class pcl::HarrisKeypoint2D<PointInT,PointOutT,IntensityT> 类HarrisKeypoint2D实现基于点云的强度字段的harris关键点检测子,其中包括多种不同的harris关键点检测算法的变种,其关键函数的说明如下: Public Member Functions HarrisKeypoint2D (ResponseMethod method=HARRIS, int window_width=3, int window_height=3, int min_distance=5, float threshold=0.0) 重构函数,method需要设置采样哪种关键点检测方法,有HARRIS,NOBLE,LOWE,WOMASI四种方法,默认为HARRIS,window_width window_height为检测窗口的宽度和高度min_distance 为两个关键点之间 容许的最小距离,threshold为判断是否为关键点的感兴趣程度的阀值,小于该阀值的点忽略,大于则认为是关键点 void setMethod (ResponseMethod type)设置检测方式 void setWindowWidth (int window_width) 设置检测窗口的宽度 void setWindowHeight (int window_height) 设置检测窗口的高度 void setSkippedPixels (int skipped_pixels) 设置在检测时每次跳过的像素的数目 void setMinimalDistance (int min_distance) 设置候选关键点之间的最小距离 void setThreshold (float threshold) 设置感兴趣的阀值 void setNonMaxSupression (bool=false) 设置是否对小于感兴趣阀值的点进行剔除,如果是true则剔除,否则返回这个点 void setRefine (bool do_refine)设置是否对所得的关键点结果进行优化, void setNumberOfThreads (unsigned int nr_threads=0) 设置该算法如果采用openMP并行机制,能够创建线程数目 (3)pcl::HarrisKeypoint3D< PointInT, PointOutT, NormalT > 类HarrisKeypoint3D和HarrisKeypoint2D类似,但是没有在点云的强度空间检测关键点,而是利用点云的3D空间的信息表面法线向量来进行关键点检测,关于HarrisKeypoint3D的类与HarrisKeypoint2D相似,除了 HarrisKeypoint3D (ResponseMethod method=HARRIS, float radius=0.01f, float threshold=0.0f) 重构函数,method需要设置采样哪种关键点检测方法,有HARRIS,NOBLE,LOWE,WOMASI四种方法,默认为HARRIS,radius为法线估计的搜索半径,threshold为判断是否为关键点的感兴趣程度的阀值,小于该阀值的点忽略,大于则认为是关键点。 (4)pcl::HarrisKeypoint6D< PointInT, PointOutT, NormalT > 类HarrisKeypoint6D和HarrisKeypoint2D类似,只是利用了欧式空间域XYZ或者强度域来候选关键点,或者前两者的交集,即同时满足XYZ域和强度域的关键点为候选关键点, HarrisKeypoint6D (float radius=0.01, float threshold=0.0) 重构函数,此处并没有方法选择的参数,而是默认采用了Tomsai提出的方法实现关键点的检测,radius为法线估计的搜索半径,threshold为判断是否为关键点的感兴趣程度的阀值,小于该阀值的点忽略,大于则认为是关键点。 (5)pcl::SIFTKeypoint< PointInT, PointOutT > 类SIFTKeypoint是将二维图像中的SIFT算子调整后移植到3D空间的SIFT算子的实现,输入带有XYZ坐标值和强度的点云,输出为点云中的SIFT关键点,其关键函数的说明如下: void setScales (float min_scale, int nr_octaves, int nr_scales_per_octave) 设置搜索时与尺度相关的参数,min_scale在点云体素尺度空间中标准偏差,点云对应的体素栅格中的最小尺寸 int nr_octaves是检测关键点时体素空间尺度的数目,nr_scales_per_octave为在每一个体素空间尺度下计算高斯空间的尺度所需要的参数 void setMinimumContrast (float min_contrast) 设置候选关键点对应的对比度下限 (6)还有很多不再一一介绍 实例分析 实验实现提取NARF关键点,并且用图像和3D显示的方式进行可视化,可以直观的观察关键点的位置和数量 narf_feature_extraction.cpp: #include <iostream> #include <boost/thread/thread.hpp> #include <pcl/range_image/range_image.h> #include <pcl/io/pcd_io.h> #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/features/narf_descriptor.h> #include <pcl/console/parse.h> typedef pcl::PointXYZ PointType; // -------------------- // -----Parameters----- // -------------------- float angular_resolution = 0.5f; ////angular_resolution为模拟的深度传感器的角度分辨率,即深度图像中一个像素对应的角度大小 float support_size = 0.2f; //点云大小的设置 pcl::RangeImage::CoordinateFrame coordinate_frame = pcl::RangeImage::CAMERA_FRAME; //设置坐标系 bool setUnseenToMaxRange = false; bool rotation_invariant = true; // -------------- // -----Help----- // -------------- void printUsage (const char* progName) { std::cout << "\n\nUsage: "<<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 to max range\n" << "-s <float> support size for the interest points (diameter of the used sphere - " "default "<<support_size<<")\n" << "-o <0/1> switch rotational invariant version of the feature on/off" << " (default "<< (int)rotation_invariant<<")\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); //视口的原点pos_vector Eigen::Vector3f look_at_vector = viewer_pose.rotation () * Eigen::Vector3f (0, 0, 1) + pos_vector; //旋转+平移look_at_vector Eigen::Vector3f up_vector = viewer_pose.rotation () * Eigen::Vector3f (0, -1, 0); //up_vector 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) { 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"; } if (pcl::console::parse (argc, argv, "-o", rotation_invariant) >= 0) cout << "Switching rotation invariant feature version "<< (rotation_invariant ? "on" : "off")<<".\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----- // ------------------------------------------------------------------ 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 ()); 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) { 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 { 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; 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, 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----- // -------------------------------------------- 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); /********************************************************************************************************* 创建RangeImageBorderExtractor对象,它是用来进行边缘提取的,因为NARF的第一步就是需要探测出深度图像的边缘, *********************************************************************************************************/ // -------------------------------- // -----Extract NARF keypoints----- // -------------------------------- pcl::RangeImageBorderExtractor range_image_border_extractor; //用来提取边缘 pcl::NarfKeypoint narf_keypoint_detector; //用来检测关键点 narf_keypoint_detector.setRangeImageBorderExtractor (&range_image_border_extractor); // narf_keypoint_detector.setRangeImage (&range_image); narf_keypoint_detector.getParameters ().support_size = support_size; //设置NARF的参数 pcl::PointCloud<int> keypoint_indices; narf_keypoint_detector.compute (keypoint_indices); std::cout << "Found "<<keypoint_indices.points.size ()<<" key points.\n"; // ---------------------------------------------- // -----Show keypoints in 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----- // ------------------------------------- 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"); // ------------------------------------------------------ // -----Extract NARF descriptors for interest points----- // ------------------------------------------------------ std::vector<int> keypoint_indices2; keypoint_indices2.resize (keypoint_indices.points.size ()); for (unsigned int i=0; i<keypoint_indices.size (); ++i) // This step is necessary to get the right vector type keypoint_indices2[i]=keypoint_indices.points[i]; pcl::NarfDescriptor narf_descriptor (&range_image, &keypoint_indices2); narf_descriptor.getParameters ().support_size = support_size; narf_descriptor.getParameters ().rotation_invariant = rotation_invariant; pcl::PointCloud<pcl::Narf36> narf_descriptors; narf_descriptor.compute (narf_descriptors); cout << "Extracted "<<narf_descriptors.size ()<<" descriptors for " <<keypoint_indices.points.size ()<< " keypoints.\n"; //-------------------- // -----Main loop----- //-------------------- while (!viewer.wasStopped ()) { range_image_widget.spinOnce (); // process GUI events viewer.spinOnce (); pcl_sleep(0.01); } } 运行结果: 未完待续**********************88888 有兴趣这可以扫描下面的二维码关注公众号与我交流,
(1)从一个点云中提取索引 如何使用一个,基于某一分割算法提取点云中的一个子集。 代码解析 #include <iostream> #include <pcl/ModelCoefficients.h> #include <pcl/io/pcd_io.h> #include <pcl/point_types.h> #include <pcl/sample_consensus/method_types.h> #include <pcl/sample_consensus/model_types.h> #include <pcl/segmentation/sac_segmentation.h> #include <pcl/filters/voxel_grid.h> #include <pcl/filters/extract_indices.h> int main (int argc, char** argv) { /********************************************************************************************************** 从输入的.PCD 文件载入数据后,创建一个VOxelGrid滤波器对数据进行下采样,在这里进行下才样是为了加速处理过程, 越少的点意味着分割循环中处理起来越快 **********************************************************************************************************/ pcl::PCLPointCloud2::Ptr cloud_blob (new pcl::PCLPointCloud2), cloud_filtered_blob (new pcl::PCLPointCloud2);//申明滤波前后的点云 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered (new pcl::PointCloud<pcl::PointXYZ>), cloud_p (new pcl::PointCloud<pcl::PointXYZ>), cloud_f (new pcl::PointCloud<pcl::PointXYZ>); // 读取PCD文件 pcl::PCDReader reader; reader.read ("table_scene_lms400.pcd", *cloud_blob); //统计滤波前的点云个数 std::cerr << "PointCloud before filtering: " << cloud_blob->width * cloud_blob->height << " data points." << std::endl; // 创建体素栅格下采样: 下采样的大小为1cm pcl::VoxelGrid<pcl::PCLPointCloud2> sor; //体素栅格下采样对象 sor.setInputCloud (cloud_blob); //原始点云 sor.setLeafSize (0.01f, 0.01f, 0.01f); // 设置采样体素大小 sor.filter (*cloud_filtered_blob); //保存 // 转换为模板点云 pcl::fromPCLPointCloud2 (*cloud_filtered_blob, *cloud_filtered); std::cerr << "PointCloud after filtering: " << cloud_filtered->width * cloud_filtered->height << " data points." << std::endl; // 保存下采样后的点云 pcl::PCDWriter writer; writer.write<pcl::PointXYZ> ("table_scene_lms400_downsampled.pcd", *cloud_filtered, false); pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients ()); pcl::PointIndices::Ptr inliers (new pcl::PointIndices ()); pcl::SACSegmentation<pcl::PointXYZ> seg; //创建分割对象 seg.setOptimizeCoefficients (true); //设置对估计模型参数进行优化处理 seg.setModelType (pcl::SACMODEL_PLANE); //设置分割模型类别 seg.setMethodType (pcl::SAC_RANSAC); //设置用哪个随机参数估计方法 seg.setMaxIterations (1000); //设置最大迭代次数 seg.setDistanceThreshold (0.01); //判断是否为模型内点的距离阀值 // 设置ExtractIndices的实际参数 pcl::ExtractIndices<pcl::PointXYZ> extract; //创建点云提取对象 int i = 0, nr_points = (int) cloud_filtered->points.size (); // While 30% of the original cloud is still there while (cloud_filtered->points.size () > 0.3 * nr_points) { // 为了处理点云包含的多个模型,在一个循环中执行该过程并在每次模型被提取后,保存剩余的点进行迭代 seg.setInputCloud (cloud_filtered); seg.segment (*inliers, *coefficients); if (inliers->indices.size () == 0) { std::cerr << "Could not estimate a planar model for the given dataset." << std::endl; break; } // Extract the inliers extract.setInputCloud (cloud_filtered); extract.setIndices (inliers); extract.setNegative (false); extract.filter (*cloud_p); std::cerr << "PointCloud representing the planar component: " << cloud_p->width * cloud_p->height << " data points." << std::endl; std::stringstream ss; ss << "table_scene_lms400_plane_" << i << ".pcd"; writer.write<pcl::PointXYZ> (ss.str (), *cloud_p, false); // Create the filtering object extract.setNegative (true); extract.filter (*cloud_f); cloud_filtered.swap (cloud_f); i++; } return (0); } 结果: 显示出来: 图1 原始点云图像 图2 下采样后点云数据 图3 分割得到的其一平面模型 图4 分割得到的其二平面模型 (2)使用ConditionalRemoval 或RadiusOutlinerRemoval移除离群点 如何在滤波模块使用几种不同的方法移除离群点,对于ConditionalRemoval滤波器,可以一次删除满足对输入的点云设定的一个或多个条件指标的所有的数据点,RadiusOutlinerRemoval滤波器,它可以删除在输入点云一定范围内没有至少达到足够多近邻的所有数据点。 关于RadiusOutlinerRemoval的理解,在点云数据中,设定每个点一定范围内周围至少有足够多的近邻,不满足就会被删除 关于ConditionalRemoval 这个滤波器删除点云中不符合用户指定的一个或者多个条件的数据点 新建文件remove_outliers.cpp #include <iostream> #include <pcl/point_types.h> #include <pcl/filters/radius_outlier_removal.h> #include <pcl/filters/conditional_removal.h> int main (int argc, char** argv) { if (argc != 2) //确保输入的参数 { std::cerr << "please specify command line arg '-r' or '-c'" << std::endl; exit(0); } pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>); pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered (new pcl::PointCloud<pcl::PointXYZ>); //填充点云 cloud->width = 5; cloud->height = 1; cloud->points.resize (cloud->width * cloud->height); for (size_t i = 0; i < cloud->points.size (); ++i) { cloud->points[i].x = 1024 * rand () / (RAND_MAX + 1.0f); cloud->points[i].y = 1024 * rand () / (RAND_MAX + 1.0f); cloud->points[i].z = 1024 * rand () / (RAND_MAX + 1.0f); } if (strcmp(argv[1], "-r") == 0){ pcl::RadiusOutlierRemoval<pcl::PointXYZ> outrem; //创建滤波器 outrem.setInputCloud(cloud); //设置输入点云 outrem.setRadiusSearch(0.8); //设置半径为0.8的范围内找临近点 outrem.setMinNeighborsInRadius (2); //设置查询点的邻域点集数小于2的删除 // apply filter outrem.filter (*cloud_filtered); //执行条件滤波 在半径为0.8 在此半径内必须要有两个邻居点,此点才会保存 } else if (strcmp(argv[1], "-c") == 0){ //创建条件限定的下的滤波器 pcl::ConditionAnd<pcl::PointXYZ>::Ptr range_cond (new pcl::ConditionAnd<pcl::PointXYZ> ()); //创建条件定义对象 //为条件定义对象添加比较算子 range_cond->addComparison (pcl::FieldComparison<pcl::PointXYZ>::ConstPtr (new pcl::FieldComparison<pcl::PointXYZ> ("z", pcl::ComparisonOps::GT, 0.0))); //添加在Z字段上大于0的比较算子 range_cond->addComparison (pcl::FieldComparison<pcl::PointXYZ>::ConstPtr (new pcl::FieldComparison<pcl::PointXYZ> ("z", pcl::ComparisonOps::LT, 0.8))); //添加在Z字段上小于0.8的比较算子 // 创建滤波器并用条件定义对象初始化 pcl::ConditionalRemoval<pcl::PointXYZ> condrem; condrem.setCondition (range_cond); condrem.setInputCloud (cloud); //输入点云 condrem.setKeepOrganized(true); //设置保持点云的结构 // 执行滤波 condrem.filter (*cloud_filtered); //大于0.0小于0.8这两个条件用于建立滤波器 } else{ std::cerr << "please specify command line arg '-r' or '-c'" << std::endl; exit(0); } std::cerr << "Cloud before filtering: " << std::endl; for (size_t i = 0; i < cloud->points.size (); ++i) std::cerr << " " << cloud->points[i].x << " " << cloud->points[i].y << " " << cloud->points[i].z << std::endl; // display pointcloud after filtering std::cerr << "Cloud after filtering: " << std::endl; for (size_t i = 0; i < cloud_filtered->points.size (); ++i) std::cerr << " " << cloud_filtered->points[i].x << " " << cloud_filtered->points[i].y << " " << cloud_filtered->points[i].z << std::endl; return (0); } 编译运行的结果为 从中可以看出ConditionalRemoval 或RadiusOutlinerRemoval的区别 RadiusOutlinerRemoval比较适合去除单个的离群点 ConditionalRemoval 比较灵活,可以根据用户设置的条件灵活过滤 微信公众号号可扫描二维码一起共同学习交流
(1)使用statisticalOutlierRemoval滤波器移除离群点 使用统计分析技术,从一个点云数据中集中移除测量噪声点(也就是离群点)比如:激光扫描通常会产生密度不均匀的点云数据集,另外测量中的误差也会产生稀疏的离群点,使效果不好,估计局部点云特征(例如采样点处法向量或曲率变化率)的运算复杂,这会导致错误的数值,反过来就会导致点云配准等后期的处理失败。 解决办法:每个点的邻域进行一个统计分析,并修剪掉一些不符合一定标准的点,稀疏离群点移除方法基于在输入数据中对点到临近点的距离分布的计算,对每一个点,计算它到它的所有临近点的平均距离,,假设得到的结果是一个高斯分布,其形状是由均值和标准差决定,平均距离在标准范围之外的点,可以被定义为离群点并可从数据中去除。 建立文件statistical_removal.cpp #include <iostream> #include <pcl/io/pcd_io.h> #include <pcl/point_types.h> #include <pcl/filters/statistical_outlier_removal.h> int main (int argc, char** argv) { pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>); pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered (new pcl::PointCloud<pcl::PointXYZ>); // 定义读取对象 pcl::PCDReader reader; // 读取点云文件 reader.read<pcl::PointXYZ> ("table_scene_lms400.pcd", *cloud); std::cerr << "Cloud before filtering: " << std::endl; std::cerr << *cloud << std::endl; // 创建滤波器,对每个点分析的临近点的个数设置为50 ,并将标准差的倍数设置为1 这意味着如果一 //个点的距离超出了平均距离一个标准差以上,则该点被标记为离群点,并将它移除,存储起来 pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor; //创建滤波器对象 sor.setInputCloud (cloud); //设置待滤波的点云 sor.setMeanK (50); //设置在进行统计时考虑查询点临近点数 sor.setStddevMulThresh (1.0); //设置判断是否为离群点的阀值 sor.filter (*cloud_filtered); //存储 std::cerr << "Cloud after filtering: " << std::endl; std::cerr << *cloud_filtered << std::endl; pcl::PCDWriter writer; writer.write<pcl::PointXYZ> ("table_scene_lms400_inliers.pcd", *cloud_filtered, false); sor.setNegative (true); sor.filter (*cloud_filtered); writer.write<pcl::PointXYZ> ("table_scene_lms400_outliers.pcd", *cloud_filtered, false); return (0); } 运行结果为: ( 2)使用参数化模型投影点云 如何将点投影到一个参数化模型上(平面或者球体等),参数化模型通过一组参数来设定,对于平面来说使用其等式形式.在PCL中有特意存储常见模型系数的数据结构 #include <iostream> #include <pcl/io/pcd_io.h> #include <pcl/point_types.h> #include <pcl/ModelCoefficients.h> //模型系数头文件 #include <pcl/filters/project_inliers.h> //投影滤波类头文件 int main (int argc, char** argv) { pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>); pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_projected (new pcl::PointCloud<pcl::PointXYZ>); //创建点云并打印出来 cloud->width = 5; cloud->height = 1; cloud->points.resize (cloud->width * cloud->height); for (size_t i = 0; i < cloud->points.size (); ++i) { cloud->points[i].x = 1024 * rand () / (RAND_MAX + 1.0f); cloud->points[i].y = 1024 * rand () / (RAND_MAX + 1.0f); cloud->points[i].z = 1024 * rand () / (RAND_MAX + 1.0f); } std::cerr << "Cloud before projection: " << std::endl; for (size_t i = 0; i < cloud->points.size (); ++i) std::cerr << " " << cloud->points[i].x << " " << cloud->points[i].y << " " << cloud->points[i].z << std::endl; // 填充ModelCoefficients的值,使用ax+by+cz+d=0平面模型,其中 a=b=d=0,c=1 也就是X——Y平面 //定义模型系数对象,并填充对应的数据 pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients ()); coefficients->values.resize (4); coefficients->values[0] = coefficients->values[1] = 0; coefficients->values[2] = 1.0; coefficients->values[3] = 0; // 创建ProjectInliers对象,使用ModelCoefficients作为投影对象的模型参数 pcl::ProjectInliers<pcl::PointXYZ> proj; //创建投影滤波对象 proj.setModelType (pcl::SACMODEL_PLANE); //设置对象对应的投影模型 proj.setInputCloud (cloud); //设置输入点云 proj.setModelCoefficients (coefficients); //设置模型对应的系数 proj.filter (*cloud_projected); //投影结果存储 std::cerr << "Cloud after projection: " << std::endl; for (size_t i = 0; i < cloud_projected->points.size (); ++i) std::cerr << " " << cloud_projected->points[i].x << " " << cloud_projected->points[i].y << " " << cloud_projected->points[i].z << std::endl; return (0); } 编译运行的结果如下 实验结果可以看出投影前的Z轴都不为0 ,都是随机产生的值,投影之后,打印的结果表明,xy的值都没有改变,z都变为0 所以该投影滤波类就是输入点云和投影模型,输出为投影到模型上之后的点云。 未完待续**************************88888
在3D视窗中以点云形式进行可视化(深度图像来自于点云),另一种是将深度值映射为颜色,从而以彩色图像方式可视化深度图像, 新建工程ch4_2,新建文件range_image_visualization.cpp,填充内容如下 #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> #include <pcl/visualization/range_image_visualizer.h> #include <pcl/visualization/pcl_visualizer.h> #include <pcl/console/parse.h> typedef pcl::PointXYZ PointType; // -------------------- // -----Parameters----- // -------------------- float angular_resolution_x = 0.5f, angular_resolution_y = angular_resolution_x; pcl::RangeImage::CoordinateFrame coordinate_frame = pcl::RangeImage::CAMERA_FRAME; bool live_update = false; // -------------- // -----Help----- // -------------- 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 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) { 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); // ------------------------------------------------------------------ // -----Read pcd file or create example point cloud if not given----- // ------------------------------------------------------------------ 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; } // ----------------------------------------------- // -----Create RangeImage from the PointCloud----- // ----------------------------------------------- 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, angular_resolution_y, pcl::deg2rad (360.0f), pcl::deg2rad (180.0f), scene_sensor_pose, coordinate_frame, noise_level, min_range, border_size); // -------------------------------------------- // -----Open 3D viewer and add point cloud----- // -------------------------------------------- /***************************************************************************************** 创建3D视窗对象,将背景颜色设置为白色,添加黑色的,点云大小为1的深度图像(点云),并使用Main函数 上面定义的setViewerPose函数设置深度图像的视点参数,被注释的部分用于添加爱坐标系,并对原始点云进行可视化 *****************************************************************************************/ 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); //图像可视化方式显示深度图像 //-------------------- // -----Main loop----- //-------------------- while (!viewer.wasStopped ()) //启动主循环以保证可视化代码的有效性,直到可视化窗口关闭 { range_image_widget.spinOnce (); //用于处理深度图像可视化类的当前事件 viewer.spinOnce (); //用于处理3D窗口当前的事件此外还可以随时更新2D深度图像,以响应可视化窗口中的当前视角,这通过命令行-1来激活 pcl_sleep (0.01); //首先从窗口中得到当前的观察位置,然后创建对应视角的深度图像,并在图像显示插件中显示 if (live_update) { scene_sensor_pose = viewer.getViewerPose(); range_image.createFromPointCloud (point_cloud, angular_resolution_x, angular_resolution_y, 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); } } } 编译结束运行可执行文件的结果为: 运行 ./range_image_visualization(没有指定.pcd文件) 使用自动生成的矩形空间点云,这里有两个窗口,一个是点云的3D可视化窗口,一个是深度图像的可视化窗口,在该窗口图像的颜色由深度决定。 当然如果指定PCD文件也可以 比如:./range_image_visualization room_scan1.pcd 其结果 微信公众号号可扫描二维码一起共同学习交流
PCLVisualizer可视化类是PCL中功能最全的可视化类,与CloudViewer可视化类相比,PCLVisualizer使用起来更为复杂,但该类具有更全面的功能,如显示法线、绘制多种形状和多个视口。本小节将通过示例代码演示PCLVisualizer可视化类的功能,从显示单个点云开始。大多数示例代码都是用于创建点云并可视化其某些特征 代码注释解析 #include <iostream> #include <boost/thread/thread.hpp> #include <pcl/common/common_headers.h> #include <pcl/features/normal_3d.h> #include <pcl/io/pcd_io.h> #include <pcl/visualization/pcl_visualizer.h> #include <pcl/console/parse.h> // -------------- // -----Help----- // -------------- void printUsage (const char* progName) { std::cout << "\n\nUsage: "<<progName<<" [options]\n\n" << "Options:\n" << "-------------------------------------------\n" << "-h this help\n" << "-s Simple visualisation example\n" << "-r RGB colour visualisation example\n" << "-c Custom colour visualisation example\n" << "-n Normals visualisation example\n" << "-a Shapes visualisation example\n" << "-v Viewports example\n" << "-i Interaction Customization example\n" << "\n\n"; } /************************************************************************************************************ /*****************************可视化单个点云:应用PCL Visualizer可视化类显示单个具有XYZ信息的点云****************/ /************************************************************************************************************/ //simpleVis函数实现最基本的点云可视化操作, boost::shared_ptr<pcl::visualization::PCLVisualizer> simpleVis (pcl::PointCloud<pcl::PointXYZ>::ConstPtr cloud) { // -------------------------------------------- // -----Open 3D viewer and add point cloud----- // -------------------------------------------- //创建视窗对象并给标题栏设置一个名称“3D Viewer”并将它设置为boost::shared_ptr智能共享指针,这样可以保证指针在程序中全局使用,而不引起内存错误 boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer (new pcl::visualization::PCLVisualizer ("3D Viewer")); //设置视窗的背景色,可以任意设置RGB的颜色,这里是设置为黑色 viewer->setBackgroundColor (0, 0, 0); /*这是最重要的一行,我们将点云添加到视窗对象中,并定一个唯一的字符串作为ID 号,利用此字符串保证在其他成员中也能 标志引用该点云,多次调用addPointCloud可以实现多个点云的添加,,每调用一次就会创建一个新的ID号,如果想更新一个 已经显示的点云,必须先调用removePointCloud(),并提供需要更新的点云ID 号, *******************************************************************************************/ viewer->addPointCloud<pcl::PointXYZ> (cloud, "sample cloud"); //用于改变显示点云的尺寸,可以利用该方法控制点云在视窗中的显示方法, viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "sample cloud"); /******************************************************************************************************* 查看复杂的点云,经常让人感到没有方向感,为了保持正确的坐标判断,需要显示坐标系统方向,可以通过使用X(红色) Y(绿色 )Z (蓝色)圆柱体代表坐标轴的显示方式来解决,圆柱体的大小可以通过scale参数来控制,本例中scale设置为1.0 ******************************************************************************************************/ viewer->addCoordinateSystem (1.0); //通过设置照相机参数使得从默认的角度和方向观察点云 viewer->initCameraParameters (); return (viewer); } /*****************************可视化点云颜色特征******************************************************/ /************************************************************************************************** 多数情况下点云显示不采用简单的XYZ类型,常用的点云类型是XYZRGB点,包含颜色数据,除此之外,还可以给指定的点云定制颜色 以示得点云在视窗中比较容易区分。点赋予不同的颜色表征其对应的Z轴值不同,PCL Visualizer可根据所存储的颜色数据为点云 赋色, 比如许多设备kinect可以获取带有RGB数据的点云,PCL Vizualizer可视化类可使用这种颜色数据为点云着色,rgbVis函数中的代码 用于完成这种操作。 ***************************************************************************************************/ /************************************************************************** 与前面的示例相比点云的类型发生了变化,这里使用的点云带有RGB数据的属性字段, ****************************************************************************/ boost::shared_ptr<pcl::visualization::PCLVisualizer> rgbVis (pcl::PointCloud<pcl::PointXYZRGB>::ConstPtr cloud) { // -------------------------------------------- // -----Open 3D viewer and add point cloud----- // -------------------------------------------- boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer (new pcl::visualization::PCLVisualizer ("3D Viewer")); viewer->setBackgroundColor (0, 0, 0); /*************************************************************************************************************** 设置窗口的背景颜色后,创建一个颜色处理对象,PointCloudColorHandlerRGBField利用这样的对象显示自定义颜色数据,PointCloudColorHandlerRGBField 对象得到每个点云的RGB颜色字段, **************************************************************************************************************/ pcl::visualization::PointCloudColorHandlerRGBField<pcl::PointXYZRGB> rgb(cloud); viewer->addPointCloud<pcl::PointXYZRGB> (cloud, rgb, "sample cloud"); viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "sample cloud"); viewer->addCoordinateSystem (1.0); viewer->initCameraParameters (); return (viewer); } /******************可视化点云自定义颜色特征**********************************************************/ /**************************************************************************************************** 演示怎样给点云着上单独的一种颜色,可以利用该技术给指定的点云着色,以区别其他的点云, *****************************************************************************************************/ //点云类型为XYZ类型,customColourVis函数将点云赋值为绿色, boost::shared_ptr<pcl::visualization::PCLVisualizer> customColourVis (pcl::PointCloud<pcl::PointXYZ>::ConstPtr cloud) { // -------------------------------------------- // -----Open 3D viewer and add point cloud----- // -------------------------------------------- boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer (new pcl::visualization::PCLVisualizer ("3D Viewer")); viewer->setBackgroundColor (0, 0, 0); //创建一个自定义的颜色处理器PointCloudColorHandlerCustom对象,并设置颜色为纯绿色 pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> single_color(cloud, 0, 255, 0); //addPointCloud<>()完成对颜色处理器对象的传递 viewer->addPointCloud<pcl::PointXYZ> (cloud, single_color, "sample cloud"); viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "sample cloud"); viewer->addCoordinateSystem (1.0); viewer->initCameraParameters (); return (viewer); } //*******************可视化点云法线和其他特征*************************************************/ /********************************************************************************************* 显示法线是理解点云的一个重要步骤,点云法线特征是非常重要的基础特征,PCL visualizer可视化类可用于绘制法线, 也可以绘制表征点云的其他特征,比如主曲率和几何特征,normalsVis函数中演示了如何实现点云的法线, ***********************************************************************************************/ boost::shared_ptr<pcl::visualization::PCLVisualizer> normalsVis ( pcl::PointCloud<pcl::PointXYZRGB>::ConstPtr cloud, pcl::PointCloud<pcl::Normal>::ConstPtr normals) { // -------------------------------------------------------- // -----Open 3D viewer and add point cloud and normals----- // -------------------------------------------------------- boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer (new pcl::visualization::PCLVisualizer ("3D Viewer")); viewer->setBackgroundColor (0, 0, 0); pcl::visualization::PointCloudColorHandlerRGBField<pcl::PointXYZRGB> rgb(cloud); viewer->addPointCloud<pcl::PointXYZRGB> (cloud, rgb, "sample cloud"); viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "sample cloud"); //实现对点云法线的显示 viewer->addPointCloudNormals<pcl::PointXYZRGB, pcl::Normal> (cloud, normals, 10, 0.05, "normals"); viewer->addCoordinateSystem (1.0); viewer->initCameraParameters (); return (viewer); } //*****************绘制普通形状************************************************// /************************************************************************************************************** PCL visualizer可视化类允许用户在视窗中绘制一般图元,这个类常用于显示点云处理算法的可视化结果,例如 通过可视化球体 包围聚类得到的点云集以显示聚类结果,shapesVis函数用于实现添加形状到视窗中,添加了四种形状:从点云中的一个点到最后一个点 之间的连线,原点所在的平面,以点云中第一个点为中心的球体,沿Y轴的椎体 *************************************************************************************************************/ boost::shared_ptr<pcl::visualization::PCLVisualizer> shapesVis (pcl::PointCloud<pcl::PointXYZRGB>::ConstPtr cloud) { // -------------------------------------------- // -----Open 3D viewer and add point cloud添加点云到视窗实例代码----- // -------------------------------------------- boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer (new pcl::visualization::PCLVisualizer ("3D Viewer")); viewer->setBackgroundColor (0, 0, 0); pcl::visualization::PointCloudColorHandlerRGBField<pcl::PointXYZRGB> rgb(cloud); viewer->addPointCloud<pcl::PointXYZRGB> (cloud, rgb, "sample cloud"); viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "sample cloud"); viewer->addCoordinateSystem (1.0); viewer->initCameraParameters (); /************************************************************************************************ 绘制形状的实例代码,绘制点之间的连线, *************************************************************************************************/ viewer->addLine<pcl::PointXYZRGB> (cloud->points[0], cloud->points[cloud->size() - 1], "line"); //添加点云中第一个点为中心,半径为0.2的球体,同时可以自定义颜色 viewer->addSphere (cloud->points[0], 0.2, 0.5, 0.5, 0.0, "sphere"); //--------------------------------------- //-----Add shapes at other locations添加绘制平面使用标准平面方程ax+by+cz+d=0来定义平面,这个平面以原点为中心,方向沿着Z方向----- //--------------------------------------- pcl::ModelCoefficients coeffs; coeffs.values.push_back (0.0); coeffs.values.push_back (0.0); coeffs.values.push_back (1.0); coeffs.values.push_back (0.0); viewer->addPlane (coeffs, "plane"); //添加锥形的参数 coeffs.values.clear (); coeffs.values.push_back (0.3); coeffs.values.push_back (0.3); coeffs.values.push_back (0.0); coeffs.values.push_back (0.0); coeffs.values.push_back (1.0); coeffs.values.push_back (0.0); coeffs.values.push_back (5.0); viewer->addCone (coeffs, "cone"); return (viewer); } /****************************************************************************************** 多视角显示:PCL visealizer可视化类允许用户通过不同的窗口(Viewport)绘制多个点云这样方便对点云比较 viewportsVis函数演示如何用多视角来显示点云计算法线的方法结果对比 ******************************************************************************************/ boost::shared_ptr<pcl::visualization::PCLVisualizer> viewportsVis ( pcl::PointCloud<pcl::PointXYZRGB>::ConstPtr cloud, pcl::PointCloud<pcl::Normal>::ConstPtr normals1, pcl::PointCloud<pcl::Normal>::ConstPtr normals2) { // -------------------------------------------------------- // -----Open 3D viewer and add point cloud and normals----- // -------------------------------------------------------- boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer (new pcl::visualization::PCLVisualizer ("3D Viewer")); viewer->initCameraParameters (); //以上是创建视图的标准代码 int v1(0); //创建新的视口 viewer->createViewPort(0.0, 0.0, 0.5, 1.0, v1); //4个参数分别是X轴的最小值,最大值,Y轴的最小值,最大值,取值0-1,v1是标识 viewer->setBackgroundColor (0, 0, 0, v1); //设置视口的背景颜色 viewer->addText("Radius: 0.01", 10, 10, "v1 text", v1); //添加一个标签区别其他窗口 利用RGB颜色着色器并添加点云到视口中 pcl::visualization::PointCloudColorHandlerRGBField<pcl::PointXYZRGB> rgb(cloud); viewer->addPointCloud<pcl::PointXYZRGB> (cloud, rgb, "sample cloud1", v1); //对第二视口做同样的操作,使得做创建的点云分布于右半窗口,将该视口背景赋值于灰色,以便明显区别,虽然添加同样的点云,给点云自定义颜色着色 int v2(0); viewer->createViewPort(0.5, 0.0, 1.0, 1.0, v2); viewer->setBackgroundColor (0.3, 0.3, 0.3, v2); viewer->addText("Radius: 0.1", 10, 10, "v2 text", v2); pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZRGB> single_color(cloud, 0, 255, 0); viewer->addPointCloud<pcl::PointXYZRGB> (cloud, single_color, "sample cloud2", v2); //为所有视口设置属性, viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "sample cloud1"); viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "sample cloud2"); viewer->addCoordinateSystem (1.0); //添加法线 每个视图都有一组对应的法线 viewer->addPointCloudNormals<pcl::PointXYZRGB, pcl::Normal> (cloud, normals1, 10, 0.05, "normals1", v1); viewer->addPointCloudNormals<pcl::PointXYZRGB, pcl::Normal> (cloud, normals2, 10, 0.05, "normals2", v2); return (viewer); } /******************************************************************************************************* 这里是处理鼠标事件的函数,每次相应鼠标时间都会回电函数,需要从event实例提取事件信息,本例中查找鼠标左键的释放事件 每次响应这种事件都会在鼠标按下的位置上生成一个文本标签。 *********************************************************************************************************/ unsigned int text_id = 0; void keyboardEventOccurred (const pcl::visualization::KeyboardEvent &event, void* viewer_void) { pcl::visualization::PCLVisualizer *viewer = static_cast<pcl::visualization::PCLVisualizer *> (viewer_void); if (event.getKeySym () == "r" && event.keyDown ()) { std::cout << "r was pressed => removing all text" << std::endl; char str[512]; for (unsigned int i = 0; i < text_id; ++i) { sprintf (str, "text#%03d", i); viewer->removeShape (str); } text_id = 0; } } /******************************************************************************************** 键盘事件 我们按下哪个按键 如果按下r健 则删除前面鼠标所产生的文本标签,需要注意的是,当按下R键时 3D相机仍然会重置 所以在PCL中视窗中注册事件响应回调函数,不会覆盖其他成员对同一事件的响应 **************************************************************************************************/ void mouseEventOccurred (const pcl::visualization::MouseEvent &event, void* viewer_void) { pcl::visualization::PCLVisualizer *viewer = static_cast<pcl::visualization::PCLVisualizer *> (viewer_void); if (event.getButton () == pcl::visualization::MouseEvent::LeftButton && event.getType () == pcl::visualization::MouseEvent::MouseButtonRelease) { std::cout << "Left mouse button released at position (" << event.getX () << ", " << event.getY () << ")" << std::endl; char str[512]; sprintf (str, "text#%03d", text_id ++); viewer->addText ("clicked here", event.getX (), event.getY (), str); } } /******************自定义交互*****************************************************************************/ /****************************************************************************************************** 多数情况下,默认的鼠标和键盘交互设置不能满足用户的需求,用户想扩展函数的某一些功能, 比如按下键盘时保存点云的信息, 或者通过鼠标确定点云的位置 interactionCustomizationVis函数进行演示如何捕捉鼠标和键盘事件,在窗口点击,将会显示 一个2D的文本标签,按下r健出去文本 ******************************************************************************************************/ boost::shared_ptr<pcl::visualization::PCLVisualizer> interactionCustomizationVis () { boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer (new pcl::visualization::PCLVisualizer ("3D Viewer")); viewer->setBackgroundColor (0, 0, 0); //以上是实例化视窗的标准代码 viewer->addCoordinateSystem (1.0); //分别注册响应键盘和鼠标事件,keyboardEventOccurred mouseEventOccurred回调函数,需要将boost::shared_ptr强制转换为void* viewer->registerKeyboardCallback (keyboardEventOccurred, (void*)viewer.get ()); viewer->registerMouseCallback (mouseEventOccurred, (void*)viewer.get ()); return (viewer); } // -------------- // -----Main----- // -------------- int main (int argc, char** argv) { // -------------------------------------- // -----Parse Command Line Arguments----- // -------------------------------------- if (pcl::console::find_argument (argc, argv, "-h") >= 0) { printUsage (argv[0]); return 0; } bool simple(false), rgb(false), custom_c(false), normals(false), shapes(false), viewports(false), interaction_customization(false); if (pcl::console::find_argument (argc, argv, "-s") >= 0) { simple = true; std::cout << "Simple visualisation example\n"; } else if (pcl::console::find_argument (argc, argv, "-c") >= 0) { custom_c = true; std::cout << "Custom colour visualisation example\n"; } else if (pcl::console::find_argument (argc, argv, "-r") >= 0) { rgb = true; std::cout << "RGB colour visualisation example\n"; } else if (pcl::console::find_argument (argc, argv, "-n") >= 0) { normals = true; std::cout << "Normals visualisation example\n"; } else if (pcl::console::find_argument (argc, argv, "-a") >= 0) { shapes = true; std::cout << "Shapes visualisation example\n"; } else if (pcl::console::find_argument (argc, argv, "-v") >= 0) { viewports = true; std::cout << "Viewports example\n"; } else if (pcl::console::find_argument (argc, argv, "-i") >= 0) { interaction_customization = true; std::cout << "Interaction Customization example\n"; } else { printUsage (argv[0]); return 0; } // ------------------------------------ // -----Create example point cloud----- // ------------------------------------ pcl::PointCloud<pcl::PointXYZ>::Ptr basic_cloud_ptr (new pcl::PointCloud<pcl::PointXYZ>); pcl::PointCloud<pcl::PointXYZRGB>::Ptr point_cloud_ptr (new pcl::PointCloud<pcl::PointXYZRGB>); std::cout << "Genarating example point clouds.\n\n"; // We're going to make an ellipse extruded along the z-axis. The colour for // the XYZRGB cloud will gradually go from red to green to blue. uint8_t r(255), g(15), b(15); for (float z(-1.0); z <= 1.0; z += 0.05) { for (float angle(0.0); angle <= 360.0; angle += 5.0) { pcl::PointXYZ basic_point; basic_point.x = 0.5 * cosf (pcl::deg2rad(angle)); basic_point.y = sinf (pcl::deg2rad(angle)); basic_point.z = z; basic_cloud_ptr->points.push_back(basic_point); pcl::PointXYZRGB point; point.x = basic_point.x; point.y = basic_point.y; point.z = basic_point.z; uint32_t rgb = (static_cast<uint32_t>(r) << 16 | static_cast<uint32_t>(g) << 8 | static_cast<uint32_t>(b)); point.rgb = *reinterpret_cast<float*>(&rgb); point_cloud_ptr->points.push_back (point); } if (z < 0.0) { r -= 12; g += 12; } else { g -= 12; b += 12; } } basic_cloud_ptr->width = (int) basic_cloud_ptr->points.size (); basic_cloud_ptr->height = 1; point_cloud_ptr->width = (int) point_cloud_ptr->points.size (); point_cloud_ptr->height = 1; // ---------------------------------------------------------------- // -----Calculate surface normals with a search radius of 0.05----- // ---------------------------------------------------------------- pcl::NormalEstimation<pcl::PointXYZRGB, pcl::Normal> ne; ne.setInputCloud (point_cloud_ptr); pcl::search::KdTree<pcl::PointXYZRGB>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZRGB> ()); ne.setSearchMethod (tree); pcl::PointCloud<pcl::Normal>::Ptr cloud_normals1 (new pcl::PointCloud<pcl::Normal>); ne.setRadiusSearch (0.05); ne.compute (*cloud_normals1); // --------------------------------------------------------------- // -----Calculate surface normals with a search radius of 0.1----- // --------------------------------------------------------------- pcl::PointCloud<pcl::Normal>::Ptr cloud_normals2 (new pcl::PointCloud<pcl::Normal>); ne.setRadiusSearch (0.1); ne.compute (*cloud_normals2); boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer; if (simple) { viewer = simpleVis(basic_cloud_ptr); } else if (rgb) { viewer = rgbVis(point_cloud_ptr); } else if (custom_c) { viewer = customColourVis(basic_cloud_ptr); } else if (normals) { viewer = normalsVis(point_cloud_ptr, cloud_normals2); } else if (shapes) { viewer = shapesVis(point_cloud_ptr); } else if (viewports) { viewer = viewportsVis(point_cloud_ptr, cloud_normals1, cloud_normals2); } else if (interaction_customization) { viewer = interactionCustomizationVis(); } //-------------------- // -----Main loop----- //-------------------- while (!viewer->wasStopped ()) { viewer->spinOnce (100); boost::this_thread::sleep (boost::posix_time::microseconds (100000)); } } 编译生成可执行文件后,运行查看 (1) ./pcl_visualizer_demo -h 依次执行查看结果 未完待续*****************************8888
可视化(visualization)是利用计算机图形学和图像处理技术,将数据转换图像在屏幕上显示出来,并进行交互处理的的理论,方法和技术, pcl_visualization库建立了能够快速建立原型的目的和可视化算法对三维点云数据操作的结果。类似于opencv的highgui例程显示二维图像,在屏幕上绘制基本的二维图形,库提供了以下几点: (1)渲染和设置视觉特性的方法(如颜色、大小、透明度等)在PCL任意n维的点云数据集pcl::PointCloud<T> format (2)在屏幕上绘制基本的3D形状的方法(例如,圆柱体,球体,线,多边形等),无论是从点集或参数方程; (3)一个直方图可视化模块(pclhistogramvisualizer)的二维图; (4)大量的几何和颜色处理pcl::PointCloud<T> datasets (5)a pcl::RangeImage 可视化模块 . 1. class pcl::visualization::CloudViewer 类CloudViewer实现创建点云可视化的窗口,以及相关的可视化功能 Public Member Functions CloudViewer (const std::string &window_name) 构建可视化点云窗口,窗口名为window_name ~CloudViewer () 注销窗口相关资源 void showCloud (const ColorCloud::ConstPtr &cloud, const std::string &cloudname="cloud") 可视化窗口显示cloud对应的点云,考虑到多个点云用键值cloudname来限定是哪一个点云 bool wasStopped (int millis_to_wait=1) 判断用户是否已经关闭窗口,如果是则需要注销窗口 void runOnVisualizationThread (VizCallable x, const std::string &key="callable") 在窗口运行期间处理x的回调函数,key为键值标识此回调函数,知道窗口关闭 void runOnVisualizationThreadOnce (VizCallable x) 值调用回调函数一次 void removeVisualizationCallable (const std::string &key="callable") 删除key对应的回调函数 boost::signals2::connection registerKeyboardCallback (void(*callback)(const pcl::visualization::KeyboardEvent &, void *), void *cookie=NULL) 注册键盘事件回调函数,cookie为回调时的参数,callback为回调函数的指针 template<typename T > boost::signals2::connection registerKeyboardCallback (void(T::*callback)(const pcl::visualization::KeyboardEvent &, void *), T &instance, void *cookie=NULL) 同上,其中的instance 指向是实现该回到函数的对象 (2)太多了这里就贴出所有的关于可视化的类 对于类的成员就不再一一介绍 class pcl::visualization::CloudViewer Simple point cloud visualization class. More... class pcl::visualization::FloatImageUtils Provide some gerneral functionalities regarding 2d float arrays, e.g., for visualization purposes More... class pcl::visualization::PCLHistogramVisualizer PCL histogram visualizer main class. More... class pcl::visualization::ImageViewerInteractorStyle An image viewer interactor style, tailored for ImageViewer. More... struct pcl::visualization::ImageViewer::ExitMainLoopTimerCallback struct pcl::visualization::ImageViewer::ExitCallback class pcl::visualization::ImageViewer ImageViewer is a class for 2D image visualization. More... class pcl::visualization::PCLVisualizerInteractorStyle PCLVisualizerInteractorStyle defines an unique, custom VTK based interactory style for PCL Visualizer applications. More... struct pcl::visualization::Figure2D Abstract class for storing figure information. More... class pcl::visualization::PCLPainter2D PCL Painter2D main class. More... class pcl::visualization::PCLPlotter PCL Plotter main class. More... class pcl::visualization::PCLVisualizer PCL Visualizer main class. More... class pcl::visualization::PointCloudColorHandler< PointT > Base Handler class for PointCloud colors. More... class pcl::visualization::PointCloudColorHandlerRandom< PointT > Handler for random PointCloud colors (i.e., R, G, B will be randomly chosen) More... class pcl::visualization::PointCloudColorHandlerCustom< PointT > Handler for predefined user colors. More... class pcl::visualization::PointCloudColorHandlerRGBField< PointT > RGB handler class for colors. More... class pcl::visualization::PointCloudColorHandlerHSVField< PointT > HSV handler class for colors. More... 等等*****************************************8 应用实例 cloud_viewer.cpp: #include <pcl/visualization/cloud_viewer.h> //类cloud_viewer头文件申明 #include <iostream> //标准输入输出头文件申明 #include <pcl/io/io.h> //I/O相关头文件申明 #include <pcl/io/pcd_io.h> //PCD文件读取 /********************************************************************************** 函数是作为回调函数,在主函数中只注册一次 ,函数实现对可视化对象背景颜色的设置,添加一个圆球几何体 *********************************************************************************/ int user_data; void viewerOneOff (pcl::visualization::PCLVisualizer& viewer) { viewer.setBackgroundColor (1.0, 0.5, 1.0); //设置背景颜色 pcl::PointXYZ o; //存储球的圆心位置 o.x = 1.0; o.y = 0; o.z = 0; viewer.addSphere (o, 0.25, "sphere", 0); //添加圆球几何对象 std::cout << "i only run once" << std::endl; } /*********************************************************************************** 作为回调函数,在主函数中注册后每帧显示都执行一次,函数具体实现在可视化对象中添加一个刷新显示字符串 *************************************************************************************/ void viewerPsycho (pcl::visualization::PCLVisualizer& viewer) { static unsigned count = 0; std::stringstream ss; ss << "Once per viewer loop: " << count++; viewer.removeShape ("text", 0); viewer.addText (ss.str(), 200, 300, "text", 0); //FIXME: possible race condition here: user_data++; } /************************************************************** 首先加载点云文件到点云对象,并初始化可视化对象viewer,注册上面的回 调函数,执行循环直到收到关闭viewer的消息退出程序 *************************************************************/ int main () { pcl::PointCloud<pcl::PointXYZRGBA>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZRGBA>); //声明cloud pcl::io::loadPCDFile ("my_point_cloud.pcd", *cloud); //加载点云文件 pcl::visualization::CloudViewer viewer("Cloud Viewer"); //创建viewer对象 //showCloud函数是同步的,在此处等待直到渲染显示为止 viewer.showCloud(cloud); //该注册函数在可视化的时候只执行一次 viewer.runOnVisualizationThreadOnce (viewerOneOff); //该注册函数在渲染输出时每次都调用 viewer.runOnVisualizationThread (viewerPsycho); while (!viewer.wasStopped ()) { //此处可以添加其他处理 //FIXME: Note that this is running in a separate thread from viewerPsycho //and you should guard against race conditions yourself... user_data++; } return 0; } 编译结果如下 未完待续*************************8888888
建立空间索引在点云数据处理中有着广泛的应用,常见的空间索引一般 是自顶而下逐级划分空间的各种空间索引结构,比较有代表性的包括BSP树,KD树,KDB树,R树,四叉树,八叉树等索引结构,而这些结构中,KD树和八叉树使用比较广泛 八叉树(Octree)是一种用于描述三维空间的树状数据结构。八叉树的每个节点表示一个正方体的体积元素,每个节点有八个子节点,这八个子节点所表示的体积元素加在一起就等于父节点的体积。一般中心点作为节点的分叉中心。 百度百科释义:八叉树(Octree)的定义是:若不为空树的话,树中任一节点的子节点恰好只会有八个,或零个,也就是子节点不会有0与8以外的数目。那么,这要用来做什么?想象一个立方体, 我们最少可以切成多少个相同等分的小立方体?答案就是8个。再想象我们有一个房间,房间里某个角落藏着一枚金币,我们想很快的把金币找出来,聪明的你会怎 么做?我们可以把房间当成一个立方体,先切成八个小立方体,然后排除掉没有放任何东西的小立方体,再把有可能藏金币的小立方体继续切八等份….如此下去, 平均在Log8(房间内的所有物品数)的时间内就可找到金币。因此,八叉树就是用在3D空间中的场景管理,可以很快地知道物体在3D场景中的位置,或侦测 与其它物体是否有碰撞以及是否在可视范围内。 实现八叉树的原理 (1). 设定最大递归深度。 (2). 找出场景的最大尺寸,并以此尺寸建立第一个立方体。 (3). 依序将单位元元素丢入能被包含且没有子节点的立方体。 (4). 若没达到最大递归深度,就进行细分八等份,再将该立方体所装的单位元元素全部分担给八个子立方体。 (5). 若发现子立方体所分配到的单位元元素数量不为零且跟父立方体是一样的,则该子立方体停止细分,因为跟据空间分割理论,细分的空间所得到的分配必定较少,若是一样数目,则再怎么切数目还是一样,会造成无穷切割的情形。 (6). 重复3,直到达到最大递归深度。 八叉树的逻辑结构如下: 假设要表示的形体V可以放在一个充分大的正方体C内,C的边长为2n,形体V=C,它的八叉树可以用以下的递归方法来定义:八 叉树的每个节点与C的一个子立方体对应, 树根与C本身相对应,如果V=C,那么V的八叉树仅有树根,如果V≠C,则将C等分为八个子立方体,每个子立方体 与树根的一个子节点相对应。只要某个子立方体不是完 全空白或完全为V所占据,就要被八等分,从而对应的节点也就有了八个子节点。这样的递 归判断、分割一直要进行到节点所对应的立方体或是完全空白,或是完全为V占 据,或是其大小已是预先定义的体素大小,并且对它与V之交作一定的“舍入”,使 体素或认为是空白的,或认为是V占据的。 PCL中Octree模块及类介绍 pcl::octree::Octree2BufBase< LeafContainerT, BranchContainerT > 实现了同时存储管理两个八叉树与内存中,可以十分高效的实现八叉树的建立管理等操作,并且节点实现对临近树节点的结构的探测,对应到空间点云,其就可以对空间曲面的动态变化进行探测,在进行空间动态变化探测中非常有用 Public Types typedef Octree2BufBase< LeafContainerT, BranchContainerT > OctreeT typedef BufferedBranchNode< BranchContainerT > BranchNode typedef OctreeLeafNode< LeafContainerT > LeafNode typedef BranchContainerT BranchContainer typedef LeafContainerT LeafContainer typedef OctreeDepthFirstIterator< OctreeT > Iterator typedef const OctreeDepthFirstIterator< OctreeT > ConstIterator typedef OctreeLeafNodeIterator< OctreeT > LeafNodeIterator typedef const OctreeLeafNodeIterator< OctreeT > ConstLeafNodeIterator typedef OctreeDepthFirstIterator< OctreeT > DepthFirstIterator typedef const OctreeDepthFirstIterator< OctreeT > ConstDepthFirstIterator typedef OctreeBreadthFirstIterator< OctreeT > BreadthFirstIterator typedef const OctreeBreadthFirstIterator< OctreeT > ConstBreadthFirstIterator Public Member Functions void setMaxVoxelIndex (unsigned int max_voxel_index_arg) Set the maximum amount of voxels per dimension. 设置在各个维度的最大的体素个数 void setTreeDepth (unsigned int depth_arg) Set the maximum depth of the octree. 设置八叉树的深度,需要在初始化八叉树时设置 unsigned int getTreeDepth () const Get the maximum depth of the octree 获得八叉树的深度 LeafContainerT * createLeaf (unsigned int idx_x_arg, unsigned int idx_y_arg, unsigned int idx_z_arg) Create new leaf node at (idx_x_arg, idx_y_arg, idx_z_arg). 创建叶节点 LeafContainerT * findLeaf (unsigned int idx_x_arg, unsigned int idx_y_arg, unsigned int idx_z_arg) Find leaf node at (idx_x_arg, idx_y_arg, idx_z_arg). 找出页节点 bool existLeaf (unsigned int idx_x_arg, unsigned int idx_y_arg, unsigned int idx_z_arg) const Check for the existence of leaf node at (idx_x_arg, idx_y_arg, idx_z_arg). 判断在(idx_x_arg, idx_y_arg, idx_z_arg)对应的叶子节点是否存在 void removeLeaf (unsigned int idx_x_arg, unsigned int idx_y_arg, unsigned int idx_z_arg) Remove leaf node at (idx_x_arg, idx_y_arg, idx_z_arg). 移除在(。。。)的节点 std::size_t getLeafCount () const Return the amount of existing leafs in the octree.返回八叉树叶子的个数 std::size_t getBranchCount () const Return the amount of existing branches in the octree. 返回八叉树分支的个数 void deleteTree () Delete the octree structure and its leaf nodes. 删除八叉树结构包括节点 void deletePreviousBuffer () Delete octree structure of previous buffer. 删除另一个缓存区对应的八叉树的结构及其字节点 void deleteCurrentBuffer () Delete the octree structure in the current buffer删除当前缓存区对应的八叉树的结构及其字节点 void switchBuffers () Switch buffers and reset current octree structure. 交换缓存区中对应的八叉树的结构和其叶子节点 void serializeTree (std::vector< char > &binary_tree_out_arg, bool do_XOR_encoding_arg=false) Serialize octree into a binary output vector describing its branch node structure. void serializeTree (std::vector< char > &binary_tree_out_arg, std::vector< LeafContainerT * > &leaf_container_vector_arg, bool do_XOR_encoding_arg=false) Serialize octree into a binary output vector describing its branch node structure and and push all DataT elements stored in the octree to a vector.串行化输出八叉树结构 void serializeLeafs (std::vector< LeafContainerT * > &leaf_container_vector_arg) Outputs a vector of all DataT elements that are stored within the octree leaf nodes. void serializeNewLeafs (std::vector< LeafContainerT * > &leaf_container_vector_arg) Outputs a vector of all DataT elements from leaf nodes, that do not exist in the previous octree buffer void deserializeTree (std::vector< char > &binary_tree_in_arg, bool do_XOR_decoding_arg=false) Deserialize a binary octree description vector and create a corresponding octree structure. void deserializeTree (std::vector< char > &binary_tree_in_arg, std::vector< LeafContainerT * > &leaf_container_vector_arg, bool do_XOR_decoding_arg=false) Deserialize a binary octree description and create a corresponding octree structure. 更多详细查看 docs.pointclouds.org/trunk/classpcl_1_1octree_1_1_octree2_buf_base.html#aeea7ecfd6ebe82e93d3c7bb869355502 应用实例 点云由海量的数据集组成,这些数据集通过距离 颜色 法线 等附加信息来描述空间的三维点,此外,点云还能易非常高的速度被创建出来,因此需要占用相当大的存储资源,一旦点云需要存储或者通过速率受限制的通信信道进行传输,提供针对这种数据的压缩方法就变得十分有用,PCL 提供了点云的压缩功能,它允许编码压缩所有类型的点云, 点云压缩示意图: 新建工程ch3_2,新建文件 point_cloud_compression.cpp #include <pcl/point_cloud.h> // 点云类型 #include <pcl/point_types.h> //点数据类型 #include <pcl/io/openni_grabber.h> //点云获取接口类 #include <pcl/visualization/cloud_viewer.h> //点云可视化类 #include <pcl/compression/octree_pointcloud_compression.h> //点云压缩类 #include <stdio.h> #include <sstream> #include <stdlib.h> #ifdef WIN32 # define sleep(x) Sleep((x)*1000) #endif class SimpleOpenNIViewer { public: SimpleOpenNIViewer () : viewer (" Point Cloud Compression Example") { } /************************************************************************************************ 在OpenNIGrabber采集循环执行的回调函数cloud_cb_中,首先把获取的点云压缩到stringstream缓冲区,下一步就是解压缩, 它对压缩了的二进制数据进行解码,存储在新的点云中解码了点云被发送到点云可视化对象中进行实时可视化 *************************************************************************************************/ void cloud_cb_ (const pcl::PointCloud<pcl::PointXYZRGBA>::ConstPtr &cloud) { if (!viewer.wasStopped ()) { // 存储压缩点云的字节流对象 std::stringstream compressedData; // 存储输出点云 pcl::PointCloud<pcl::PointXYZRGBA>::Ptr cloudOut (new pcl::PointCloud<pcl::PointXYZRGBA> ()); // 压缩点云 PointCloudEncoder->encodePointCloud (cloud, compressedData); // 解压缩点云 PointCloudDecoder->decodePointCloud (compressedData, cloudOut); // 可视化解压缩的点云 viewer.showCloud (cloudOut); } } /************************************************************************************************************** 在函数中创建PointCloudCompression类的对象来编码和解码,这些对象把压缩配置文件作为配置压缩算法的参数 所提供的压缩配置文件为OpenNI兼容设备采集到的点云预先确定的通用参数集,本例中使用MED_RES_ONLINE_COMPRESSION_WITH_COLOR 配置参数集,用于快速在线的压缩,压缩配置方法可以在文件/io/include/pcl/compression/compression_profiles.h中找到, 在PointCloudCompression构造函数中使用MANUAL——CONFIGURATION属性就可以手动的配置压缩算法的全部参数 ******************************************************************************************************************/ void run () { bool showStatistics = true; //设置在标准设备上输出打印出压缩结果信息 // 压缩选项详情在: /io/include/pcl/compression/compression_profiles.h pcl::io::compression_Profiles_e compressionProfile = pcl::io::MED_RES_ONLINE_COMPRESSION_WITH_COLOR; // 初始化压缩和解压缩对象 其中压缩对象需要设定压缩参数选项,解压缩按照数据源自行判断 PointCloudEncoder = new pcl::io::OctreePointCloudCompression<pcl::PointXYZRGBA> (compressionProfile, showStatistics); PointCloudDecoder = new pcl::io::OctreePointCloudCompression<pcl::PointXYZRGBA> (); /*********************************************************************************************************** 下面的代码为OpenNI兼容设备实例化一个新的采样器,并且启动循环回调接口,每从设备获取一帧数据就回调函数一次,,这里的 回调函数就是实现数据压缩和可视化解压缩结果。 ************************************************************************************************************/ //创建从OpenNI获取点云的抓取对象 pcl::Grabber* interface = new pcl::OpenNIGrabber (); // 建立回调函数 boost::function<void (const pcl::PointCloud<pcl::PointXYZRGBA>::ConstPtr&)> f = boost::bind (&SimpleOpenNIViewer::cloud_cb_, this, _1); //建立回调函数和回调信息的绑定 boost::signals2::connection c = interface->registerCallback (f); // 开始接受点云的数据流 interface->start (); while (!viewer.wasStopped ()) { sleep (1); } interface->stop (); // 删除压缩与解压缩的实例 delete (PointCloudEncoder); delete (PointCloudDecoder); } pcl::visualization::CloudViewer viewer; pcl::io::OctreePointCloudCompression<pcl::PointXYZRGBA>* PointCloudEncoder; pcl::io::OctreePointCloudCompression<pcl::PointXYZRGBA>* PointCloudDecoder; }; int main (int argc, char **argv) { SimpleOpenNIViewer v; //创建一个新的SimpleOpenNIViewer 实例并调用他的run方法 v.run (); return (0); } 编译后运行的结果如下: 图示为带有RGB纹理信息的实时可视化结果,缩放可视化结果看到经过压缩后点云进行了重采样,纹理信息有所丢失,但数据量有所减小,在实际应用中需要折中取舍 同时在压缩和解压缩的过程中 因为设置compressedData为true所以在标准输出上打印处压缩率帧数等信息: 压缩配置文件:压缩配置文件为PCL点云编码器定义了参数集,并针对压缩从openNI采集器获取的普通点云进行了优化设置,注意,解码对象不需要用参数表示,因为它在解码是检测并获取对应编码参数配置,例如下面的压缩配置文件 LOW_RES_ONLINE_COMPRESSION_WITHOUT_COLOR, //分别率为 1 cm^3 无颜色 快速在线编码 LOW_RES_ONLINE_COMPRESSION_WITH_COLOR, //分别率为 1 cm^3 有颜色 快速在线编码 MED_RES_ONLINE_COMPRESSION_WITHOUT_COLOR, //分别率为 5 mm^3 无颜色 快速在线编码 MED_RES_ONLINE_COMPRESSION_WITH_COLOR, //分别率为 5 mm^3 有颜色 快速在线编码 HIGH_RES_ONLINE_COMPRESSION_WITHOUT_COLOR, //分别率为 1 mm^3 无颜色 快速在线编码 HIGH_RES_ONLINE_COMPRESSION_WITH_COLOR, //分别率为 1 mm^3 有颜色 快速在线编码 LOW_RES_OFFLINE_COMPRESSION_WITHOUT_COLOR, //分别率为 1 cm^3 无颜色 高效离线编码 LOW_RES_OFFLINE_COMPRESSION_WITH_COLOR, //分别率为 1 cm^3 有颜色 高效离线编码 MED_RES_OFFLINE_COMPRESSION_WITHOUT_COLOR, //分别率为 5 mm^3 无颜色 高效离线编码 MED_RES_OFFLINE_COMPRESSION_WITH_COLOR, //分别率为 5 mm^3 有颜色 高效离线编码 HIGH_RES_OFFLINE_COMPRESSION_WITHOUT_COLOR, //分别率为 1 mm^3 无颜色 高效离线编码 HIGH_RES_OFFLINE_COMPRESSION_WITH_COLOR, //分别率为 1 mm^3 有颜色 高效离线编码 MANUAL_CONFIGURATION //允许为高级参数化进行手工配置 微信公众号号可扫描二维码一起共同学习交流 未完待续*********************************************8888
(小技巧记录:博客园编辑的网页界面变小了使用Ctrl ++来变大网页字体) 通过雷达,激光扫描,立体摄像机等三维测量设备获取的点云数据,具有数据量大,分布不均匀等特点,作为三维领域中一个重要的数据来源,点云主要是表征目标表面的海量点的集合,并不具备传统网格数据的几何拓扑信息,所以点云数据处理中最为核心的问题就是建立离散点间的拓扑关系,实现基于邻域关系的快速查找。 k-d树 (k-dimensional树的简称),是一种分割k维数据空间的数据结构。主要应用于多维空间关键数据的搜索(如:范围搜索和最近邻搜索)。K-D树是二进制空间分割树的特殊的情况。用来组织表示K维空间中点的几何,是一种带有其他约束的二分查找树,为了达到目的,通常只在三个维度中进行处理因此所有的kd_tree都将是三维的kd_tree,kd_tree的每一维在指定维度上分开所有的字节点,在树 的根部所有子节点是以第一个指定的维度上被分开。 k-d树算法可以分为两大部分,一部分是有关k-d树本身这种数据结构建立的算法,另一部分是在建立的k-d树上如何进行最邻近查找的算法。 构建算法 k-d树是一个二叉树,每个节点表示一个空间范围。表1给出的是k-d树每个节点中主要包含的数据结构。 域名 数据类型 描述 Node-data 数据矢量 数据集中某个数据点,是n维矢量(这里也就是k维) Range 空间矢量 该节点所代表的空间范围 split 整数 垂直于分割超平面的方向轴序号 Left k-d树 由位于该节点分割超平面左子空间内所有数据点所构成的k-d树 Right k-d树 由位于该节点分割超平面右子空间内所有数据点所构成的k-d树 parent k-d树 父节点 先以一个简单直观的实例来介绍k-d树算法。假设有6个二维数据点{(2,3),(5,4),(9,6),(4,7),(8,1),(7,2)},数据点 位于二维空间内(如图1中黑点所示)。k-d树算法就是要确定图1中这些分割空间的分割线(多维空间即为分割平面,一般为超平面)。下面就要通过一步步展 示k-d树是如何确定这些分割线的。 由于此例简单,数据维度只有2维,所以可以简单地给x,y两个方向轴编号为0,1,也即split={0,1}。 (1)确定split域的首先该取的值。分别计算x,y方向上数据的方差得知x方向上的方差最大,所以split域值首先取0,也就是x轴方向; (2)确定Node-data的域值。根据x轴方向的值2,5,9,4,8,7排序选出中值为7,所以Node-data = (7,2)。这样,该节点的分割超平面就是通过(7,2)并垂直于split = 0(x轴)的直线x = 7; (3)确定左子空间和右子空间。分割超平面x = 7将整个空间分为两部分,如图2所示。x < = 7的部分为左子空间,包含3个节点{(2,3),(5,4),(4,7)};另一部分为右子空间,包含2个节点{(9,6),(8,1)}。 (4)k-d树的构建是一个递归的过程。然后对左子空间和右子空间内的数据重复根节点的过程就可以得到下一级子节点(5,4)和(9,6)(也就是 左右子空间的'根'节点),同时将空间和数据集进一步细分。如此反复直到空间中只包含一个数据点,如图1所示。最后生成的k-d树如图3所示。 关于Kdtree算法的相关内容网上有很多比如blog.csdn.net/silangquan/article/details/41483689 查找算法 在k-d树中进行数据的查找也是特征匹配的重要环节,其目的是检索在k-d树中与查询点距离最近的数据点。这里先以一个简单的实例来描述最邻近查找的基本思路。 星号表示要查询的点(2.1,3.1)。通过二叉搜索,顺着搜索路径很快 就能找到最邻近的近似点,也就是叶子节点(2,3)。而找到的叶子节点并不一定就是最邻近的,最邻近肯定距离查询点更近,应该位于以查询点为圆心且通过叶 子节点的圆域内。为了找到真正的最近邻,还需要进行'回溯'操作:算法沿搜索路径反向查找是否有距离查询点更近的数据点。此例中先从(7,2)点开始进行 二叉查找,然后到达(5,4),最后到达(2,3),此时搜索路径中的节点为<(7,2),(5,4),(2,3)>,首先以(2,3)作为 当前最近邻点,计算其到查询点(2.1,3.1)的距离为0.1414,然后回溯到其父节点(5,4),并判断在该父节点的其他子节点空间中是否有距离查 询点更近的数据点。以(2.1,3.1)为圆心,以0.1414为半径画圆,如图4所示。发现该圆并不和超平面y = 4交割,因此不用进入(5,4)节点右子空间中去搜索。 PCL中kd_tree模块及类的介绍 对此类的函数有更加详细的介绍 类KdTree关键成员函数 virtual void pcl::KdTree< PointT >::setInputCloud ( const PointCloudConstPtr & cloud, const IndicesConstPtr & indices = IndicesConstPtr () ) 设置输入点云,参数cloud 作为输入点云的共享指针引用,indices为在kd_tree中使用的点对应的索引,如果不设置,则默认使用整个点云填充kd_tree virtual int pcl::KdTree< PointT >::nearestKSearch ( int index, int k, std::vector< int > & k_indices, std::vector< float > & k_sqr_distances ) const 纯虚函数,具体实现在其子类KdTreeFLANN中,其用来进行K 领域搜索,k_sqr_distances 为搜索完成后每个邻域点与查询点的欧式距离, 还更多的成员的介绍查看 docs.pointclouds.org/trunk/classpcl_1_1_kd_tree.html#ac81c442ff9c9b1e03c10cb55128e726d 应用实例 新建文件 #include <pcl/point_cloud.h> //点类型定义头文件 #include <pcl/kdtree/kdtree_flann.h> //kdtree类定义头文件 #include <iostream> #include <vector> #include <ctime> int main (int argc, char** argv) { srand (time (NULL)); //用系统时间初始化随机种子 //创建一个PointCloud<pcl::PointXYZ> pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>); // 随机点云生成 cloud->width = 1000; //此处点云数量 cloud->height = 1; //表示点云为无序点云 cloud->points.resize (cloud->width * cloud->height); for (size_t i = 0; i < cloud->points.size (); ++i) //循环填充点云数据 { cloud->points[i].x = 1024.0f * rand () / (RAND_MAX + 1.0f); cloud->points[i].y = 1024.0f * rand () / (RAND_MAX + 1.0f); cloud->points[i].z = 1024.0f * rand () / (RAND_MAX + 1.0f); } //创建KdTreeFLANN对象,并把创建的点云设置为输入,创建一个searchPoint变量作为查询点 pcl::KdTreeFLANN<pcl::PointXYZ> kdtree; //设置搜索空间 kdtree.setInputCloud (cloud); //设置查询点并赋随机值 pcl::PointXYZ searchPoint; searchPoint.x = 1024.0f * rand () / (RAND_MAX + 1.0f); searchPoint.y = 1024.0f * rand () / (RAND_MAX + 1.0f); searchPoint.z = 1024.0f * rand () / (RAND_MAX + 1.0f); // K 临近搜索 //创建一个整数(设置为10)和两个向量来存储搜索到的K近邻,两个向量中,一个存储搜索到查询点近邻的索引,另一个存储对应近邻的距离平方 int K = 10; std::vector<int> pointIdxNKNSearch(K); //存储查询点近邻索引 std::vector<float> pointNKNSquaredDistance(K); //存储近邻点对应距离平方 //打印相关信息 std::cout << "K nearest neighbor search at (" << searchPoint.x << " " << searchPoint.y << " " << searchPoint.z << ") with K=" << K << std::endl; if ( kdtree.nearestKSearch (searchPoint, K, pointIdxNKNSearch, pointNKNSquaredDistance) > 0 ) //执行K近邻搜索 { //打印所有近邻坐标 for (size_t i = 0; i < pointIdxNKNSearch.size (); ++i) std::cout << " " << cloud->points[ pointIdxNKNSearch[i] ].x << " " << cloud->points[ pointIdxNKNSearch[i] ].y << " " << cloud->points[ pointIdxNKNSearch[i] ].z << " (squared distance: " << pointNKNSquaredDistance[i] << ")" << std::endl; } /********************************************************************************** 下面的代码展示查找到给定的searchPoint的某一半径(随机产生)内所有近邻,重新定义两个向量 pointIdxRadiusSearch pointRadiusSquaredDistance来存储关于近邻的信息 ********************************************************************************/ // 半径 R内近邻搜索方法 std::vector<int> pointIdxRadiusSearch; //存储近邻索引 std::vector<float> pointRadiusSquaredDistance; //存储近邻对应距离的平方 float radius = 256.0f * rand () / (RAND_MAX + 1.0f); //随机的生成某一半径 //打印输出 std::cout << "Neighbors within radius search at (" << searchPoint.x << " " << searchPoint.y << " " << searchPoint.z << ") with radius=" << radius << std::endl; if ( kdtree.radiusSearch (searchPoint, radius, pointIdxRadiusSearch, pointRadiusSquaredDistance) > 0 ) //执行半径R内近邻搜索方法 { for (size_t i = 0; i < pointIdxRadiusSearch.size (); ++i) std::cout << " " << cloud->points[ pointIdxRadiusSearch[i] ].x << " " << cloud->points[ pointIdxRadiusSearch[i] ].y << " " << cloud->points[ pointIdxRadiusSearch[i] ].z << " (squared distance: " << pointRadiusSquaredDistance[i] << ")" << std::endl; } return 0; } 编译执行的结果如图: 微信公众号号可扫描二维码一起共同学习交流 未完待续************************************8
(1)学习如何连接两个不同点云为一个点云,进行操作前要确保两个数据集中字段的类型相同和维度相等,同时了解如何连接两个不同点云的字段(例如颜色 法线)这种操作的强制约束条件是两个数据集中点的数目必须一样,例如:点云A是N个点XYZ点,点云B是N个点的RGB点,则连接两个字段形成点云C是N个点xyzrgb类型 新建文件concatenate_clouds.cpp CMakeLists.txt concatenate_clouds.cpp : #include <iostream> #include <pcl/io/pcd_io.h> //io模块 #include <pcl/point_types.h> //数据类型 int main (int argc, char** argv) { if (argc != 2) //提示如果执行可执行文件输入两个参数 -f 或者-p { std::cerr << "please specify command line arg '-f' or '-p'" << std::endl; exit(0); } //申明三个pcl::PointXYZ点云数据类型,分别为cloud_a, cloud_b, cloud_c pcl::PointCloud<pcl::PointXYZ> cloud_a, cloud_b, cloud_c; //存储进行连接时需要的Normal点云,Normal (float n_x, float n_y, float n_z) pcl::PointCloud<pcl::Normal> n_cloud_b; //存储连接XYZ与normal后的点云 pcl::PointCloud<pcl::PointNormal> p_n_cloud_c; // 创建点云数据 //设置cloud_a的个数为5 cloud_a.width = 5; cloud_a.height = cloud_b.height = n_cloud_b.height = 1; //设置都为无序点云 cloud_a.points.resize (cloud_a.width * cloud_a.height); //总数 if (strcmp(argv[1], "-p") == 0) //判断是否为连接a+b=c(点云连接) { cloud_b.width = 3; cloud_b.points.resize (cloud_b.width * cloud_b.height); } else{ n_cloud_b.width = 5; //如果是连接XYZ与normal则生成5个法线(字段间连接) n_cloud_b.points.resize (n_cloud_b.width * n_cloud_b.height); } //以下循环生成无序点云填充上面定义的两种类型的点云数据 for (size_t i = 0; i < cloud_a.points.size (); ++i) { //cloud_a产生三个点(每个点都有X Y Z 三个随机填充的值) cloud_a.points[i].x = 1024 * rand () / (RAND_MAX + 1.0f); cloud_a.points[i].y = 1024 * rand () / (RAND_MAX + 1.0f); cloud_a.points[i].z = 1024 * rand () / (RAND_MAX + 1.0f); } if (strcmp(argv[1], "-p") == 0) for (size_t i = 0; i < cloud_b.points.size (); ++i) { //如果连接a+b=c,则cloud_b用三个点作为xyz的数据 cloud_b.points[i].x = 1024 * rand () / (RAND_MAX + 1.0f); cloud_b.points[i].y = 1024 * rand () / (RAND_MAX + 1.0f); cloud_b.points[i].z = 1024 * rand () / (RAND_MAX + 1.0f); } else for (size_t i = 0; i < n_cloud_b.points.size (); ++i) { //如果连接xyz+normal=xyznormal则n_cloud_b用5个点作为normal数据 n_cloud_b.points[i].normal[0] = 1024 * rand () / (RAND_MAX + 1.0f); n_cloud_b.points[i].normal[1] = 1024 * rand () / (RAND_MAX + 1.0f); n_cloud_b.points[i].normal[2] = 1024 * rand () / (RAND_MAX + 1.0f); } /******************************************************************* 定义了连接点云会用到的5个点云对象:3个输入(cloud_a cloud_b 和n_cloud_b) 两个输出(cloud_c n_cloud_c)然后就是为两个输入点云cloud_a和 cloud_b或者cloud_a 和n_cloud_b填充数据 ********************************************************************/ //输出Cloud A std::cerr << "Cloud A: " << std::endl; for (size_t i = 0; i < cloud_a.points.size (); ++i) std::cerr << " " << cloud_a.points[i].x << " " << cloud_a.points[i].y << " " << cloud_a.points[i].z << std::endl; //输出Cloud B std::cerr << "Cloud B: " << std::endl; if (strcmp(argv[1], "-p") == 0) for (size_t i = 0; i < cloud_b.points.size (); ++i) std::cerr << " " << cloud_b.points[i].x << " " << cloud_b.points[i].y << " " << cloud_b.points[i].z << std::endl; else//输出n_Cloud_b for (size_t i = 0; i < n_cloud_b.points.size (); ++i) std::cerr << " " << n_cloud_b.points[i].normal[0] << " " << n_cloud_b.points[i].normal[1] << " " << n_cloud_b.points[i].normal[2] << std::endl; // Copy the point cloud data if (strcmp(argv[1], "-p") == 0) { cloud_c = cloud_a; cloud_c += cloud_b;//把cloud_a和cloud_b连接一起创建cloud_c 后输出 std::cerr << "Cloud C: " << std::endl; for (size_t i = 0; i < cloud_c.points.size (); ++i) std::cerr << " " << cloud_c.points[i].x << " " << cloud_c.points[i].y << " " << cloud_c.points[i].z << " " << std::endl; } else { //连接字段 把cloud_a和 n_cloud_b字段连接 一起创建 p_n_cloud_c) pcl::concatenateFields (cloud_a, n_cloud_b, p_n_cloud_c); std::cerr << "Cloud C: " << std::endl; for (size_t i = 0; i < p_n_cloud_c.points.size (); ++i) std::cerr << " " << p_n_cloud_c.points[i].x << " " << p_n_cloud_c.points[i].y << " " << p_n_cloud_c.points[i].z << " " << p_n_cloud_c.points[i].normal[0] << " " << p_n_cloud_c.points[i].normal[1] << " " << p_n_cloud_c.points[i].normal[2] << std::endl; } return (0); } CMakeLists.txt: cmake_minimum_required(VERSION 2.8 FATAL_ERROR) project(ch2_2) find_package(PCL 1.2 REQUIRED) include_directories(${PCL_INCLUDE_DIRS}) link_directories(${PCL_LIBRARY_DIRS}) add_definitions(${PCL_DEFINITIONS}) add_executable (concatenate_clouds concatenate_clouds.cpp) target_link_libraries (concatenate_clouds ${PCL_LIBRARIES}) 编译执行后的结果,仔细研究看一下就可以看出点云连接和字段间连接的区别,字段间连接是在行的基础后连接,而点云连接是在列的下方连接,最重要的就是要考虑维度问题,同时每个点云都有XYZ三个数据值 字段间连接: 点云连接 (2)对于获取传感器的深度信息可以使用OpenNI Grabber类,(其中涉及到如何安装传感器的驱动等问题,比如我使用的是kinect 1.0 可能会遇到一些安装问题,但是网上还是有很多的解决办法的,在这里不对于叙述) 新建文件openni_grabber.cpp #include <pcl/point_cloud.h> //点云类定义头文件 #include <pcl/point_types.h> //点 类型定义头文件 #include <pcl/io/openni_grabber.h> //OpenNI数据流获取头文件 #include <pcl/common/time.h> //时间头文件 //类SimpleOpenNIProcessor 的回调函数,作为在获取数据时,对数据进行处理的回调函数的封装,在本例中并没有什么处理,只是实时的在标准输出设备打印处信息。 class SimpleOpenNIProcessor { public: void cloud_cb_ (const pcl::PointCloud<pcl::PointXYZRGBA>::ConstPtr &cloud) { static unsigned count = 0; static double last = pcl::getTime (); //获取当前时间 if (++count == 30) //每30ms一次输出 { double now = pcl::getTime (); // >>右移 std::cout << "distance of center pixel :" << cloud->points [(cloud->width >> 1) * (cloud->height + 1)].z << " mm. Average framerate: " << double(count)/double(now - last) << " Hz" << std::endl; count = 0; last = now; } } void run () { pcl::Grabber* interface = new pcl::OpenNIGrabber(); //创建OpenNI采集对象 // 定义回调函数 boost::function<void (const pcl::PointCloud<pcl::PointXYZRGBA>::ConstPtr&)> f = boost::bind (&SimpleOpenNIProcessor::cloud_cb_, this, _1); boost::signals2::connection c = interface->registerCallback (f);//注册回调函数 interface->start (); //开始接受点云数据 //直到用户按下Ctrl -c while (true) boost::this_thread::sleep (boost::posix_time::seconds (1)); // 停止采集 interface->stop (); } }; int main () { SimpleOpenNIProcessor v; v.run (); return (0); } CMakeLists.txt cmake_minimum_required(VERSION 2.8 FATAL_ERROR) project(openni_grabber) find_package(PCL 1.2 REQUIRED) include_directories(${PCL_INCLUDE_DIRS}) link_directories(${PCL_LIBRARY_DIRS}) add_definitions(${PCL_DEFINITIONS}) add_executable (openni_grabber openni_grabber.cpp) target_link_libraries (openni_grabber ${PCL_LIBRARIES}) 编译后执行可执行文件的结果如下 微信公众号号可扫描二维码一起共同学习交流 未完待续*******************************************8
(1)学习向PCD文件写入点云数据 建立工程文件ch2,然后新建write_pcd.cpp CMakeLists.txt两个文件 write_pcd.cpp : #include <iostream> //标准C++库中的输入输出的头文件 #include <pcl/io/pcd_io.h> //PCD读写类相关的头文件 #include <pcl/point_types.h> //PCL中支持的点类型的头文件 int main (int argc, char** argv) { //实例化的模板类PointCloud 每一个点的类型都设置为pcl::PointXYZ /************************************************* 点PointXYZ类型对应的数据结构 Structure PointXYZ{ float x; float y; float z; }; **************************************************/ pcl::PointCloud<pcl::PointXYZ> cloud; // 创建点云 并设置适当的参数(width height is_dense) cloud.width = 5; cloud.height = 1; cloud.is_dense = false; //不是稠密型的 cloud.points.resize (cloud.width * cloud.height); //点云总数大小 //用随机数的值填充PointCloud点云对象 for (size_t i = 0; i < cloud.points.size (); ++i) { cloud.points[i].x = 1024 * rand () / (RAND_MAX + 1.0f); cloud.points[i].y = 1024 * rand () / (RAND_MAX + 1.0f); cloud.points[i].z = 1024 * rand () / (RAND_MAX + 1.0f); } //把PointCloud对象数据存储在 test_pcd.pcd文件中 pcl::io::savePCDFileASCII ("test_pcd.pcd", cloud); //打印输出存储的点云数据 std::cerr << "Saved " << cloud.points.size () << " data points to test_pcd.pcd." << std::endl; for (size_t i = 0; i < cloud.points.size (); ++i) std::cerr << " " << cloud.points[i].x << " " << cloud.points[i].y << " " << cloud.points[i].z << std::endl; return (0); } CMakeLists.txt:(第一次接触CMake所以注释的比较多,废话比较多,所以有助于理解) cmake_minimum_required ( VERSION 2.6 FATAL_ERROR) #对于cmake版本的最低版本的要求 project(ch2) #建立的工程名,例如源代码目录路径的变量名为CH_DIR #工程存储目录变量名为CH_BINARY_DIR #要求工程依赖的PCL最低版本为1.3,并且版本至少包含common和IO两个模块 这里的REQUIRED意味着如果对应的库找不到 则CMake配置的过程将完全失败, #因为PCL是模块化的,也可以如下操作: # 一个组件 find_package(PCL 1.6 REQUIRED COMPONENTS io) # 多个组件 find_package(PCL 1.6 REQUIRED COMPONENTS commom io) # 所有组件 find_package(PCL 1.6 REQUIRED ) find_package(PCL 1.3 REQUIRED) #下面的语句是利用CMake的宏完成对PCL的头文件路径和链接路径变量的配置和添加,如果缺少下面几行,生成文件的过程中就会提示 #找不到相关的头文件,在配置CMake时,当找到了安装的PCL,下面相关的包含的头文件,链接库,路径变量就会自动设置 # PCL_FOUND:如果找到了就会被设置为1 ,否则就不设置 # PCL_INCLUDE_DIRS:被设置为PCL安装的头文件和依赖头文件的目录 # PCL_LIBRARIES:被设置成所建立和安装的PCL库头文件 # PCL_LIBRARIES_DIRS:被设置成PCL库和第三方依赖的头文件所在的目录 # PCL_VERSION:所找到的PCL的版本 # PCL_COMPONENTS:列出所有可用的组件 # PCL_DEFINITIONS:列出所需要的预处理器定义和编译器标志 include_directories(${PCL_INCLUDE_DIRS}) link_directories(${PCL_LIBRARIES_DIRS}) add_definitions(${PCL_DEFINITIONS}) #这句话告诉CMake从单个源文件write_pcd建立一个可执行文件 add_executable(write_pcd write_pcd.cpp) #虽然包含了PCL的头文件,因此编译器知道我们现在访问所用的方法,我们也需要让链接器知道所链接的库,PCL找到库文件由 #PCL_COMMON_LIBRARIES变量指示,通过target_link_libraries这个宏来出发链接操作 target_link_libraries(write_pcd ${PCL_COMMON_LIBRARIES} ${PCL_IO_LIBRARIES}) 之后就 cd 到文件下 mkdir build cd build cmake .. make 生成可执行文件后执行的结果: (2)学习如何从PCD文件读取点云数据 读取PCD点云数据只需在工程文件下建立新的文件write_pcd.cpp write.cpp: #include <iostream> //标准C++库中的输入输出的头文件 #include <pcl/io/pcd_io.h> //PCD读写类相关的头文件 #include <pcl/point_types.h> //PCL中支持的点类型的头文件 int main (int argc, char** argv) { //创建一个PointCloud<pcl::PointXYZ> boost共享指针并进行实例化 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>); //打开点云文件 if (pcl::io::loadPCDFile<pcl::PointXYZ> ("test_pcd.pcd", *cloud) == -1) { PCL_ERROR ("Couldn't read file test_pcd.pcd \n"); return (-1); } //默认就是而二进制块读取转换为模块化的PointCLoud格式里pcl::PointXYZ作为点类型 然后打印出来 std::cout << "Loaded " << cloud->width * cloud->height << " data points from test_pcd.pcd with the following fields: " << std::endl; for (size_t i = 0; i < cloud->points.size (); ++i) std::cout << " " << cloud->points[i].x << " " << cloud->points[i].y << " " << cloud->points[i].z << std::endl; return (0); } 那么要编译此文件只需在CMakeLists.txt最下面添加两行代码 add_executable(write_pcd write_pcd.cpp)add_executable(read_pcd read_pcd.cpp)target_link_libraries(write_pcd ${PCL_COMMON_LIBRARIES} ${PCL_IO_LIBRARIES})target_link_libraries(read_pcd ${PCL_COMMON_LIBRARIES} ${PCL_IO_LIBRARIES}) 编译后执行的结果如下 如果想看PCD文件的数据,可以找到test_pcd.pcd后缀名改为.txt即可打开如下所示: (仔细查看文件头 的顺序也就是之前介绍的文件头顺序) # .PCD v0.7 - Point Cloud Data file format VERSION 0.7 FIELDS x y z SIZE 4 4 4 TYPE F F F COUNT 1 1 1 WIDTH 5 HEIGHT 1 VIEWPOINT 0 0 0 1 0 0 0 POINTS 5 DATA ascii 0.35222197 -0.15188313 -0.10639524 -0.3974061 -0.47310591 0.29260206 -0.73189831 0.66710472 0.44130373 -0.73476553 0.85458088 -0.036173344 -0.46070004 -0.2774682 -0.91676188 总结 pcl::PointCloud<pcl::PointXYZ> cloud ; //写入点云数据的声明,就是三个float类型的数据, pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);这是声明的数据类型,用来存储我们打开的点云数据格式,是共享指针类型 微信公众号号可扫描二维码一起共同学习交流 未完待续******************************8
I/O模块中共有21个类 (1)class pcl::FIleReader:定义了PCD文件的读取接口,主要用作其他读取类的父类 pcl::FileReader有pcl::PCDReader和pcl::PLYReader子类 (2)class pcl::FIleWrite : 与class pcl::FIleReader对应,是写入PCD文件类的接口定义,作为其他写入类的父类,pcl::Filewriter有pcl::PCDwriter和pcl::PLYWriter子类 (3) class pcl::Grabber:类Grabber为PCL1.X对应的设备驱动接口的基类定义 父类 子类 pcl::ONIGrabber pcl::Grabber pcl::OpenNIGrabber pcl::PCDGrabberBase ----------pcl::PCDGrabber< PointT > (4)classopenni_wrapper::OpenNIDevice:定义OpenNI设备的基类,用于获取包括红外数据,RGB数据,深度图像数据 父类 子类 openni_wrapper::DeviceONI openni_wrapper::OpenNIDevice openni_wrapper::DeviceKinect openni_wrapper::DevicePrimesense openni_wrapper::DeviceXtionPro PCD(点云数据)文件格式,以下几种格式 (1)PLY是一种多边形文件格式, (2)STL是3D System公司创建的模型文件格式,主要应用于CAD,CAM领域 (3)OBJ是从几何学上定义的文件格式, (4)X3D是符合ISO标准的基于XML的文件格式,表示3D计算机图形数据 PCD文件头格式 每个PCD文件包含一个文件头,确定和声明文件中存储这点云的数据的某种特性,PCD文件必须用ASCII码来编码, (1)VERSION---------指定PCD文件版本 (2) FIELSS------------指定一个点恶意有的每一个维度和字段的名字例如 FILEDS x y z #XYZ data FILEDS x y z rgb #XYZ + color FILEDS x y z normal_x normal_y normal_z #XYZ +surface normal FILEDS j1 j2 j3 #moment invariants ..... (3) SIZE-----------用字节数指定每一个维度的大小 例如 unsigned char/char? has 1 byte unsigned short/short? has 2 byte double ?has 8 byte (4) TYPE------------用一个字符指定每一个维度的类型 被接受类型有 I----------------表示有符号类型 int8(char) int16 (short) int32(int) U----------------表示无符号类型 ------------------ F----------------表示浮点类型 (5)COUNT----------指定每一维度包含的元数目(默认情况下,没有设置 的话,所有维度的数目被设置为1) (6)WIDTH------用点的数量表示点云数据集的宽度,根据有序点云还是无序点云,WIDTH有两层解释: 1,它能确定无序数据集的点云中点的个数, 2,它能确定有序点云数据集的宽度 注意有序点云数据集,意味着点云是类似与图像的结构,数据分为行和列,这种点云的实例包括立体摄像机和时间飞行摄像机生成的数据,有序数据集的优势在于,预先了解相邻点(和像素点类似)的关系,邻域操作更加高效,这样就加速了计算并降低了PCL中某些算法的成本。例如:WIDTH 640 #每行有640个点 (7)HEIGHT---------------用点的数目表示点云数据集的高度。类似于WIDTH也有两层解释, 有序点云的例子:WIDTH 640 #像图像一样的有序结构,有640行480列, HEIGHT 480 #这样该数据集中共有640*480=307200个人点 无序点云例子: WIDTH 307200 HEIGHT 1 #有307200个点的有序点云数据集 (8)VIEWPOINT--------------------指定数据集中点云的获取视角。VIEWPOINT有可能在不同坐标系之间转换的时候应用,在辅助获取其他特征时,也比较有用, 例如曲面发现,在判断方向一致性时,需要知道视点的方位 视点信息被指为平移(tx ty tz) +四元数(qw qx qy qz) (9 ) POINTS----------------指定点云中点的总数 (10) DATA---------------指定存储点云数据的数据结构,有两种形式:ASCII和二进制 (注意PCD文件的文件头部分必须是以上部分顺序的精确的指定) 4 数据存储类型: (1)如果易ASCII形式,每一点占据一个新行, (2)如果以二进制的形式,这里数据是数组向量的PCL 例子 PCD 文件的一个片段 #。PCD v.7 --Point Cloud Data file format VERSION .7 FIELDS x y z rgb SIZE 4 4 4 4 TYPE F FFF COUNT 1 1 1 1 WIDTH 213 HEIGHT 1 VIEWPOINT 0 0 0 1 0 0 0 POINTS 213 DATA ascii 0.93773 0.33763 0 4.218e+06 0.90805 0.35641 0 4.2108e+06 微信公众号号可扫描二维码一起共同学习交流 未完待续**************************
PCL中可用的PointT类型: PointXYZ——成员变量:float x,y,z; PointXYZ是使用最常见的一个点数据类型,因为他之包含三维XYZ坐标信息,这三个浮点数附加一个浮点数来满足存储对齐,可以通过points[i].data[0]或points[i].x访问点X的坐标值 union { float data[4]; struct { float x; float y; float z; }; }; PointXYZI——成员变量:float x,y,z,intensity PointXYZI是一个简单的X Y Z坐标加intensity的point类型,是一个单独的结构体,并且满足存储对齐,由于point的大部分操作会把data[4]元素设置成0或1(用于变换), 不能让intensity与XYZ在同一个结构体中,如果这样的话其内容将会被覆盖,例如:两个点的点积会把第四个元素设置为0,否则点积没有意义, union{ float data[4]; struct { float x; float y; float z; }; }; union{ struct{ float intensity; }; float data_c[4]; }; PointXYZRGBA——成员变量:float x,y,z;uint32_t rgba 除了RGBA信息被包含在一个整型变量中,其他的和PointXYZI类似 union{ float data[4]; struct { float x; float y; float z; }; }; union{ struct{ float rgba; }; float data_c[4]; }; PointXYZRGB——float x,y,z,rgb 除了RGB信息被包含在一个浮点数据变量中,其他的和 PointXYZRGBA union{ float data[4]; struct { float x; float y; float z; }; }; union{ struct{ float rgb; }; float data_c[4]; }; PointXY——成员变量:float x,y 简单的二维x-y结构代码 struct{ float x; float y; }; InterestPoint——成员变量:float x,y,z,strength除了strength表示关键点的强度测量值,其他的和PointXYZI union{ float data[4]; struct { float x; float y; float z; }; }; union{ struct{ float strength; }; float data_c[4]; }; Normal——成员变量:float normal[3],curvature; 另一个常用的数据类型,Normal结构体表示给定点所在样本曲面上的法线方向,以及对应曲率的测量值,例如访问法向量的第一个坐标可以通过points[i].data_n[0]或者points[i].normal[0]或者points[i] union{ float data_n[4] float normal[3]; struct { float normal_x; float normal_y; float normal_z; }; }; union{ struct{ float curvature; }; float data_c[4]; }; PointNormal——成员变量:float x,y,z; float normal[3] ,curvature ; PointNormal是存储XYZ数据的point结构体,并且包括了采样点的法线和曲率 union{ float data[4]; struct { float x; float y; float z; }; }; union{ float data_n[4] float normal[3]; struct { float normal_x; float normal_y; float normal_z; }; }; union{ struct{ float curvature; }; float data_c[4]; }; 未完待续***************************************88888888888 备注:关于点云库PCL的学习,可以扫描二维码关注公众号,有兴趣的可以直接在公众号回复与我交流,相互学习,
PCL(PointCloudLibrary)是在吸收了前人点云相关研究基础上建立起来的大型跨平台开源C++编程库,它实现了大量点云相关的通用算法和高效数据结构,涉及到点云获取、滤波、分割、配准、检索、特征提取、识别、追踪、曲面重建、可视化等。支持多种操作系统平台,可在Windows、Linux、Android、MacOSX、部分嵌入式实时系统上运行。如果说OpenCV是2D信息获取与处理的结晶,那么PCL就在3D信息获取与处理上具有同等地位,PCL是BSD授权方式,可以免费进行商业和学术应用 。 PCL的潜在应用领域PCL能解决什么问题呢?机器人领域移动机器人对其工作环境的有效感知、辨识与认知,是其进行自主行为优化并可靠完成所承担任务的前提和基础。如何实现场景中物体的有效分类与识别是移动机器人场景认知的核心问题,目前基于视觉图像处理技术来进行场景的认知是该领域的重要方法。但移动机器人在线获取的视觉图像质量受光线变化影响较大,特别是在光线较暗的场景更难以应用,随着RGBD获取设备的大量推广,在机器人领域势必掀起一股深度信息结合2D信息的应用研究热潮,深度信息的引入能够使机器人更好地对环境进行认知、辨识,与图像信息在机器人领域的应用一样,需要强大智能软件算法支撑,PCL就为此而生,最重要的是PCL本身就是为机器人而发起的开源项目,PCL中不仅提供了对现有的RGBD信息的获取设备的支持,还提供了高效的分割、特征提取、识别、追踪等最新的算法,最重要的是它可以移植到android、ubuntu等主流Linux平台上,PCL无疑将会成为机器人应用领域一把瑞士军刀。CAD/CAM、逆向工程大部分工业产品是根据二维或三维CAD模型制造而成,但有时因为数据丢失、设计多次更改、实物引进等原因,产品的几何模型无法获得,因而常常需要根据现有产品实物生成物体几何模型。逆向工程技术能够对产品实物进行测绘,重构产品表面三维几何模型,生成产品制造所需的数字化文档。在一些工业领域,如汽车制造业,许多零件的几何模型都通过逆向工程由油泥模型或实物零件获得,目前在CAD/CAM领域利用激光点云进行高精度测量与重建成为趋势,同时引来了新的问题,通过获取的海量点云数据,来提取重建模型的几何参数,或者形状模型,对模型进行智能检索,从点云数据获取模型的曲面模型等,诸如此类的问题解决方案在PCL中都有涉及。例如kdtree和octree对海量点云进行高效压缩存储与管理,其中滤波、配准、特征描述与提前基础处理,可以应用于模型的智能检索,以及后期的曲面重建和可视化都在PCL中有相应的模块。总之,三维点云数据的处理是逆向工程中比较重要的一环,PCL中间所有的模块正是为此而生的。激光遥感测量能够直接获取高精度三维地面点数据,是对传统测量技术在高程数据获取及自动化快速处理方面的重要技术补充。激光遥感测量系统在地形测绘、环境检测、三维城市建模、地球科学、行星科学等诸多领域具有广泛的发展前景,是目前最先进的能实时获取地形表面三维空间信息和影像的遥感系统。目前,在各种提取地面点的算法中,算法结果与世界结果之间差别较大,违背了实际情况,PCL中强大的模块可以助力此处的各种需求。虚拟现实、人机交互虚拟现实技术(简称VR),又称灵境技术,是以沉浸性、交互性和构想性为基本特征的计算机高级人机界面。它综合利用了计算机图形学、仿真技术、多媒体技术、人工智能技术、计算机网络技术、并行处理技术和多传感器技术,模拟人的视觉、听觉、触觉等感觉器官功能,使人能够沉浸在计算机生成的虚拟境界中,并能够通过语言、手势等自然的方式与之进行实时交互,创建了一种适人化的多维信息空间,具有广阔的应用前景。 对于3D点云处理来说,PCL完全是一个的模块化的现代C++模板库。其基于以下第三方库:Boost、Eigen、FLANN、VTK、CUDA、OpenNI、Qhull,实现点云相关的获取、滤波、分割、配准、检索、特征提取、识别、追踪、曲面重建、可视化等。PCL利用OpenMP、GPU、CUDA等先进高性能计算技术,通过并行化提高程序实时性。K近邻搜索操作的构架是基于FLANN(FastLibraryforApproximateNearestNeighbors)所实现的,速度也是目前技术中最快的。PCL中的所有模块和算法都是通过Boost共享指针来传送数据的,因而避免了多次复制系统中已存在的数据的需要,从0.6版本开始,PCL就已经被移入到Windows,MacOS和Linux系统,并且在Android系统也已经开始投入使用,这使得PCL的应用容易移植与多方发布 PCL包括多个子模块库。最重要的PCL模块库有如下:过滤器Filters、特征Features、关键点Keypoints、注册Registration、Kd树Kd-tree、八叉树Octree、切分Segmentation、Sample Consensus、Surface、Range Image、文件读写I/O、Visualization、通用库Common、Search 原文地址:http://pointclouds.org/documentation/tutorials/walkthrough.php#walkthrough 过滤器Filters 下图是一个噪音消除的示例。因为测量的误差,某些点集存在大量的阴影点,而这将使得局部点云的3D特征建立复杂化。通过对每个点的邻区 的统计分析,削除未达到一定标准的点,可以将其中一些异常的数据点滤去。在PCL中稀疏噪音的消除方法是在输入的点集数据中计算各点到其邻区的分布概率。 对于每个点,计算其到所有相邻点的距离,假定其结果应该是符合特定均值和标准差的高斯分布,定义全局的可容忍的平均值和标准差,将所有不在可容忍误差内的 点都认为是噪音点而删除。 特征Features 在3D特征教程里有一个例子展示了特征的基本理论。 特征库包括数据结构和从点云建立3D特征的方法。3D特征为对于特定点其周围的可用的几何关系信息,如特定的三维点、位置或空间。对于查询点周围的点云数据通常简称为k-neighborhood。 两个最广泛使用的几何点属性是曲面的曲率估计和查询点p的法线。利用点的k-neighborhood计算所得的这两个属性都可以作为本地特征。为 了确定有效的k-neighborhood点云数据,输入的点云数据通常会按空间划分为多个小块区域,如八叉树或KD-trees,然后在这些区域中查找 相邻的点数据。根据具体的应用情况,可以选择p点附近的固定数量的k个点或者是距离p点r距离内的所有点。毫无疑问,最简单的计算p点的法线和曲率变化的 方法是在k-neighborhood点曲面中将特征分解计算(例如分别计算特征向量和特征值),当特征值最小时所相关的特征向量即可近似为p点的法向 量,同时曲面的曲率变化可以通过如下公式计算得到:(曲率变化计算不理解其推导过程,后续理解后在详述)。 关键点Keypoints 关键点库含有两个点云集的关键点检测算法。关键点(也叫做兴趣点)是指在一副图像或者一个点云集中能够利用一个明确标准检测出来的稳定的、独特的 点。一般情况下关键点的数量要远远少于点云集中的点数。通常将关键点信息和各个关键点的本地特征信息组合起来形成一组compact—yet descriptive—representation的原始数据。 注册Registration 将一些点集数据在统一的世界坐标系下组合起来即为注册。其关键在于确定各个点集之间的关联点,计算出各个关联点之间最近似的变换矩阵,对所有的原始数据集不停的重复此过程直到,直到出现各个点集之间的对齐误差小于指定的阈值,即可认为注册过程完成。 注册库包含了的大量的点云注册算法,其可以处理的点集数据不论是否为有序。例如,PCL在强注册算法下,可以将拒绝坏的点集而只将好的点集注册。 Kd树Kd-tree Kd树的基本原理可以在其基础教程里了解。 Kd树库的基础数据结构使用了FLANN以便可以快速的进行邻区搜索。 Kd树按空间划分生成叶子节点,各个叶子节点里存放点数据,其可以按半径搜索或邻区搜索。最近邻区搜索是点云处理中的一样核心操作,在点集之间确定关联点、特征描述、点的邻区搜索时都会用到。 八叉树Octree 八叉树库提供了直接从点云数据创建树的方法。其可支持的操作有:空间分割、下采样、和搜索。每个八叉树的节点都有八个子节点或者没有子节点(叶节点)。根节点包含囊括所有点的立方体空间,每层深度的子节点都是上层空间按各轴除2的子空间。 八叉树通常用来作邻区搜索,如相邻区间内搜索、K邻区搜索、指定半径内搜索。八叉树会自动的调整根据点集数据调整其空间尺度。叶节点还提供了一些额 外的操作,如空间的占有率查询、每空间单位内的点密度。库还提供了将八叉树编码为二进制文件以及从二进制文件解析为八叉树的功能。此外库中还使用了内存池 技术减少了频繁内存的申请和释放开销,提高了八叉树的效率。 下图展示了一个八叉树的最底层的子节点空间。每个斯坦福兔表面的点即红色的点都在八叉树子节点空间内,这幅图就是有八叉树的viewer创建。 切分Segmentation 切分库包主要为将一个点云切分为多个片段簇。切分最适合处理由一些空间隔离区域组成的点云,在这种情况下,点云常常被分解为一些能够独立处理的簇。 关于簇的基本原理可以到其簇的提取教程里面了解。下面两幅图分别展示了片面模型(左)的切分和圆柱模型(右)的切分。 备注:关于点云库PCL的学习,可以扫描二维码关注公众号,有兴趣的可以直接在公众号回复与我交流,相互学习,
将博客搬家至http://blog.csdn.net/u013019296