SC-Lego-LOAM解析(上)

2023-05-16

文章目录

      • 正文
      • imageProjection
      • featureAssociation
        • Feature Extraction

正文

SC-Lego-LOAM实际上应该并不对应某一篇特定的论文,而是韩国KAIST在github开源的代码,其实质上是融合了ScanContext和Lego LOAM两篇论文,代码及上述两篇论文的出处在这里就不赘述了,在下面的链接里。

https://github.com/irapkaist/SC-LeGO-LOAM

下面正式开始,根据仓库readme的提示,启动方式为:

roslaunch lego_loam run.launch
所以从launch文件看起~
Launch:

<launch>

    。。。。。。

    <!--- LeGO-LOAM -->
    <node pkg="lego_loam" type="imageProjection" name="imageProjection" output="screen"/>
    <node pkg="lego_loam" type="featureAssociation" name="featureAssociation" output="screen"/>
    <node pkg="lego_loam" type="mapOptmization" name="mapOptmization" output="screen"/>
    <node pkg="lego_loam" type="transformFusion" name="transformFusion" output="screen"/>

</launch>

(简洁起见,这里删除了参数的加载和rviz可视化的代码,只保留主线部分,不是说上面不重要,只是为了思路的清晰。)

可以看出这里共启动了4个node,其中最后一个node好像是输出日志之类的东西,对整体的作用影响不大,主要功能的是由前面3个node来实现的,而且是存在数据流的依次传递和处理。

imageProjection

这个node主要的功能是对激光雷达数据进行预处理,包括分割,标注和发布。

从cmake文件看出来,该node的头文件和具体实现分别在image_projection.h和image_projection.cpp中,大家可以先看一下头文件看看大致包括啥,这里直接进入.cpp实现。

int main(int argc, char **argv) {

    ros::init(argc, argv, "lego_loam");

    lego_loam::ImageProjection IP;

    ROS_INFO("\033[1;32m---->\033[0m Image Projection Started.");
    ros::spin();
    return 0;


}

main函数中对ROS进行了初始化,然后就初始化了lego_loam::ImageProjection这么个东西,所以所有的内容都是在lego_loam::ImageProjection的构造函数中。

ImageProjection::ImageProjection() : nh("~") {

        // init params
        InitParams();
        // subscriber
        subLaserCloud = nh.subscribe<sensor_msgs::PointCloud2>(pointCloudTopic.c_str(), 1,
                                                               &ImageProjection::cloudHandler, this);
        // publisher
        pubFullCloud = nh.advertise<sensor_msgs::PointCloud2>("/full_cloud_projected", 1);
        pubFullInfoCloud = nh.advertise<sensor_msgs::PointCloud2>("/full_cloud_info", 1);

        pubGroundCloud = nh.advertise<sensor_msgs::PointCloud2>("/ground_cloud", 1);
        pubSegmentedCloud = nh.advertise<sensor_msgs::PointCloud2>("/segmented_cloud", 1);
        pubSegmentedCloudPure = nh.advertise<sensor_msgs::PointCloud2>("/segmented_cloud_pure", 1);
        pubSegmentedCloudInfo = nh.advertise<cloud_msgs::cloud_info>("/segmented_cloud_info", 1);
        pubOutlierCloud = nh.advertise<sensor_msgs::PointCloud2>("/outlier_cloud", 1);  // 离群点或异常点

        nanPoint.x = std::numeric_limits<float>::quiet_NaN();
        nanPoint.y = std::numeric_limits<float>::quiet_NaN();
        nanPoint.z = std::numeric_limits<float>::quiet_NaN();
        nanPoint.intensity = -1;// 无效点的标志

        // new内存
        allocateMemory();
        // 清空,初始化
        resetParameters();
    }

这里定义了1个subscriber,以及n个publisher,并对其他一系列成员变量进行初始化,重点在于subscriber的回调函数ImageProjection::cloudHandler,它才是激光雷达数据流的走向,进入该callback~

void ImageProjection::cloudHandler(const sensor_msgs::PointCloud2ConstPtr &laserCloudMsg) {
        // 1. Convert ros message to pcl point cloud
        copyPointCloud(laserCloudMsg);
        // 2. Start and end angle of a scan
        findStartEndAngle();
        // 3. Range image projection
        projectPointCloud();
        // 4. Mark ground points
        groundRemoval();
        // 5. Point cloud segmentation
        cloudSegmentation();
        // 6. Publish all clouds
        publishCloud();
        // 7. Reset parameters for next iteration
        resetParameters();
    }

回调函数内又调用了7个函数,完成了该node对单帧激光雷达数据所需的所有处理:

  1. 拷贝。将ROS定义的PointCloud保存成PCL的PointCloud,因为前者方便借助ROS进行通信,后者方便处理,同时去除了nan,是为了后面的计算中不会出现各种异常情况;

  2. 计算起止角度范围。多线激光雷达第一个点和最后一个点并不是严格的360°,这里计算出起至角度后保存在自定义的cloud_msgs::cloud_info类型的成员变量segMsg中,请注意,这里的segMsg很重要,它保存了当前帧的一些重要信息,包括起至角度,每个线的起至序号,及成员变量fullCloud中每个点的状态。

  3. 投影点云。把无序点云以角度展开成图像的形式,计算所在行列和深度,其中不同线对应不同行,不同航向角代表不同列,这里以x轴的负方向开始逆时针列序列号逐渐递增,即图像中的从左到右。以Mat图像保存深度,并以单值索引在pcl::PointCloud fullCloud/fullInfoCloud中依次保存点的三维坐标,所在行列(fullCloud)和深度(fullInfoCloud)等。这里的PointCloud与第1步直接拷贝出来的,主要的不同之处在于,根据计算出来的行列,重新组织了点在PointCloud中的顺序,例如,激光雷达本身可能是先列后行的方向输出的点云,或者是livox雷达那种非重复扫描出来的结果,这里都会被重新组织成先行后列的顺序保存。

  4. 分割地面。在贴近地面的几个线中提取地面点,并在groundMat中标记1即地面,labelMat中标记-1,即nan或地面点 ,不会参与后面的分割。

  5. 非地面分割。这个函数主要完成了两个任务,一是通过广度优先搜索,从非地面点中找出所有连成片的入射角比较小的patch上的点,并在labelMat标注patch的编号(从1开始);二是把所有地面点和刚分割出来的patch上的点合并保存在segmentedCloud中,这也是该node需要传递给下一个node进行特征提取和匹配的点云,并在segMsg中对应位置处保存每个点的属性(比如该点是不是地面,深度,属于第几列等)。

  6. 发布。然后就是发布,包括该帧的segMsg,完整点云fullCloud/fullInfoCloud(步骤3),地面点云(步骤4),发从非地面提取出来的点和降采样的地面点(步骤5),外点(步骤5,实际为比较小的patch上的点)。

  7. 重置。清空成员变量,准备下一次的处理

    其他的所有成员都被这个回调函数并所调用,到这里,imageProjection也结束了,因为它的构造函数中定义了雷达消息的回调函数,每当新的数据到来,回调函数调用一系列成员函数进行预处理(主要是分割出地面和非地面的成块的稳定的面,并发布出去)后,又发布出去给后面的node用来提取特征和odo。总结而言,这个node就是对数据进行预处理,分割出来一部分需要的点。

featureAssociation

还是从main函数开始:

int main(int argc, char **argv) {
    ros::init(argc, argv, "lego_loam");

    ROS_INFO("\033[1;32m---->\033[0m Feature Association Started.");

    lego_loam::FeatureAssociation FA;
    ros::Rate rate(200);
    while (ros::ok())   // while ( 1 )
    {
        ros::spinOnce();

        FA.runFeatureAssociation();

        rate.sleep();
    }

    ros::spin();
    return 0;
}

跟上一个node差不多,还是把初始化了lego_loam::FeatureAssociation FA这么个东西,推测很多的工作是在构造函数中进行的,比如说订阅消息和回调,不同之处在于这里以200Hz的频率在主动调用FA.runFeatureAssociation()这个函数。

先看lego_loam::FeatureAssociation的构造函数:

FeatureAssociation::FeatureAssociation() : nh("~") {

        InitParams();
        // subscriber
        // callback 基本都是sensor msg->pcl,同时记录该种消息的时间戳和标志位
        // 然后统一到runFeatureAssociation中处理
        subLaserCloud = nh.subscribe<sensor_msgs::PointCloud2>("/segmented_cloud", 1,  // 分割后的点云
                                                               &FeatureAssociation::laserCloudHandler, this);
        subLaserCloudInfo = nh.subscribe<cloud_msgs::cloud_info>("/segmented_cloud_info", 1,  // 分割后带几何信息的点云
                                                                 &FeatureAssociation::laserCloudInfoHandler, this);
        subOutlierCloud = nh.subscribe<sensor_msgs::PointCloud2>("/outlier_cloud", 1,
                                                                 &FeatureAssociation::outlierCloudHandler, this);
        subImu = nh.subscribe<sensor_msgs::Imu>(imuTopic, 50, &FeatureAssociation::imuHandler, this);  // imu数据处理

        // publisher
        pubCornerPointsSharp = nh.advertise<sensor_msgs::PointCloud2>("/laser_cloud_sharp", 1);
        pubCornerPointsLessSharp = nh.advertise<sensor_msgs::PointCloud2>("/laser_cloud_less_sharp", 1);
        pubSurfPointsFlat = nh.advertise<sensor_msgs::PointCloud2>("/laser_cloud_flat", 1);
        pubSurfPointsLessFlat = nh.advertise<sensor_msgs::PointCloud2>("/laser_cloud_less_flat", 1);

        pubLaserCloudCornerLast = nh.advertise<sensor_msgs::PointCloud2>("/laser_cloud_corner_last", 2);
        pubLaserCloudSurfLast = nh.advertise<sensor_msgs::PointCloud2>("/laser_cloud_surf_last", 2);
        pubOutlierCloudLast = nh.advertise<sensor_msgs::PointCloud2>("/outlier_cloud_last", 2);
        pubLaserOdometry = nh.advertise<nav_msgs::Odometry>("/laser_odom_to_init", 5);

        initializationValue();
    }

这里订阅了上一节点发出来的分割出来的点云,点云的属性,外点以及IMU消息,并设置了回调函数。对于前三者的回调,感兴趣的读者可以看一下,就是简单的保存并修改标志位(表示收到这种消息了,用来做消息同步),并未对数据做太多额外的处理;对于IMU则复杂一些,它从IMU数据中提取出姿态,角速度和线加速度,其中姿态用来消除重力对线加速度的影响。然后函数AccumulateIMUShiftAndRotation用来做积分,包括根据姿态,将加速度往世界坐标系下进行投影,再根据匀加速度运动模型积分得到速度和位移,同时,对角速度也进行了积分。

好,以上几个回调函数基本只是对数据进行了转寸,IMU也不过是进行了积分,并未涉及点云该如何处理,也没有说怎么与IMU数据做融合。回过头来再看main函数中主动循环调用的runFeatureAssociation函数都做了什么:

void FeatureAssociation::runFeatureAssociation() {
        // 200Hz的检查频率,远远高于雷达数据,注意这里没有考虑IMU
        if (newSegmentedCloud && newSegmentedCloudInfo && newOutlierCloud &&
            std::abs(timeNewSegmentedCloudInfo - timeNewSegmentedCloud) < 0.05 &&
            std::abs(timeNewOutlierCloud - timeNewSegmentedCloud) < 0.05) {

            newSegmentedCloud = false;
            newSegmentedCloudInfo = false;
            newOutlierCloud = false;
        } else {
            return;
        }
        /**
            1. Feature Extraction
        */
        adjustDistortion();  //  imu去畸变

        // jin: 对分割出来的点和地面点计算曲率
        calculateSmoothness(); //  计算光滑性

        // jin: 标记两个平面中容易被遮挡的点,以及与两侧相比深度突出的点
        markOccludedPoints();  // 距离较大或者距离变动较大的点标记

        // jin: 与loam一样,提取四种特征点,其中一般平面点进行了降采样.为了保证均匀,进行了6分区,且选中的点两侧的点不会被选中
        // 结果保存在成员变量中
        extractFeatures();

        // 发布四种特征点
        publishCloud(); // cloud for visualization

        /**
        2. Feature Association
        */
        if (!systemInitedLM) {
            checkSystemInitialization();// 将当前帧作为上一帧,并并初始化里程计位姿(主要是roll和pitch初始化,因为这两者是和水平面相关的,其余默认为0),然后返回
            return;
        }

        updateInitialGuess();  //  更新初始位姿

        //  一个是找特征平面,通过面之间的对应关系计算出变换矩阵。
        // 另一个部分是通过角、边特征的匹配,计算变换矩阵。
        // jin: 依次使用平面点和角点(都是sharp和flat点,非一般点),对相对位姿进行优化,保存在成员变量matP中
        // 特点在于,先使用平面点经历多次优化后,再使用角点(可能因为平面点更多,因而会更稳定吧)
        updateTransformation();

        // jin: 累计位姿到transformSum中
        integrateTransformation();  //  计算旋转角的累积变化量

        // jin: 发布里程计位姿,发布tf变换
        publishOdometry();

        // jin: 将当前帧数据统一到最终点时刻,重置作为last kd-tree,同时发布角点和面点(都是一般的)
        publishCloudsLast(); // cloud to mapOptimization
    }

当前一个节点发布的分割出来的点云,点云的属性以及外点都被接收到后,该函数才被真正执行。根据注释,该函数分为Feature Extraction和Feature Association两个相对独立的部分:

Feature Extraction

  1. adjustDistortion:畸变校正。前面IMU消息的回调函数中对数据进行了转存和积分,这里是根据激光雷达一帧扫描期间,IMU积分得到的位姿变换,通过插值的形式,对点云畸变进行校正。由于激光雷达与IMU坐标系定义不同,因此函数中先对坐标进行了交换,仅仅是为了将点云在IMU坐标系下表示。
    下面仅以姿态为例说明如何进行的畸变校正,对于某个激光pi,采集到该点时雷达本身的位姿Ti已经与采集第一个点p0时的位姿T0不同了,如果我们还认为这个点也是在T0处获取到的,这是不准确的,特别是当雷达扫描频率比较低,且雷达运动比较剧烈的情况下,所以,需要根据T0和Ti之间的相对变换,对点pi进行补偿,补偿的结果,就是这个Ti位姿处观测到的pi,如果是在T0处被观测到的,它的坐标应该是怎样的,这样就消除了雷达本身运动周期的影响,经过畸变消除,可以认为,所有点都是一瞬间在T0处采集得到的,而不存在内畸变的问题。然后,问题就变成了如何确定Ti与T0之间的相对位姿,我们知道IMU一直在计算积分,频率远远比雷达扫描要高,但是两者的时间戳是离散的并不是一一对应的,因此,采集到pi点的时刻ti时雷达的位姿并不能直接给出来,但是,IMU可以给出前后两个距离ti非常接近的时刻积分出来的位姿,这个由于IMU频率非常高所以两者之间时间非常短,可以通过线性插值的形式确定ti时刻的位姿Ti。

  2. calculateSmoothness:计算曲率。根据两侧的10个点,计算曲率,这里的曲率并没有特别准确的实际意义,只是一个量的大小的概念,如果有,那就是该点与周围10个点的均值p之间欧式距离的平方的100倍,这里并没有进行开根号操作,是为了减少不必要的计算,因为曲率并不参加最终的优化,只是衡量一个点光滑与否的标志,是个相对的概念。需要注意cloudSmoothness这个vector中保存了曲率与点索引的对,后面根据曲率对点进行排序可以直接得到点的索引顺序。

  3. markOccludedPoints:对于深度变化比较明显的相邻两个点,远处的那一个更容易被遮挡,不稳定;如果某个点,和两侧的点距离差距都比较大,那么有可能是噪声等,也不稳定。在cloudNeighborPicked中标注为1,后面的特征提取中不会再考虑。

  4. extractFeatures:根据曲率的大小,提取面点和焦点,这里的流程和LOAM基本一样。首先进行分区,把每个线分成了6个区,在每个区内提取一定数量的角点和面点,这是为了让点分布更均匀,匹配时效果会更稳定更好。然后就是在一个区的范围内,按照曲率对索引进行排序,曲率最大的20个非地面点为角点,其中最最大的2个强角点,两者是包含的关系;曲率最最小的4个地面点为强面点,其余所有地面点都是一般面点,点数太多需要进行降采样。还有一个细节就是,在确定提取某个点后,其周围一定范围内的点cloudNeighborPicked会被标注为1,不会被选中,这是为了防止提取出来的特征点过度聚集在某一处。

  5. publishCloud:发布4种特征点。

到这里特征提取的工作已经完成了,在LOAM里面,这个其实是作为单独的一个node存在的。

Feature Association部分,主要完成的是一个激光里程计:

    if (!systemInitedLM) {
        checkSystemInitialization();
        return;
    }

    updateInitialGuess();  //  更新初始位姿

    //  一个是找特征平面,通过面之间的对应关系计算出变换矩阵。
    // 另一个部分是通过角、边特征的匹配,计算变换矩阵。
    // jin: 依次使用平面点和角点(都是sharp和flat点,非一般点),对相对位姿进行优化,保存在成员变量matP中
    // 特点在于,先使用平面点经历多次优化后,再使用角点(可能因为平面点更多,因而会更稳定吧)
    updateTransformation();

    // jin: 累计位姿到transformSum中
    integrateTransformation();  //  计算旋转角的累积变化量

    // jin: 发布里程计位姿,发布tf变换
    publishOdometry();

    // jin: 将当前帧数据统一到最终点时刻,重置作为last kd-tree,同时发布角点和面点(都是一般的)
    publishCloudsLast(); // cloud to mapOptimization
  1. checkSystemInitialization:激光里程计初始化。激光里程计是连续帧之间匹配的过程,需要有一个参考帧,如果系统没有经过初始化,那么就把当前帧作为参考帧,并初始化当前总的位姿transformSum(除了俯仰角和滚转角外,其余参数全部初始化为0)。

  2. updateInitialGuess:根据IMU积分的结果,估计一个初始位姿transformCur,这个位姿指的是激光雷达采集到最后一个点时,相对于采集到第一个点时,雷达的发生的相对位姿变换。

  3. updateTransformation:通过约束优化相对位姿。对于当前帧的每一个面点,在上一帧找到最近邻以及另外两个不共线的点组成面,该点到面的距离就是要减小的残差,对于角点,就找到最近邻和另外一个点构建约束,被优化的对象就是transformCur,即找到一个相对位姿变换,使得总体的残差最小。需要注意的是,上一帧的点云已经被统一到上一帧的结束时刻,也就是当前帧的起始时刻,因此,这里的匹配是当前帧的终止位姿相对于起始位姿的,即transformCur,这是没有问题的。

  4. integrateTransformation:把优化得到的transformCur,累加到transformSum中。

  5. publishOdometry:发布里程计位姿位姿和tf变换,这里的变换实际上是odo相对于地图原点的,存在持续的误差积累的;

  6. publishCloudsLast:根据优化的结果transformCur,将当前帧特征点云统一到终止时刻,用来作为下次匹配的参考(这也呼应了我在步骤3中最后的表述),同时发布特征点云。

到这里通过连续帧间匹配的方式,odo已经把当前雷达所在的位姿优化到transformSum处,并将点云校正到此处再次进行发布。但是,这是里程计连续帧间匹配的结果,会存在持续的误差的积累的过程,与在地图中的实际位姿可能已经发生了非常大的差距,但是,短时间内的结果都还是准确的。接下来 ,就是如何通过与地图这种绝对的参考进行匹配,对这种积累误差进行修正。

参考:
SC-Lego-LOAM解析(上)
SC-Lego-LOAM解析(中)
SC-Lego-LOAM解析(下)

本文经允许后转自知乎:https://zhuanlan.zhihu.com/p/348281520

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SC-Lego-LOAM解析(上) 的相关文章

  • 论文阅读 | LOAM:实时Lidar里程计和建图

    Zhang J Singh S LOAM Lidar odometry and mapping in real time C Robotics Science and Systems 2014 2 9 1 9 64 inproceeding
  • lego-loam加入imu数据建图,使用自己的数据集建图

    配置lego loam教程 https blog csdn net qq 35102059 article details 122671432 spm 61 1001 2014 3001 5501 激光雷达与imu的外参标定教程 https
  • SLAM会议笔记(一)LOAM

    LOAM Lidar Odometry and Mapping in Real time ABSTRACT 将复杂的SLAM问题分离为两个算法 xff0c 一个高频低精度的运动估计 xff0c 另一个低一个数量级的点云匹配和配准算法 REL
  • SLAM会议笔记(三)V-LOAM

    Visual lidar Odometry and Mapping Low drift Robust and Fast Abstract 提出了一种新的使用激光雷达里程计和视觉里程计的框架 xff0c 提升了表现 xff0c 特别是在剧烈运
  • SLAM会议笔记(四)Lego-LOAM

    LeGO LOAM Lightweight and Ground Optimized Lidar Odometry and Mapping on Variable Terrain Abstract 提出一种轻量级的ground optimi
  • loam中imu消除重力加速度的数学推导

    最近在看loam的源码发现里面有一段关于imu消除重力加速度的源码 xff0c 刚开始看不明白后来终于搞清楚了 xff0c 欢迎大家批评指正 要理解这个问题首先得明白欧拉角到旋转矩阵的变换 先上图 此图描述的是先绕X xff0c 再绕Y x
  • 运行LeGO-LOAM

    参考 链接 xff1a https blog csdn net weixin 39754100 article details 112186264 https blog csdn net NEU Ocean article details
  • 在用robosense的rslidar(16线)运行lego-loam遇到的问题总结

    在将rslidar通过github上开源的工具包转换成velodyne的点云格式后 运行测试lego loam时遇到了关于kdtree的报错问提 pcl KdTreeFLANN setInputCloud Cannot create a K
  • A-LOAM源码阅读

    LOAM 论文地址 xff1a https www ri cmu edu pub files 2014 7 Ji LidarMapping RSS2014 v8 pdf A LOAM地址 xff1a https github com HKU
  • velodyne运行Loam_velodyne过程记录

    刚拿到手的3D激光雷达 xff0c 运行一下试试 xff08 1 xff09 loam velodyne环境配置 cd catkin ws src git clone https github com laboshinl loam velo
  • A-LOAM学习

    A LOAM学习 一 复现1 1 Ubuntu 和 ROS1 2 Ceres Solver1 3 PCL 二 下载A LOAM三 下数据集 一 复现 1 1 Ubuntu 和 ROS A LOAM 1 2 Ceres Solver span
  • A-LOAM学习

    A LOAM学习 一 kittiHelper cpp二 scanRegistration cpp三 laserOdometry cpp四 laserMapping cpp 一 kittiHelper cpp 本代码旨在实现 将kitti数据
  • LOAM进行点云地图创建

    3D激光点云数据处理入门 xff08 一 xff09 使用LOAM进行点云地图创建 LOAM 原理简述topic关系算法分析算法伪代码 LOAM 建图实践创建你的 ROS Workspace下载LOAM Package下载数据包运行 LOA
  • SC-Lego-LOAM解析(上)

    文章目录 正文imageProjectionfeatureAssociationFeature Extraction 正文 SC Lego LOAM实际上应该并不对应某一篇特定的论文 xff0c 而是韩国KAIST在github开源的代码
  • SC-Lego-LOAM解析(下)

    回环检测是一个相对独立的模块 xff0c 这里再开一篇专门说明 前面两篇已经说过 xff0c 先对点云做了预处理 xff0c 然后进行连续帧之间的匹配即激光odom xff0c 然后是scan to map匹配 xff0c 并保存关键帧的位
  • 保存并查看Lego-Loam的三维点云地图

    Loam的安装及运行方法可以参考 https blog csdn net qq 36396941 article details 82973772 本文提供ROS wiki http wiki ros org loam velodyne上无
  • LOAM_velodyne学习(三)

    终于到第三个模块了 我们先来回顾下之前的工作 点云数据进来后 经过前两个节点的处理可以完成一个完整但粗糙的里程计 可以概略地估计出Lidar的相对运动 如果不受任何测量噪声的影响 这个运动估计的结果足够精确 没有任何漂移 那我们可以直接利用
  • 使用C/C++编程控制LEGO EV3

    环境搭建 1 安装Eclipse 选择Eclipse IDE for C C Developers 网址 http www eclipse org downloads 2 安装c4ev3 网址 https c4ev3 github io 该
  • 作为科技迷,你必须要了解的乐高机器人常识!

    Source by Fans 主要材料 乐高机器人常识 所需工具 乐高机器人常识 制作步骤 第1步 从今天起 给大家盘点一下主流的机器人开发套件 谈及机器人套件 乐高是回避不掉的 既然这样 那我们索性从乐高机器人套件开始说起 第2步 乐高
  • 利用python语言编程控制LEGO EV3

    1 环境搭建 安装WinSCP 网址 https sourceforge net projects winscp 下载一个自己喜欢的python IDE 例如pycharm 将EV3的系统换为ev3dev 参考网址 https www ev

随机推荐

  • 5.0 NuttX File System

    转载请注明出处 xff1a 5 0 NuttX File System Alvin Peng的博客 CSDN博客 文章均出自个人理解 前言 前一段时间折腾了几个驱动 xff08 PWM Serial I2C xff09 xff0c 这次来折
  • 使用cmake构建一个大型项目框架

    文章目录 使用CMake构建一个大型项目工程1 大型工程目录结构介绍1 1 工程目录结构介绍1 2 工程目录说明 xff08 我是这样设计的 xff0c 你们也可以参考类似这样设计 xff09 1 3 最外层CMakeLists txt说明
  • github下载加速的几种方法

    文章目录 1 github加速的几种办法1 1 把github的代码 xff0c 转到码云上1 2 有人做了github的代下载网站 xff0c 可以从上面进行下载1 3 使用cnpmjs镜像进行加速1 4 使用国外服务器进行搭桥 2 总结
  • EasyLogger的代码解读和移植(linux和stm)

    文章目录 1 EasyLogger目录结构分析 2 EasyLogger之docs查看总结 2 1 EasyLogger之docs查看 2 1 2 api gt kernel md文档 2 1 3 port gt kernel md文档 2
  • C++之socket.io编译使用

    文章目录 1 什么是socket io2 开发环境配置2 1 获取socket io的源码2 2 cmake安装2 3 boost安装2 3 1 获取源码2 3 2 解压编译下载 2 4 rapidjson下载2 5 websocketpp
  • github push的改版

    1 记录一次github 推送时的错误 错误如下 xff1a remote Please see https github blog 2020 12 15 token authentication requirements for git
  • Frp内网穿透

    Frp内网穿透 所有经过服务器的内网穿透都是有一个服务端和客户端 因为都需要借助服务器的公网ip来访问进而达到内网穿透的效果 frp的github开源地址 https github com fatedier frp frp的说明文档 htt
  • linux常见的几种排序方法

    我们以数组a 61 2 6 8 9 1 2 进行排序输出作为列子 xff1a 下面我来总结几种方法来帮助大家学习 1 xff1a 常规排序 首先2和6对比 xff0c 2不比6大不因此不交换 xff0c 所以还是268912 xff0c 然
  • 值得推荐的C/C++框架和库

    值得学习的C语言开源项目 Libevent libev是一个开源的事件驱动库 xff0c 基于epoll xff0c kqueue等OS提供的基础设施 其以高效出名 xff0c 它可以将IO事件 xff0c 定时器 xff0c 和信号统一起
  • cmake教程4(find_package使用)

    本文主要内容如下 xff1a 1 cmake find package的基本原理 2 如何编写自己的 cmake module模块 3 使用cmake find package 使用不同版本的opencv lib问题 xff08 openc
  • A*算法

    如此好贴 xff0c 不能不转 xff01 原文地址 xff1a http dev gameres com Program Abstract Arithmetic AmitAStar mht 本文版权归原作者 译者所有 xff0c 我只是转
  • 3.5RC_Channel 和 SRV_channel

    前言 这部分是之前在折腾ROVER的时候梳理了一遍 xff0c 但是没有形成记录 xff0c 现在从新从对象的角度来分析一遍 xff0c 包括映射 转换 数据结构 等方面进行梳理 xff1b 数据结构 首先分析的入口是从Rover read
  • CAN通信详解

    本章我们将向大家介绍如何使用STM32自带的CAN控制器来实现两个开发板之间的CAN通讯 xff0c 并将结果显示在TFTLCD模块上 本章分为如下几个部分 xff1a 30 1 CAN简介 30 2 硬件设计 30 3 软件设计 30 4
  • VC中出现的一些小问题的解决办法

    本人用的是vc6 0的 xff0c 在实际调试过程中出现一些小的问题 xff0c 通过网上查询资料得以解决 xff0c 特此在此整理下 xff0c 方便后者参考 第一问题 xff1a 程序源码编写完成后编译没有问题 xff0c 而链接时出现
  • C++编译报错`XXX‘被多次定义总结;未定义的引用等等

    1 C 43 43 编译报错 96 XXX 被多次定义总结 报错原因 xff1a 诸如类似的报错都是因为可能在两个或者多个 cpp文件 h文件定义该全局变量 xff0c 属于重复定义问题 解决办法是 xff1a 在VScode全局搜索该变量
  • C++必备万能头文件“#include<bits/stdc++.h>”

    c 43 43 代码一个简单的头文件 xff1a span class token macro property span class token directive hash span span class token directive
  • Livox SLAM(带LIO+闭环检测优化)

    主题 xff1a Livox雷达LIO 43 闭环检测优化 开源地址 xff1a LiDAR SLAM 该开源为 Livox雷达实现了一个一体化且即用型的 LiDAR 惯性里程计系统 前端基于基于开源里程计框架LIO Livox获取里程计信
  • 大疆livox定制的格式CustomMsg格式转换pointcloud2

    官方livox driver驱动livox雷达发出的点云topic有两种 xff0c 一种是大疆览沃定制的格式CustomMsg格式 xff0c 另一种是将CustomMsg格式 转换过的pointcloud2格式 xff0c 参见 Liv
  • paddlepaddle

    项目用到了paddlespeech2 xff0c 学了几天paddlepaddle xff0c 简单记录一下 文章目录 1 手写数字识别任务2 极简方案构建手写数字识别模型模型设计训练配置训练过程模型测试 3 手写数字识别 之数据处理4 手
  • SC-Lego-LOAM解析(上)

    文章目录 正文imageProjectionfeatureAssociationFeature Extraction 正文 SC Lego LOAM实际上应该并不对应某一篇特定的论文 xff0c 而是韩国KAIST在github开源的代码