第三周PCL学习(三)

这周学习目录
1、可视化
2、深度图像

一、可视化Visualization
1.1 可视化的基本概念
可视化(Visualization)是利用计算机图形学和图像处理技术,将数据转换成图形或图像在屏幕上显示出来,并进行交互处理的理论、方法和技术。
1.2 PCLVisualizer介绍

class pcl: : visualization: : PCL Visualizer
类PCLVisuafizer为PCL可视化3D点云的主要类。其内部实现了添加各种3D对象以及交互实现等,比其他类实现的功能更齐全。

类PCLVisualizer的关键函数说明:

  • PCL Visualizer (int &.argc, char **argv, canst std: : string &.name="",PCLVisualizerlnteractorStyle *style= PCLVisualizerlnteractorStyle:: New (),const bool create_mteractor true):重构函数,其中name为创建窗口名,style为交互类实现,默认为PCLVisualizerlnteractorStyle对象,create_interactor设置是否重建 交互对象,默认是创建的。
  • void spin () 调用内部渲染函数,重新渲染输出。
  • void spinOnce (int time 1, bool force_redraw false):调用内部渲染函数一次,重新渲染输出时间最大不超过time,单位ms,force_ re­draw设置是否强制 重新绘制。
  • void addCoordinateSystem (double scale =1.0 , int viewport 0):在坐标原点(0,0,0)添加指示坐标轴,viewport为需要添加的视口,默认在所有视口中都添加,scale设置指示坐标轴的放大系数。
  • void setBackgroundColor (const double &.r, const double &.g, canst double &.b, int viewport = O) 设置指定viewport 视口的背景色。
    等等还有很多函数。
1.3 CloudViewer介绍

class pcl: : visualization: : CloudViewer
类CloudViewer实现创建点云可视化的窗口,以及相关的可视化功能。

CloudViewer的关键成员函数

  • CloudViewer (const std: :string &.window_name) :构建可视化点云窗口,窗口名为window_name。
  • void showCloud (const ColorCloud: : ConstPtr &.cloud, const std: : string&. cloudname = “cloud”): 在可视化点云窗口中显示 cloud对应的点云,考虑到多个点云用键值cloudname 来限定具体是哪个点云。
  • bool wasStopped (int millis_to_ wait= 1) :判断用户是否已关闭窗口,如果是则需要注销窗口,millis_to_wait为在注销窗口之前的等待。
  • void runOnVisualizationThread (VizCallable x, const std: : string & key = " callable") :在窗口运行期间处理x 回调函数,key为键值标识此回调函数,直到窗口关闭,可以多次使用回调函数。**重点:**此X为回调函数(返回值为空,参数是pcl::visualization::PCLVisualizer &viewer)
  • void run On VisualizationThreadOnce (VizCallable x) 同上,但只调用回询函数一次。
a、CloudViewer实现:
// 使用CloudViewer,CloudViewer是PCLVisualizer的子类,所以可以传参,进行设置背景颜色,
// CloudViewer是没有setBackground属性
#include <vtkAutoInit.h>
VTK_MODULE_INIT(vtkRenderingOpenGL);
VTK_MODULE_INIT(vtkInteractionStyle);

#include<thread>
#include<pcl/visualization/cloud_viewer.h>
#include<pcl/point_cloud.h>
#include<pcl/io/pcd_io.h>
#include<pcl/point_types.h>

using namespace std;
static int num = 0;
//int user_data;
// 设置背景
void viewerOneOff(pcl::visualization::PCLVisualizer &viewer) {
	viewer.setBackgroundColor(1, 0.5, 1);  // 设置背景颜色
	// 设置点云尺寸大小
	viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3);
}

//设置文字
void viewerText(pcl::visualization::PCLVisualizer &viewer) {

	stringstream s;  // 定义字符串
	s << " viewer loop " << num++ ; // 输出文字
	viewer.removeShape("Text", 0);  // 删除“Text”形状
	//viewer.addText(s.str(), 100, 200, "Text", 0);  // 定义文字框
	viewer.setPosition(100, 200);
	pcl::PointXYZ position;
	position.x = 1.0;
	position.y = 1.0;
	position.z = 1.0;
	viewer.addText3D(s.str(), position, 0.3, 0.3, 0.3,0.4,"Text",0);  // 将Text3D添加到“Text”形状里
	// user_data++;
}


void getCube(pcl::visualization::PCLVisualizer& viewer) {
	num++;
	//cout << "num : "<< num << endl;
	string cube = "cube" + num;
	float r = rand() + 0.1;
	float b = rand() + 0.2;
	float g = rand() + 0.3;
	viewer.addCube(0, 1, 0, 1, 0, 1, r, g, b, "cube" + num , 0);
	//std::cout <<cube<<"  run !!!" << std::endl;
}

int main() {
	pcl::visualization::CloudViewer viewer("可视化");
	//pcl::PointCloud<pcl::PointXYZ> cloud;  // 在viewer.showCloud()作为参数是会报错的,因为其接受Ptr参数

	// 当参数为PointXYZRGB就会加载点云颜色数据,如果为PointXYZ,则会输出白色数据。
	pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZRGB>());
	pcl::io::loadPCDFile("maize.pcd", *cloud);
	//if (pcl::io::loadPCDFile("maize.pcd", *cloud)) {
	//	cout << "w文件不存在" << endl;
	//	return -1;
	//}
	// 加载点云数据
	viewer.showCloud(cloud);
	// 一次性线程,加载一次viewerOneOff
	viewer.runOnVisualizationThreadOnce(viewerOneOff);
	// 多次加载
	viewer.runOnVisualizationThread(viewerText);
	
	while (!viewer.wasStopped()) {
		//user_data++;
		// 可以加一些其他的活动
		std::this_thread::sleep_for(100ms);
		viewer.runOnVisualizationThreadOnce(getCube);
	}
	
	return 0;
}

运行结果:
在这里插入图片描述

1.3 Range_image_visualizer的介绍
可视化深度图像的两种方法,在3D视窗中以点云形式进行可视化(深度图像来源于点云),另一种是将深度值映射为颜色,从而以彩色图像方式可视化深度图像。
#include <iostream>
#include <boost/thread/thread.hpp>  // boost中的线程
#include <pcl/common/common_headers.h>
#include <pcl/pcl_macros.h>
#include <pcl/range_image/range_image.h>  // 深度图像
#include <pcl/io/pcd_io.h>  // 读取pcd数据
#include <pcl/visualization/range_image_visualizer.h>  // 深度图像可视化要通过这个头文件
#include <pcl/visualization/pcl_visualizer.h>  // 可视化
#include <pcl/console/parse.h>


typedef pcl::PointXYZ PointType;  // 重定义
// 全局参数
float angular_resolution = 0.5f;  // 分辨率
pcl::RangeImage::CoordinateFrame coordinate_frame = pcl::RangeImage::CAMERA_FRAME;  // 定义深度图像坐标系为照相机坐标系
pcl::RangeImage::CoordinateFrame coordinate_frame1 = pcl::RangeImage::LASER_FRAME;  // 定义深度图像坐标系为照相机坐标系
bool live_update = false;  // 设置更新为false

// -----打印帮助-----
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"
		<< "-l           live update - update the range image according to the selected view in the 3D viewer.\n"
		<< "-h           this help\n"
		<< "\n\n";
}


// 深度图像位置,Eigen::Affine3f&可以保存摄像头位置
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]);

	//viewer.camera_.pos[0] = pos_vector[0];
	//viewer.camera_.pos[1] = pos_vector[1];
	//viewer.camera_.pos[2] = pos_vector[2];
	//viewer.camera_.focal[0] = look_at_vector[0];
	//viewer.camera_.focal[1] = look_at_vector[1];
	//viewer.camera_.focal[2] = look_at_vector[2];
	//viewer.camera_.view[0] = up_vector[0];
	//viewer.camera_.view[1] = up_vector[1];
	//viewer.camera_.view[2] = up_vector[2];
	viewer.updateCamera();
}

// -----Main-----
int
main03(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, "-r", angular_resolution) >= 0)
		std::cout << "Setting angular resolution to " << angular_resolution << "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 = pcl::deg2rad(angular_resolution);

	// 读取给定的pcd点云文件或者自行创建随机点云
	pcl::PointCloud<PointType>::Ptr point_cloud_ptr(new pcl::PointCloud<PointType>);
	pcl::PointCloud<PointType>& point_cloud = *point_cloud_ptr;
	Eigen::Affine3f scene_sensor_pose(Eigen::Affine3f::Identity());
	std::vector<int> pcd_filename_indices = pcl::console::parse_file_extension_argument(argc, argv, "pcd");
	if (!pcd_filename_indices.empty())
	{
		std::string filename = argv[pcd_filename_indices[0]];
		if (pcl::io::loadPCDFile(filename, point_cloud) == -1)
		{
			std::cout << "Was not able to open file \"" << filename << "\".\n";
			printUsage(argv[0]);
			return 0;
		}
		scene_sensor_pose = Eigen::Affine3f(Eigen::Translation3f(point_cloud.sensor_origin_[0],
			point_cloud.sensor_origin_[1],
			point_cloud.sensor_origin_[2])) *
			Eigen::Affine3f(point_cloud.sensor_orientation_);
	}
	else
	{
		std::cout << "\nNo *.pcd file given => Genarating example point cloud.\n\n";
		for (float x = -0.5f; x <= 0.5f; x += 0.01f)
		{
			for (float y = -0.5f; y <= 0.5f; y += 0.01f)
			{
				PointType point;  point.x = x;  point.y = y;  point.z = 2.0f - y;
				point_cloud.points.push_back(point);
			}
		}
		point_cloud.width = (int)point_cloud.points.size();
		point_cloud.height = 1;
	}
	//从点云创建深度图像对象
	float noise_level = 0.0;
	float min_range = 0.0f;
	int border_size = 1;

	// boost::shared_ptr共享指针,代表该指针在整个程序全局中都可以使用,不用担心内存错误
	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);
	//创建3D视图并且添加点云进行显示
	pcl::visualization::PCLVisualizer viewer("3D Viewer");
	viewer.setBackgroundColor(1, 1, 1);
	// 自定义点云颜色(白色)
	pcl::visualization::PointCloudColorHandlerCustom<pcl::PointWithRange> range_image_color_handler(range_image_ptr, 0, 0, 0);
	// 添加点云数据到viewer中,第一个参数是图像指针,第二个参数是颜色属性,第三个参数是ID号
	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);  // 添加坐标系
	pcl::visualization::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);
	//主循环
	while (!viewer.wasStopped())
	{
		// 深度图像渲染一次
		range_image_widget.spinOnce();
		// 3D viewer渲染一次
		viewer.spinOnce();
		// 更新间隔10ms
		pcl_sleep(0.01);
		if (live_update)
		{
			scene_sensor_pose = viewer.getViewerPose();
			range_image.createFromPointCloud(point_cloud, angular_resolution, 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);
		}
	}
}
程序运行:首先通过cmake把cpp文件和CmakeLists文件进行编译生成Debug文件,然后再命令行中输入命令运行程序。
./range_image_visualization.exe -h :是帮助的意思,可以看一下命令的含义所在。
./range_image_visualization.exe rabbit.pcd -r:是当分辨率为0.5时的深度图像。但是不是动态的。
./range_image_visualization.exe rabbit.pcd -l:是动态深度图像,当移动3D Viewer中的模型位置时,深度图像也会发生变化。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

深度的描述:图中不可区视域(代表无限远)用浅绿光表示,远距离区域(范围无限远激光,扫描时可获取信息)用淡蓝色表示,注意两者虽然都是无穷远,但前者是在激光扫描不可见区域,后者是在可见区域的无穷远。
1.4 实例三
ModelCoefficients 介绍:可以定义ModelCoefficients 得到各种各样的图形。前提是知道图形的函数方程,如下ax+by+cz+d = 0是一个平面方程,所以
	pcl::ModelCoefficients coeffs;
	coeffs.values.push_back(0.0);  // 是a的值
	coeffs.values.push_back(0.0);  // b的值
	coeffs.values.push_back(1.0);  // c的值
	coeffs.values.push_back(0.0);  // d的值
分屏的介绍:意思就是在把一个Viewer分成n块。这个就要利用到viewport
	int v1(0);  // 定义一个viewport为0
	viewer->createViewPort(0.0, 0.0, 0.5, 1.0, v1);   // 创建一个视口,索引为v1,(0.0,0.0)可以看成一个窗口左上角的位置,
	(0.5,1.0)代表窗口x轴的一半(宽度),1.0代表窗口的高度
	viewer->setBackgroundColor(0, 0, 0, v1);  / /定义背景颜色
	int v2(0);  // 定义另一个viewport为1
	viewer->createViewPort(0.5, 0.0, 1.0, 1.0, v2);
	viewer->setBackgroundColor(0.3, 0.3, 0.3, v2);  // 定义背景颜色
上面几行代码创建新的视口,所需的4个参数是视口在X轴的最小值、最大值、 Y轴的最小值、最大值,取值在0~1。
 我们创建的视口分布于窗口的左半部分,最后 一个字符串参数,用来唯一标志该视口,在其他改变该视口内容的调用中需要以该唯 一标志为参数
/* \author Geoffrey Biggs */

#include <iostream>
#include <boost/thread/thread.hpp>
#include <pcl/common/common_headers.h>
#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>
// 帮助
void
printUsage1(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";
}

boost::shared_ptr<pcl::visualization::PCLVisualizer> simpleVis(pcl::PointCloud<pcl::PointXYZ>::ConstPtr cloud)
{
	//创建3D窗口并添加点云
	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, 1, "sample cloud");
	viewer->addCoordinateSystem(1.0);
	viewer->initCameraParameters();  // 初始化所有参数
	return (viewer);
}

boost::shared_ptr<pcl::visualization::PCLVisualizer> rgbVis(pcl::PointCloud<pcl::PointXYZRGB>::ConstPtr cloud)
{
	//创建3D窗口并添加点云	
	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();
	return (viewer);
}

boost::shared_ptr<pcl::visualization::PCLVisualizer> customColourVis(pcl::PointCloud<pcl::PointXYZ>::ConstPtr cloud)
{
	//创建3D窗口并添加点云
	boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer(new pcl::visualization::PCLVisualizer("3D Viewer"));
	viewer->setBackgroundColor(0, 0, 0);
	pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> single_color(cloud, 0, 255, 0);
	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);
}

boost::shared_ptr<pcl::visualization::PCLVisualizer> normalsVis(
	pcl::PointCloud<pcl::PointXYZRGB>::ConstPtr cloud, pcl::PointCloud<pcl::Normal>::ConstPtr normals)
{
	//创建3D窗口并添加点云其包括法线  
	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);
}


boost::shared_ptr<pcl::visualization::PCLVisualizer> shapesVis(pcl::PointCloud<pcl::PointXYZRGB>::ConstPtr cloud)
{
	//创建3D窗口并添加点云    
	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");
	viewer->addSphere(cloud->points[0], 0.2, 0.5, 0.5, 0.0, "sphere");
	//在其他位置添加基于模型参数的平面及圆锥体
	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);
}


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)
{
	// 创建3D窗口并添加显示点云其包括法线
	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);
	viewer->setBackgroundColor(0, 0, 0, v1);
	viewer->addText("Radius: 0.01", 10, 10, "v1 text", v1);
	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);
}


unsigned int text_id = 0;
void keyboardEventOccurred(const pcl::visualization::KeyboardEvent &event,
	void* viewer_void)
{
	boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer = *static_cast<boost::shared_ptr<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;
	}
}

void mouseEventOccurred(const pcl::visualization::MouseEvent &event,
	void* viewer_void)
{
	boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer = *static_cast<boost::shared_ptr<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);
	}
}

boost::shared_ptr<pcl::visualization::PCLVisualizer> interactionCustomizationVis()
{
	cout << "Interaction...."<<endl;
	boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer(new pcl::visualization::PCLVisualizer("3D Viewer"));
	viewer->setBackgroundColor(0, 0, 0);
	viewer->addCoordinateSystem(1.0);

	viewer->registerKeyboardCallback(keyboardEventOccurred, (void*)&viewer);
	viewer->registerMouseCallback (mouseEventOccurred, (void*)&viewer);

	return (viewer);
}
// -----Main-----
int
main04(int argc, char** argv)
{
	// 解析命令行参数
	if (pcl::console::find_argument(argc, argv, "-h") >= 0)
	{
		printUsage1(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
	{
		printUsage1(argv[0]);
		return 0;
	}
	// 自行创建一随机点云
	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";
	// 以椭圆为边线沿z轴拉伸获取其点云,并赋予红绿蓝渐变色。
	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;
	// 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);
	//  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();
	}
	// 主循环
	while (!viewer->wasStopped())
	{
		viewer->spinOnce(100);
		boost::this_thread::sleep(boost::posix_time::microseconds(100000));
	}
}

运行结果:一个是分窗口的结果,一个是添加图形的结果
在这里插入图片描述在这里插入图片描述

二、深度图像
2.1 深度图像的概念
深度图像(Depth Images)也被称为距离影像(Range Images), 是指将从图像采集器到场景中各点的距离(深度)值作为像素值的图像,
它直接反映了景物可见表面的几何形状,利用它可以很方便的解决3D目标描述中的许多问题。 深度图像经过坐标转换可以计算为
点云数据,有规则及必要信息的点云数据也可以反算为深度图像数据。
2.2 深度图像的创建
#include <pcl/range_image/range_image.h>
#include<pcl/visualization/range_image_visualizer.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);  // 将point添加到点云数据中
		}
	}
	pointCloud.width = (uint32_t)pointCloud.points.size();
	pointCloud.height = 1;
	//以1度为角分辨率,从上面创建的点云创建深度图像。
	float angularResolution = (float)(1.0f * (M_PI / 180.0f));
	// 1度转弧度
	float maxAngleWidth = (float)(360.0f * (M_PI / 180.0f));
	// 360.0度转弧度
	float maxAngleHeight = (float)(180.0f * (M_PI / 180.0f));
	// 180.0度转弧度
	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;  // 归一化的的Z缓冲区来创建深度图像
	float minRange = 0.0f;  // 所有在这个半径范围内的数据被忽略
	int borderSize = 1;  // 将在图像周围留下当前视点不可见点的边界
	pcl::RangeImage rangeImage;  // 定义深度图像
	// 从点云数据来创建深度图像
	rangeImage.createFromPointCloud(pointCloud, angularResolution, maxAngleWidth, maxAngleHeight, sensorPose, coordinate_frame, noiseLevel, minRange, borderSize);
	
	std::cout << rangeImage << "\n";

	//显示深度图像
	pcl::visualization::RangeImageVisualizer range_image_widget("Range image");
	range_image_widget.showRangeImage(rangeImage);
	//主循环
	while (!range_image_widget.wasStopped())
	{
		// 深度图像渲染一次
		range_image_widget.spinOnce();
		
		// 更新间隔10ms
		pcl_sleep(0.01);
	}
}
这部分定义了创建深度图像时需要的设置参数,将角度分辨率定义为1°意味着由邻近的像素点所对应的每个光束之间相差1°,
maxAngleWidth=360和maxAn­gleHeight= 180意味着,进行模拟的距离传感器对周围的环境拥有一个完整的360° 视角,
用户在任何数据集下都可以使用此设置,因为最终获取的深度图像将被裁剪到有空间物体存在的区域范围。但是,用户可以
通过减小数值来节省一些计算资源,例如:对于传感器后面没有可以观测的点时,一个水平视角为18矿的激光扫描仪,即
maxAngleWidth= 180 就足够了这样只需要观察距离传感器前面就可以了,因为后面没有需要观察的场景。sensorPose定义
了模拟深度图像获取传感器的6自由度位置,其原始值为横滚角roll,俯仰角pitch、偏航角yaw都为0,coordinate_ frame= CAMERA_FRAME
说明系统的X轴是向右的,Y轴是向下的,Z轴是向前的,另外一个选择是LASER_FRAME,其X轴向前,Y轴向左,Z轴向上。noiseLevel= 0是指使用
一个归一化的Z缓冲器来创建深度图像,但是如果想让邻近点集都落在同一 个像素单元,用户可以设置一个较高的值,例如noiseLevel= 0. 05
可以理解为,深度距离值是通过查询点半径为5cm的圆内包含的点用来平均计算而得到的。如果minRange>O, 则所有模拟器所在位置半径minRange
内的邻近点都将被忽略,即为盲区。在栽剪图像时,如果borderSize>O,将在图像周围留下当前视点不可见点的边界。

在这里插入图片描述在这里插入图片描述

2.3 深度图像边界的提取

#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;
// --------------------
// -----参数-----
// --------------------
float angular_resolution = 0.5f;  // 设置角度分辨率
pcl::RangeImage::CoordinateFrame coordinate_frame = pcl::RangeImage::CAMERA_FRAME;  // 设置坐标系
bool setUnseenToMaxRange = false;  // 设置所有不能观察的点都是远距离的
// --------------
// -----帮助-----
// --------------
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";
}
// --------------
// -----主函数-----
// --------------
int
main02(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";
	}
	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);
	// ------------------------------------------------------------------
	// -----读取pcd文件,如果没有给出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())
	{
		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;
	}
	// -----------------------------------------------
	// -----从点云创建深度图像-----
	// -----------------------------------------------
	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();
	// --------------------------------------------
	// -----打开三维浏览器并添加点云-----
	// --------------------------------------------
	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");
	// -------------------------
	// -----提取边界-----
	// -------------------------
	pcl::RangeImageBorderExtractor border_extractor(&range_image);
	pcl::PointCloud<pcl::BorderDescription> border_descriptions;
	border_extractor.compute(border_descriptions);
	// ----------------------------------
	// -----在三维浏览器中显示点集-----
	// ----------------------------------
	pcl::PointCloud<pcl::PointWithRange>::Ptr border_points_ptr(new pcl::PointCloud<pcl::PointWithRange>), veil_points_ptr(new pcl::PointCloud<pcl::PointWithRange>), 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");
	//-------------------------------------
	// -----在深度图像中显示点集-----
	// ------------------------------------
	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");
	// -------------------------------------
	//--------------------
	// -----主循环-----
	//--------------------
	while (!viewer.wasStopped())
	{
		range_image_borders_widget->spinOnce();
		viewer.spinOnce();
		pcl_sleep(0.01);
	}
}
	pcl::RangeImageBorderExtractor border_extractor(&range_image);
	pcl::PointCloud<pcl::BorderDescription> border_descriptions;
	border_extractor.compute(border_descriptions);
	此部分创建了 RangelmageBorderExtractor 这个对象,给了它深度图像,并且计算后存储边界信息在 border_descriptions 中。
运行结果。
./range_image_border_extraction.exe rabbit.pcd -r :设置角度分辨率为0.5时的结果
./range_image_border_extraction.exe rabbit.pcd -c:设置不同的坐标系
./range_image_border_extraction.exe rabbit.pcd -m:如果用户没有这些远距离的点云,则可采用命令行参数-m,这样设置所有不能观察到的点都为远距离的

在这里插入图片描述

./range_image_border_extraction.exe rabbit.pcd -r :设置角度分辨率为0.5时的运行结果

在这里插入图片描述在这里插入图片描述

./range_image_border_extraction.exe rabbit.pcd -m的运行结果如下

在这里插入图片描述在这里插入图片描述

三、总结
	本周学习了可视化和深度图像的表示方法,明白了使用这些代码的意义所在,这些代码都来源于点云库PCL学习教程和PCL官方文档。
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: CSDN PCL学习教程是一个非常值得推荐的学习资源。PCL(Point Cloud Library)是一个开源的机器人视觉库,用于处理和分析点云数据。 CSDN PCL学习教程的内容丰富全面,适合初学者和有一定基础的人士。教程以通俗易懂的语言介绍了PCL库的基本知识和应用,包括点云数据的读取、滤波、特征提取、配准、分割等等。其中还包括了一些实际的案例,通过动手实践可以更好地理解和运用PCL库。 CSDN PCL学习教程的优点是它提供了详细的步骤和示例代码,使学习者能够快速入门和上手。教程中也附有一些常见问题的解答和错误排查的方法,方便遇到困难时进行参考和解决。 此外,CSDN是一个知名的IT技术社区,拥有众多活跃的技术博客和论坛。在学习教程过程中,学习者还能够参与讨论和交流,获取更多的帮助和指导。 总的来说,CSDN PCL学习教程是一个实用、可靠的学习资源,对于想要学习和应用PCL库的人来说是一个不错的选择。它提供了全面的学习内容和实例,能够满足不同层次的学习需求,并能够帮助学习者快速掌握PCL库的使用。 ### 回答2: CSDN PCL学习教程是一个针对计算机视觉领域的开源库Point Cloud Library(PCL)的学习资源。PCL是一个强大的开源点云处理框架,可以用于点云数据的处理、配准、分割、特征提取等多种任务。 该教程提供了丰富的学习内容和实践案例。首先,教程介绍了PCL的基础知识,包括点云数据的表示方式、常用的滤波方法等。接着,教程详细介绍了PCL的各种功能模块,如滤波器、配准、特征描述子等,通过实例演示了它们的使用方法和效果。 此外,教程也提供了一些高级主题的学习资料,如深度学习与点云处理的结合、三维重建等。这些内容可以帮助学习者进一步深入PCL的应用领域。 通过学习CSDN PCL学习教程,学习者能够掌握PCL的基本原理和使用方法,了解点云数据处理的常用技巧。通过实践案例的学习学习者能够将PCL应用到具体的项目中,并具备进行自主研究和开发的能力。 总之,CSDN PCL学习教程是一个全面而丰富的学习资源,对于想要进一步学习和应用点云处理的计算机视觉领域从业者和研究者来说是一个不错的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值