激光雷达与组合贯导(imu)联合标定方案三(含源码)

9 篇文章 9 订阅
该博客介绍了使用激光雷达与惯导进行联合标定的方法,包括记录标记点GPS位置,通过滤波提取点云,同步点云与四元素、GPS数据,将GPS坐标投影到墨卡托坐标系,然后计算标记点在惯导坐标系中的坐标。通过ICP算法优化雷达到惯导的转换矩阵,实现不同标记点位置的精确匹配。
摘要由CSDN通过智能技术生成

原理

拿纸箱或角反射器作标记物,记录标记点gps位置,让无人机携带激光雷达与组合贯导扫描标记点,提取出含标记点的某一帧点云及其对应的四元素与gps,然后可以通过matlab提取出标记物点云坐标,同时可以根据四元素与gps计算出此时标记点在贯导坐标系中的坐标。按照这种方法,改变标记物位置,获取多组点对,然后通过ICP算法求解出雷达到贯导之间的旋转矩阵。

提取单帧点云及其对应的四元素与gps,并求解出贯导坐标系下的标记点坐标

为了方便提取角点,保存单帧点云前先对其进行滤波处理
为了获得该帧对应的四元素与gps,对点云与贯导获取的数据先时间戳对齐

求解贯导坐标系下的标记点坐标

获取的gps并不能直接用,首先先将gps投影到墨卡托坐标系下,计算出其在地图坐标系(M)下贯导与标记点§间的平移关系:

这里的平移并不是贯导坐标系下的标记点坐标,假设贯导坐标系原点处还有一个与地图坐标系经纬轴平行的坐标系B,关系如图:
在这里插入图片描述

【这里为了方便理解,贯导坐标系M与B坐标系原点没有重合】
通过坐标系转换可知B坐标系下的角点坐标为:
在这里插入图片描述

通过四元数获得贯导坐标系M到B坐标系的旋转R,B坐标系下的角点坐标左乘旋转矩阵R的逆即可得到惯导坐标系下的角点坐标:

在这里插入图片描述

代码

//*****************************************************************************************************
//     保存单帧点云中的角反射器角点及其在imu坐标系下的坐标
//     
//    source devel/setup.bash && rosrun cam_laser_calib pcd_points_gps 5 111.pcd   //5表示保存第5帧,111.pcd表示保存的pcd文件名
//  (我直接在相机雷达标定的功能包里写的,所以包名为“cam_laser_calib” )
//    pcl_viewer 111.pcd   
//**************************************************************************************************


#include<ros/ros.h>
#include<iostream>
#include<fstream>
#include<sstream>
#include<string>
#include<vector>
#include <math.h>

#include <stdio.h>
#include<sensor_msgs/PointCloud2.h>  
#include "std_msgs/String.h"
#include <pcl_conversions/pcl_conversions.h>
#include <pcl/point_types.h>
#include <pcl/PCLPointCloud2.h>
#include <pcl/conversions.h>
#include <pcl_ros/transforms.h>
#include <pcl/visualization/cloud_viewer.h>
#include <pcl/filters/filter.h>
#include "tf/transform_datatypes.h"
#include <tf2_msgs/TFMessage.h>
#include <pcl/point_cloud.h>
#include <pcl/io/pcd_io.h>
#include <pcl/common/impl/io.hpp>
#include <sensor_msgs/PointCloud2.h>
#include <pcl/filters/passthrough.h>  //直通滤波
#include <sbg_driver/SbgImuData.h>
#include <sbg_driver/SbgEkfQuat.h>
#include <sbg_driver/SbgGpsPos.h>
#include <sbg_driver/SbgEkfNav.h>
#include <message_filters/subscriber.h>
#include <message_filters/synchronizer.h>
#include <message_filters/sync_policies/approximate_time.h>
#include <message_filters/sync_policies/exact_time.h>
#include <message_filters/time_synchronizer.h>
#include <pcl_ros/point_cloud.h>
#include <pcl/sample_consensus/method_types.h>
#include <pcl/sample_consensus/model_types.h>
#include <pcl/sample_consensus/ransac.h>
#include <pcl/sample_consensus/sac_model_plane.h>
#include <pcl/segmentation/sac_segmentation.h>
#include <pcl/visualization/cloud_viewer.h>

#include <sensor_msgs/PointCloud2.h>
#include <sensor_msgs/PointCloud.h>
#include <Eigen/Eigen>
#include <Eigen/Dense>
#include <Eigen/Geometry>
#include <Eigen/Eigenvalues>


//角反射器的经纬坐标
double gj=126.62823496;
double gw=45.71438903;
double gz=138.263;


int i=1;
int m=0;//保存第m帧

std::string pcd_mame;

#define PI 3.1415926
using namespace std;
using namespace pcl;

//把经纬坐标投影到Web墨卡托 

void lonLat2WebMercator(double lon,double lat,double *x,double *y)
{
    *x = lon * 20037508.34 / 180;
    double ly = log(tan((90+ lat)*PI/360))/(PI/180);
    *y =  ly *20037508.34/180;

}

void GetDirectDistance(double srcLon, double srcLat,double gz, double destLon, double destLat,double tz,double *x,double *y,double *z)
{
    double x1,x2,y1,y2;
    lonLat2WebMercator(srcLon,srcLat,&x1,&y1);
    lonLat2WebMercator(destLon,destLat,&x2,&y2);
    *x=x2-x1;
    *y=y2-y1;
    *z=tz-gz;

}


pcl::visualization::CloudViewer viewer("viewer");

void chatterCallback(const  boost::shared_ptr<const sensor_msgs::PointCloud2>& pointmsg,const  boost::shared_ptr<const sbg_driver::SbgEkfNav>& gpsmsg,const  boost::shared_ptr<const sbg_driver::SbgEkfQuat>& quatmsg)
{

   cout<<"i="<<i<<endl;

   pcl::PCLPointCloud2 pcl_pc2;
   pcl_conversions::toPCL(*pointmsg,pcl_pc2);

   pcl::PointCloud<pcl::PointXYZ>::Ptr tmp_cloud(new pcl::PointCloud<pcl::PointXYZ>);
   pcl::fromPCLPointCloud2(pcl_pc2,*tmp_cloud);

   //  点云直通滤波 
    pcl::PointCloud<pcl::PointXYZ> cloud;
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_ptr; 
    pcl::PointCloud<pcl::PointXYZ> cloud_new;

    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered (new pcl::PointCloud<pcl::PointXYZ>);//点云指针对象
   
    pcl::PassThrough<pcl::PointXYZ> pass; //设置滤波器对象
    pass.setInputCloud(tmp_cloud);//设置输入点云
    pass.setFilterFieldName("y"); //设置过滤时所需要的点云类型的y字段
    pass.setFilterLimits(-6,6); //设置在过滤字段上的范围
    pass.setFilterLimitsNegative(false); //设置保留范围内的还是过滤掉范围内的
    pass.filter(*cloud_filtered); //执行滤波

    cloud = *cloud_filtered;
   //-----------------------------------------------------------------------------------
    for (int k = 0; k <cloud.size(); k++)
    {
       if(cloud.points[k].x<2.5&&cloud.points[k].y<2.5&&cloud.points[k].z<2.5)
        {
            cloud.points[k].x= 0 ;
            cloud.points[k].y= 0 ;
            cloud.points[k].z= 0 ;
         }

     }
    cloud_ptr=cloud.makeShared();//转为指针类型
    viewer.showCloud(cloud_ptr); 


  if(i==m)

  {

   //保存单帧点云
   
    pcl::io::savePCDFileASCII(pcd_mame,*cloud_ptr);//*tmp_cloud
    printf("成功保存第%d帧,并命名为:%s\n",m,pcd_mame.c_str());

    //提取四元素与gps
     double x,y,z,w,tj,tw,tz;
    
     x = quatmsg->quaternion.x;//四元数
     y = quatmsg->quaternion.y;
     z = quatmsg->quaternion.z;
     w = quatmsg->quaternion.w;
     tj = gpsmsg->position.y;//jin
     tw = gpsmsg->position.x;//wei
     tz = gpsmsg->position.z;
     //cout<<"tz==="<<tz<<endl;
     //平移计算  

     double nj,nw,nz;
     GetDirectDistance(gj,gw,gz,tj,tw,tz,&nj,&nw,&nz);//计算GPS变化量
     Eigen::MatrixXd map_imu(3,1);//map坐标系下的imu坐标
     map_imu(0,0)=nw;
     map_imu(1,0)=nj;
     map_imu(2,0)=-nz;
    // 四元数转换为旋转矩阵 

     Eigen::Quaterniond quaternion(w,x,y,z);
     Eigen::Matrix3d rotation_matrix;
     rotation_matrix=quaternion.toRotationMatrix();
     Eigen::Matrix3d matrix_1;//求逆
     matrix_1 =rotation_matrix.inverse();
   //imu坐标系下的角点坐标 

    Eigen::MatrixXd imu_xyz=matrix_1*map_imu;
    FILE *fpWrite=fopen("imu_xyz.txt","a");
    fprintf(fpWrite,"%16.10f %16.10f %16.10f\n",imu_xyz(0,0),imu_xyz(1,0),imu_xyz(2,0));
    fclose(fpWrite);

  }


  i++;

}



int main(int argc,char** argv)
{
  ros::init (argc, argv, "ros_points");
  ros::NodeHandle n;

  string m_=argv[1];
  m= atoi(m_.c_str());
  printf("保存第%d帧\n",m);
  pcd_mame = argv[2]; 
  cout<<"保存的pcd点云名为:"<<pcd_mame<<'\n'; 

  message_filters::Subscriber<sensor_msgs::PointCloud2> points_sub(n, "/velodyne_points", 2000);
  message_filters::Subscriber<sbg_driver::SbgEkfNav> gps_sub(n, "/sbg/ekf_nav", 2000);     
  message_filters::Subscriber<sbg_driver::SbgEkfQuat> quat_sub(n, "/sbg/ekf_quat", 2000);   
  typedef message_filters::sync_policies::ApproximateTime<sensor_msgs::PointCloud2,sbg_driver::SbgEkfNav,sbg_driver::SbgEkfQuat> MySyncPolicy;//sensor_msgs::PointCloud2,
  message_filters::Synchronizer<MySyncPolicy> sync(MySyncPolicy(2000),points_sub,gps_sub, quat_sub);  // 同步points_sub,
  sync.registerCallback(boost::bind(&chatterCallback, _1,_2,_3));   // _1, 

 
  ros::spin ();
}

提取点云坐标

方法一:通过matlab提取
将保存的单帧点云用matlab打开,拾取对应点云坐标。。。

clc
Pc=pcread('.\pcd\111.pcd');
%打开点云文件
pcshow(Pc)%显示点云

方法二:pcl程序提取

//*****************************************************************************************************
//    获得单帧点云中的角反射器角点
//
//    按中shift键,然后在角点处鼠标左击
//    source devel/setup.bash && rosrun cam_laser_calib pcd_points_get 111.pcd   //111.pcd  表示打开的pcd点云文件名
//    pcl_viewer 111.pcd
//**************************************************************************************************



#include <string>
#include <pcl/io/pcd_io.h>
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl/visualization/pcl_visualizer.h>
 
using PointT = pcl::PointXYZRGB;
using PointCloudT = pcl::PointCloud<PointT>;
 
// 用于将参数传递给回调函数的结构体
struct CallbackArgs {
    PointCloudT::Ptr clicked_points_3d;
    pcl::visualization::PCLVisualizer::Ptr viewerPtr;
};
 
void pickPointCallback(const pcl::visualization::PointPickingEvent &event, void *args) {
    CallbackArgs *data = (CallbackArgs *) 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);
    // 绘制红色点
    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;

    FILE *fpWrite=fopen("lidar_xyz.txt","a");
    fprintf(fpWrite,"%16.10f %16.10f %16.10f\n",current_point.x,current_point.y,current_point.z);
    fclose(fpWrite);
    
}
 
int main(int argc, char *argv[]) {

    std::string b = argv[1]; 
    std::cout<<"打开pcd点云文件:"<<b<<'\n'; 
    std::string file_name(b);
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>());
    pcl::visualization::PCLVisualizer::Ptr viewer(new pcl::visualization::PCLVisualizer("viewer"));
    // 加载点云
    if (pcl::io::loadPCDFile(file_name, *cloud) == -1) {
        std::cerr << "could not load file: " << file_name << std::endl;
    }
    std::cout << cloud->points.size() << std::endl;
    // 显示点云
    viewer->addPointCloud(cloud, "cloud");
    viewer->setCameraPosition(0, 0, -2, 0, -1, 0, 0);
    // 添加点拾取回调函数
    CallbackArgs  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(pickPointCallback, (void*)&cb_args);
    std::cout << "Shift+click on three floor points, then press 'Q'..." << std::endl;
 
    viewer->spin();
    std::cout << "done." << std::endl;
 
    while (!viewer->wasStopped())
    {
        viewer->spinOnce(100);
    }
    return 0;
}

matlab的ICP计算

clear;
close all;
clc;

data_source=load('imu.txt');%惯导坐标点
data_target=load('lidar.txt');%雷达坐标点
data_source=data_source';
data_target=data_target';
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%绘制两幅原始图像
x1=data_source(1,:);
y1=data_source(2,:);
z1=data_source(3,:);
x2=data_target(1,:);
y2=data_target(2,:);
z2=data_target(3,:);
figure;
scatter3(x1,y1,z1,'b');
hold on;
scatter3(x2,y2,z2,'r');
hold off;
 
 
T_final=eye(4,4);   %旋转矩阵初始值
iteration=0;
Rf=T_final(1:3,1:3);
Tf=T_final(1:3,4);
data_target=Rf*data_target+Tf*ones(1,size(data_target,2));    %初次更新点集(代表粗配准结果)
err=1;
while(err>0.001)
    iteration=iteration+1;    %迭代次数
    disp(['迭代次数ieration=',num2str(iteration)]);
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %利用欧式距离找出对应点集
    k=size(data_target,2);
    for i = 1:k
        data_q1(1,:) = data_source(1,:) - data_target(1,i);    % 两个点集中的点x坐标之差
        data_q1(2,:) = data_source(2,:) - data_target(2,i);    % 两个点集中的点y坐标之差
        data_q1(3,:) = data_source(3,:) - data_target(3,i);    % 两个点集中的点z坐标之差
        distance = data_q1(1,:).^2 + data_q1(2,:).^2 + data_q1(3,:).^2;  % 欧氏距离
        [min_dis, min_index] = min(distance);   % 找到距离最小的那个点
        data_mid(:,i) = data_source(:,min_index);   % 将那个点保存为对应点
        error(i) = min_dis;     % 保存距离差值
    end
     
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %去中心化
    data_target_mean=mean(data_target,2);
    data_mid_mean=mean(data_mid,2);
    data_target_c=data_target-data_target_mean*ones(1,size(data_target,2));
    data_mid_c=data_mid-data_mid_mean*ones(1,size(data_mid,2));
     
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %SVD分解
    W=zeros(3,3);
    for j=1:size(data_target_c,2)
        W=W+data_mid_c(:,j)*data_target_c(:,j)';
    end
    [U,S,V]=svd(W);
    Rf=U*V';
    Tf=data_mid_mean-Rf*data_target_mean;
    err=mean(error);
    T_t=[Rf,Tf];
    T_t=[T_t;0,0,0,1];
    T_final=T_t*T_final;   %更新旋转矩阵
    disp(['误差err=',num2str(err)]);
    disp('旋转矩阵T=');
    disp(T_final);
     
    data_target=Rf*data_target+Tf*ones(1,size(data_target,2));    %更新点集
    if iteration>=200
        break
    end
end
 
disp(inv(T0));  %旋转矩阵真值
 
x1=data_source(1,:);
y1=data_source(2,:);
z1=data_source(3,:);
x2=data_target(1,:);
y2=data_target(2,:);
z2=data_target(3,:);
figure;
scatter3(x1,y1,z1,'b');
hold on;
scatter3(x2,y2,z2,'r');
hold off;

参考:
文献:一种基于点云匹配的激光雷达/IMU联合标定方法
博客(点云提取):https://blog.csdn.net/u014072827/article/details/110948996

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值