10. 激光雷达到车身坐标系外参的标定方法(lidar2car)

news2025/1/21 12:15:59

目录

  • 0. 论文及代码
  • 1. 标定原理
  • 2. 拟合平面
  • 3. 标定roll/pitch/height
  • 4. 标定yaw
    • 4.1 理解从B_spline拟合的轨迹中得到vehicle航向
  • 5. 精度

0. 论文及代码

参考论文:SensorX2car: Sensors-to-car calibration for autonomous driving in road
scenarios
参考代码:lidar2car

1. 标定原理

①在车辆行驶中,lidar 可以观测到地面,我们通过提取点云中的地面并拟合平面方程,根据平面方程法向量与(0,0,1)之间的旋转可以得到 lidar 和车身之间的 roll/pitch;
②求解(0,0,0)点到提取地面的距离再减去轮胎半径得到 lidar 到车身后轴中心的高度;
③ 得到 roll/pitch 后将点云与地面对齐,然后通过点云配准(或者SLAM算法)得到 lidar 的 DR,根据此 DR 轨迹 进行 B 样条曲线拟合并求导得到车体坐标系下的航向角变化
④上述航向角与 DR 中 lidar 的 航向作差,即 lidar 系和车体系之间的 yaw,至此 lidar in vehicle 的 roll/pitch/height/yaw 全部标定成功。

2. 拟合平面

拟合平面的pipline如下:首先就是为了减少计算量,对激光雷达观测到的点云进行滤波操作。然后利用ransac算法拟合出initial plane,接着通过微调平面参数使其能覆盖更多的“地面内点”。最后根据“地面内点”通过SVD分解方法得到平面的法向量。
在这里插入图片描述

    /* @brief: plane params: normal=[a,b,c]; intercept= d;
    	ax + by + cz + d = 0;
     */
struct PlaneParam {
    PlaneParam() {}
    PlaneParam(const Eigen::Vector3d& n, double i) : normal(n), intercept(i) {}
    Eigen::Vector3d normal;
    double intercept;
};

/* @brief: ground extraction object */
 class GroundExtractor {
 public:
    GroundExtractor() = default;
    ~GroundExtractor() = default;

    /* @brief: ground extraction using random ransac algorithm,
     * aim at the slaver lidar*/
    bool RandomRansacFitting(const PointCloudPtr in_cloud,
                             PointCloudPtr g_cloud,
                             PointCloudPtr ng_cloud,
                             PlaneParam * plane);

 private:
    size_t RandIndex(size_t range); // Random point selection
    bool CalArea(const PointType& p1,
                 const PointType& p2,
                 const PointType& p3,
                 double* area); // Make sure the three points you pick fit into a triangle
    // In ransac, normal vectors are calculated based on randomly selected planes
    bool FittingPlane(PointCloudPtr in_cloud, PlaneParam *plane);
    // Solving plane normal vector by SVD decomposition
    bool FittingPlaneMesh(const PointCloudPtr in_cloud, PlaneParam *plane);
    // Fine-tune the plane parameters to include more inliers
    bool RandomSearchPlane(const PointCloudPtr in_cloud, PlaneParam &best_plane, int &max_inlier_points,
                           double n1_scope, double n2_scope, double n3_scope, double i_scope, int iteration_times);

 private:
 	/* @breif ransac params
 	*/
    const int rr_iter_times_ = 5;
    const int rr_max_rand_iters_ = 500;
    const double rr_gpoints_rate_ = 0.4;
    const double rr_min_area_thre_ = 0.25;
    const double rr_fit_dist_thre_ = 0.1;

};

3. 标定roll/pitch/height

得到平面方程之后,我们可以计算激光雷达原点到拟合平面的距离,此距离就是lidar到地面的高度。同时,我们可以根据平面法向量和向量[0,0,1]计算地面与lidar的旋转(roll和pitch)。
在这里插入图片描述
注意: 这一步只能得到roll,pitch。

        Eigen::Vector3d master_z(0, 0, 1);
        Eigen::Vector3d rot_axis = master_gplane.normal.cross(master_z);
        rot_axis.normalize();
        double alpha = -std::acos(master_gplane.normal.dot(master_z));
        
        // extrinsic: plane to master lidar
        Eigen::Matrix3d R_mp;
        R_mp = Eigen::AngleAxisd(alpha, rot_axis);
        Eigen::Vector3d t_mp(0, 0,
                         -master_gplane.intercept / master_gplane.normal(2));
        Eigen::Matrix4d T_pm = Util::GetMatrix(t_mp, R_mp).inverse();
        double roll = Util::GetRoll(T_pm);
        double pitch = Util::GetPitch(T_pm);
/* avoid using it: matrix.block<3, 3>(0, 0).eulerAngles(0, 1, 2)[0];*/
    static double GetRoll(const Eigen::Matrix4d& matrix) {
        Eigen::Matrix3d R = matrix.block<3, 3>(0, 0);
        
        Eigen::Vector3d n = R.col(0);
        Eigen::Vector3d o = R.col(1);
        Eigen::Vector3d a = R.col(2);
        double y = atan2(n(1), n(0));
        double r = atan2(a(0) * sin(y) - a(1) * cos(y),
                         -o(0) * sin(y) + o(1) * cos(y));
        return r;
    }
    static double GetPitch(const Eigen::Matrix4d& matrix) {
        Eigen::Matrix3d R = matrix.block<3, 3>(0, 0);
        Eigen::Vector3d n = R.col(0);
        double y = atan2(n(1), n(0));
        double p = atan2(-n(2), n(0) * cos(y) + n(1) * sin(y));

        return p;
    }

4. 标定yaw

得到激光雷达到地面的roll和pitch之后,就可以讲激光点云与地面对齐,然后我们使用SLAM算法或者点云配准方法,得到lidar的DR轨迹[ X i , Y i , Y a w i X_i, Y_i, Yaw_i Xi,Yi,Yawi]。
另一方面,我们可以根据上面的[ X i , Y i X_i, Y_i Xi,Yi]进行B_spline拟合出一条轨迹。对此轨迹每个时刻求偏导,可以得到车辆各时刻的航向变化。
在这里插入图片描述
在这里插入图片描述
已知lidar的航向变化以及车辆的航向变化,他们两个应该是拥有一个航向偏差同步变化的。于是两者之差即是lidar与vehicle的航向外参。

bool YawCalib::GetYawSegs(const DataTable &sample_x, const DataTable &sample_y, std::vector<DataTable> &samples_yaw){
    
    BSpline bspline_x = BSpline::Builder(sample_x).degree(bspine_degree_).smoothing(BSpline::Smoothing::PSPLINE).alpha(0.03).build();
    BSpline bspline_y = BSpline::Builder(sample_y).degree(bspine_degree_).smoothing(BSpline::Smoothing::PSPLINE).alpha(0.03).build();

    int discarded_nums = int(pose_num_ * 0.05);
    DataTable tmp_yaw;
    int last_t = 0;
    double last_yaw = 0;

    for (int i = discarded_nums; i < pose_num_ - discarded_nums; i += time_gap_)
    {
        DenseVector t(1);
        t(0) = i;
        double dx = bspline_x.evalJacobian(t)(0, 0);
        double dy = bspline_y.evalJacobian(t)(0, 0);
        double ddx = bspline_x.evalHessian(t)(0, 0);
        double ddy = bspline_y.evalHessian(t)(0, 0);
        double cur_x = fabs(ddx) / pow(1 + dx * dx, 1.5);
        double cur_y = fabs(ddy) / pow(1 + dy * dy, 1.5);

        // delete unmoving points
        if (dx * dx + dy * dy < 1e-3)
            continue;
        // detele points with large curvature
        if (cur_x > 0.015 || cur_y > 0.015)
            continue;

        double yaw = atan2(dy, dx);
        t(0) = i + bspine_degree_;
        if(tmp_yaw.getNumSamples() != 0 && i - last_t > time_gap_ * 5)
        {
            if(tmp_yaw.getNumSamples() > 20) 
            {
                samples_yaw.push_back(tmp_yaw);
            }
            tmp_yaw = DataTable();
        }
        if(tmp_yaw.getNumSamples() != 0 && fabs(yaw - last_yaw) > M_PI){
            if(yaw - last_yaw > M_PI) {
                yaw -= 2 * M_PI;
            }
            else if(yaw - last_yaw < -M_PI) {
                yaw += 2 * M_PI;
            }
        }
        tmp_yaw.addSample(t, yaw);
        last_yaw = yaw;
        last_t = i;
    }
    if(tmp_yaw.getNumSamples() > 20) 
    {
        samples_yaw.push_back(tmp_yaw);
    }
    return true;
}

4.1 理解从B_spline拟合的轨迹中得到vehicle航向

假设车辆坐标系的前向为Y,车辆右侧指向为X,天向为Z;
在这里插入图片描述
假设下图中绿色为激光雷达的坐标系。
在这里插入图片描述
当车辆直线行驶时,我们通过SLAM算法或者点云匹配算法得到的激光雷达位姿[ X i , Y i , Y a w i X_i, Y_i, Yaw_i Xi,Yi,Yawi]中的 Y a w i = 0 Yaw_i = 0 Yawi=0,但是根据激光雷达位置[ X i , Y i X_i, Y_i Xi,Yi]进行B_spline拟合的曲线求偏导得到的角度表示vehicle的航向在以激光雷达为参考系下的表示,它不为0。
在这里插入图片描述

5. 精度

根据工作中的开发情况来看,旋转精度可以达到0.01度(其中yaw角标定依赖SLAM算法或者点云配准精度),高度精度可以达到0.05m。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1063999.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

kafka初体验基础认知部署

kafka 基础介绍 Apache Kafka是一个分布式流处理平台&#xff0c;最初由LinkedIn开发并于2011年开源。它主要用于解决大规模数据的实时流式处理和数据管道问题。 Kafka是一个分布式的发布-订阅消息系统&#xff0c;可以快速地处理高吞吐量的数据流&#xff0c;并将数据实时地分…

医院PACS系统源码 PACS系统源码

医用软件中的影像归档与传输系统软件&#xff08;Picture Archiving and Communication System&#xff0c;简称PACS&#xff09;是一种用于存储、管理和传输医学影像数据的系统。其主要功能包括&#xff1a; 影像存储&#xff1a;PACS可以将医学影像数据以数字化的形式存储在服…

YoloV8训练自己的模型 Pycharm Remote Development

参考视频&#xff1a;https://www.youtube.com/watch?vm9fH9OWn8YM YOLO官方网站&#xff1a;GitHub - ultralytics/ultralytics: NEW - YOLOv8 &#x1f680; in PyTorch > ONNX > OpenVINO > CoreML > TFLite 在本地的pycharm上面建立一个项目 使用scp把代码传…

星球作业(第十一期)Android中Binder简述

Binder 什么是binder&#xff1f;简述下它的工作过程和使用场景。 什么是Binder&#xff1f; Binder是Android中的一个类&#xff0c;实现了IBinder接口&#xff1b; 从IPC的角度来说&#xff0c;Binder是Android中的一种通讯方式&#xff1b; 从Android Framework角度来说&a…

一文搞清楚Java中常见的IO模型

什么是IO 首先&#xff0c;我们要清楚什么是IO&#xff0c;根据冯诺依曼结构&#xff0c;计算机结构分为5部分&#xff1a;运算器、控制器、存储器、输入设备和输出设备。 输入设备和输出设备都属于外设&#xff0c;网卡、硬盘这种既可以属于输入设备也可以属于输出设备。 输入…

深度学习-卷积神经网络-ResNET

文章目录 前言1.resnet2.作者3.精度&#xff08;TOP-5&#xff09;4.论文一览5.竞赛排名6.网络退化7.残差8.残差 1.作者 前言 本文来自B站&#xff1a; ResNet深度残差网络 1.resnet 2.作者 3.精度&#xff08;TOP-5&#xff09; 4.论文一览 5.竞赛排名 6.网络退化 ResNet解…

拆解常见的6类爆款标题写作技巧!

究竟是先写好文章再拟标题还是先确定标题再写文章呢&#xff1f;很多写稿小白都会有这样的疑惑。 在“人人皆可新媒体”的时代&#xff0c;公众号推文类型琳琅满目&#xff0c;每个人都可以建立自己的公众号&#xff0c;写出自己想写的文章。 但怎样起标题、起什么样的标题&a…

MyCat安装文档

JDK安装 JDK具体安装步骤如下&#xff1a; 1. 上传安装包 使用FinalShell自带的上传工具将jdk的二进制发布包上传到Linux 由于上述在进行文件上传时&#xff0c;选择的上传目录为根目录 /&#xff0c;上传完毕后&#xff0c;我们执行指令 cd / 切换到根目录下&#xff0c;查…

STM32 10个工程篇:1.IAP远程升级(六)

在IAP远程升级的最后一篇博客里&#xff0c;笔者想概括性地梳理总结IAP程序设计中值得注意的问题&#xff0c;诚然市面上或者工作后存在不同版本的IAP下位机和上位机软件&#xff0c;也存在不同定义的报文格式&#xff0c;甚至对于相似的知识点不同教程又有着完全不同的解读&am…

You Know What is C++嵌套类

C嵌套类 一、嵌套类1.嵌套类和访问权限2.作用域3.访问控制 一、嵌套类 在一个类的内部定义另一个类&#xff0c;我们称之为嵌套类&#xff0c;或者嵌套类型。引入嵌套类&#xff0c;因为外围类需要使用嵌套类对象作为底层实现&#xff0c;并且该嵌套类只用于外围类的实现&…

黑马JVM总结(二十七)

&#xff08;1&#xff09;synchronized代码块 synchronized代码块的底层原理&#xff0c;它是给一个对象进行一个加锁操作&#xff0c;它是如何保证如果你出现了synchronized代码块中出现了问题&#xff0c;它需要给这个对象有一个正确的解锁操作呢&#xff0c;加锁解锁是成对…

为什么网络安全明明缺口很大,却看起来招聘很少呢?

2023 年我国网络空间安全人才数量缺口超过了 140 万&#xff0c;就业人数却只有 10 多万&#xff0c;缺口高达了 93%。这里就有人会问了&#xff1a; 1、网络安全行业为什么这么缺人&#xff1f; 2、明明人才那么稀缺&#xff0c;为什么招聘时招安全的人员却没有那么多呢&…

10、【Qlib】【主要组件】高频交易嵌套决策执行框架

10、【Qlib】【主要组件】高频交易嵌套决策执行框架 简介简介 日间交易(例如,投资组合管理)和当日交易(例如,订单执行)是量化投资中的两个热门话题,并且通常会分别进行研究。 为了获得日间和当日交易的联合交易绩效,它们必须相互作用,并共同进行回测。为了支持多级的…

7.Tensors For Beginneers - Convector Components

介绍协向量时&#xff0c;曾说过它们有点像 行向量&#xff0c; 行向量确实以某种方式代表了协向量&#xff0c; 这里说明一下&#xff1a; 协向量是不变的&#xff1b; 协向量组件是可变的。 协向量不依赖坐标系&#xff0c;协向量的组件取决于坐标系。 当我们说协向量具有组…

基于SSM的旅游攻略网站设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

【SpringCloud】微服务技术栈入门3 - Gateway快速上手

目录 GatewayWebFlux网关基本配置过滤器与断言工厂全局过滤器跨域处理 CORS Gateway WebFlux gateway 基于 webflux 构建 WebFlux 是基于反应式流概念的响应式编程框架&#xff0c;用于构建异步非阻塞的 Web 应用程序。它支持响应式编程范式&#xff0c;并提供了一种响应式的方…

Java中阻塞队列原理、特点、适用场景

文章目录 阻塞队列对比、总览阻塞队列本质思想主要队列讲解ArrayBlockingQueueLinkedBlockingQueueSynchronousQueueLinkedTransferQueuePriorityBlockingQueueDelayQueueLinkedBlockingDeque 阻塞队列对比、总览 阻塞队列本质思想 阻塞队列都是线程安全的队列. 其最主要的功能…

3分钟基于Chat GPT完成工作中的小程序

1. 写在前面 GPT自从去年爆发以来&#xff0c;各大公司在大模型方面持续发力&#xff0c;行业大模型也如雨后春笋一般发展迅速&#xff0c;日常工作中比较多的应用场景还是问答模式&#xff0c;作为写程序的辅助也偶尔使用。今天看到一篇翻译的博客“我用 ChatGPT&#xff0c;…

python通过socket 搭建极简web服务器

环境&#xff1a;win11、python 3.9.2 背景&#xff1a;python的web框架众多&#xff0c;常见的如django、flask、tornado等&#xff0c;其底层是什么还是有些许的疑问&#xff0c;所以查找相关资料&#xff0c;实现浏览器访问&#xff0c;并返回相关信息 时间&#xff1a;20…