#每周一篇论文2#[感知] 激光雷达外参数自标定

激光雷达外参数自标定

基于激光雷达的车载三维重建系统和感知系统工作时需要对激光雷达的外参数(三个旋转参数和三个平移参数)进行标定。
激光雷达的外参数的标定是指求解激光雷达测量坐标系相对于其他传感器测量坐标系的相对变换关系,即旋转平移变换矩阵。
本方法进行激光雷达外参标定使用的方法是求解激光雷达的地平面与理想地平面的变换矩阵,方法可分为以下步骤:

  • 点云滤波
  • 设置ROI
  • 使用RANSCAN算法进行地平面分割
  • 提取激光雷达的地平面向量,计算激光雷达的地平面向量与理想地 平面向量[0,0,1] 的变换矩阵
  • 对激光雷达外参标定进行系统评价
  • 使用DBSCAN算法对标定参数进行聚类,计算最大类的标定参数的均值,最为最优标定标定输出。

点云滤波

一般离激光雷达距离越近地面数据越多,这里截取激光雷达前后左右50米的作为有效数据。

/**********************************************************************************************************
	功能: 滤波

	输入:points(原始数据)、roi_point(滤波后数据);

	返回:...
**********************************************************************************************************/
void set_roi(PointCloudT &points, PointCloudT::Ptr roi_point)
{
    // 创建条件滤波器:通过x, y范围来过滤点云
    pcl::PassThrough<pcl::PointXYZI> pass;
    // false表示保持这些点在范围内
    pass.setFilterLimitsNegative(false);
    // 设置输入点云
    pass.setInputCloud(points.makeShared());
    // 设置滤波对象x
    pass.setFilterFieldName("x");
    //  设置范围
    pass.setFilterLimits(-25.0, 25.0);
    // 进行滤波,并将返回值赋值给points
    pass.filter(points);
    // 设置滤波对象y
    pass.setInputCloud(points.makeShared());
    pass.setFilterFieldName("y");
    pass.setFilterLimits(-25.0, 20.0);
    pass.filter(*roi_point);
}

设置ROI

对点云数据的z轴数据进行排序,截取Z/3点云数据作为感兴趣区域。(地面的z轴小)

/**********************************************************************************************************
    @brief Compare function to sort points. Here use z axis.
    @return z-axis accent
    // 用户排序的参数
**********************************************************************************************************/
bool point_cmp(PointT a, PointT b){
    return a.z<b.z;
}

void extract_initial_seeds_(const PointCloudT::Ptr p_sorted, const PointCloudT::Ptr g_seeds_pc)
{
    sort((*p_sorted).points.begin(),(*p_sorted).end(),point_cmp);
    g_seeds_pc->clear();
    for(int i=0;i<p_sorted->points.size()/4;i++)
    {
        g_seeds_pc->points.push_back(p_sorted->points[i]);
    }
   
}

地平面分割

使用ransacn数据对ROI数据进行最大平面检测。(滤波、ROI阶段、点云特性,当前最大平面基本为地平面)

/**********************************************************************************************************
	功能: 使用 ransac检测地面

	输入:cloud_filtered(ROI数据)、out_point_cloud(地平面数据);

	返回:...
**********************************************************************************************************/
void ransac_ground_detect(PointCloudT &cloud_filtered, PointCloudT & out_point_cloud)
{
    // 模型的系数 
    pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients);
    // 地面上各点的指数 the indices of the points in the ground
    pcl::PointIndices::Ptr inliers (new pcl::PointIndices);
    //使用SAC_RANSAC进行分割
    pcl::SACSegmentation<PointT> seg;
    //设置对估计模型优化
    seg.setOptimizeCoefficients (true);
    //if the step before is true the following config is obligatory
    //设置分割模型类型,检测平面
    seg.setModelType (pcl::SACMODEL_PLANE);
    //设置方法【聚类或随机样本一致性】
    seg.setMethodType (pcl::SAC_RANSAC);
    // 设置内点到模型的距离允许最大值
    seg.setDistanceThreshold (0.1);
    // 设置输入点云
    seg.setInputCloud (cloud_filtered.makeShared());
    //实现了对接地点指数和接地面系数的分割和保存
    seg.segment (*inliers, *coefficients);
    // 可以通过内点的个数判断地面分割是否正确
    pcl::copyPointCloud(cloud_filtered, inliers->indices, out_point_cloud);
}

计算变换矩阵

/**********************************************************************************************************
	功能: 进行平面拟合 得到法向量normal_和 th_dist_d_.计算法向量normal_与[0,0,1]向量的变换矩阵

	输入:g_ground_pc(最大平面数据)、roll(X轴)、pitch(Y轴)、yaw(Z轴):欧拉角;
        (x, y, z):位移;

	返回:...
**********************************************************************************************************/
//  和 th_dist_d_. 更新拟合平面的A B C D
void estimate_plane_(PointCloudT::Ptr g_ground_pc, double &roll, double &pitch, double &yaw, double &offset_z)
{
    MatrixXf normal_;
    // Create covarian matrix in single pass.
    // TODO: compare the efficiency.
    Eigen::Matrix3f cov;
    Eigen::Vector4f pc_mean; // 归一化坐标值
    // computeMeanAndCovarianceMatrix主要是PCA过程中计算平均值和协方差矩阵 ,对地面点(最小的n个点)进行计算协方差和平均值
    pcl::computeMeanAndCovarianceMatrix(*g_ground_pc, cov, pc_mean);
    // Singular Value Decomposition: SVD
    JacobiSVD<MatrixXf> svd(cov,Eigen::DecompositionOptions::ComputeFullU);
    // use the least singular vector as normal
    normal_ = (svd.matrixU().col(2)); // 取最小的特征值对应的特征向量作为法向量
    // mean ground seeds value
    Eigen::Vector3f seeds_mean = pc_mean.head<3>(); // seeds_mean 地面点的平均值

    // according to normal.T*[x,y,z] = -d
    offset_z = -(normal_.transpose()*seeds_mean)(0,0); // 计算d   D=d
    // set distance threhold to `th_dist - d`
    //th_dist_d_ = th_dist_ - offset_z;// 这里只考虑在拟合的平面上方的点 小于这个范围的点当做地面
    
    // return the equation parameters
    
    Eigen::MatrixXf vectorAfter = MatrixXf::Zero(3, 1);
    vectorAfter << 0.0, 0.0,1.0;
    Eigen::Matrix4f rotationMatrix = rotation_matrix_from_vectors(normal_,vectorAfter);
    
    
    
    Eigen::Matrix3d rotation_matrix;
    rotation_matrix<<   rotationMatrix(0, 0), rotationMatrix(0, 1), rotationMatrix(0, 2),
                        rotationMatrix(1, 0), rotationMatrix(1, 1), rotationMatrix(1, 2),
                        rotationMatrix(2, 0), rotationMatrix(2, 1), rotationMatrix(2, 2);

    // 2,1,0;
    Eigen::Vector3d eulerAngle = rotation_matrix.eulerAngles(0,1,2);
    yaw = eulerAngle(2);
    roll = eulerAngle(1);
    pitch = eulerAngle(0);
 
          
    yaw = fmod( ( yaw + 2*M_PI),  (2*M_PI) ) / M_PI * 180;
    roll = fmod( ( roll + 2*M_PI),  (2*M_PI) ) / M_PI * 180;
    pitch = fmod( ( pitch + 2*M_PI),  (2*M_PI) ) / M_PI * 180;
    
    /*
	std::cout << "yaw:"<< yaw << ", roll:" << roll << ", pitch:" << pitch << std::endl;  //45 -0 0
	std::cout << "offset_z:"<< offset_z << std::endl;  //45 -0 0
	cout<<  rotationMatrix(0, 0) <<","<< rotationMatrix(0, 1) <<","<< rotationMatrix(0, 2) <<","<< rotationMatrix(0, 3)<< endl; 
    cout<<  rotationMatrix(1, 0) <<","<< rotationMatrix(1, 1) <<","<< rotationMatrix(1, 2) <<","<< rotationMatrix(1, 3)<< endl;
    cout<<  rotationMatrix(2, 0) <<","<< rotationMatrix(2, 1) <<","<< rotationMatrix(2, 2) <<","<< rotationMatrix(2, 3)<< endl; 
    cout<<  rotationMatrix(3, 0) <<","<< rotationMatrix(3, 1) <<","<< rotationMatrix(3, 2) <<","<< rotationMatrix(3, 3)<< endl; 
    */
}

系统评价

// 点云变换
pcl::transformPointCloud(*g_seeds_pc, *cloud_filtered_ptr, transform_matrix);
/**********************************************************************************************************
    功能:评判校准是否有效
    
    输入:cloud_filtered_ptr(地面数据)、roi_point_num(roi中点的数量)

    输出:校准是否有效
    
    评判维度:
        1. 通过比较 ROI点云数量与 地面点云数量。
        2. 计算均值和方差

**********************************************************************************************************/

bool access_calibrate(PointCloudT::Ptr cloud_filtered_ptr, int roi_point_num)
{
    std::vector< double > resultSet;
	cloud_filtered_ptr->width = cloud_filtered_ptr->points.size();;
	cloud_filtered_ptr->height = 1;

	for (std::size_t i = 0; i < cloud_filtered_ptr->points.size(); ++i)
	{
	    if( cloud_filtered_ptr->points[i].z >= -0.1 && cloud_filtered_ptr->points[i].z <= 0.1)
        {
            resultSet.push_back(cloud_filtered_ptr->points[i].z);
        }
	}
	double sum = std::accumulate(std::begin(resultSet), std::end(resultSet), 0.0);
    double mean =  sum / resultSet.size(); //均值
    double accum  = 0.0;
    std::for_each (std::begin(resultSet), std::end(resultSet), [&](const double d) {accum  += (d-mean)*(d-mean);});
    double stdev = sqrt(accum/(resultSet.size()-1)); //方差
    double num_ratio = resultSet.size() *1.0 / roi_point_num; // 有效数据量/roi数据量
    
    
    cout<< roi_point_num <<"," << num_ratio << "," << resultSet.size() << "," << abs(mean) << "," << abs(stdev) <<endl;
    if( num_ratio>=min_num_ratio && resultSet.size() >= min_ground_num && abs(mean)<= max_mean && abs(stdev)<=max_stdev )
        return true;
        
   return false;
}

参数输出

/**********************************************************************************************************
功能:存储校准参数

 输入:roll(X轴)、pitch(Y轴)、yaw(Z轴):欧拉角;
        (x, y, z):位移;
        lidar_matrix(变换矩阵)

 输出:校准文件

 返回:void
**********************************************************************************************************/
void generate_yaml_file( double yaw,double roll,double pitch,
                            double offset_x,double offset_y,double offset_z,
                            Eigen::Matrix4f lidar_matrix)
{
    // 用于读写yaml文件的对象
    static CalibrationYaml cYaml;
    Calibration_Lidar cLidar;
    cLidar.x = offset_x;
    cLidar.y = offset_y;
    cLidar.z = offset_z;
    cLidar.roll = roll;
    cLidar.pitch = pitch;
    cLidar.yaw = yaw;
    cLidar.lidar_matrix = lidar_matrix;
      // Write the results in an yaml file
    cYaml.yamlWrite(output_file_path, cLidar);
    
    /*
    // 读校准参数
    Calibration_Lidar cLidar2  = cYaml.yamlRead(output_file_path);
    cout<<  cLidar2.lidar_matrix(0, 0) <<","<< cLidar2.lidar_matrix(0, 1) <<","<< cLidar2.lidar_matrix(0, 2) <<","<< cLidar2.lidar_matrix(0, 3)<< endl; 
    cout<<  cLidar2.lidar_matrix(1, 0) <<","<< cLidar2.lidar_matrix(1, 1) <<","<< cLidar2.lidar_matrix(1, 2) <<","<< cLidar2.lidar_matrix(1, 3)<< endl;
    cout<<  cLidar2.lidar_matrix(2, 0) <<","<< cLidar2.lidar_matrix(2, 1) <<","<< cLidar2.lidar_matrix(2, 2) <<","<< cLidar2.lidar_matrix(2, 3)<< endl; 
    cout<<  cLidar2.lidar_matrix(3, 0) <<","<< cLidar2.lidar_matrix(3, 1) <<","<< cLidar2.lidar_matrix(3, 2) <<","<< cLidar2.lidar_matrix(3, 3)<< endl; 
    */
}

最优输出

	if( access_value )
        {
            std::cout << "The init calibration is finished, you could now stop the program!" << std::endl;
            roll_correction = roll_correction + roll;
            yaw_correction = yaw_correction + yaw;
            pitch_correction = pitch_correction + pitch;
            x_correction = x_correction + offset_x;
            y_correction = y_correction + offset_y;
            z_correction = z_correction + offset_z;
            counter++;
        
       
        }
        else
        {
            std::cout << "The init calibration is not finished, you could now stop the program!" << std::endl;
        }
        std::cout << "roll_correction " << roll << std::endl;
        std::cout << "pitch_correction " << pitch << std::endl;
        std::cout << "yaw_correction " << yaw << std::endl;
        std::cout << "z_correction " << offset_z << std::endl;
        
        if(counter == 1000) 
        {
            isCalibrationFinished = true;
            roll_correction = roll_correction/counter;
            yaw_correction = yaw_correction/counter;
            pitch_correction = pitch_correction/counter;
            x_correction = x_correction/counter;
            y_correction = y_correction/counter;
            z_correction = z_correction/counter;
            // 创建变换矩阵
            Eigen::Matrix4f transform_matrix1 = create_transform_matrix( yaw_correction/ 180 * M_PI, roll_correction/ 180 * M_PI, pitch_correction/ 180 * M_PI, 
                                            x_correction, y_correction, z_correction);
            // 存储校准参数
            generate_yaml_file( yaw_correction, roll_correction, pitch_correction,
                        x_correction, y_correction, z_correction,
                        transform_matrix1);
            std::cout<< "finished---------------------"<<std::endl;
            std::cout << "roll_correction " << roll_correction << std::endl;
            std::cout << "pitch_correction " << pitch_correction << std::endl;
            std::cout << "yaw_correction " << yaw_correction << std::endl;
            std::cout << "x_correction " << x_correction << std::endl;
            std::cout << "y_correction " << y_correction << std::endl;
            std::cout << "z_correction " << z_correction << std::endl;
            cout<<  transform_matrix1(0, 0) <<","<< transform_matrix1(0, 1) <<","<< transform_matrix1(0, 2) <<","<< transform_matrix1(0, 3)<< endl; 
            cout<<  transform_matrix1(1, 0) <<","<< transform_matrix1(1, 1) <<","<< transform_matrix1(1, 2) <<","<< transform_matrix1(1, 3)<< endl;
            cout<<  transform_matrix1(2, 0) <<","<< transform_matrix1(2, 1) <<","<< transform_matrix1(2, 2) <<","<< transform_matrix1(2, 3)<< endl; 
            cout<<  transform_matrix1(3, 0) <<","<< transform_matrix1(3, 1) <<","<< transform_matrix1(3, 2) <<","<< transform_matrix1(3, 3)<< endl; 
        
        }

校准效果

x-z平面:
在这里插入图片描述y-z平面:
在这里插入图片描述

资源下载

  • 3
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 22
    评论
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

听雨听风眠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值