最近开始学习点云处理,发现要使用的PCL库和Eigen库有很多API都没不懂,现在边啃边记录一下。
PointT
来表示点云的类型,但是实际上PointT只是一个总的名称,它有很多种类型:
- PointXYZ:三维XYZ坐标信息
- PointXYZI:除了上述的XYZ坐标信息,还有一个强度信息,intensity
- PointXYZRGB:除了上述的XYZ坐标信息,还有RGB信息
- PointXY:只有XY坐标信息,这种类型用得比较多是在单线雷达里头
- …
pcl::PassThrough pass;
需要设置几个参数,首先是选择沿什么坐标轴进行过滤,然后是设置过滤的范围,然后设置过滤范围内的点云还是范围外的点云 pcl::PassThrough<pcl::PointXYZ> pass;// 创建滤波器对象
pass.setInputCloud (cloud_raw_tf);//设置输入点云
pass.setFilterFieldName ("y");//滤波字段名被设置为Y轴方向
pass.setFilterLimits (-0.2, 0.5);//可接受的范围为薄片marker厚度范围
pass.setFilterLimitsNegative (true);//设置保留范围内 还是 过滤掉范围内
pass.filter (*cloud_no_ground); //执行滤波,保存过滤结果在cloud_final*/
pcl::VoxelGrid filter;
leafsize
越大,则表示体素越大,最后输出的点云点数越少。 pcl::VoxelGrid<pcl::PointXYZ> filter;
filter.setInputCloud(cloud_no_ground_tf);
filter.setLeafSize(0.03f, 0.03f, 0.03f);
filter.filter(*cloud_downsampled);
pcl::StatisticalOutlierRemoval sor;
,MeanK
的值,也就是刚刚说的邻近点的数量,还有setStddevMulThresh
设置标准差倍数 pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor; //remove the outlier
sor.setInputCloud(cloud_downsampled);
sor.setMeanK(5); //K近邻搜索点个数
sor.setStddevMulThresh(1.0); //标准差倍数
sor.setNegative(false); //保留未滤波点(内点)
sor.filter(*cloud_filtered); //保存滤波结果到cloud_filter
还有另一种去离群点的方法:根据空间点半径范围内临近点数量来剔除离群点,对应的类名是 RadiusOutlinerRemoval,设定每个点一定半径范围内要有几个邻点,满足的保留,不满足的剔除。半径和邻点数目的参数分别是setRadiusSearch
和setMinNeighborsInRadius
pcl::RadiusOutlierRemoval<pcl::PointXYZ> pcFilter; //创建滤波器对象
pcFilter.setInputCloud(cloud); //设置待滤波的点云
pcFilter.setRadiusSearch( 0.8); // 设置搜索半径
pcFilter.setMinNeighborsInRadius( 2); // 设置一个内点最少的邻点数目
pcFilter.filter(*cloud_filtered); //滤波结果存储到cloud_filtered
pcl::transformPointCloud(*source_cloud, *target_cloud, transform)
Eigen::Matrix4f T_trans;
T_trans(0,0) = cos(M_PI/2);
T_trans(0,1) = -sin(M_PI/2);
T_trans(0,2) = 0;
T_trans(1,0) = sin(M_PI/2);
T_trans(1,1) = cos(M_PI/2);
T_trans(1,2) = 0;
T_trans(2,0) = 0;
T_trans(2,1) = 0;
T_trans(2,2) = 1;
T_trans(0,3) = 0; //x
T_trans(1,3) = 0; //y
T_trans(2,3) = 0; //z
pcl::transformPointCloud(*source_cloud, *target_cloud, T_trans)
setNearPlaneDistance
)和远平面距离(setFarPlaneDistance
),也是下面图中的前裁剪平面和后裁剪平面,网上随便找的图,但是意思都是一样的。 pcl::FrustumCulling<pcl::PointXYZ> fc_now_cloud;
fc_now_cloud.setInputCloud(cloud_filtered); //设置需要过滤的点云给滤波对象
//std::cout<<"use second ToF? "<
fc_now_cloud.setVerticalFOV(180*yaw_range/M_PI); // BBox V FoV
fc_now_cloud.setHorizontalFOV(180*pitch_range/M_PI); BBox H FoV
//fc_now_cloud.setVerticalFOV(30); // BBox V FoV
//fc_now_cloud.setHorizontalFOV(15); BBox H FoV
fc_now_cloud.setNearPlaneDistance(0.0); //min disinfection distance
fc_now_cloud.setFarPlaneDistance(2.0); //max disinfection distance
//ROS_INFO("pcl::FrustumCulling set param. done");
//transform "frustum" from /usb_cam to /pico_flexx_optical_frame (main) pico_flexx_optical_frame ---> /UV; /usb_cam; /pico_flexx_second_optical_frame
transform_bboxcenter = Eigen::Affine3f::Identity();
transform_bboxcenter.rotate (Eigen::AngleAxisf (-M_PI/2, Eigen::Vector3f::UnitY()));
transform_bboxcenter.rotate (Eigen::AngleAxisf (M_PI/2, Eigen::Vector3f::UnitX()));
transform_bboxcenter.rotate (Eigen::AngleAxisf (-yaw, Eigen::Vector3f::UnitZ()));
if (use_second_LiDAR == true)
{
std::cout<<"camera original pitch: "<< pitch << " extra part: "<< M_PI * 22.5/180 << std::endl << std::endl << std::endl << std::endl;
transform_bboxcenter.rotate (Eigen::AngleAxisf (pitch + M_PI * 22.5/180 , Eigen::Vector3f::UnitY()));
}
else
{
transform_bboxcenter.rotate (Eigen::AngleAxisf (pitch, Eigen::Vector3f::UnitY()));
}
transform_bboxcenter.rotate (Eigen::AngleAxisf ( - yaw, Eigen::Vector3f::UnitZ()));
camera_now_pose = Eigen::Matrix4f::Identity();
camera_now_pose(0, 0) = transform_bboxcenter(0, 0);
camera_now_pose(1, 0) = transform_bboxcenter(1, 0);
camera_now_pose(2, 0) = transform_bboxcenter(2, 0);
camera_now_pose(0, 1) = transform_bboxcenter(0, 1);
camera_now_pose(1, 1) = transform_bboxcenter(1, 1);
camera_now_pose(2, 1) = transform_bboxcenter(2, 1);
camera_now_pose(0, 2) = transform_bboxcenter(0, 2);
camera_now_pose(1, 2) = transform_bboxcenter(1, 2);
camera_now_pose(2, 2) = transform_bboxcenter(2, 2);
camera_now_pose(0, 3) = 0.065; //x
camera_now_pose(1, 3) = 0.015; //y
camera_now_pose(2, 3) = 0.0; //z
fc_now_cloud.setCameraPose(camera_now_pose);
fc_now_cloud.filter(*cloud_frustum);
pcl::NormalEstimation
//计算表面法线
pcl::NormalEstimation<pcl::PointXYZ,pcl::Normal> ne_src;
ne_src.setInputCloud(cloud_src);
pcl::search::KdTree< pcl::PointXYZ>::Ptr tree_src(new pcl::search::KdTree< pcl::PointXYZ>()); //创建一个空的kdtree对象,并把它传递给法线估计对象
ne_src.setSearchMethod(tree_src);
pcl::PointCloud<pcl::Normal>::Ptr cloud_src_normals(new pcl::PointCloud< pcl::Normal>);
ne_src.setRadiusSearch(0.02); //使用半径在查询点周围2厘米范围内的所有邻元素
ne_src.compute(*cloud_src_normals); // 计算特征值
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);
if(pcl::io::loadPCDFile<pcl::PointXYZ>("test_file.pcd",*cloud)==-1)
{
PCL_ERROR("Couldn't read file test_pcd.pcd\n");
return(-1);
}
for(size_t i=0;i<cloud->points.size();++i)
printf("%lf %lf %lf", cloud->points[i].x, cloud->points[i].y, cloud->points[i].z );
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_output (new pcl::PointCloud<pcl::PointXYZ>);
cloud_output->points.resize(100); //里面定义的100是这个点云的size,根据实际情况设定,这点很关键,不要设置多余的,因为多余的点默认是0 0 0,就会在原点有一堆重叠的0 0 0点
cloud_output->points[0].x = 1.0;
cloud_output->points[0].y = 1.1;
cloud_output->points[0].z = 1.2;
...
//自定义里面的xyz值就可以了,然后就可以常规操作,把这些手动输入的点云信息,在rviz可视化出来看看,有没有问题
ros::Publisher cloud_output_Pub = node.advertise<sensor_msgs::PointCloud2>("/cloud_output", 1);
sensor_msgs::PointCloud2 cloud_output_msg;
pcl::toROSMsg(*cloud_output, cloud_output_msg);//把这个点云的msg发出去就可以了
cloud_output_msg.header.frame_id = "odom";//这里也很关键,这些点基于什么坐标系下的,根据实际情况设定
cloud_output_msg.header.stamp = ros::Time::now();
cloud_output_Pub.publish(cloud_output_msg);
变换矩阵有两种使用方式:
(1)普通矩阵 Matrix4f
这个方法不仅能建44的,33也可以,直接改成Matrix3f就行
说回4*4的变换矩阵,还是老结构 [ R T 0 1 ] \begin{bmatrix} R & T \\ 0 & 1 \end{bmatrix} [R0T1]
例程如下:
Eigen::Matrix4f transform_1 = Eigen::Matrix4f::Identity(); // 定义一个单位4*4矩阵
float theta = M_PI/4; // 弧度角
transform_1 (0,0) = cos (theta);
transform_1 (0,1) = -sin(theta);
transform_1 (1,0) = sin (theta);
transform_1 (1,1) = cos (theta);
// 在 X 轴上定义一个 2.5 米的平移.
transform_1 (0,3) = 2.5;
// 打印变换矩阵
std::cout << transform_1 << std::endl;
(2)使用 Affine3f
,这个是专门用于矩阵的平移+旋转,这种类型有两个成员,分别对应平移和旋转
例程如下:
Eigen::Affine3f transform_2 = Eigen::Affine3f::Identity();
// 在 X 轴上定义一个 2.5 米的平移.
transform_2.translation() << 2.5, 0.0, 0.0; // X Y Z
// Eigen::AngleAxisf 有两个参数,第一个是要旋转的角度,第二个设置绕什么轴转动
// 和前面一样的旋转; Z 轴上旋转 theta 弧度,AngelAxisf(theta, axis)得到一个3*3的旋转矩阵
transform_2.rotate (Eigen::AngleAxisf (theta, Eigen::Vector3f::UnitZ()));
(3)在不知道矩阵大小的情况下建立一个动态矩阵,使用 Eigen::Matrix
有时矩阵的大小是在计算过程中才能确定的,所以就需要用到上面这个动态参数来表示行和列。
注意,Eigen::Matrix
定义的矩阵最大是,50*50的,所以如果行或列超过50,只能用上面的动态参数表示。