精华内容
下载资源
问答
  • Livox_mapping Livox_mapping是Livox LiDAR的映射包。 该软件包当前包含低速映射的基本功能。 一些关键问题: 支持多个livox激光雷达; 不同特征提取; 对于较小的FOV情况,请删除里程表; 在开发包时,我们...
  • Livox ROS Driver() livox_ros_driver是一个新的ROS软件包,专门用于连接Livox生产的LiDAR产品。 该驱动程序可以在安装了ROS环境(靛蓝,动力学,旋律)的ubuntu 14.04 / 16.04 / 18.04操作系统下运行。 可以运行...
  • Livox激光模拟 提供用于插件的软件包。 要求 ROS(=动能) 凉亭(= 7.0, ) Ubuntu(= 16.04) 结果 阿维亚 40年代中期 70年代中期 远程 地平线 用法 请注意,已发布的点云消息在主分支中为sensor_msg / ...
  • Livox SDK包含Livox SDK通信协议,Livox SDK核心,Livox SDK API,Linux示例和ROS演示。 先决条件 x86和ARM的Ubuntu 14.04 / Ubuntu 16.04 / Ubuntu 18.04(Nvidia TX2) Windows 7/10,Visual Studio 2015更新3/...
  • Livox Viewer 0.8.0.

    2020-07-22 10:29:05
    Livox Viewer是一款专为Livox激光探测测距仪和Liovx Hub设计的,可用于实时显示连接至计算机的所有激光探测测距仪点云数据的软件。 通过Livox Viewer,用户可轻松查看、记录并储存点云数据,以便后期使用。
  • 该解决方案提供了一种手动校准Livox LiDAR与摄像机之间的外部参数的方法,该方法已在Mid-40,Horizo​​n和Tele-15系列上得到验证。 它包括相机固有参数的校准,校准数据的获取,相机与LiDAR之间外部参数的计算以及...
  • livox驱动安装方法

    2020-01-16 15:01:29
    livox驱动方法 ubuntu平台 ros驱动 .
  • 利用livox激光雷达和hikvision相机进行三维重建 二,环境: VS2017 + PCL1.8.1 三,实验步骤: 3.1 KITTI三维重建 用KITTI的配准信息将点云投影到图像上获得真彩色点云 滤波 重采样平滑 法线计算 将点云坐标,颜色...
  • 下载 CloudCompare 2.11 版本,将此 dll 放置到其 plugins 文件夹下即可读取大疆 livox 的 lvx 文件。详细参考博客:https://blog.csdn.net/weixin_40331125/article/details/111905853
  • livox_camera_calib livox_camera_calib是无目标环境中高分辨率LiDAR(例如Livox)和相机之间的可靠,高精度的外部校准工具。 我们的算法可以在室内和室外场景中运行,并且仅需要场景中的边缘信息。 如果场景合适,...
  • Livox SDK的C++代码,用于某雷达的二次开发
  • livox_driver_plus-源码

    2021-04-12 18:23:02
    我将驱动程序修改为在标签(PointXYZRTL)中存储Livox Lidar ID。 Livox ROS Driver() livox_ros_driver是一个新的ROS软件包,专门用于连接Livox生产的LiDAR产品。 该驱动程序可以在安装了ROS环境(靛蓝,动力学...
  • livox_lane_detection-源码

    2021-05-07 16:23:22
    Livox车道检测 介绍 该存储库用作点云车道检测的推理套件。 它支持一般车道线类型和道路附近对象的语义分割。 依存关系 Python3.6+ Pytorch1.0+ (在1.4.0上测试) OpenCV Python Numpy 引用 如果您发现此存储库...
  • Livox Viewer 0.11.0.zip

    2021-10-09 16:04:25
    Livox Viewer 0.11.0.zip请勿下载
  • Livox 雷达推出的特有的非重复扫描方式,使得特征提取变得不那么直观,而且LIvox 推出的自由的数据结构方式,加上ROS原有的 Pointcloud2 使得很多开源的SLAM 算法只支持一种数据格式, 导致很多辛辛苦苦采集的数据不...

    传统的 旋转式雷达,激光固定在雷达的旋转部件上, 依靠转子的转动而获得360的旋转视野,由于旋转部件的存在,为了获得更加精准的数据,需要跟多的人工校准和更复杂的设计,也因此推高了其价格。 不过因为很直观的数据方式,所以 edge 特征和 plane 特征也比较直观。

    Livox 雷达推出的特有的非重复扫描方式,使得特征提取变得不那么直观,而且LIvox 推出的自由的数据结构方式,加上ROS原有的 Pointcloud2 使得很多开源的SLAM 算法只支持一种数据格式, 导致很多辛辛苦苦采集的数据不能直接使用。

    接下来就自己总结一下 Livox 激光雷达的一些特征

    开源SLAM算法

    Horizon 发布时间较早, 相关算法支持也最好。

    • LIO-Livox : Lidar-Inertial Odometry, 使用了内置的 6 轴IMU, 目前只支持 horizon 雷达, 雷达数据结构只支持 livox_ros_driver/CustomMsg. LINK
    • Livox-mapping: 仅使用激光雷达的建图包, 同时支持了 Mid 系列和 horizon 系统的扫描方式, 但Horizon 数据结构还是必须为 livox_ros_driver/CustomMsg, Mid 系列的数据类型为 sensor_msgs::PointCloud。LINK
    • hku-mars/loam_livox: Lidar only 的建图包, 只支持 Mid 系列(sensor_msgs::PointCloud2) 格式的数据。 LINK
    • BALM ,使用了bundle adjustment 的仅使用激光的建图包, 同时支持三种, horizon 支持 livox_ros_driver/CustomMsg 格式,Mid 系列的数据类型为 sensor_msgs::PointCloud, 也提供了velodyne 激光的 sensor_msgs::PointCloud 格式。link
    • KIT-ISAS/lili-om: LINK LiDAR-Inertial Odometry, 但此处没有使用内置的IMU, 而是使用的的 9 轴 Xsens MTi-670 (200 Hz) IMU, Livox雷达内置的是一个 6 轴激光雷达, 支持Horizon 和 Velodyne 雷达 LINK

    Horzion 特征提取

    目前能找到的开源算法中, 都只支持 livox_ros_driver/CustomMsg 数据格式, 其内容为:
    link

    ## livox_ros_driver/CustomMsg
    # Livox publish pointcloud msg format.
    Header header             # ROS standard message header
    uint64 timebase           # The time of first point
    uint32 point_num          # Total number of pointclouds
    uint8  lidar_id           # Lidar device id number
    uint8[3]  rsvd            # Reserved use
    CustomPoint[] points      # Pointcloud data
    
    ## livox_ros_driver/CustomPoint
    # Livox costom pointcloud format.
    uint32 offset_time      # offset time relative to the base time
    float32 x               # X axis, unit:m
    float32 y               # Y axis, unit:m
    float32 z               # Z axis, unit:m
    uint8 reflectivity      # reflectivity, 0~255
    uint8 tag               # livox tag
    uint8 line              # laser number in lidar
    

    其特点是, 我们能够根据 激光数据的 timebase 和每个激光数据点的 offset_time 推出每个点的时间先后顺序,同时此数据结构也提供了 每个点所在的激光序号, Horizon中有五个激光束。

    以官方的Livox_mapping 为例:
    首先其使用 livox_repub.cpp 文件,将 livox livox_ros_driver::CustomMsg 数据转换成 sensor_msgs::PointCloud2 数据结构:

    ros::Subscriber sub_livox_msg1 = nh.subscribe<livox_ros_driver::CustomMsg>(
          "/livox/lidar", 100, LivoxMsgCbk1);
    pub_pcl_out1 = nh.advertise<sensor_msgs::PointCloud2>("/livox_pcl0", 100);
    
    ros::Publisher pub_pcl_out0, pub_pcl_out1;
    uint64_t TO_MERGE_CNT = 1; 
    constexpr bool b_dbg_line = false;
    std::vector<livox_ros_driver::CustomMsgConstPtr> livox_data;
    
    void LivoxMsgCbk1(const livox_ros_driver::CustomMsgConstPtr& livox_msg_in) {
      livox_data.push_back(livox_msg_in);
      if (livox_data.size() < TO_MERGE_CNT) return;
    
      pcl::PointCloud<PointType> pcl_in;
    
      for (size_t j = 0; j < livox_data.size(); j++) {
        auto& livox_msg = livox_data[j];
        auto time_end = livox_msg->points.back().offset_time;
        for (unsigned int i = 0; i < livox_msg->point_num; ++i) {
          PointType pt;
          pt.x = livox_msg->points[i].x;
          pt.y = livox_msg->points[i].y;
          pt.z = livox_msg->points[i].z;
          float s = livox_msg->points[i].offset_time / (float)time_end;
    
          pt.intensity = livox_msg->points[i].line +livox_msg->points[i].reflectivity /10000.0 ; // The integer part is line number and the decimal part is timestamp
          pt.curvature = s*0.1;
          pcl_in.push_back(pt);
        }
      }
    
      unsigned long timebase_ns = livox_data[0]->timebase;
      ros::Time timestamp;
      timestamp.fromNSec(timebase_ns);
    
      sensor_msgs::PointCloud2 pcl_ros_msg;
      pcl::toROSMsg(pcl_in, pcl_ros_msg);
      pcl_ros_msg.header.stamp.fromNSec(timebase_ns);
      pcl_ros_msg.header.frame_id = "/livox";
      pub_pcl_out1.publish(pcl_ros_msg);
      livox_data.clear();
    }
    
    

    其中主要部分是

    auto time_end = livox_msg->points.back().offset_time;
    
    PointType pt;
    pt.x = livox_msg->points[i].x;
    pt.y = livox_msg->points[i].y;
    pt.z = livox_msg->points[i].z;
    float s = livox_msg->points[i].offset_time / (float)time_end;
    
    pt.intensity = livox_msg->points[i].line +livox_msg->points[i].reflectivity /10000.0 ; // The integer part is line number and the decimal part is timestamp
    pt.curvature = s*0.1; 
    pcl_in.push_back(pt);
    

    其中在这里记录了currature , 是该点在总时间段的比例, 并将其放在 curvature 字段。在 intensity 字段放的 line number 和 反射率。

    在 scanRegistration_horizon.cpp 中, 其定义了 horizon的特征提取的方式。

    平面点(plane)的提取

        float depth = sqrt(laserCloud->points[i].x * laserCloud->points[i].x +
                           laserCloud->points[i].y * laserCloud->points[i].y +
                           laserCloud->points[i].z * laserCloud->points[i].z);
    
        // if(depth < 2) depth_threshold = 0.05;
        // if(depth > 30) depth_threshold = 0.1;
        //left curvature
        float ldiffX = 
                    laserCloud->points[i - 4].x + laserCloud->points[i - 3].x
                    - 4 * laserCloud->points[i - 2].x
                    + laserCloud->points[i - 1].x + laserCloud->points[i].x;
    
        float ldiffY = 
                    laserCloud->points[i - 4].y + laserCloud->points[i - 3].y
                    - 4 * laserCloud->points[i - 2].y
                    + laserCloud->points[i - 1].y + laserCloud->points[i].y;
    
        float ldiffZ = 
                    laserCloud->points[i - 4].z + laserCloud->points[i - 3].z
                    - 4 * laserCloud->points[i - 2].z
                    + laserCloud->points[i - 1].z + laserCloud->points[i].z;
    
        float left_curvature = ldiffX * ldiffX + ldiffY * ldiffY + ldiffZ * ldiffZ;
        
     if(left_curvature < 0.01){
    
          std::vector<PointType> left_list;
    
          for(int j = -4; j < 0; j++){
            left_list.push_back(laserCloud->points[i+j]);
          }
    
          if( left_curvature < 0.001) CloudFeatureFlag[i-2] = 1; //surf point flag  && plane_judge(left_list,1000) 
          
          left_surf_flag = true;
        }
        else{
          left_surf_flag = false;
        }
    

    There is same process for right curve

    角点Edge特征提取

    //surf-surf corner feature
        if(left_surf_flag && right_surf_flag){
         debugnum4 ++;
    
         Eigen::Vector3d norm_left(0,0,0);
         Eigen::Vector3d norm_right(0,0,0);
         for(int k = 1;k<5;k++){
             Eigen::Vector3d tmp = Eigen::Vector3d(laserCloud->points[i-k].x-laserCloud->points[i].x,
                                laserCloud->points[i-k].y-laserCloud->points[i].y,
                                laserCloud->points[i-k].z-laserCloud->points[i].z);
            tmp.normalize();
            norm_left += (k/10.0)* tmp;
         }
         for(int k = 1;k<5;k++){
             Eigen::Vector3d tmp = Eigen::Vector3d(laserCloud->points[i+k].x-laserCloud->points[i].x,
                                laserCloud->points[i+k].y-laserCloud->points[i].y,
                                laserCloud->points[i+k].z-laserCloud->points[i].z);
            tmp.normalize();
            norm_right += (k/10.0)* tmp;
         }
    
          //calculate the angle between this group and the previous group
          double cc = fabs( norm_left.dot(norm_right) / (norm_left.norm()*norm_right.norm()) );
          //calculate the maximum distance, the distance cannot be too small
          Eigen::Vector3d last_tmp = Eigen::Vector3d(laserCloud->points[i-4].x-laserCloud->points[i].x,
                                                     laserCloud->points[i-4].y-laserCloud->points[i].y,
                                                     laserCloud->points[i-4].z-laserCloud->points[i].z);
          Eigen::Vector3d current_tmp = Eigen::Vector3d(laserCloud->points[i+4].x-laserCloud->points[i].x,
                                                        laserCloud->points[i+4].y-laserCloud->points[i].y,
                                                        laserCloud->points[i+4].z-laserCloud->points[i].z);
          double last_dis = last_tmp.norm();
          double current_dis = current_tmp.norm();
    
          if(cc < 0.5 && last_dis > 0.05 && current_dis > 0.05 ){ //
            debugnum5 ++;
            CloudFeatureFlag[i] = 150;
          }
    

    完成了 特征提取就是,mapping 的过程, 此部分和 LOAM 部分类似。

    IMU对 Lidar 数据矫正

    根据IMU 对数据进行矫正是十分有必要的, 依靠IMU 高频率的对角速度和线性加速度的测量 预测每一个点相对初始点的旋转误差和线性误差可以比较充分的纠正位姿:以下是一个纠正前后的对比实例:
    在这里插入图片描述
    可以看到由于运动扭曲的桌腿得到了恢复, 这对于特征的跟踪十分有帮助。

    这里有一个 IMU integrator 对imu 数据进行 PreIntegration link

    void IMUIntegrator::GyroIntegration(double lastTime){
      double current_time = lastTime;
      for(auto & imu : vimuMsg){
        Eigen::Vector3d gyr;
        gyr << imu->angular_velocity.x,
                imu->angular_velocity.y,
                imu->angular_velocity.z;
        double dt = imu->header.stamp.toSec() - current_time;
        ROS_ASSERT(dt >= 0);
        // 将旋转角速度转换为 旋转矩阵
        Eigen::Matrix3d dR = Sophus::SO3d::exp(gyr*dt).matrix();
        // 得到此时的 orientation
        Eigen::Quaterniond qr(dq*dR);
        if (qr.w()<0)
          qr.coeffs() *= -1;
        // 正则化, 得到(积分后的)旋转矩阵
        dq = qr.normalized();
        current_time = imu->header.stamp.toSec();
      }
    }
    
    void IMUIntegrator::PreIntegration(double lastTime, const Eigen::Vector3d& bg, const Eigen::Vector3d& ba){
      Reset();
      linearized_bg = bg;
      linearized_ba = ba;
      double current_time = lastTime;
      for(auto & imu : vimuMsg){
        Eigen::Vector3d gyr;
        gyr <<  imu->angular_velocity.x,
                imu->angular_velocity.y,
                imu->angular_velocity.z;
        Eigen::Vector3d acc;
        acc << imu->linear_acceleration.x * gnorm,
                imu->linear_acceleration.y * gnorm,
                imu->linear_acceleration.z * gnorm;
        double dt = imu->header.stamp.toSec() - current_time;
        if(dt <= 0 )
          ROS_WARN("dt <= 0");
        gyr -= bg;
        acc -= ba;
        double dt2 = dt*dt;
        Eigen::Vector3d gyr_dt = gyr*dt;
        
        // 将小量李代数 转化到李群
        Eigen::Matrix3d dR = Sophus::SO3d::exp(gyr_dt).matrix();
        Eigen::Matrix3d Jr = Eigen::Matrix3d::Identity();
        double gyr_dt_norm = gyr_dt.norm(); // Return to the suqarnorm
        if(gyr_dt_norm > 0.00001){
          Eigen::Vector3d k = gyr_dt.normalized();
          Eigen::Matrix3d K = Sophus::SO3d::hat(k); //李代数到反对称矩阵
          Jr =   Eigen::Matrix3d::Identity()
                 - (1-cos(gyr_dt_norm))/gyr_dt_norm*K
                 + (1-sin(gyr_dt_norm)/gyr_dt_norm)*K*K;
        }
        Eigen::Matrix<double,15,15> A = Eigen::Matrix<double,15,15>::Identity();
        A.block<3,3>(0,3) = -0.5*dq.matrix()*Sophus::SO3d::hat(acc)*dt2;
        A.block<3,3>(0,6) = Eigen::Matrix3d::Identity()*dt;
        A.block<3,3>(0,12) = -0.5*dq.matrix()*dt2;
        A.block<3,3>(3,3) = dR.transpose();
        A.block<3,3>(3,9) = - Jr*dt;
        A.block<3,3>(6,3) = -dq.matrix()*Sophus::SO3d::hat(acc)*dt;
        A.block<3,3>(6,12) = -dq.matrix()*dt;
        Eigen::Matrix<double,15,12> B = Eigen::Matrix<double,15,12>::Zero();
        B.block<3,3>(0,3) = 0.5*dq.matrix()*dt2;
        B.block<3,3>(3,0) = Jr*dt;
        B.block<3,3>(6,3) = dq.matrix()*dt;
        B.block<3,3>(9,6) = Eigen::Matrix3d::Identity()*dt;
        B.block<3,3>(12,9) = Eigen::Matrix3d::Identity()*dt;
        jacobian = A * jacobian;
        covariance = A * covariance * A.transpose() + B * noise * B.transpose();
        dp += dv*dt + 0.5*dq.matrix()*acc*dt2;
        dv += dq.matrix()*acc*dt;
        Eigen::Matrix3d m3dR = dq.matrix()*dR;
        Eigen::Quaterniond qtmp(m3dR);
        if (qtmp.w()<0)
          qtmp.coeffs() *= -1;
        dq = qtmp.normalized();
        dtime += dt;
        current_time = imu->header.stamp.toSec();
      }
    }
    
        boost::shared_ptr<std::list<Estimator::LidarFrame>> lidar_list;
    	    if(!vimuMsg.empty()){
    	    	if(!LidarIMUInited) {
    	    		// if get IMU msg successfully, use gyro integration to update delta_Rl
    			    lidarFrame.imuIntegrator.PushIMUMsg(vimuMsg);
    			    lidarFrame.imuIntegrator.GyroIntegration(time_last_lidar);
    			    delta_Rb = lidarFrame.imuIntegrator.GetDeltaQ().toRotationMatrix();
    			    delta_Rl = exTlb.topLeftCorner(3, 3) * delta_Rb * exTlb.topLeftCorner(3, 3).transpose();
    
    			    // predict current lidar pose
    			    lidarFrame.P = transformAftMapped.topLeftCorner(3,3) * delta_tb
    			                   + transformAftMapped.topRightCorner(3,1);
    			    Eigen::Matrix3d m3d = transformAftMapped.topLeftCorner(3,3) * delta_Rb;
    			    lidarFrame.Q = m3d;
    
    			    lidar_list.reset(new std::list<Estimator::LidarFrame>);
    			    lidar_list->push_back(lidarFrame);
    		    }else{
    			    // if get IMU msg successfully, use pre-integration to update delta lidar pose
    			    lidarFrame.imuIntegrator.PushIMUMsg(vimuMsg);
    			    lidarFrame.imuIntegrator.PreIntegration(lidarFrameList->back().timeStamp, lidarFrameList->back().bg, lidarFrameList->back().ba);
    
    			    const Eigen::Vector3d& Pwbpre = lidarFrameList->back().P;
    			    const Eigen::Quaterniond& Qwbpre = lidarFrameList->back().Q;
    			    const Eigen::Vector3d& Vwbpre = lidarFrameList->back().V;
    
    			    const Eigen::Quaterniond& dQ =  lidarFrame.imuIntegrator.GetDeltaQ();
    			    const Eigen::Vector3d& dP = lidarFrame.imuIntegrator.GetDeltaP();
    			    const Eigen::Vector3d& dV = lidarFrame.imuIntegrator.GetDeltaV();
    			    double dt = lidarFrame.imuIntegrator.GetDeltaTime();
    
    			    lidarFrame.Q = Qwbpre * dQ;
    			    lidarFrame.P = Pwbpre + Vwbpre*dt + 0.5*GravityVector*dt*dt + Qwbpre*(dP);
    			    lidarFrame.V = Vwbpre + GravityVector*dt + Qwbpre*(dV);
    			    lidarFrame.bg = lidarFrameList->back().bg;
    			    lidarFrame.ba = lidarFrameList->back().ba;
    
    			    Eigen::Quaterniond Qwlpre = Qwbpre * Eigen::Quaterniond(exRbl);
    			    Eigen::Vector3d Pwlpre = Qwbpre * exPbl + Pwbpre;
    
    			    Eigen::Quaterniond Qwl = lidarFrame.Q * Eigen::Quaterniond(exRbl);
    			    Eigen::Vector3d Pwl = lidarFrame.Q * exPbl + lidarFrame.P;
    
    			    delta_Rl = Qwlpre.conjugate() * Qwl;
    			    delta_tl = Qwlpre.conjugate() * (Pwl - Pwlpre);
    			    delta_Rb = dQ.toRotationMatrix();
    			    delta_tb = dP;
    
    			    lidarFrameList->push_back(lidarFrame);
    			    lidarFrameList->pop_front();
    			    lidar_list = lidarFrameList;
    	    	}
    	    }else{
    	    	if(LidarIMUInited)
    	    	  break;
    	    	else{
    			    // predict current lidar pose
    			    lidarFrame.P = transformAftMapped.topLeftCorner(3,3) * delta_tb
    			                   + transformAftMapped.topRightCorner(3,1);
    			    Eigen::Matrix3d m3d = transformAftMapped.topLeftCorner(3,3) * delta_Rb;
    			    lidarFrame.Q = m3d;
    
    			    lidar_list.reset(new std::list<Estimator::LidarFrame>);
    			    lidar_list->push_back(lidarFrame);
    	    	}
    	    }
    
    	    // remove lidar distortion 矫正运动
    	    RemoveLidarDistortion(laserCloudFullRes, delta_Rl, delta_tl);
    
    void RemoveLidarDistortion(pcl::PointCloud<PointType>::Ptr& cloud,
                               const Eigen::Matrix3d& dRlc, const Eigen::Vector3d& dtlc){
      int PointsNum = cloud->points.size();
      for (int i = 0; i < PointsNum; i++) {
        Eigen::Vector3d startP;
        float s = cloud->points[i].normal_x;
        Eigen::Quaterniond qlc = Eigen::Quaterniond(dRlc).normalized();
        Eigen::Quaterniond delta_qlc = Eigen::Quaterniond::Identity().slerp(s, qlc).normalized();
        const Eigen::Vector3d delta_Plc = s * dtlc;
        startP = delta_qlc * Eigen::Vector3d(cloud->points[i].x,cloud->points[i].y,cloud->points[i].z) + delta_Plc;
        Eigen::Vector3d _po = dRlc.transpose() * (startP - dtlc);
    
        cloud->points[i].x = _po(0);
        cloud->points[i].y = _po(1);
        cloud->points[i].z = _po(2);
        cloud->points[i].normal_x = 1.0;
      }
    }
    
    

    IMU - GTSAM 中的使用

    //北向角度、东向角度、地向角度,姿态w,姿态x,姿态y,姿态z,北向速度,东向速度,地向速度
    //N,E,D,qx,qy,qz,qw,VelN,VelE,VelD
    //i:表示初始化
    i,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0
    0,0.000100,0.0,0.0,0.0,0.0,0.0
    1,0.000000,0.0,0.0,0.0,0.0,0.0,1.0
    0,0.000200,0.0,0.0,0.0,0.0,0.0
    0,0.000300,0.0,0.0,0.0,0.0,0.0
    0,0.000400,0.0,0.0,0.0,0.0,0.0
    0,0.000500,0.0,0.0,0.0,0.0,0.0
    
    
    #include <gtsam/navigation/CombinedImuFactor.h>
    #include <gtsam/navigation/GPSFactor.h>
    #include <gtsam/navigation/ImuFactor.h>
    #include <gtsam/slam/dataset.h>
    #include <gtsam/slam/BetweenFactor.h>
    #include <gtsam/slam/PriorFactor.h>
    #include <gtsam/slam/dataset.h>
    #include <gtsam/nonlinear/LevenbergMarquardtParams.h>
    #include <gtsam/nonlinear/NonlinearFactorGraph.h>
    #include <gtsam/nonlinear/LevenbergMarquardtOptimizer.h>
    #include <gtsam/inference/Symbol.h>
    #include <fstream>
    #include <iostream>
    
    using namespace std;
    using namespace gtsam;
    using symbol_shorthand::X;//用作表示    姿态(x,y,z,r,p,y)
    using symbol_shorthand::V;//用表示      速度导数(xdot,ydot,zdot)
    using symbol_shorthand::B;//陀螺仪残差  (ax,ay,az,gx,gy,gz)
    string inputfile="/home/n1/notes/gtsam/Imu/imuAndGPSdata.csv";
    string outputfile="/home/n1/notes/gtsam/Imu/result1.csv";
    PreintegrationType *imu_preintegrated_;
    //    Matrix3 biasAccCovariance;     3*3矩阵 加速度计的协防差矩阵,(可以根据残差计算加速度雅克比矩阵逐步更新)
    //    Matrix3 biasOmegaCovariance;   3*3矩阵 陀螺仪的协防差矩阵, (可以根据残差计算雅克比矩阵递归更新预计分值,防止从头计算)
    //    Matrix6 biasAccOmegaInt;       6*6矩阵 位置残关于加速度和速度残差的协防差,用作更新预计分
    int main(int argc, const char** argv) {
        FILE* fp=fopen(outputfile.c_str(),"w+");
        //输出
        fprintf(fp,"#time(s),x(m),y(m),z(m),qx,qy,qz,qw,gt_x(m),gt_y(m),gt_z(m),gt_qx,gt_qy,gt_qz,gt_qw\n");
    
    
        //解析 CSV
        ifstream file(inputfile.c_str());
        string value;
    
        Eigen::Matrix<double,10,1> initial_state=Eigen::Matrix<double,10,1>::Zero();
        N,E,D,qx,qy,qz,qw,VelN,VelE,VelD
        getline(file,value,',');
        for(int i=0;i<9;i++){
            getline(file,value,',');
            initial_state(i)=atof(value.c_str());//转为浮点型
        }
        getline(file,value,'\n');//换行
        initial_state(9) = atof(value.c_str());
        Rot3 prior_rotation=Rot3::Quaternion(initial_state(6),initial_state(3),initial_state(4),initial_state(5));
        Point3 prior_point(initial_state(0),initial_state(1),initial_state(2));
        Pose3 prior_pose(prior_rotation,prior_point);                               //初始位姿
        Vector3 prior_velocity(initial_state(7),initial_state(8),initial_state(9)); //初始速度
        imuBias::ConstantBias prior_imu_bias;//残差,默认设为0
    
        //初始化值
        Values initial_values;
        int correction_count=0;
        //位姿
        initial_values.insert(X(correction_count),prior_pose);
        //速度
        initial_values.insert(V(correction_count),prior_velocity);
        //残差
        initial_values.insert(B(correction_count),prior_imu_bias);
        cout << "initial state:\n" << initial_state.transpose() <<endl;
        //设置噪声模型
        //一般为设置为对角噪声
        noiseModel::Diagonal::shared_ptr pose_noise_model=noiseModel::Diagonal::Sigmas(Vector6(0.01,0.01,0.01,0.5,0.5,0.5));
        noiseModel::Diagonal::shared_ptr velocity_noise_model=noiseModel::Isotropic::Sigma(3,0.1);
        noiseModel::Diagonal::shared_ptr bias_noise_model=noiseModel::Isotropic::Sigma(6,0.001);
        NonlinearFactorGraph *graph = new NonlinearFactorGraph();
        graph->add(PriorFactor<Pose3>(X(correction_count), prior_pose, pose_noise_model));
        graph->add(PriorFactor<Vector3>(V(correction_count), prior_velocity,velocity_noise_model));
        graph->add(PriorFactor<imuBias::ConstantBias>(B(correction_count), prior_imu_bias,bias_noise_model));
        //使用传感器信息构建IMU的噪声模型
        double accel_noise_sigma = 0.0003924;
        double gyro_noise_sigma = 0.000205689024915;
        double accel_bias_rw_sigma = 0.004905;
        double gyro_bias_rw_sigma = 0.000001454441043;
    
        Matrix33 measured_acc_cov=Matrix33::Identity(3,3)*pow(accel_noise_sigma,2);
        Matrix33 measured_omega_cov=Matrix33::Identity(3,3)*pow(gyro_bias_rw_sigma,2);
        Matrix33 integration_error_cov=Matrix33::Identity(3,3)*1e-8;        //速度积分误差
        Matrix33 bias_acc_cov=Matrix33::Identity(3,3)*pow(accel_bias_rw_sigma,2);
        Matrix33 bias_omega_cov=Matrix33::Identity(3,3)*pow(gyro_bias_rw_sigma,2);
        Matrix66 bias_acc_omega_int=Matrix66::Identity(6,6)*1e-5;           //积分骗到误差
    
        boost::shared_ptr<PreintegratedCombinedMeasurements::Params> p=PreintegratedCombinedMeasurements::Params::MakeSharedD(0.0);
        //MakeSharedD:NED坐标系,g默认为 9.81,这里设置为0
        //MakeSharedU:NEU坐标系,g默认为 9.81
        
        //设置预积分分参数
        p->accelerometerCovariance=measured_acc_cov;
        p->integrationCovariance=integration_error_cov;
        p->gyroscopeCovariance=measured_omega_cov;
    
        //预计分测量值
        p->biasAccCovariance=bias_acc_cov;
        p->biasAccOmegaInt=bias_acc_omega_int;
        p->biasOmegaCovariance=bias_omega_cov;
    #ifdef USE_COMBINED
      imu_preintegrated_ = new PreintegratedCombinedMeasurements(p, prior_imu_bias);
    #else
      imu_preintegrated_ = new PreintegratedImuMeasurements(p, prior_imu_bias);
    #endif
        //保存上一次的imu积分值和结果
        NavState prev_state(prior_pose,prior_velocity);
        NavState prop_state=prev_state;
        imuBias::ConstantBias prev_bias=prior_imu_bias;//
    
        //记录总体误差
        double current_position_error = 0.0, current_orientation_error = 0.0;
    
        double output_time=0;
        double dt=0.005;    //积分时间
    
        //使用数据进行迭代
        while(file.good()){
            getline(file,value,',');
            int type=atoi(value.c_str());//字符转为整形
            if (type == 0) { // IMU 测量数据
                Eigen::Matrix<double,6,1> imu = Eigen::Matrix<double,6,1>::Zero();
                //读取imu数据
                for (int i=0; i<5; ++i) {
                    getline(file, value, ',');
                    imu(i) = atof(value.c_str());
                }
                getline(file, value, '\n');
                imu(5) = atof(value.c_str());
                // 检测测量值加入预计分
                imu_preintegrated_->integrateMeasurement(imu.head<3>(), imu.tail<3>(), dt);
    
            }else if(type ==1){//Gps测量数据
                Eigen::Matrix<double,7,1> gps=Eigen::Matrix<double,7,1>::Zero();
                for(int i=0;i<6;i++){
                    getline(file,value,',');
                    gps(i)=atof(value.c_str());
                }
                getline(file, value, '\n');
                gps(6)=atof(value.c_str());
                correction_count++;
            
    #ifdef USE_COMBINED
        //预计分测量值
            PreintegratedCombinedMeasurements *preint_imu_combined = dynamic_cast<PreintegratedCombinedMeasurements*>(imu_preintegrated_);
        //IMU 因子
        //typedef NoiseModelFactor6<Pose3, Vector3, Pose3, Vector3,imuBias::ConstantBias, imuBias::ConstantBias>
            CombinedImuFactor imu_factor(X(correction_count-1), V(correction_count-1),
                                       X(correction_count  ), V(correction_count  ),
                                       B(correction_count-1), B(correction_count  ),
                                       *preint_imu_combined);
            graph->add(imu_factor);
    #else
        //预计分测量值
            PreintegratedImuMeasurements *preint_imu = dynamic_cast<PreintegratedImuMeasurements*>(imu_preintegrated_);
            ImuFactor imu_factor(X(correction_count-1), V(correction_count-1),
                               X(correction_count  ), V(correction_count  ),
                               B(correction_count-1),
                               *preint_imu);
            graph->add(imu_factor);
            imuBias::ConstantBias zero_bias(Vector3(0, 0, 0), Vector3(0, 0, 0));
            graph->add(BetweenFactor<imuBias::ConstantBias>(B(correction_count-1),
                                                          B(correction_count  ),
                                                          zero_bias, bias_noise_model));
    #endif
            noiseModel::Diagonal::shared_ptr correction_noise=noiseModel::Isotropic::Sigma(3,1.0);
            GPSFactor gps_factor(X(correction_count),
                                Point3(gps(0),gps(1),gps(2)),//(N,E,D)
                                correction_noise);
            graph->add(gps_factor);
            //迭代更新求解imu预测值
            prop_state=imu_preintegrated_->predict(prev_state,prev_bias);
            initial_values.insert(X(correction_count), prop_state.pose());
            initial_values.insert(V(correction_count), prop_state.v());
            initial_values.insert(B(correction_count), prev_bias);
            //求解
            LevenbergMarquardtOptimizer optimizer(*graph,initial_values);
            Values result=optimizer.optimize();
    
            //更新下一步预计分初始值
            //导航状态
            prev_state=NavState(result.at<Pose3>(X(correction_count)),
                                result.at<Vector3>(V(correction_count)));
            //偏导数
            prev_bias=result.at<imuBias::ConstantBias>(B(correction_count));
            //更新预计分值
            imu_preintegrated_->resetIntegrationAndSetBias(prev_bias);
            
            //计算角度误差和误差
            Vector3 gtsam_position=prev_state.pose().translation();
            //位置误差
            Vector3 position_error=gtsam_position-gps.head<3>();
            //误差的范数
            current_position_error=position_error.norm();//归一化
            
            //姿态误差
            Quaternion gtsam_quat=prev_state.pose().rotation().toQuaternion();
            Quaternion gps_quat(gps(6),gps(3),gps(4),gps(5));
            Quaternion quat_error=gtsam_quat*gps_quat.inverse();
            quat_error.normalized();//归一化
            Vector3 euler_angle_error(quat_error.x()*2,quat_error.y()*2,quat_error.z()*2);//转换为欧拉角误差
            current_orientation_error=euler_angle_error.norm();
    
            //输出误差
            cout << "Position error:" << current_position_error << "\t " << "Angular error:" << current_orientation_error << "\n";
                  fprintf(fp, "%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f\n",
                  output_time, gtsam_position(0), gtsam_position(1), gtsam_position(2),
                  gtsam_quat.x(), gtsam_quat.y(), gtsam_quat.z(), gtsam_quat.w(),
                  gps(0), gps(1), gps(2),
                  gps_quat.x(), gps_quat.y(), gps_quat.z(), gps_quat.w());
    
            output_time += 1.0;
            }
            else{
                cerr << "ERROR parsing file\n";
                return 1;
            }
        }
        fclose(fp);
        cout << "完成,结果见:" <<outputfile  << endl;
        return 0;
    }
    
    

    IMU Preintegration

    IMU 的预积分已经成为了标准操作。 但是目前使用livox的算法, 只用内置的 6 轴的 IMU 进行激光数据的矫正, 还没有直接使用IMU进行预积分处理, 也没有找到6轴IMU 预计分的代码结果用以对比。 因为9 轴 imu 能够通过 磁力计提供较为准确的 航向角, 而通过angular_veolcity 不可避免的拥有累计误差,所以9轴的确实比较直接快捷。 不知道读者怎么看.

    参考资料:

    1. https://github.com/KIT-ISAS/lili-om
    2. https://github.com/smilefacehh/LIO-SAM-DetailedNote
    3. https://github.com/YuePanEdward/MULLS
    4. https://github.com/hyye/lio-mapping
    5. https://github.com/ChaoqinRobotics/LINS—LiDAR-inertial-SLAM
    6. https://github.com/Livox-SDK/LIO-Livox
    7. https://github.com/Livox-SDK/livox_mapping
    8. https://github.com/hku-mars/loam_livox
    9. https://github.com/hku-mars/BALM
    展开全文
  • 最近在学习slam建图,想尝试使用大疆mid40利用loam_livox来进行建图,整个过程中遇到了特别多的坑,重装了五六次系统,所幸尝试之前将系统镜像了。 这个算法不需要里程计,利用激光雷达发布的实时点云信息或者录制的...

    0 引言

            最近在学习slam建图,想尝试使用大疆mid40利用loam_livox来进行建图,整个过程中遇到了特别多的坑,重装了五六次系统,所幸尝试之前将系统镜像了。
            这个算法不需要里程计,利用激光雷达发布的实时点云信息或者录制的bag包均可实时建图。
            loam_livox建图需要安装ceres_solver、eigen3、pcl1.9。个人感觉最困难的地方就在这里,pcl版本官方是给定了的,需要将ubuntu16自带的pcl1.7升级为pcl1.9,而ceres_slover与eigen3的版本是没有给的,版本不对应便无法编译成功,找了大半天,最后发现安装ceres_slover1.14.0与eigen3.3.7是可以跑通算法的。而我在利用cartographer进slam建图时,安装的ceres_slover1.11.0以及eigen3.2.92,采用正常的安装方法,两者只能跑一个,因此花了很多时间研究多版本兼容的问题。
            现将整个过程记录下来,供自己与大家参考。

    1 源码下载
    1.1 为loam_livox建立的工作空间

    mkdir -p ~/loam_ws/src
    cd ~/loam_ws/src
    catkin_init_workspace


    1.2 loam_livox下载

    git clone https://github.com/hku-mars/loam_livox.git


    1.3 安装依赖
     

    sudo apt-get install ros-kinetic-cv-bridge ros-kinetic-tf ros-kinetic-message-filters ros-kinetic-image-transport

    2 eigen3.3.7安装
    2.1 源码下载
    从下面这个网站中下载压缩包后解压到主目录

    https://gitlab.com/libeigen/eigen/-/tree/3.3.7


    2.2 编译
    采用正常编译方式会将eigen编译到/usr/local/include下,而此目录已有我此前安装的eigen3.2.92,因此需将它安装在自己的指定目录下。

    cd ~/eigen-3.3.7
    mkdir build
    mkdir eigen_loam


    随后

    cd build
    cmake -DCMAKE_INSTALL_PREFIX="/home/lww/eigen-3.3.7/eigen_loam/" ..


    注:这里build文件夹用来存放编译产生的文件,eigen_loam用来作为eigen的安装位置

    make
    sudo make install


    安装成功
    此时会发现eigen_loam文件夹中include文件下有eigen3文件,将它复制一份,重命名为eigen337,再复制到/usr/local/include/下

    cd ~/eigen-3.3.7/eigen_loam/include
    sudo cp -R eigen337 /usr/local/include/


    至此,eigen3.3.7安装成功
    3 ceres_solver1.14.0安装
    3.1 源码下载
    github上下载压缩包后解压到主目录

    https://github.com/ceres-solver/ceres-solver/releases/tag/1.14.0


    3.2 编译

    cd ~/ceres-solver-1.14.0
    mkdir build
    mkdir cere_loam


    由于cere-solver-1.14.0必须依赖于eigen3.3.7,无法采用eigen3.2.92编译,而默认目录下安装的是eigen3.2.92,因此需要为ceres指定eigen3.3.7安装目录,我们在上面已经将eigen3.3.7复制到了/usr/local/include下面,修改一下路径即可。
    3.2.1 修改CMakeLists.txt文件

    sudo gedit CMakeLists.txt


    在find_package(Eigen REQUIRED)前加入

    set(EIGEN_DIR /usr/local/include/eigen337)


    3.2.2 修改FindEigen.cmake

    cd cmake
    sudo gedit FindEigen.cmake


    修改如下所示,对比原文件参照:

    # Ceres Solver - A fast non-linear least squares minimizer
    # Copyright 2015 Google Inc. All rights reserved.
    # http://ceres-solver.org/
    #
    # Redistribution and use in source and binary forms, with or without
    # modification, are permitted provided that the following conditions are met:
    #
    # * Redistributions of source code must retain the above copyright notice,
    #   this list of conditions and the following disclaimer.
    # * Redistributions in binary form must reproduce the above copyright notice,
    #   this list of conditions and the following disclaimer in the documentation
    #   and/or other materials provided with the distribution.
    # * Neither the name of Google Inc. nor the names of its contributors may be
    #   used to endorse or promote products derived from this software without
    #   specific prior written permission.
    #
    # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
    # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    # POSSIBILITY OF SUCH DAMAGE.
    #
    # Author: alexs.mac@gmail.com (Alex Stewart)
    #
    
    # FindEigen.cmake - Find Eigen library, version >= 3.
    #
    # This module defines the following variables:
    #
    # EIGEN_FOUND: TRUE iff Eigen is found.
    # EIGEN_INCLUDE_DIRS: Include directories for Eigen.
    # EIGEN_VERSION: Extracted from Eigen/src/Core/util/Macros.h
    # EIGEN_WORLD_VERSION: Equal to 3 if EIGEN_VERSION = 3.2.0
    # EIGEN_MAJOR_VERSION: Equal to 2 if EIGEN_VERSION = 3.2.0
    # EIGEN_MINOR_VERSION: Equal to 0 if EIGEN_VERSION = 3.2.0
    # FOUND_INSTALLED_EIGEN_CMAKE_CONFIGURATION: True iff the version of Eigen
    #                                            found was built & installed /
    #                                            exported as a CMake package.
    #
    # The following variables control the behaviour of this module:
    #
    # EIGEN_PREFER_EXPORTED_EIGEN_CMAKE_CONFIGURATION: TRUE/FALSE, iff TRUE then
    #                           then prefer using an exported CMake configuration
    #                           generated by Eigen over searching for the
    #                           Eigen components manually.  Otherwise (FALSE)
    #                           ignore any exported Eigen CMake configurations and
    #                           always perform a manual search for the components.
    #                           Default: TRUE iff user does not define this variable
    #                           before we are called, and does NOT specify
    #                           EIGEN_INCLUDE_DIR_HINTS, otherwise FALSE.
    # EIGEN_INCLUDE_DIR_HINTS: List of additional directories in which to
    #                          search for eigen includes, e.g: /timbuktu/eigen3.
    #
    # The following variables are also defined by this module, but in line with
    # CMake recommended FindPackage() module style should NOT be referenced directly
    # by callers (use the plural variables detailed above instead).  These variables
    # do however affect the behaviour of the module via FIND_[PATH/LIBRARY]() which
    # are NOT re-called (i.e. search for library is not repeated) if these variables
    # are set with valid values _in the CMake cache_. This means that if these
    # variables are set directly in the cache, either by the user in the CMake GUI,
    # or by the user passing -DVAR=VALUE directives to CMake when called (which
    # explicitly defines a cache variable), then they will be used verbatim,
    # bypassing the HINTS variables and other hard-coded search locations.
    #
    # EIGEN_INCLUDE_DIR: Include directory for CXSparse, not including the
    #                    include directory of any dependencies.
    
    # Called if we failed to find Eigen or any of it's required dependencies,
    # unsets all public (designed to be used externally) variables and reports
    # error message at priority depending upon [REQUIRED/QUIET/<NONE>] argument.
    macro(EIGEN_REPORT_NOT_FOUND REASON_MSG)
      unset(EIGEN_FOUND)
      unset(EIGEN_INCLUDE_DIRS)
      unset(FOUND_INSTALLED_EIGEN_CMAKE_CONFIGURATION)
      # Make results of search visible in the CMake GUI if Eigen has not
      # been found so that user does not have to toggle to advanced view.
      mark_as_advanced(CLEAR EIGEN_INCLUDE_DIR)
      # Note <package>_FIND_[REQUIRED/QUIETLY] variables defined by FindPackage()
      # use the camelcase library name, not uppercase.
      if (Eigen_FIND_QUIETLY)
        message(STATUS "Failed to find Eigen - " ${REASON_MSG} ${ARGN})
      elseif (Eigen_FIND_REQUIRED)
        message(FATAL_ERROR "Failed to find Eigen - " ${REASON_MSG} ${ARGN})
      else()
        # Neither QUIETLY nor REQUIRED, use no priority which emits a message
        # but continues configuration and allows generation.
        message("-- Failed to find Eigen - " ${REASON_MSG} ${ARGN})
      endif ()
      return()
    endmacro(EIGEN_REPORT_NOT_FOUND)
    
    # Protect against any alternative find_package scripts for this library having
    # been called previously (in a client project) which set EIGEN_FOUND, but not
    # the other variables we require / set here which could cause the search logic
    # here to fail.
    unset(EIGEN_FOUND)
    
    # -----------------------------------------------------------------
    # By default, if the user has expressed no preference for using an exported
    # Eigen CMake configuration over performing a search for the installed
    # components, and has not specified any hints for the search locations, then
    # prefer an exported configuration if available.
    if (NOT DEFINED EIGEN_PREFER_EXPORTED_EIGEN_CMAKE_CONFIGURATION
        AND NOT EIGEN_INCLUDE_DIR_HINTS)
      message(STATUS "No preference for use of exported Eigen CMake configuration "
        "set, and no hints for include directory provided. "
        "Defaulting to preferring an installed/exported Eigen CMake configuration "
        "if available.")
      set(EIGEN_PREFER_EXPORTED_EIGEN_CMAKE_CONFIGURATION TRUE)
    endif()
    
    if (EIGEN_PREFER_EXPORTED_EIGEN_CMAKE_CONFIGURATION)
      # Try to find an exported CMake configuration for Eigen.
      #
      # We search twice, s/t we can invert the ordering of precedence used by
      # find_package() for exported package build directories, and installed
      # packages (found via CMAKE_SYSTEM_PREFIX_PATH), listed as items 6) and 7)
      # respectively in [1].
      #
      # By default, exported build directories are (in theory) detected first, and
      # this is usually the case on Windows.  However, on OS X & Linux, the install
      # path (/usr/local) is typically present in the PATH environment variable
      # which is checked in item 4) in [1] (i.e. before both of the above, unless
      # NO_SYSTEM_ENVIRONMENT_PATH is passed).  As such on those OSs installed
      # packages are usually detected in preference to exported package build
      # directories.
      #
      # To ensure a more consistent response across all OSs, and as users usually
      # want to prefer an installed version of a package over a locally built one
      # where both exist (esp. as the exported build directory might be removed
      # after installation), we first search with NO_CMAKE_PACKAGE_REGISTRY which
      # means any build directories exported by the user are ignored, and thus
      # installed directories are preferred.  If this fails to find the package
      # we then research again, but without NO_CMAKE_PACKAGE_REGISTRY, so any
      # exported build directories will now be detected.
      #
      # To prevent confusion on Windows, we also pass NO_CMAKE_BUILDS_PATH (which
      # is item 5) in [1]), to not preferentially use projects that were built
      # recently with the CMake GUI to ensure that we always prefer an installed
      # version if available.
      #
      # [1] http://www.cmake.org/cmake/help/v2.8.11/cmake.html#command:find_package
      find_package(Eigen3 QUIET
                          NO_MODULE
                          NO_CMAKE_PACKAGE_REGISTRY
                          NO_CMAKE_BUILDS_PATH)
      if (EIGEN3_FOUND)
        message(STATUS "Found installed version of Eigen: ${Eigen3_DIR}")
      else()
        # Failed to find an installed version of Eigen, repeat search allowing
        # exported build directories.
        message(STATUS "Failed to find installed Eigen CMake configuration, "
          "searching for Eigen build directories exported with CMake.")
        # Again pass NO_CMAKE_BUILDS_PATH, as we know that Eigen is exported and
        # do not want to treat projects built with the CMake GUI preferentially.
        find_package(Eigen3 QUIET
                            NO_MODULE
                            NO_CMAKE_BUILDS_PATH)
        if (EIGEN3_FOUND)
          message(STATUS "Found exported Eigen build directory: ${Eigen3_DIR}")
        endif()
      endif()
      if (EIGEN3_FOUND)
        set(FOUND_INSTALLED_EIGEN_CMAKE_CONFIGURATION TRUE)
        set(EIGEN_FOUND ${EIGEN3_FOUND})
        set(EIGEN_INCLUDE_DIR "${EIGEN3_INCLUDE_DIR}" CACHE STRING
          "Eigen include directory" FORCE)
      else()
        message(STATUS "Failed to find an installed/exported CMake configuration "
          "for Eigen, will perform search for installed Eigen components.")
      endif()
    endif()
    
    if (NOT EIGEN_FOUND)
      # Search user-installed locations first, so that we prefer user installs
      # to system installs where both exist.
      list(APPEND EIGEN_CHECK_INCLUDE_DIRS
        /usr/local/include
        /usr/local/homebrew/include # Mac OS X
        /opt/local/var/macports/software # Mac OS X.
        /opt/local/include
        /usr/include
        /usr/local/include/eigen337)
      # Additional suffixes to try appending to each search path.
      list(APPEND EIGEN_CHECK_PATH_SUFFIXES
        eigen3 # Default root directory for Eigen.
        Eigen/include/eigen3 # Windows (for C:/Program Files prefix) < 3.3
        Eigen3/include/eigen3 ) # Windows (for C:/Program Files prefix) >= 3.3
    
      # Search supplied hint directories first if supplied.
      find_path(EIGEN_INCLUDE_DIR
        NAMES Eigen/Core
        HINTS ${EIGEN_INCLUDE_DIR_HINTS}
        PATHS $"/usr/local/include/eigen337"
        PATH_SUFFIXES ${EIGEN_CHECK_PATH_SUFFIXES})
    
      if (NOT EIGEN_INCLUDE_DIR OR
          NOT EXISTS ${EIGEN_INCLUDE_DIR})
        eigen_report_not_found(
          "Could not find eigen3 include directory, set EIGEN_INCLUDE_DIR to "
          "path to eigen3 include directory, e.g. /usr/local/include/eigen337.")
      endif (NOT EIGEN_INCLUDE_DIR OR
        NOT EXISTS ${EIGEN_INCLUDE_DIR})
    
      # Mark internally as found, then verify. EIGEN_REPORT_NOT_FOUND() unsets
      # if called.
      set(EIGEN_FOUND TRUE)
    endif()
    
    # Extract Eigen version from Eigen/src/Core/util/Macros.h
    if (EIGEN_INCLUDE_DIR)
      set(EIGEN_VERSION_FILE /usr/local/include/eigen337/Eigen/src/Core/util/Macros.h)
      if (NOT EXISTS ${EIGEN_VERSION_FILE})
        eigen_report_not_found(
          "Could not find file: ${EIGEN_VERSION_FILE} "
          "containing version information in Eigen install located at: "
          "${EIGEN_INCLUDE_DIR}.")
      else (NOT EXISTS ${EIGEN_VERSION_FILE})
        file(READ ${EIGEN_VERSION_FILE} EIGEN_VERSION_FILE_CONTENTS)
    
        string(REGEX MATCH "#define EIGEN_WORLD_VERSION [0-9]+"
          EIGEN_WORLD_VERSION "${EIGEN_VERSION_FILE_CONTENTS}")
        string(REGEX REPLACE "#define EIGEN_WORLD_VERSION ([0-9]+)" "\\1"
          EIGEN_WORLD_VERSION "${EIGEN_WORLD_VERSION}")
    
        string(REGEX MATCH "#define EIGEN_MAJOR_VERSION [0-9]+"
          EIGEN_MAJOR_VERSION "${EIGEN_VERSION_FILE_CONTENTS}")
        string(REGEX REPLACE "#define EIGEN_MAJOR_VERSION ([0-9]+)" "\\1"
          EIGEN_MAJOR_VERSION "${EIGEN_MAJOR_VERSION}")
    
        string(REGEX MATCH "#define EIGEN_MINOR_VERSION [0-9]+"
          EIGEN_MINOR_VERSION "${EIGEN_VERSION_FILE_CONTENTS}")
        string(REGEX REPLACE "#define EIGEN_MINOR_VERSION ([0-9]+)" "\\1"
          EIGEN_MINOR_VERSION "${EIGEN_MINOR_VERSION}")
    
        # This is on a single line s/t CMake does not interpret it as a list of
        # elements and insert ';' separators which would result in 3.;2.;0 nonsense.
        set(EIGEN_VERSION "${EIGEN_WORLD_VERSION}.${EIGEN_MAJOR_VERSION}.${EIGEN_MINOR_VERSION}")
      endif (NOT EXISTS ${EIGEN_VERSION_FILE})
    endif (EIGEN_INCLUDE_DIR)
    
    # Set standard CMake FindPackage variables if found.
    if (EIGEN_FOUND)
      set(EIGEN_INCLUDE_DIRS ${EIGEN_INCLUDE_DIR})
    endif (EIGEN_FOUND)
    
    # Handle REQUIRED / QUIET optional arguments and version.
    include(FindPackageHandleStandardArgs)
    find_package_handle_standard_args(Eigen
      REQUIRED_VARS EIGEN_INCLUDE_DIRS
      VERSION_VAR EIGEN_VERSION)
    
    # Only mark internal variables as advanced if we found Eigen, otherwise
    # leave it visible in the standard GUI for the user to set manually.
    if (EIGEN_FOUND)
      mark_as_advanced(FORCE EIGEN_INCLUDE_DIR
        Eigen3_DIR) # Autogenerated by find_package(Eigen3)
    endif (EIGEN_FOUND)


    3.2.3 编译

    cd ~/ceres-solver-1.14.0/build
    cmake -DCMAKE_INSTALL_PREFIX="/home/lww/ceres-solver-1.14.0/ceres_loam/" ..
    make -j4
    sudo make install


    编译成功
    4 pcl1.9安装
    4.1 安装依赖

         sudo apt-get update
         sudo apt-get install git build-essential linux-libc-dev
         sudo apt-get install cmake cmake-gui
         sudo apt-get install libusb-1.0-0-dev libusb-dev libudev-dev
         sudo apt-get install mpi-default-dev openmpi-bin openmpi-common  
         sudo apt-get install libflann1.8 libflann-dev
         sudo apt-get install libeigen3-dev
         sudo apt-get install libboost-all-dev
         sudo apt-get install libvtk5.10-qt4 libvtk5.10 libvtk5-dev
         sudo apt-get install libqhull* libgtest-dev
         sudo apt-get install freeglut3-dev pkg-config
         sudo apt-get install libxmu-dev libxi-dev
         sudo apt-get install mono-complete
         sudo apt-get install qt-sdk openjdk-8-jdk openjdk-8-jre


    4.2 源码下载
    github上下载压缩包后解压到主目录下

    https://github.com/PointCloudLibrary/pcl/releases


    4.3 编译

    cd ~/pcl-pcl-1.9.0
    mkdir build
    cd build
    cmake -DCMAKE_BUILD_TYPE=None -DCMAKE_INSTALL_PREFIX=/usr \
          -DBUILD_GPU=ON -DBUILD_apps=ON -DBUILD_examples=ON \
          -DCMAKE_INSTALL_PREFIX=/usr ..
    make -j4
    sudo make install


    安装成功
    5 loam_livox源码编译
    5.1 修改loam_livox的CMakeLists.txt文件

    cd ~/loam_ws/src/loam_livox
    sudo gedit CMakeLists.txt


    需修改部分如下所示

    find_package(Eigen3 REQUIRED)
    find_package(OpenCV REQUIRED)
    find_package(Ceres 1.14.0 REQUIRED PATHS /home/lww/ceres-solver-1.14.0/ceres_loam NO_DEFAULT_PATH)
    #find_package(Ceres REQUIRED)
    find_package(PCL 1.9 REQUIRED)


    5.2 源码编译

    cd ~/loam_ws
    catkin_make


    6 loam_livox试跑
    6.1 下载官方bag包
    在下方链接中下载CYT_02.bag,放至主目录下

    https://drive.google.com/drive/folders/1HWomWWPSEVvka2QVB2G41iRvSwIt5NWf


    6.2 试跑

    source ~/loam_ws/devel/setup.bash
    roslaunch loam_livox rosbag.launch


    另起终端

    rosbag play CYT_02.bag


    建图成功
    可用自己录制的bag包进行试验

    展开全文
  • 点击上方“3D视觉工坊”,选择“星标”干货第一时间送达作者丨Livox 览沃激光雷达@知乎来源丨https://zhuanlan.zhihu.com/p/442741667编辑丨3D视觉工...

    点击上方“3D视觉工坊”,选择“星标”

    干货第一时间送达

    50903a32eb0e544d76f6b0e0ea20fb50.png

    作者丨Livox 览沃激光雷达@知乎

    来源丨https://zhuanlan.zhihu.com/p/442741667

    编辑丨3D视觉工坊

    一、什么是激光雷达自运动畸变

    激光雷达通过发射激光束来测量周围环境物体的距离和方位,从而判断车辆与障碍物的相对位置。当其发射的激光束足够多时,这一个个的激光点将汇集成一片点云,勾勒出其所处的三维环境信息,这便是我们常说的点云数据。

    对于多数激光雷达而言,尽管激光的发射与接收很快,但构成点云的每一个点仍非同一时刻生成的。一般我们会将100ms (对应典型值10Hz) 内累积的数据作为一帧点云输出。若在这100ms内,激光雷达本体或安装所在的机体发生绝对位置的变化,那么此帧点云中每一个点的坐标系就是不同的。直观上看,这一帧点云数据就会发生一定的“变形”,不能真实对应所探测到的环境信息,类似于拍照时手抖了,拍出来的照片就会糊。这便是激光雷达的自运动畸变。

    二、自运动畸变产生的本质以及校正

    我们来具体看一看自运动畸变是什么样的。激光雷达点云自运动畸变的形态,与其扫描方式是相关的。比如传统360度机械式激光雷达每一帧,是以雷达为中心环绕扫描一周(100ms)得到的。当雷达本体或所在车体静止时,扫描起始点和终止点可以比较好地闭合(坐标原点始终保持不变)。而当雷达或自车运动时,自运动畸变就会发生,环绕一圈的数据就会发生扭曲,导致环绕不再闭合(不同点的坐标原点不同)。

    6e74c98df88d7b7fa38e73cfb139e3ad.png

    图1 360度机械式激光雷达自运动畸变示意

    我们再深入分析一下这一现象的本质。简单来说,激光雷达点云自运动畸变的产生本质上是一帧中每一个点的坐标系不同。如下图,左图 p1~p3 表示激光雷达依次扫描到的三个位置点,这三点在真实世界中共线。但由于激光雷达自身在一帧时间内存在“剧烈”运动,如中间图所示,雷达自身分别在三个不同的实际姿态下对三个点进行了扫描。因此在最后得到的点云中(最右图),三个点坐标实际处于不同的坐标系,看起来不再共线了。

    f978dcca88dc7f942e5678f4e631f499.png

    图2 点云坐标系发生变化

    下图 3 也给出一个实际应用的例子。

    搭载 Livox 激光雷达的车辆因自身掉头发生了自运动畸变:远处的墙体和车辆都因为自车快速旋转产生了分层现象。

    6298173af521b3d47ae138ade64f6286.png 86fd2157ddc0b2801ad2ee066ce5528f.png

    图3 由于车体运动,路边停放的车辆点云出现分层

    那么,自运动畸变要怎么校正呢?显然,只要我们把车开得足够的慢……

    当然不是,只需要我们将这一帧内所有点的坐标系都转换到同一个,如图1 第一个点 p1 所在的雷达坐标系,这本质上就是对雷达的运动进行补偿。

    我们以 表示 在雷达坐标系1 中的坐标,坐标系 i 到 j 的转换表示为 。则一帧点云中每个点到第一个点坐标系的转化分别为 ……,用下式可轻松将对应点坐标转到第一个点所在坐标系:

    原理上看起来非常简单(实际也非常简单)。只要知道每个点的 就行了,那到底怎么知道呢?

    在实际应用中,一般首先设法测量激光雷达的运动信息,如一帧点云首尾(100ms间隔)的雷达位姿变化 T。然后根据某点到初始点或末尾点之间的时间差 Δt,通过短时匀速假设进行线性插值得到该点的 。

    位姿变化 T 可通过惯性导航系统(INS)或激光雷达里程计(LiDAR Odometry, 如 LIO-Livox )提供的位姿信息获得。若使用惯性测量单元(IMU,可提供角速度以及加速度信息)计算位姿变化,则需要额外提供雷达或者自车的初始速度信息。

    那 Δt 的值怎么获得呢?

    Livox 激光雷达输出自带每个点的时间戳。在获取点云的时候就可从点云数据包 Custom Msg中直接读取到。而其他雷达则可能需要根据各自雷达的 SDK 所提供信息或自行手动解算得到每个点的时间戳。

    按上述公式将各个点坐标转换到同一坐标系,就是去畸变的过程了。

    下图 4 展示了去畸变后的点云。

    8c3abfb8913decdaa20f055cc54c47ee.png

    图4 上例点云去畸变后效果

    三、自运动畸变校正工具使用及说明

    上述去畸变的过程代码已上传 Github(https://github.com/Livox-SDK/livox_cloud_undistortion), 有兴趣的读者欢迎点击下方阅读原文查看。

    代码说明:

    依赖:livox_ros_driver,PCL,ROS编译:在工作空间下使用指令 catkin_make运行:

    source devel/setup.bash

    接口说明:

    在 data_process.h 中定义了 ImuProcess 类,该类的成员函数 UndistortPcl 为去畸变函数,该函数参数中 Sophus::SE3d Tbe 为当前帧点云帧头和帧尾之间的位姿,如果可以直接提供该位姿,则可以调用该函数进行去畸变。如果只有IMU数据,则调用 ImuProcess 的成员函 Process 进行去畸变。

    使用:

    输入:此工具基于ros开发,因此输入信息为两个 topic,点云 topic 为 /livox/lidar, customMsg 格式,IMU 信息 topic为/livox/imu输出:校正后的点云 /livox_unidistort

    特别说明:平移畸变的校正需要用户根据各自的平移信息来源(GPS位置坐标、速度等)手动计算对应时间差下的平移变化,作为 UndistortPcl 输入。

    本文仅做学术分享,如有侵权,请联系删文。

    3D视觉精品课程推荐:

    1.面向自动驾驶领域的多传感器数据融合技术

    2.面向自动驾驶领域的3D点云目标检测全栈学习路线!(单模态+多模态/数据+代码)
    3.彻底搞透视觉三维重建:原理剖析、代码讲解、及优化改进
    4.国内首个面向工业级实战的点云处理课程
    5.激光-视觉-IMU-GPS融合SLAM算法梳理和代码讲解
    6.彻底搞懂视觉-惯性SLAM:基于VINS-Fusion正式开课啦
    7.彻底搞懂基于LOAM框架的3D激光SLAM: 源码剖析到算法优化
    8.彻底剖析室内、室外激光SLAM关键算法原理、代码和实战(cartographer+LOAM +LIO-SAM)

    9.从零搭建一套结构光3D重建系统[理论+源码+实践]

    10.单目深度估计方法:算法梳理与代码实现

    11.自动驾驶中的深度学习模型部署实战

    12.相机模型与标定(单目+双目+鱼眼)

    重磅!3DCVer-学术论文写作投稿 交流群已成立

    扫码添加小助手微信,可申请加入3D视觉工坊-学术论文写作与投稿 微信交流群,旨在交流顶会、顶刊、SCI、EI等写作与投稿事宜。

    同时也可申请加入我们的细分方向交流群,目前主要有3D视觉CV&深度学习SLAM三维重建点云后处理自动驾驶、多传感器融合、CV入门、三维测量、VR/AR、3D人脸识别、医疗影像、缺陷检测、行人重识别、目标跟踪、视觉产品落地、视觉竞赛、车牌识别、硬件选型、学术交流、求职交流、ORB-SLAM系列源码交流、深度估计等微信群。

    一定要备注:研究方向+学校/公司+昵称,例如:”3D视觉 + 上海交大 + 静静“。请按照格式备注,可快速被通过且邀请进群。原创投稿也请联系。

    308dba8debbc0983b1b9b4431423a7bb.png

    ▲长按加微信群或投稿

    064141e6e7c21917e7047174e7f641fd.png

    ▲长按关注公众号

    3D视觉从入门到精通知识星球:针对3D视觉领域的视频课程(三维重建系列三维点云系列结构光系列手眼标定相机标定激光/视觉SLAM自动驾驶等)、知识点汇总、入门进阶学习路线、最新paper分享、疑问解答五个方面进行深耕,更有各类大厂的算法工程人员进行技术指导。与此同时,星球将联合知名企业发布3D视觉相关算法开发岗位以及项目对接信息,打造成集技术与就业为一体的铁杆粉丝聚集区,近4000星球成员为创造更好的AI世界共同进步,知识星球入口:

    学习3D视觉核心技术,扫描查看介绍,3天内无条件退款

    2b36e059079049d2db599e85c09f7df7.png

     圈里有高质量教程资料、答疑解惑、助你高效解决问题

    觉得有用,麻烦给个赞和在看~  

    展开全文
  • livox 使用

    千次阅读 2021-03-02 11:23:10
    cd Livox-SDK cd build && \ cmake .. -DCMAKE_SYSTEM_NAME=Linux -DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc -DCMAKE_CXX_COMPILER=aarch64-linux-gnu-g++ make 修改 静态ip 来自 sudo gedit /etc/ne

    viewer

    windows 下可以使用
    linux 无法使用报错:编译环境问题

    SDK

    1. 下载编译:略
      TX2 下编译:
    cd Livox-SDK
    cd build && \
    cmake .. -DCMAKE_SYSTEM_NAME=Linux -DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc -DCMAKE_CXX_COMPILER=aarch64-linux-gnu-g++
    make
    sudo make install 
    
    1. 修改 静态ip
      来自
      在这里插入图片描述
    sudo  gedit /etc/network/interfaces
    

    设置ip

    auto enp3s0
    iface enp3s0 inet static #设置为静态 
    address 192.168.1.1   #ip 地址 livox默认:ip为:192.168.1.xxx
    netmask 255.255.255.0 #子网掩码 livox默认:netmask 255.255.255.0 
    gateway 192.168.1.1 #网关	   livox默认:192.168.1.1
    

    重启网络:

    sudo /etc/init.d/networking restart
    
    1. 测试
      详见
    ./lidar_sample
    ./lidar_sample_cc -c "广播码" -l
    ./Livox-SDK/build/sample_cc/lidar/lidar_sample_cc -c "3JEDHC900100781" 
    

    存在问题:
    重新插拔会造成网络波动,需要重启网卡才能重新连接

    ROS_driver

    1. 安装
    git clone https://github.com/Livox-SDK/livox_ros_driver.git
    git clone https://github.com/Livox-SDK/livox_mapping.git
    
    
    1. 编译
    catkin_make
    
    1. 测试
    roslaunch livox_ros_driver livox_lidar.launch bd_list:="3JEDHC900100781"
    //bd_list="广播码"
    roslaunch livox_mapping mapping_mid.launch
    

    loam_livox

        roslaunch loam_livox livox.launch
        roslaunch livox_ros_driver livox_lidar.launch bd_list:="3JEDHC900100781"
    

    时间同步

    livox 支持如下方式

    1. PTP 时钟:IEEE 1588v2.0 PTP 网络协议同步
      <1> 简介:转载
      高精度时间同步协议,可以到达亚微秒级精度,硬件实现时可以达到ns 级的精度,软件实现时通常可以达到ms 级的精度
      <2> 特点:
      -直接在 MAC 层进行 PTP 协议包分析 , 这样可以不经过UDP 协议栈 , 减少PTP 在协议栈中驻留时间 , 提高同步的精确度。
      -主从同步系统,一般采用硬件时间戳,并配合一些对NTP更高精度的延时测量算法
      -也可以承载在 UDP 上时 , 软件可以采用 SOCKET 进行收发 UDP包 , 事件消息的 UDP 端口号 319 , 普通消息的组播端口号为 320 ,但其精度就大大降低
      -在物理硬件要求主从端都是PTP设备,且网络不能太大,其中间经过的交换机设备也必须支持PTP协议,并且主从时间网络链路唯一,不存在交替的PTP通道。
      -采用相对时间同步机制。一个参与者被选作主时间钟,其将发送同步信息到从站。主站将发送同步报文到网络。所有的从站计算时间延迟。
      <3>:时钟同步
      传输路径延时: D e l a y = [ ( t 4 – t 1 ) – ( t 3 – t 2 ) ] / 2 Delay = [(t4 – t1) – (t3 – t2)]/2 Delay=[(t4t1)(t3t2)]/2
      时间偏移: O f f s e t = ( t 2 − t 1 ) − D e l a y = [ ( t 2 – t 1 ) + ( t 3 – t 4 ) ] / 2 Offset = (t2 - t1) - Delay = [(t2 – t1) + (t3 – t4)]/2 Offset=(t2t1)Delay=[(t2t1)+(t3t4)]/2
      在这里插入图片描述
      <4>:livox作为slave端,和master时钟设备进行ptp时间同步
      linux 配置PTP
      -安装(需要联网)
    git clone http://git.code.sf.net/p/linuxptp/code linuxptp
    cd linuxptp
    make
    sudo make install
    

    -查看内核(需要3.0 以上)

     uname -srm
    

    -查看网卡需要支持硬件时间戳

    ethtool -T eth0
    

    -开启ptp4l 主时钟

    sudo ptp4l -i eth0 -l 6 -m
    #-L legacy硬件时间戳
    #-i interface 确定一个ptp的端口。
    #-m 将消息打印到标准输出。
    #可以使用winreshasrk 查看
    sudo wireshark
    #选择 eth0 如果有时钟证明存在良好
    

    -让系统时间和PTP硬件时钟同步

    sudo phc2sys -c eth0 -s CLOCK_REALTIME -O 0
    #-c 时间接收设备
    #-s 时间源设备
    #-o 接收时间源偏移意识按
    
    1. GPS:秒脉冲+GPRMC时间数据,GPS时间同步
    2. PPS:秒脉冲同步,需要通过串口等获取脉冲时间修正点云,不推荐使用。
    展开全文
  • livox_loam是HKU做的一个非重复扫描激光雷达的SLAM系统,使用的是大疆的livox_mid40这款激光雷达代码开源:https://github.com/hku-mars/loam_livox他的github里面有两篇相关论文,分别是livox系统和livox的闭环检测...
  • 罗马不是一天建成的,这一大作是沿袭了该实验室多年来的积累,结合了vins-mono、fast-lio等里程计的特性,加上livox雷达的稠密点云属性(他们超喜欢livox雷达),目前为止是我看视频看到的最好建图效果,可惜还没...
  • 从数据流视角解析 loam_livox

    千次阅读 多人点赞 2020-05-15 10:30:32
    解析一套代码,一般有两种视角。一种是按类(class)解析,另一种...loam_livox 的整体结构并不复杂,核心节点就两个:livox_scanRegistration 主要用于读入数据、特征提取;livox_laserMapping 主要用于位姿估计、建图。
  • ~/test_work_space$ catkin_make 添加环境变量 ~/catkin_ws$ source devel/setup.bash 运行 下载github上的bag并且运行,运行下面这一行代码,rviz窗口就弹出来拉 ~/catkin_ws$ roslaunch livox_mapping mapping_...
  • livox的一些使用

    2021-08-30 10:36:27
    livox Horizon的使用 一 .连接激光雷达livox Horizon,在windows下利用大疆livox官网的软件生成.lvx文件 下载官网: 链接:大疆览沃官网 直接到下载中心下载Windows版 下载后文件内容如下: 你可以通过电源转接插座...
  • Livox Mid-40 安装配置

    2021-05-11 15:45:09
    近期拿到了大疆Livox Mid-40激光雷达,但是发现大家大家在配置那一块说得都不是很详细,所以自己开始一边配置一边记录,主要是包括硬件链接等详细情况说明。 参考博客: ... ... 文件下载: ...
  • 点击上方“计算机视觉工坊”,选择“星标”干货第一时间送达Loam livox(2019 IROS)介绍:大疆出品,必属精品。固态激光雷达里程计的工作现阶段还是比较少的,大疆自己出了固态...
  • 大疆Livox Mid-70雷达的使用

    千次阅读 2021-06-10 21:25:17
    (一)、使用Livox Viewer测试雷达 1、设置静态IP sudo ifconfig enx081f7152092a 192.168.1.50 查看IP是否设置成功 lacyexsale@lacyexsale-KLV-WX9:~$ ifconfig enx081f7152092a Link encap:以太网 硬件地址 08:1f...
  • Livox Lidar+海康Camera实时生成彩色点云

    千次阅读 热门讨论 2021-06-01 17:12:26
    Livox Lidar + HIKROBOT Camera系列: 海康Camera MVS Linux SDK二次开发ROS packge过程记录(c++) 海康Camera+Livox Lidar生成彩色点云ROS packge
  • 利用 Livox-SDK 开发的程序,很早就完成了,今天把它们整理出来分享出来给大家。 解析 lvx 文件 exe 下载地址:lvx 批量转 las。这是一个解析 lvx 文件的轻量级的控制台程序。支持 lvx 批量转 las,从 livox-hub 中...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 826
精华内容 330
关键字:

livox