Lidar_imu自动标定源码阅读(一)——registration部分

源码阅读,能力有限,如有某处理解错误,请指出,谢谢。

logging.hpp:

宏定义#define#ifndef#endif:

宏定义#define #ifndef #endif_马小超i的博客-CSDN博客_#ifndef 多个宏目录#define 宏定义一、无参宏定义二、带参宏定义#ifndef 条件编译#define 宏定义在C或C++语言源程序中允许用一个标识符来表示一个字符串,称为“宏”。“define”为宏定义命令。被定义为“宏”的标识符称为“宏名”。在编译预处理时,对程序中所有出现的“宏名”,都用宏定义中的字符串去代换,这称为“宏代换”或“宏展开”。宏定义是由源程序中的宏定义命令完成的。宏代换是由预处理程序自动完成的。优点: (1) 方便程序的修改。这个就不多说了。(2...https://blog.csdn.net/qq_21989927/article/details/108619694?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165994503916782246467579%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=165994503916782246467579&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-108619694-null-null.142%5Ev39%5Econtrol,185%5Ev2%5Econtrol&utm_term=ifndef%20define%20endif%E4%BD%9C%E7%94%A8&spm=1018.2226.3001.4187strrchr()函数:

strrchr函数_悦来客栈的老板的博客-CSDN博客/*函数名称: strrchr函数原型:char *strrchr(const char *str, int c);所属库: string.h函数功能:查找一个字符c在另一个字符串str中末次出现的位置(也就是从str的右侧开始查找字符c首次出现的位置),并返回从字符串中的这个位置起,一直到字符串结束的所有字符。如果未能找到指定字符,那么函数将返回NULL。这个函数和shttps://blog.csdn.net/qq523176585/article/details/11923695?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165994544416782350819884%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=165994544416782350819884&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-11923695-null-null.142%5Ev39%5Econtrol,185%5Ev2%5Econtrol&utm_term=strrchr%E5%87%BD%E6%95%B0&spm=1018.2226.3001.4187

#ifndef LOGGING_HPP_   //先测试LOGGING_HPP_是否被定义过
#define LOGGING_HPP_

#define OUTPUT
#define __FILENAME__                                                           \
  (strrchr(__FILE__, '/') ? (strrchr(__FILE__, '/') + 1) : __FILE__)

#ifdef OUTPUT
#define LOGI(...)                                                              \
  (printf("[INFO] [%d@%s] ", __LINE__, __FILENAME__), printf(__VA_ARGS__),     \
   printf("\n"))
#define LOGW(...)                                                              \
  (printf("\33[33m[WARN] [%d@%s] ", __LINE__, __FILENAME__),                   \
   printf(__VA_ARGS__), printf("\033[0m\n"))
#define LOGE(...)                                                              \
  (printf("\33[31m[ERROR] [%d@%s] ", __LINE__, __FILENAME__),                  \
   printf(__VA_ARGS__), printf("\033[0m\n"))
#else
#define LOGI(...) ((void)0)
#define LOGW(...) ((void)0)
#define LOGE(...) ((void)0)
#endif

#ifdef DEBUG
#define LOGDEBUG(...) (printf(__VA_ARGS__), printf("\n"))
#else
#define LOGDEBUG(...) ((void)0)
#endif

#endif // LOGGING_HPP_

//#ifndef #define #endif联用

transform_util.hpp:

Eigen库块操作:
Eigen子矩阵操作_weixin_34297300的博客-CSDN博客1 子矩阵操作简介子矩阵操作又称块操作,在矩阵运算中,子矩阵的提取和操作应用也十分广泛。因此Eigen中也提供了相关操作的方法。提取的子矩阵在操作过程中既可以用作左值也可以用作右值。2 块操作的一般使用方法在Eigen中最基本的快操作运算是用.block()完成的。提取的子矩阵同样分为动态大小和固定大小。块操作构建动态大小子矩阵提取块大小为(p,q),起始于(i,j)m...https://blog.csdn.net/weixin_34297300/article/details/93329457?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165994680716781790787704%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=165994680716781790787704&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-93329457-null-null.142%5Ev39%5Econtrol,185%5Ev2%5Econtrol&utm_term=%E7%9F%A9%E9%98%B5block%E5%87%BD%E6%95%B0&spm=1018.2226.3001.4187Eigen 中旋转向量、旋转矩阵、欧拉角、四元数的初始化及相互转换:

eigen 中旋转向量、旋转矩阵、欧拉角、四元数的初始化及相互转换_可即的博客-CSDN博客_eigen 欧拉角转旋转矩阵一、旋转向量1.0 初始化旋转向量:旋转角为alpha,旋转轴为(x,y,z) Eigen::AngleAxisd rotation_vector(alpha,Vector3d(x,y,z)) 1.1 旋转向量转旋转矩阵 Eigen::Matrix3d rotation_matrix;rotation_matrix=rotation_vector.matrix(); Eigen::Matrix3d rotation_matrix;rotation_matrix=rotation_vector.tohttps://blog.csdn.net/xiaojinger_123/article/details/124376199?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165997168316782248587203%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=165997168316782248587203&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-124376199-null-null.142%5Ev39%5Econtrol,185%5Ev2%5Econtrol&utm_term=AngleAxisd&spm=1018.2226.3001.4187

#ifndef TRANSFORM_UTIL_HPP_
#define TRANSFORM_UTIL_HPP_

#include <eigen3/Eigen/Core>
#include <eigen3/Eigen/Dense>

//定义内联函数,进行角度和弧度的相互转换
inline double rad2deg(double radians) { return radians * 180.0 / M_PI; }
inline double deg2rad(double degrees) { return degrees * M_PI / 180.0; }

class TransformUtil {
public:
  //=default 来要求编译器生成默认构造函数,仅仅是因为我们既需要其他形式的构造函数,也需要默认的构造函数。析构函数同样如此。
  TransformUtil() = default;
  ~TransformUtil() = default;

  static Eigen::Matrix4d GetMatrix(const Eigen::Vector3d &translation,
                                   const Eigen::Matrix3d &rotation) {
    Eigen::Matrix4d ret = Eigen::Matrix4d::Identity();
    //block<p,q>(i,j)提取块大小为(p,q),起始于(i,j)
    ret.block<3, 1>(0, 3) = translation;
    ret.block<3, 3>(0, 0) = rotation;
    return ret;
  }

  static Eigen::Matrix4d Matrix4FloatToDouble(const Eigen::Matrix4f &matrix) {
    Eigen::Matrix4d ret = Eigen::Matrix4d::Identity();
    ret << matrix(0), matrix(4), matrix(8), matrix(12), matrix(1), matrix(5),
        matrix(9), matrix(13), matrix(2), matrix(6), matrix(10), matrix(14),
        matrix(3), matrix(7), matrix(11), matrix(15);
    return ret;
  }

  static Eigen::Matrix4d GetDeltaT(const float var[6]) {
    //欧拉角转换为旋转矩阵,按照内旋(绕自身的旋转轴进行旋转,按照z、y、x进行相乘)得到旋转矩阵
    auto deltaR = Eigen::Matrix3d(
        Eigen::AngleAxisd(deg2rad(var[2]), Eigen::Vector3d::UnitZ()) *
        Eigen::AngleAxisd(deg2rad(var[1]), Eigen::Vector3d::UnitY()) *
        Eigen::AngleAxisd(deg2rad(var[0]), Eigen::Vector3d::UnitX()));
    Eigen::Matrix4d deltaT = Eigen::Matrix4d::Identity();
    deltaT.block<3, 3>(0, 0) = deltaR;
    deltaT(0, 3) = var[3];
    deltaT(1, 3) = var[4];
    deltaT(2, 3) = var[5];
    return deltaT;
  }
};

#endif // TRANSFORM_UTIL_HPP_

registration.hpp:

#pragma once一般由编译器提供保证:同一个文件不会被包含多次。这里所说的”同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件。无法对一个头文件中的一段代码作#pragma once声明,而只能针对文件。

Eigen库:

Core #include<Eigen/Core>,包含Matrix和Array类,基础的线性代数运算和数组操作。

Geometry #include<Eigen/Geometry>,包含旋转,平移,缩放,2维和3维的各种变换。

LU #include<Eigen/LU>,包含求逆,行列式,LU分解。

Cholesky #include<Eigen/Cholesky>,包含LLT和LDLT Cholesky分解。

SVD `#include<Eigen/SVD>,包含SVD分解。

QR `#include<Eigen/QR>,包含QR分解。

Eigenvalues #include<Eigen/Eigenvalues>,包含特征值,特征向量分解。

Sparse #include<Eigen/Sparse>,包含稀疏矩阵的存储和运算。

Dense #include<Eigen/Dense>,包含了Core/Geometry/LU/Cholesky/SVD/QR/Eigenvalues模块。

Eigen #include<Eigen/Eigen>,包含Dense和Sparse

#include <pcl/io/pcd_io.h>                        //PCD文件的读写
#include <pcl/point_types.h>                    //点类型定义
#include <pcl/point_cloud.h>                     //点云类定义
#include <pcl/kdtree/kdtree_flann.h>        //KD-Tree搜索
pcl头文件介绍:

PCL:头文件汇总及说明_孙 悟 空的博客-CSDN博客_pcl头文件PCL官网上给出的功能模块(modules)如下滤波器 Filters特征 Features关键点 Keypoints配准 RegistrationKd(K-dimensional)树 KdTree八叉树 Octree分割 Segmentation采样一致性 Sample Consesus表面 Surface范围图像 Range Image输入输出 I/O可视化 Visualization常用 Common搜索 Searchpcl头文件汇总及说明#include <https://blog.csdn.net/weixin_46098577/article/details/110951471?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165992690316780366551203%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=165992690316780366551203&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-110951471-null-null.142%5Ev39%5Econtrol,185%5Ev2%5Econtrol&utm_term=pcl%E5%A4%B4%E6%96%87%E4%BB%B6&spm=1018.2226.3001.4187

pcl 点云对象的两种定义方式的区别与转换:

PCL学习(4.5)——点云对象的两种定义方式的区别与转换_菜鸟知识搬运工的博客-CSDN博客创建与访问第一种,是一种vector的赋值方式,将point数据push_back到pcl::PointXYZ类型的模板中。pcl::PointCloud&lt;pcl::PointXYZ&gt; pointCloud;pcl::PointXYZ point; point.x = 2.0f - y; point.y = y; point.z = z;...https://blog.csdn.net/qq_30815237/article/details/86509741?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~default-1-86509741-blog-110189154.pc_relevant_sortByAnswer&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~default-1-86509741-blog-110189154.pc_relevant_sortByAnswer&utm_relevant_index=1

#pragma once

#include <eigen3/Eigen/Core>
#include <eigen3/Eigen/Dense>
#include <eigen3/Eigen/Geometry>
#include <pcl/io/pcd_io.h>
#include <pcl/octree/octree_search.h>
#include <pcl/point_cloud.h>

struct NDTParameter {
  double ndt_maxIterations = 50;
  double ndt_transformation_epsilon = 0.05;
  double ndt_step_size = 0.1; // need to adjust
  double resolution = 10;     // nedd to adjust
};

struct PlaneParam {
  PlaneParam() {}
  PlaneParam(const Eigen::Vector3d &n, double i) : normal(n), intercept(i) {}
  Eigen::Vector3d normal;
  double intercept;
};

struct PlaneParamError {
  PlaneParamError() {}
  PlaneParamError(double n, double i) : normal_error(n), intercept_error(i) {}
  double normal_error;
  double intercept_error;
};

struct PointCloudBbox {
  int min_x = 0;
  int min_y = 0;
  int min_z = 0;

  int max_x = 0;
  int max_y = 0;
  int max_z = 0;
};

class Registrator {
public:
  Registrator();
  ~Registrator();

  bool
  GroundPlaneExtraction(const pcl::PointCloud<pcl::PointXYZI>::Ptr &in_cloud,
                        pcl::PointCloud<pcl::PointXYZI>::Ptr g_cloud,
                        pcl::PointCloud<pcl::PointXYZI>::Ptr ng_cloud,
                        PlaneParam &plane);
  bool
  GroundPlaneExtraction(const pcl::PointCloud<pcl::PointXYZI>::Ptr &in_cloud,
                        PlaneParam &g_param);

  void
  PointCloudDownSampling(const pcl::PointCloud<pcl::PointXYZI>::Ptr &in_cloud,
                         double voxel_size,
                         pcl::PointCloud<pcl::PointXYZI>::Ptr &out_cloud);

  void
  PointCloudFilterByROI(const pcl::PointCloud<pcl::PointXYZI>::Ptr &in_cloud,
                        const PointCloudBbox &roi,
                        pcl::PointCloud<pcl::PointXYZI>::Ptr &out_cloud);

  //计算体素
  size_t ComputeVoxelOccupancy(float var[6]);

  double ComputeGroundRegistrationError(float var[6]);

  // registration method
  bool RegistrationByGroundPlane(Eigen::Matrix4d &transform);
  bool RegistrationByVoxelOccupancy(Eigen::Matrix4d &transform);

  // load data
  void LoadOdometerData(const std::string odometer_file,
                        const Eigen::Matrix4d &initial_extrinsic);
  void LoadLidarPCDs(const std::string &pcds_dir);

  void SaveStitching(const std::string &stitching_path);

private:
  //利用指针类模板,创建点云类对象,采用“->points[i]”方式赋值。
  pcl::PointCloud<pcl::PointXYZI>::Ptr all_cloud_;
  pcl::octree::OctreePointCloudSearch<pcl::PointXYZI>::Ptr all_octree_;

  //创建数组用于存放不同的点云数据
  std::vector<pcl::PointCloud<pcl::PointXYZI>> pcds_;
  std::vector<pcl::PointCloud<pcl::PointXYZI>> pcds_g_cloud_;
  std::vector<pcl::PointCloud<pcl::PointXYZI>> pcds_ng_cloud_;
  std::vector<std::string> timestamp_;
  //创建数组4*4矩阵用于存放lidar位姿
  std::vector<Eigen::Matrix4d> lidar_poses_;

  float curr_best_extrinsic_[6] = {0};
  Eigen::Matrix4d lidar2imu_initial_ = Eigen::Matrix4d::Identity();

  int intensity_threshold_ = 35;
};

registration.cpp:

#include "registration.hpp"

#include <pcl/common/transforms.h>   //点云坐标变化
#include <pcl/conversions.h>   //数据类型的转换
#include <pcl/features/normal_3d.h>   //法线特征估计
#include <pcl/filters/extract_indices.h>   //索引提取
#include <pcl/kdtree/kdtree_flann.h>   //kdtree头文件
#include <pcl/octree/octree_search.h>   //octree头文件
#include <pcl/point_types.h>   //点类型定义
#include <pcl/registration/gicp.h>  //GICP配准
#include <pcl/registration/icp.h>   //ICP配准
#include <pcl/registration/icp_nl.h>   //非线性ICP配准
#include <pcl/registration/ndt.h>   //NDT配准
#include <pcl/sample_consensus/method_types.h>   //随机参数估计方法
#include <pcl/sample_consensus/model_types.h>   //模型定义
#include <pcl/segmentation/sac_segmentation.h>   //基于采样一致性分割

#include "logging.hpp"
#include "omp.h"
#include "transform_util.hpp"

Registrator::Registrator() {
  all_cloud_.reset(new pcl::PointCloud<pcl::PointXYZI>());
  all_octree_.reset(
      new pcl::octree::OctreePointCloudSearch<pcl::PointXYZI>(0.1));
  all_octree_->setInputCloud(all_cloud_);
};

Registrator::~Registrator() {
  //shrink_to_fit()函数,可以在执行erase之后让vector的size()等于capacity(),使删除数据后的数组的capacity()大小等于现在的现在的size()大小
  pcds_.clear();
  pcds_.shrink_to_fit();
  pcds_g_cloud_.clear();
  pcds_g_cloud_.shrink_to_fit();
  pcds_ng_cloud_.clear();
  pcds_ng_cloud_.shrink_to_fit();
};

//加载里程计数据,利用lidar2imu的初始外参标定,得到里程计位姿数据对应时间戳lidar的位姿
void Registrator::LoadOdometerData(const std::string odometer_file,
                                   const Eigen::Matrix4d &initial_extrinsic) {
  lidar2imu_initial_ = initial_extrinsic;
  std::ifstream file(odometer_file);
  if (!file.is_open()) {
    LOGW("can not open %s", odometer_file);
    return;
  }
  // load pose and timestamp
  std::string line;
  while (getline(file, line)) {
    std::stringstream ss(line);
    std::string timeStr;
    ss >> timeStr;
    timestamp_.emplace_back(timeStr);
    Eigen::Matrix4d Ti = Eigen::Matrix4d::Identity();
    ss >> Ti(0, 0) >> Ti(0, 1) >> Ti(0, 2) >> Ti(0, 3) >> Ti(1, 0) >>
        Ti(1, 1) >> Ti(1, 2) >> Ti(1, 3) >> Ti(2, 0) >> Ti(2, 1) >> Ti(2, 2) >>
        Ti(2, 3);
    Ti *= initial_extrinsic;
    lidar_poses_.emplace_back(Ti);
  }
  file.close();
}

//加载点云数据,对点云数据进行地面点和非地面点提取(分割和滤波),并利用体素网格法对提取后的点云数据进行下采样
void Registrator::LoadLidarPCDs(const std::string &pcds_dir) {
  pcds_g_cloud_.reserve(timestamp_.size());
  pcds_g_cloud_.resize(timestamp_.size());
  pcds_ng_cloud_.reserve(timestamp_.size());
  pcds_ng_cloud_.resize(timestamp_.size());
  pcds_.resize(timestamp_.size());
  pcl::PointCloud<pcl::PointXYZI>::Ptr cloud(
      new pcl::PointCloud<pcl::PointXYZI>);
  pcl::PointCloud<pcl::PointXYZI>::Ptr filter_cloud(
      new pcl::PointCloud<pcl::PointXYZI>);
  pcl::PointCloud<pcl::PointXYZI>::Ptr g_cloud(
      new pcl::PointCloud<pcl::PointXYZI>);
  pcl::PointCloud<pcl::PointXYZI>::Ptr ng_cloud(
      new pcl::PointCloud<pcl::PointXYZI>);
  pcl::PointCloud<pcl::PointXYZI>::Ptr g_filter_cloud(
      new pcl::PointCloud<pcl::PointXYZI>);
  pcl::PointCloud<pcl::PointXYZI>::Ptr ng_filter_cloud(
      new pcl::PointCloud<pcl::PointXYZI>);
  pcl::PointCloud<pcl::PointXYZI>::Ptr ng_filter_cloud_roi(
      new pcl::PointCloud<pcl::PointXYZI>);
  PointCloudBbox roi;
  roi.max_x = 100;
  roi.min_x = -100;
  roi.max_y = 50;
  roi.min_y = -50;
  roi.max_z = 5;
  roi.min_z = -5;
  PlaneParam plane;
  for (size_t i = 0; i < timestamp_.size(); ++i) {
    std::string lidar_file_name = pcds_dir + "/" + timestamp_[i] + ".pcd";
    if (pcl::io::loadPCDFile(lidar_file_name, *cloud) < 0) {
      LOGW("can not open %s", lidar_file_name);
      return;
    }
    pcds_[i] = *cloud;
    GroundPlaneExtraction(cloud, g_cloud, ng_cloud, plane);

    PointCloudDownSampling(g_cloud, 1, g_filter_cloud);
    pcds_g_cloud_[i] = *g_filter_cloud;
    PointCloudDownSampling(ng_cloud, 0.5, ng_filter_cloud);
    // PointCloudFilterByROI(ng_filter_cloud, roi, ng_filter_cloud_roi);
    pcds_ng_cloud_[i] = *ng_filter_cloud_roi;
    printf("\rload: %lu/%lu, %s", i, timestamp_.size() - 1,
           lidar_file_name.c_str());
  }
  LOGI("load done!");
  LOGI("the number of pcd is %d", pcds_g_cloud_.size());
}

//根据lidar的位姿信息得到点云的位置信息,并将有效的点云信息进行保存
void Registrator::SaveStitching(const std::string &stitching_path) {
  int interval = 10;
  Eigen::Matrix4d delta_pose = TransformUtil::GetDeltaT(curr_best_extrinsic_);
  //size_t 类型表示C中任何对象所能达到的最大长度,它是无符号整数
  for (size_t i = 0; i < pcds_.size(); i++) {
    if (i % interval != 0)
      continue;

    //lidar_poses_为由里程计数据利用初始化的外参标定数据得到的lidar位姿
    Eigen::Matrix4d lidar_pose = lidar_poses_[i];
    //得到lidar姿态发生后的位置
    lidar_pose *= delta_pose;

    //auto的原理就是根据后面的值,来自己推测前面的类型是什么。auto的作用就是为了简化变量初始化,如果这个变量有一个很长很长的初始化类型,就可以用auto代替。
    //利用pcl_isfinite()检查点是否属于无效点,返回值为bool类型,如果属于无效点,则跳过该点
    for (const auto &src_pt : pcds_[i].points) {
      if (!pcl_isfinite(src_pt.x) || !pcl_isfinite(src_pt.y) ||
          !pcl_isfinite(src_pt.z))
        continue;
      Eigen::Vector3d p(src_pt.x, src_pt.y, src_pt.z);
      Eigen::Vector3d p_res;
      //得到点云相对于激光雷达位置的位置信息p_res,点云X/Y/Z方向的偏移量是以激光雷达的安装位置作为原点
      p_res = lidar_pose.block<3, 3>(0, 0) * p + lidar_pose.block<3, 1>(0, 3);
      pcl::PointXYZI dst_pt;
      dst_pt.x = p_res(0);
      dst_pt.y = p_res(1);
      dst_pt.z = p_res(2);
      dst_pt.intensity = src_pt.intensity;
      if (dst_pt.intensity >= intensity_threshold_ &&
          !all_octree_->isVoxelOccupiedAtPoint(dst_pt)) {
        all_octree_->addPointToCloud(dst_pt, all_cloud_);
      }
    }
  }
  all_cloud_->width = all_cloud_->points.size();//设置点云宽度
  all_cloud_->height = 1; //高度为1,说明为无序点云
  pcl::io::savePCDFileASCII(stitching_path, *all_cloud_);

  all_cloud_->clear();
  all_octree_->deleteTree();
}

//通过计算体素,得到yaw、tx、ty数据的标定误差
bool Registrator::RegistrationByVoxelOccupancy(Eigen::Matrix4d &transform) {
  //
  const float rpy_resolution = 0.05;
  const float xyz_resolution = 0.01;
  float var[6] = {0}, bestVal[6] = {0};
  std::string varName[6] = {"roll", "pitch", "yaw", "tx", "ty", "tz"};
  int direction[2] = {1, -1};
  for (size_t i = 0; i < 6; i++) {
    var[i] = curr_best_extrinsic_[i];
    bestVal[i] = curr_best_extrinsic_[i];
  }

  size_t min_voxel_occupancy = ComputeVoxelOccupancy(var);
  int max_iteration = 60;
  // yaw
  for (int j = 0; j <= 1; j++) {
    for (int iter = 1; iter < max_iteration; iter++) {
      var[2] = iter * direction[j] * rpy_resolution;
      std::cout << varName[2] << ": " << var[2] << std::endl;
      size_t cnt = ComputeVoxelOccupancy(var);
      if (cnt < min_voxel_occupancy * (1 - 1e-4)) {
        min_voxel_occupancy = cnt;
        bestVal[2] = var[2];
        std::cout << "points decrease to: " << min_voxel_occupancy << std::endl;

      } else {
        std::cout << "points increase to: " << cnt << std::endl;
        break;
      }
    }
  }
  var[2] = bestVal[2];
  // tx
  for (int j = 0; j <= 1; j++) {
    for (int iter = 1; iter < max_iteration; iter++) {

      var[3] = iter * direction[j] * xyz_resolution;
      std::cout << varName[3] << ": " << var[3] << std::endl;
      size_t cnt = ComputeVoxelOccupancy(var);
      if (cnt < min_voxel_occupancy * (1 - 1e-4)) {
        min_voxel_occupancy = cnt;
        bestVal[3] = var[3];
        std::cout << "points decrease to: " << min_voxel_occupancy << std::endl;

      } else {
        std::cout << "points increase to: " << cnt << std::endl;
        break;
      }
    }
  }
  var[3] = bestVal[3];
  // ty
  for (int j = 0; j <= 1; j++) {
    for (int iter = 1; iter < max_iteration; iter++) {

      var[4] = iter * direction[j] * xyz_resolution;
      std::cout << varName[4] << ": " << var[4] << std::endl;
      size_t cnt = ComputeVoxelOccupancy(var);
      if (cnt < min_voxel_occupancy * (1 - 1e-4)) {
        min_voxel_occupancy = cnt;
        bestVal[4] = var[4];
        std::cout << "points decrease to: " << min_voxel_occupancy << std::endl;

      } else {
        std::cout << "points increase to: " << cnt << std::endl;
        break;
      }
    }
  }
  var[4] = bestVal[4];
  for (size_t i = 0; i < 6; i++) {
    curr_best_extrinsic_[i] = bestVal[i];
  }
  std::cout << "roll: " << bestVal[0] << ", pitch: " << bestVal[1]
            << ", yaw: " << bestVal[2] << ", tx: " << bestVal[3]
            << ", ty: " << bestVal[4] << ", tz: " << bestVal[5] << std::endl;
  std::cout << "points: " << min_voxel_occupancy << std::endl;

  // calib result
  Eigen::Matrix4d deltaT = TransformUtil::GetDeltaT(bestVal);
  transform = lidar2imu_initial_ * deltaT;
  transform = transform.inverse().eval();

  return true;
}

//调用ComputeGroundRegistrationError()函数,计算得到的误差可以看作标定过程中的roll、pitch角误差
bool Registrator::RegistrationByGroundPlane(Eigen::Matrix4d &transform) {

  const float rpy_resolution = 0.02;
  float var[6] = {0}, bestVal[6] = {0};
  std::string varName[6] = {"roll", "pitch", "yaw", "tx", "ty", "tz"};
  int direction[2] = {1, -1};
  int max_iteration = 300;
  double min_error = ComputeGroundRegistrationError(var);
  // roll
  for (int j = 0; j <= 1; j++) {
    for (int iter = 1; iter < max_iteration; iter++) {

      var[0] = iter * direction[j] * rpy_resolution;
      std::cout << varName[0] << ": " << var[0] << std::endl;
      double error = ComputeGroundRegistrationError(var);
      if (error < min_error) {
        min_error = error;
        bestVal[0] = var[0];
        std::cout << "error decrease to: " << min_error << std::endl;

      } else {
        std::cout << "error increase to: " << error << std::endl;
        break;
      }
    }
  }
  var[0] = bestVal[0];
  // pitch
  for (int j = 0; j <= 1; j++) {
    for (int iter = 1; iter < max_iteration; iter++) {

      var[1] = iter * direction[j] * rpy_resolution;
      std::cout << varName[1] << ": " << var[1] << std::endl;
      double error = ComputeGroundRegistrationError(var);
      if (error < min_error) {
        min_error = error;
        bestVal[1] = var[1];
        std::cout << "error decrease to: " << min_error << std::endl;

      } else {
        std::cout << "error increase to: " << error << std::endl;
        break;
      }
    }
  }
  var[1] = bestVal[1];
  for (size_t i = 0; i < 6; i++) {
    curr_best_extrinsic_[i] = bestVal[i];
  }
  // calib result
  Eigen::Matrix4d deltaT = TransformUtil::GetDeltaT(bestVal);
  transform = lidar2imu_initial_ * deltaT;
  transform = transform.inverse().eval();
  return true;
}

//使用VoxelGrid滤波器(体素化网格方法)对点云进行下采样
void Registrator::PointCloudDownSampling(
    const pcl::PointCloud<pcl::PointXYZI>::Ptr &in_cloud, double voxel_size,
    pcl::PointCloud<pcl::PointXYZI>::Ptr &out_cloud) {
  // 创建滤波器对象
  pcl::VoxelGrid<pcl::PointXYZI> sor;
  sor.setInputCloud(in_cloud);
  sor.setLeafSize(voxel_size, voxel_size, voxel_size);设置三维栅格的大小,数值越大,栅格越大,采样后的点云数据越小
  sor.filter(*out_cloud);
}

void Registrator::PointCloudFilterByROI(
    const pcl::PointCloud<pcl::PointXYZI>::Ptr &in_cloud,
    const PointCloudBbox &roi,
    pcl::PointCloud<pcl::PointXYZI>::Ptr &out_cloud) {
  out_cloud->clear();
  for (const auto &src_pt : in_cloud->points) {
    if (src_pt.x > roi.min_x && src_pt.x < roi.max_x) {
      if (src_pt.y > roi.min_y && src_pt.y < roi.max_y) {
        if (src_pt.z > roi.min_z && src_pt.z < roi.max_z) {
          out_cloud->points.push_back(src_pt);
        }
      }
    }
  }
}

//对输入的点云数据进行分割和滤波,提取地面点和非地面点,并将得到的系数coefficient赋值给plane
bool Registrator::GroundPlaneExtraction(
    const pcl::PointCloud<pcl::PointXYZI>::Ptr &in_cloud,
    pcl::PointCloud<pcl::PointXYZI>::Ptr g_cloud,
    pcl::PointCloud<pcl::PointXYZI>::Ptr ng_cloud, PlaneParam &plane) {
  //对系数和内点进行实例化
  pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients);
  pcl::PointIndices::Ptr inliers(new pcl::PointIndices);
  //创建分割对象
  pcl::SACSegmentation<pcl::PointXYZI> seg;
  //可选设置
  seg.setOptimizeCoefficients(true);//设置对估计的模型做优化处理
  //必选设置
  seg.setModelType(pcl::SACMODEL_PLANE);//设置分割模型类别
  seg.setMethodType(pcl::SAC_RANSAC);//设置使用那个随机参数估计方法
  seg.setDistanceThreshold(0.2);//设置是否为模型内点的距离阈值
  seg.setInputCloud(in_cloud);
  seg.segment(*inliers, *coefficients);
  //判断分割是否成功
  if (inliers->indices.size() == 0) {
    PCL_ERROR("Could not estimate a planar model for the given dataset.");
    return (-1);
  }
  // 创建滤波器对象
  pcl::ExtractIndices<pcl::PointXYZI> extract;
  // 分离内层
  extract.setInputCloud(in_cloud);
  extract.setIndices(inliers);//设置分割后的内点为需要提取的点集
  extract.setNegative (false);//设置提取内点而非外点
  extract.filter(*g_cloud);//提取输出存储到g_clound
  extract.setNegative(true);/提取出其余的点,也就是外点
  extract.filter(*ng_cloud);//提取输出存储到ng_clound
  plane.normal(0) = coefficients->values[0];
  plane.normal(1) = coefficients->values[1];
  plane.normal(2) = coefficients->values[2];
  plane.intercept = coefficients->values[3];
  return true;
}

bool Registrator::GroundPlaneExtraction(
    const pcl::PointCloud<pcl::PointXYZI>::Ptr &in_cloud, PlaneParam &plane) {
  //分割结果-系数因子coeffcients:比如一个平面的方程式为:aX + bY + cZ + d = 0;在此次实验中得出的系数因子为:0 0 1 -1;即 a=0 , b=0 , c=1, d=-1;将平面上的点代入该方程即可验证结果为正确。分割结果中包括在平面模型内的点的下标(在原始点云中),通过这个下标,就可以将平面内和平面外的点云分割开,单独显示。
  pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients);
  pcl::PointIndices::Ptr inliers(new pcl::PointIndices);
  pcl::SACSegmentation<pcl::PointXYZI> seg;
  seg.setOptimizeCoefficients(true);
  seg.setModelType(pcl::SACMODEL_PLANE);
  seg.setMethodType(pcl::SAC_RANSAC);
  seg.setDistanceThreshold(0.2);
  seg.setInputCloud(in_cloud);
  seg.segment(*inliers, *coefficients);
  if (inliers->indices.size() == 0) {
    PCL_ERROR("Could not estimate a planar model for the given dataset.");
    exit(1);
  }

  plane.normal(0) = coefficients->values[0];
  plane.normal(1) = coefficients->values[1];
  plane.normal(2) = coefficients->values[2];
  plane.intercept = coefficients->values[3];
  return true;
}

//计算体素,先求出点云,然后点云数据进行搜索,得到体素
size_t Registrator::ComputeVoxelOccupancy(float var[6]) {
  int interval = 10;
  Eigen::Matrix4d delta_pose = TransformUtil::GetDeltaT(var);
  for (size_t i = 0; i < pcds_.size(); i++) {
    if (i % interval != 0)
      continue;

    Eigen::Matrix4d lidar_pose = lidar_poses_[i];
    lidar_pose *= delta_pose;

#pragma omp parallel for
    for (const auto &src_pt : pcds_[i].points) {
      if (!pcl_isfinite(src_pt.x) || !pcl_isfinite(src_pt.y) ||
          !pcl_isfinite(src_pt.z))
        continue;

      Eigen::Vector3d p(src_pt.x, src_pt.y, src_pt.z);
      Eigen::Vector3d p_res;
      p_res = lidar_pose.block<3, 3>(0, 0) * p + lidar_pose.block<3, 1>(0, 3);
      pcl::PointXYZI dst_pt;
      dst_pt.x = p_res(0);
      dst_pt.y = p_res(1);
      dst_pt.z = p_res(2);
      dst_pt.intensity = src_pt.intensity;
      if (dst_pt.intensity >= intensity_threshold_ &&
          !all_octree_->isVoxelOccupiedAtPoint(dst_pt)) {

#pragma omp critical
        all_octree_->addPointToCloud(dst_pt, all_cloud_);
      }
    }
    printf("\rprocessing: %lu/%lu", i, pcds_.size() - 1);
    std::fflush(stdout);
  }
  size_t pcdCnt = all_cloud_->size();
  all_cloud_->clear();
  all_octree_->deleteTree();
  return pcdCnt;
}

//计算通过通过地面登记的误差
double Registrator::ComputeGroundRegistrationError(float var[6]) {
  pcl::PointCloud<pcl::PointXYZI>::Ptr transformed_cloud(
      new pcl::PointCloud<pcl::PointXYZI>);
  std::vector<Eigen::Vector3d> normals_list;
  std::vector<double> intercepts_list;
  Eigen::Vector3d normals_mean(0, 0, 0);
  double intercepts_mean = 0;
  PlaneParam plane_param;

  Eigen::Matrix4d delta_pose = TransformUtil::GetDeltaT(var);
  //对每帧点云进行分割,将分割后得到的系数进行求和
  for (size_t i = 0; i < pcds_g_cloud_.size(); i++) {
    Eigen::Matrix4d lidar_pose = lidar_poses_[i];
    lidar_pose *= delta_pose;
    //pcl::transformPointCloud(*source_cloud, *target_cloud, transform),其中source_cloud, target_cloud的类型为pcl::PointCloud<pcl::PointXYZ>::Ptr,函数的作用是通过转换矩阵transform将source_cloud转换后存到target_cloud中保存。
    pcl::transformPointCloud(pcds_g_cloud_[i], *transformed_cloud, lidar_pose);
    GroundPlaneExtraction(transformed_cloud, plane_param);
    normals_list.push_back(plane_param.normal);
    intercepts_list.push_back(plane_param.intercept);

    normals_mean += plane_param.normal;
    intercepts_mean += plane_param.intercept;
  }

  //所求的和取平均值
  normals_mean = normals_mean / pcds_g_cloud_.size();
  intercepts_mean = intercepts_mean / pcds_g_cloud_.size();
  double normal_error = 0;
  double intercept_error = 0;
  double min = 0, max = 0;
  //计算每帧点云对应平面和所求平均平面之间的角度误差
  for (int i = 0; i < normals_list.size(); i++) {
    //acos()为取反余弦,
    double alpha = std::acos(normals_list[i].dot(normals_mean));
    normal_error += std::abs(alpha);
    intercept_error += std::abs(intercepts_list[i] - intercepts_mean);
  }
  return normal_error;
}

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: 这个问题翻译成中文是:b'lidar_imu_calib标定过程'。 回答:lidar_imu_calib是指激光雷达和惯性测量单元(IMU)之间的标定过程,通过该过程可以解决激光雷达和IMU在安装中的相对姿态和时间同步问题。该过程需要收集一些基准数据,例如IMU的加速度计和陀螺仪数据,以及激光雷达的点云数据。然后将这些数据引入标定算法中,得到激光雷达和IMU之间的相对位姿和时间偏差,最后将它们纠正并同步,从而使系统达到更高的精度。 ### 回答2: Lidar_imu_calib是一种激光雷达和惯性测量单元的联合标定方法。它旨在从激光雷达的数据和惯性传感器的数据中获取相机、激光雷达和车辆的位姿(即位置和姿态)信息。 lidar_imu_calib标定过程可以分为以下几步: 1.采集数据:首先需要采集车辆在各种不同的姿态和运动条件下的数据,包括车速变化、车辆俯仰和横滚角变化等。同时,需要记录激光雷达和惯性传感器的输出数据。 2.匹配点云和IMU数据:利用系统时间戳进行点云数据和IMU数据的对齐,通过坐标系变换将两者的数据进行匹配。 3.计算位姿:根据匹配后的数据,计算车辆的位姿,包括车辆位置和姿态(即旋转角度),这是通过解决非线性优化问题来完成的。 4.评估误差:标定结果需要进行评估,通过比较计算出的车辆真实姿态和标定结果之间的差异来确定标定的准确性。 5.优化标定结果:根据评估结果进行标定结果的优化,即根据误差来调整标定结果,以提高标定准确性。 总之,lidar_imu_calib标定是激光雷达与惯性测量单元联合标定的方法,通过匹配点云和IMU数据,计算位姿,评估误差和优化标定结果等步骤,得到车辆的位姿信息,从而提高自动驾驶车辆的安全性和性能。 ### 回答3: Lidar_imu_calib是一种遥感设备,由激光雷达和惯性测量单元(IMU)组成,它的目的是3D空间中的可视化地图构建。Lidar可以测量环境中物体的位置和形状,而IMU可以测量设备的位置和运动状态。因此,Lidar_imu_calib的精度和准确性对于它的应用非常重要。为此,在使用Lidar_imu_calib之前,必须进行标定Lidar_imu_calib标定过程试图确定几何和姿态转换矩阵,这个矩阵用于将LidarIMU测量结果在同一个坐标系下进行配置。其中,几何变换矩阵被用来纠正从LidarIMU获得的点云数据中发生的误差,姿态变换矩阵则用于纠正导致视角变化的角度问题。 标定过程的首要步骤是采集数据,包括LidarIMU的原始数据以及因为设备不同而引起的差异。通过在一定时间内在多个场景下对数据进行采集,可以获得更加丰富的数据,并确保标定能够在多种条件下表示准确。 数据采集之后,接下来需要进行数据处理。主要是通过使用非线性最小二乘法以最小化两个矩阵的几何和姿态误差。 这个过程需要大量的计算能力和优秀的算法以最大化标定的准确度。最终的标定参数是由几何和姿态矩阵的组合产生的,并被应用到Lidar_imu_calib设备以及期望的应用程序中。 总之,Lidar_imu_calib标定是一项复杂的过程,它需要充分的数据采集、数据处理和优秀的算法来确保标定结果的准确性和精度。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值