L 点云数据管理
点云压缩,点云索引(KDtree、Octree),点云LOD(金字塔),海量点云的渲染
KDTree 一种递归的邻近搜索策略
kd树(k-dimensional树的简称),是一种分割k维数据空间的数据结构。
主要应用于多维空间关键数据的搜索(如:范围搜索和最近邻搜索)。
类似与二分查找算法思想
Kd树按空间划分生成叶子节点,各个叶子节点里存放点数据,其可以按半径搜索或邻区搜索。
PCL中的Kd tree的基础数据结构使用了FLANN以便可以快速的进行邻区搜索。
kd树(K-dimension tree)是一种对k维空间中的实例点进行存储以便对其进行快速检索的树形数据结构。
kd树是是一种二叉树,表示对k维空间的一个划分,构造kd树相当于不断地用垂直于坐标轴的超平面将K维空间切分,
构成一系列的K维超矩形区域。kd树的每个结点对应于一个k维超矩形区域。
利用kd树可以省去对大部分数据点的搜索,从而减少搜索的计算量。
对于一个空间,如果某个角落里有个盒子我们却不知道在哪儿。
但是"神"可以告诉我们这个盒子在或者不在某范围内,
显而易见的方法就是把空间化成8个卦限,然后询问在哪个卦限内。
再将存在的卦限继续化成8个。
意思大概就是太极生两仪,两仪生四象,四象生八卦,
就这么一直划分下去,最后一定会确定一个非常小的空间。
对于点云而言,只要将点云的立方体凸包用octree生成很多很多小的卦限,
那么在相邻卦限里的点则为相邻点。
显然,对于不同点云应该采取不同的搜索策略,如果点云是疏散的,
分布很广泛,且每什么规律(如lidar 雷达 测得的点云或双目视觉捕捉的点云)kdTree能更好的划分,
而octree则很难决定最小立方体应该是多少。太大则一个立方体里可能有很多点云,太小则可能立方体之间连不起来。
如果点云分布非常规整,是某个特定物体的点云模型,则应该使用ocTree,
因为很容易求解凸包并且点与点之间相对距离无需再次比对父节点和子节点,更加明晰。
点云压缩,点云索引(KDtree、Octree),点云LOD(金字塔),海量点云的渲染
KDTree 一种递归的邻近搜索策略
kd树(k-dimensional树的简称),是一种分割k维数据空间的数据结构。
主要应用于多维空间关键数据的搜索(如:范围搜索和最近邻搜索)。
其实KDTree就是二叉搜索树的变种。这里的K = 3.
Kd树按空间划分生成叶子节点,各个叶子节点里存放点数据,其可以按半径搜索或邻区搜索。
PCL中的Kd tree的基础数据结构使用了FLANN以便可以快速的进行邻区搜索。
kd树(K-dimension tree)是一种对k维空间中的实例点进行存储以便对其进行快速检索的树形数据结构。
kd树是是一种二叉树,表示对k维空间的一个划分,构造kd树相当于不断地用垂直于坐标轴的超平面将K维空间切分,
构成一系列的K维超矩形区域。kd树的每个结点对应于一个k维超矩形区域。
利用kd树可以省去对大部分数据点的搜索,从而减少搜索的计算量。
对于一个空间,如果某个角落里有个盒子我们却不知道在哪儿。
但是"神"可以告诉我们这个盒子在或者不在某范围内,
显而易见的方法就是把空间化成8个卦限,然后询问在哪个卦限内。
再将存在的卦限继续化成8个。
意思大概就是太极生两仪,两仪生四象,四象生八卦,
就这么一直划分下去,最后一定会确定一个非常小的空间。
对于点云而言,只要将点云的立方体凸包用octree生成很多很多小的卦限,
那么在相邻卦限里的点则为相邻点。
显然,对于不同点云应该采取不同的搜索策略,如果点云是疏散的,
分布很广泛,且每什么规律(如lidar 雷达 测得的点云或双目视觉捕捉的点云)kdTree能更好的划分,
而octree则很难决定最小立方体应该是多少。太大则一个立方体里可能有很多点云,太小则可能立方体之间连不起来。
如果点云分布非常规整,是某个特定物体的点云模型,则应该使用ocTree,
因为很容易求解凸包并且点与点之间相对距离无需再次比对父节点和子节点,更加明晰。
典型的例子是斯坦福的兔子。
kdtree 近邻搜索 + 半径内最近领搜索
#include <pcl/point_cloud.h> #include <pcl/kdtree/kdtree_flann.h> #include <iostream> #include <vector> #include <ctime>//time int main (int argc, char** argv) { srand (time (NULL));//随机数 time_t begin,end; begin = clock(); //开始计时 // 点云对象指针 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_ptr (new pcl::PointCloud<pcl::PointXYZ>); pcl::PointCloud<pcl::PointXYZ>& cloud = *cloud_ptr; // 产生假的点云数据 cloud.width = 400000;//40万数据点 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); } // kdtree对象 pcl::KdTreeFLANN<pcl::PointXYZ> kdtree; // 输入点云 kdtree.setInputCloud (cloud_ptr); // 随机定义一个 需要搜寻的点 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 个最近点去搜索 nearest neighbor search 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; // 开始搜索 /*********************************************************************************************** kdtree 近邻搜索 template<typename PointT> virtual int pcl::KdTree< PointT >::nearestKSearch ( const PointT & p_q, int k, std::vector< int > & k_indices, std::vector< float > & k_sqr_distances ) const [pure virtual] Search for k-nearest neighbors for the given query point. Parameters: [in] the given query point [in] k the number of neighbors to search for [out] the resultant indices of the neighboring points [out] the resultant squared distances to the neighboring points Returns: number of neighbors found ********************************************************************************************/ if ( kdtree.nearestKSearch (searchPoint, K, pointIdxNKNSearch, pointNKNSquaredDistance) > 0 ) { 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; } // 半径内最近领搜索 Neighbors within radius search 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 ) { 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; } //-------------------------------------------------------------------------------------------- end = clock(); //结束计时 double Times = double(end - begin) / CLOCKS_PER_SEC; //将clock()函数的结果转化为以秒为单位的量 std::cout<<"time: "<<Times<<"s"<<std::endl; return 0; }
八叉树空间分割进行点分布区域的压缩
/* 八叉树空间分割进行点分布区域的压缩 通过对连续帧之间的数据相关分析,检测出重复的点云,并将其去除掉再进行传输. 点云由庞大的数据集组成,这些数据集通过距离、颜色、法线等附加信息来描述空间三维点。 此外,点云能以非常高的速率被创建出来,因此需要占用相当大的存储资源, 一旦点云需要存储或者通过速率受限制的通信信道进行传输,提供针对这种数据的压缩方法就变得十分有用。 PCL库提供了点云压缩功能,它允许编码压缩所有类型的点云,包括“无序”点云, 它具有无参考点和变化的点尺寸、分辨率、分布密度和点顺序等结构特征。 而且,底层的octree数据结构允许从几个输入源高效地合并点云数据 . Octree八插树是一种用于描述三维空间的树状数据结构。 八叉树的每个节点表示一个正方体的体积元素,每个节点有八个子节点, 将八个子节点所表示的体积元素加在一起就等于父节点的体积。 Octree模型:又称为八叉树模型,若不为空树的话, 树中任一节点的子节点恰好只会有八个,或零个,也就是子节点不会有0与8以外的数目。 Log8(房间内的所有物品数)的时间内就可找到金币。 因此,八叉树就是用在3D空间中的场景管理,可以很快地知道物体在3D场景中的位置, 或侦测与其它物体是否有碰撞以及是否在可视范围内。 */ #include <pcl/point_cloud.h> #include <pcl/point_types.h> #include <pcl/io/openni_grabber.h>//kinect #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")//基于 可视化对象创建 {} void cloud_cb_ (const pcl::PointCloud<pcl::PointXYZRGBA>::ConstPtr &cloud) { if (!viewer.wasStopped ()) { // stringstream to store compressed point cloud std::stringstream compressedData;//压缩后的点云 存储压缩点云的字节流对象 // output pointcloud pcl::PointCloud<pcl::PointXYZRGBA>::Ptr cloudOut (new pcl::PointCloud<pcl::PointXYZRGBA> ()); // compress point cloud 点云压缩编码 压缩到stringstream缓冲区 PointCloudEncoder->encodePointCloud (cloud, compressedData); // decompress point cloud 点云解码 输出 PointCloudDecoder->decodePointCloud (compressedData, cloudOut); // show decompressed point cloud viewer.showCloud (cloudOut);//可视化 } } void run () { bool showStatistics = true;//设置在标准设备上输出打印出压缩结果信息 // 压缩选项详见 /io/include/pcl/compression/compression_profiles.h 分辨率5mm3,有颜色,快速在线编码 // http://www.pclcn.org/study/shownews.php?lang=cn&id=125 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> (); // create a new grabber for OpenNI devices pcl::Grabber* interface = new pcl::OpenNIGrabber (); //创建从 OpenNI获取点云的抓取对象 这里的回调函数实现数据压缩和可视化解压缩结果。 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 point cloud compression instances delete (PointCloudEncoder); delete (PointCloudDecoder); } pcl::visualization::CloudViewer viewer; // 创建PointCloudCompression类的对象来编码和解码 pcl::io::OctreePointCloudCompression<pcl::PointXYZRGBA>* PointCloudEncoder; pcl::io::OctreePointCloudCompression<pcl::PointXYZRGBA>* PointCloudDecoder; }; int main (int argc, char **argv) { SimpleOpenNIViewer v; v.run (); return (0); } /* 压缩选项详见 /io/include/pcl/compression/compression_profiles.h LOW_RES_ONLINE_COMPRESSION_WITHOUT_COLOR:分辨率1cm3,无颜色,快速在线编码 LOW_RES_ONLINE_COMPRESSION_WITH_COLOR:分辨率1cm3,有颜色,快速在线编码 MED_RES_ONLINE_COMPRESSION_WITHOUT_COLOR:分辨率5mm3,无颜色,快速在线编码 MED_RES_ONLINE_COMPRESSION_WITH_COLOR:分辨率5mm3,有颜色,快速在线编码 HIGH_RES_ONLINE_COMPRESSION_WITHOUT_COLOR:分辨率1mm3,无颜色,快速在线编码 HIGH_RES_ONLINE_COMPRESSION_WITH_COLOR:分辨率1mm3,有颜色,快速在线编码 LOW_RES_OFFLINE_COMPRESSION_WITHOUT_COLOR:分辨率1cm3,无颜色,高效离线编码 LOW_RES_OFFLINE_COMPRESSION_WITH_COLOR:分辨率1cm3,有颜色,高效离线编码 MED_RES_OFFLINE_COMPRESSION_WITHOUT_COLOR:分辨率5mm3,无颜色,高效离线编码 MED_RES_OFFLINE_COMPRESSION_WITH_COLOR:分辨率5mm3,有颜色,高效离线编码 HIGH_RES_OFFLINE_COMPRESSION_WITHOUT_COLOR:分辨率5mm3,无颜色,高效离线编码 HIGH_RES_OFFLINE_COMPRESSION_WITH_COLOR:分辨率5mm3,有颜色,高效离线编码 MANUAL_CONFIGURATION允许为高级参数化进行手工配置 */
基于Octree的空间划分及搜索操作
“体素内近邻搜索(Neighbors within Voxel Search)”、
“K近邻搜索(K Nearest Neighbor Search)”和“半径内近邻搜索(Neighbors within Radius Search)”。
/* 基于Octree的空间划分及搜索操作 pcl::octree::OctreePointCloudSearch octree octree.voxelSearch octree.nearestKSearch octree.radiusSearch octree是一种用于管理稀疏3D数据的树状数据结构, 每个内部节点都正好有八个子节点,本小节中我们学习如何用octree在点云数据中进行空间划分及近邻搜索, 特别地,解释了如何完成 “体素内近邻搜索(Neighbors within Voxel Search)”、 “K近邻搜索(K Nearest Neighbor Search)”和 “半径内近邻搜索(Neighbors within Radius Search)”。 */ #include <pcl/point_cloud.h> #include <pcl/octree/octree_search.h> #include <iostream> #include <vector> #include <ctime> int main (int argc, char** argv) { srand ((unsigned int) time (NULL));//随机数种子 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_ptr (new pcl::PointCloud<pcl::PointXYZ>); pcl::PointCloud<pcl::PointXYZ>& cloud = *cloud_ptr;//引用 // 产生点云 1000个 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); } /* 然后创建一个octree实例, 用设置分辨率进行初始化, 该octree用它的叶节点存放点索引向量, 该分辨率参数描述最低一级octree的最小体素的尺寸, 因此octree的深度是分辨率和点云空间维数的函数, 如果知道点云的边界框, 应该用defineBoundingBox方法把它分配给octree, 然后通过点云指针把所有点增加到octree中。 */ float resolution = 128.0f;//八叉树分辨率即体素(长宽高)的大小 //初始化octree pcl::octree::OctreePointCloudSearch<pcl::PointXYZ> octree (resolution); octree.setInputCloud (cloud_ptr);//输入点云 指针 octree.addPointsFromInputCloud ();//构建octree 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); // ====【1】==体素内近邻搜索 Neighbors within voxel search std::vector<int> pointIdxVec;//存储体素近邻搜索的结果向量 if(octree.voxelSearch(searchPoint,pointIdxVec)) //执行搜索,返回结果到pointIdxVe { // 打印搜索点信息 std::cout << "Neighbors within voxel search at (" << searchPoint.x << " " << searchPoint.y << " " << searchPoint.z << ")" << std::endl; // 打印寻找到的体素近邻点 for (size_t i = 0; i < pointIdxVec.size (); ++i) std::cout << " " << cloud.points[pointIdxVec[i]].x << " " << cloud.points[pointIdxVec[i]].y << " " << cloud.points[pointIdxVec[i]].z << std::endl; } // 【2】==== K近邻搜索 ==K nearest neighbor search int K = 10;//搜寻点 附近的 点数 std::vector<int> pointIdxNKNSearch;//存储k近邻搜索 点索引结果 std::vector<float> pointNKNSquaredDistance; //与上面对应的平方距离 std::cout << "K nearest neighbor search at (" << searchPoint.x << " " << searchPoint.y << " " << searchPoint.z << ") with K=" << K << std::endl; if (octree.nearestKSearch (searchPoint, K, pointIdxNKNSearch, pointNKNSquaredDistance) > 0) { 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; } //====【3】=====半径内近邻搜索====Neighbors within radius search 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 (octree.radiusSearch (searchPoint, radius, pointIdxRadiusSearch, pointRadiusSquaredDistance) > 0) { 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; }
无序点云数据集的空间变化检测
/* 无序点云数据集的空间变化检测 pcl::octree::OctreePointCloudChangeDetector octree是一种用于管理稀疏3D数据的树状数据结构, 我们学习如何利用octree实现用于多个无序点云之间的空间变化检测, 这些点云可能在尺寸、分辨率、密度和点顺序等方面有所差异。 通过递归地比较octree的树结构,可以鉴定出由octree产生的 体素组成之间的区别所代表的空间变化, 此外,我们解释了如何使用PCL的octree“双缓冲”技术, 以便能实时地探测多个点云之间的空间组成差异。 */ #include <pcl/point_cloud.h> #include <pcl/octree/octree_pointcloud_changedetector.h> #include <iostream> #include <vector> #include <ctime> int main (int argc, char** argv) { srand ((unsigned int) time (NULL));//随机数种子 //=====【1】八叉树分辨率即体素的大小=========== float resolution = 32.0f; //=====【2】初始化空间变化检测对象=============== pcl::octree::OctreePointCloudChangeDetector<pcl::PointXYZ> octree (resolution); // ======【3】新建一个点云 cloudA pcl::PointCloud<pcl::PointXYZ>::Ptr cloudA_ptr (new pcl::PointCloud<pcl::PointXYZ> ); pcl::PointCloud<pcl::PointXYZ>& cloudA = *cloudA_ptr; // 为点云 cloudA 产生数据 所生成的点数据用于建立八叉树octree对象。 cloudA.width = 128; cloudA.height = 1; cloudA.points.resize (cloudA.width * cloudA.height); for (size_t i = 0; i < cloudA.points.size (); ++i) { cloudA.points[i].x = 64.0f * rand () / (RAND_MAX + 1.0f); cloudA.points[i].y = 64.0f * rand () / (RAND_MAX + 1.0f); cloudA.points[i].z = 64.0f * rand () / (RAND_MAX + 1.0f); } //======【4】添加点云到八叉树,构建八叉树 ======== octree.setInputCloud (cloudA_ptr);//设置输入点云 octree.addPointsFromInputCloud ();//从输入点云构建八叉树 // 点云cloudA是参考点云,用其建立的八叉树对象描述它的空间分布 //OctreePointCloudChangeDetector类继承自Octree2BufBase类, //Octree2BufBase类允许同时在内存中保存和管理两个octree, //另外,它应用了内存池,该机制能够重新利用已经分配了的节点对象, //因此减少了在生成多个点云八叉树对象时昂贵的内存分配和释放操作。 //通过访问“octree.switchBuffers()”, //重置了八叉树octree对象的缓冲区,但把之前的octree数据仍保留在内存中。 octree.switchBuffers ();// 交换八叉树缓存,但是cloudA对应的八叉树结构仍在内存中 // ======【5】新建另一个点云 cloudB===================== pcl::PointCloud<pcl::PointXYZ>::Ptr cloudB_ptr (new pcl::PointCloud<pcl::PointXYZ> ); pcl::PointCloud<pcl::PointXYZ>& cloudB = *cloudB_ptr; // 为点云 cloudB 产生数据 该点云用于建立新的八叉树结构, // 该新的八叉树与前一个cloudA对应的八叉树共享octree对象,但同时在内存中驻留。 cloudB.width = 128; cloudB.height = 1; cloudB.points.resize (cloudB.width * cloudB.height); for (size_t i = 0; i < cloudB.points.size (); ++i) { cloudB.points[i].x = 64.0f * rand () / (RAND_MAX + 1.0f); cloudB.points[i].y = 64.0f * rand () / (RAND_MAX + 1.0f); cloudB.points[i].z = 64.0f * rand () / (RAND_MAX + 1.0f); } //添加 cloudB到八叉树 octree.setInputCloud (cloudB_ptr); octree.addPointsFromInputCloud (); /* 为了检索到获取存在于cloudB的点集R,此R并没有cloudA中元素, B - B交A 可以调用getPointIndicesFromNewVoxels方法,通过探测两个八叉树之间体素的不同, 它返回cloudB中新加点的索引的向量,通过索引向量可以获取R点集。 很明显,这样就探测了cloudB相对于cloudA变化的点集, 但是只能探测在cloudA上增加的点集,而不能探测在cloudA上减少的点集。 */ std::vector<int> newPointIdxVector; //存储新加点的索引的向量 //获取前一cloudA对应的八叉树 在 cloudB对应八叉树中 A内没有的点集 octree.getPointIndicesFromNewVoxels (newPointIdxVector); //打印结果点到标准输出 std::cout << "Output from getPointIndicesFromNewVoxels:" << std::endl; for (size_t i = 0; i < newPointIdxVector.size (); ++i) std::cout << i << "# Index:" << newPointIdxVector[i] << " Point: " << cloudB.points[newPointIdxVector[i]].x << " " << cloudB.points[newPointIdxVector[i]].y << " " << cloudB.points[newPointIdxVector[i]].z << std::endl; return 0; }