激光雷达运动畸变去除方法

2023-05-16

文章目录

    • 一、激光雷达运动畸变产生的原因
    • 二、为什么需要解决这个问题?
    • 三、去除运动畸变的原理
    • 四、运动去畸变的方法
    • 1、纯估计方法(ICP / VICP)
    • 2、传感器辅助的方法(odom,IMU)
    • 3、融合的方法

本文针对二维激光雷达产生的运动畸变进行了分析,如有错误请指正!

一、激光雷达运动畸变产生的原因

在机器人运动过程中,每个激光点都在不同的基准位姿上产生(就是在不同的时刻发出激光时机器人的位置是不同的(由于机器人自身的运动),这也是运动畸变产生的根源所在)。
激光扫描时伴随着机器人的运动,每个角度的激光数据都不是瞬时获得的,当激光雷达扫描的频率比较低的时候,机器人运动带来的激光帧的运动误差是不能被忽略的。
当扫描频率是5Hz的激光雷达,一帧数据的收尾时间差是200ms,如果机器人以0.5m/s的速度沿着x方向行走并扫描前面的墙体,那么200ms后尾部的测量距离和首部的测量距离在x方向上就差10cm。
综上:激光雷达产生畸变的原因有三:

  • 激光点数据不是瞬时获得
  • 激光测量时伴随着机器人的运动
  • 激光帧率较低时,机器人的运动不能忽略(目前 国产的激光雷达 旋转的频率大约在是10Hz ~~100ms)
    在这里插入图片描述
    蓝色是产生运动畸变之后轨迹。

二、为什么需要解决这个问题?

一般激光雷达驱动封装一帧数据时, 默认一帧激光数据的所有激光点是在同一个时刻和同一个位姿下采集的,在低频率扫描的激光雷达应用中(5-10Hz),该问题是不可忽视的。当频率比较高时,我们默认激光数据是在同一个时刻同一个位姿下采集的,可忽视该问题。

三、去除运动畸变的原理

去除激光雷达运动畸变的原理是把一帧激光雷达数据的每个激光点对应的激光雷达坐标转换到不同时刻的机器人里程计上(近似对应的里程计的位置,达到尽可能去除畸变的目的)

四、运动去畸变的方法

1、纯估计方法(ICP / VICP)

  • 《1》ICP(Iterative Cloest Point)方法
    目的:ICP方法是最通用的用来求解两个点云集合转换关系的方法。简单的理解:就是匹配两幅点云,通过ICP算法实现,我们需要求解点云配准的误差最小即可。
    在这里插入图片描述
    已知对应点的求解方法(解析解)
    当我们知道了两幅点云的点对时(也就是两幅点云的对应点),我们会有解析解(不需要最小二乘去求解):
    数学描述:
    在这里插入图片描述

未知对应点的求解方法
在这里插入图片描述
ICP算法的缺点:
没有考虑激光的运动畸变,因此得到的激光数据是错误的(计算的时候假设是正确的),从而造成点云匹配发生错误,通过不断的迭代,虽然能够收敛,能够求解,但是存在误差。
在这里插入图片描述

  • 《2》VICP方法介绍

VICP算法考虑了激光的运动畸变,也就是考虑了机器人的运动和速度的存在。

  • ICP算法的变种
  • 考虑了机器人的运动
  • 匀速运动(假设是匀速运动)
  • 进行匹配的同时估计机器人的速度
    在这里插入图片描述
    在这里插入图片描述
    矫正后的结果
    在这里插入图片描述
    VICP 方法的缺点:(1)低频率情况下(5Hz),匀速运动假设不成立 (2)数据预处理和状态估计过程耦合
    如何解决上述问题呢?
    我们尽可能的反应运动情况,实现预处理和状态估计的解耦,我们提出了传感器辅助的方法。

2、传感器辅助的方法(odom,IMU)

传感器辅助的方法就是极高的位姿更新频率(200Hz),可以比较准确的反应运动情况,以及较高精度的局部位姿估计和状态估计完全解耦

《1》惯性测量单元IMU

  • 直接测量角速度和线加速度
  • 具有较高的角速度测量精度(比较准确)
  • 测量频率极高(1kHz~8kHz)
  • 线加速度精度太差,二次积分在局部的精度依然很差(这一点我们就可以否定他在移动机器人上面的使用,主要原因是精度不高)

《2》轮式里程计odom

  • 直接测量机器人的位移和角度
  • 具有较高的局部角度测量精度
  • 具有较高的局部位置测量精度
  • 更新速度较高(100Hz~200Hz)

如何使用odom去除运动畸变?
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
COde

#include <ros/ros.h>
#include <tf/tf.h>
#include <tf/transform_broadcaster.h>
#include <tf/transform_listener.h>
#include <sensor_msgs/LaserScan.h>
#include <iostream>
#include <dirent.h>
#include <fstream>
#include <iostream>
#include <string>
//如果使用调试模式,可视化点云,需要安装PCL
#define debug_ 0

#if debug_
#include <pcl-1.7/pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl-1.7/pcl/visualization/cloud_viewer.h>
pcl::visualization::CloudViewer g_PointCloudView("PointCloud View");//初始化一个pcl窗口
#endif

class LidarMotionCalibrator
{
public:
    //构造函数,初始化tf_、订阅者、回调函数ScanCallBack
    LidarMotionCalibrator(tf::TransformListener* tf)
    {
        tf_ = tf;
        scan_sub_ = nh_.subscribe(scan_sub_name_, 10, &LidarMotionCalibrator::ScanCallBack, this);
        scan_pub_ = nh_.advertise<sensor_msgs::LaserScan>(scan_pub_name_, 1000);
    }
    //析构函数,释放tf_
    ~LidarMotionCalibrator()
    {
        if(tf_!=NULL)
            delete tf_;
    }
    // 拿到原始的激光数据来进行处理
    void ScanCallBack(const sensor_msgs::LaserScanConstPtr& scan_msg)
    {
        //转换到矫正需要的数据
        ros::Time startTime, endTime;
        //一帧scan数据到来首先得出,开始结束的时间戳、数据的size
        startTime = scan_msg->header.stamp;
        sensor_msgs::LaserScan laserScanMsg = *scan_msg;

        //得到最终点的时间
        int beamNum = laserScanMsg.ranges.size();
        endTime = startTime + ros::Duration(laserScanMsg.time_increment * beamNum);

        // 将数据复制出来
        std::vector<double> angles,ranges;
        for(int i = 0; i < beamNum;i++)
        {
            double lidar_dist = laserScanMsg.ranges[i];//单位米
            double lidar_angle = laserScanMsg.angle_min + laserScanMsg.angle_increment * i;//单位弧度

            ranges.push_back(lidar_dist);
            angles.push_back(lidar_angle);
        }
        #if debug_
        visual_cloud_.clear();
        //转换为pcl::pointcloud for visuailization
        //数据矫正前、封装打算点云可视化、红色
        visual_cloud_scan(ranges,angles,255,0,0);
        #endif
        //进行矫正
        Lidar_Calibration(ranges,angles,startTime,endTime,tf_);
        //数据矫正后、封装打算点云可视化、绿色
        //转换为pcl::pointcloud for visuailization
        #if debug_
        visual_cloud_scan(ranges,angles,0,255,0);
        #endif
        //发布矫正后的scan
        //ROS_INFO("scan_time:%f",ros::Duration(laserScanMsg.time_increment * beamNum).toSec());
        scan_cal_pub(ranges,startTime,ros::Duration(laserScanMsg.time_increment * beamNum).toSec());
        //进行显示
        #if debug_
        g_PointCloudView.showCloud(visual_cloud_.makeShared());
        #endif
    }
	/*==============================================================
	声明:这里的scan数据重新封装并不完全正确,每个去畸变的激光数据的角度
	并没有封装到新的scan中,原因是sensor_msgs::LaserScan的角度是按照分
	辨率给定的,相邻相差一致,而去畸变的角度差是不固定,如果重新定义scan
	的数据类型,其他功能包需要改动的就太多了,暂时,不做更改。虽然不完全
	正确,但是除了机器人猛烈旋转情况下的数据,没有去畸变成功,其他直线或
	者低速旋转运动的畸变还是有效的去除了。之后,有机会的话,我会把这部分
	完全修改正确,届时,将更新本篇代码部分。这里不影响使用。
	==============================================================*/
    //矫正之后的scan数据的发布函数,这里默认发布所有360度的所有数据,正着安装,所填写的参数是rplidar A1
    void scan_cal_pub(const std::vector<double> &ranges,ros::Time pubTime,double scan_time)
    {
        //定义sensor_msgs::LaserScan数据
        sensor_msgs::LaserScan tempScan;
        tempScan.header.stamp=pubTime;
        tempScan.header.frame_id=scan_frame_name_;
        tempScan.angle_min=-3.12413907051;
        tempScan.angle_max=3.14159274101;
        tempScan.angle_increment=(tempScan.angle_max-tempScan.angle_min)/(ranges.size()-1);//这个要注意
        tempScan.scan_time=(float)scan_time;                                                //单位s
        tempScan.time_increment=scan_time/(ranges.size()-1);                               //这个是变化的
        
        tempScan.range_min=0.15;
        tempScan.range_max=6.0;
        
        tempScan.ranges.resize(ranges.size());
        tempScan.intensities.resize(ranges.size());

        //填充雷达数据,判断填充的是否正确
        for(size_t i=0;i<ranges.size();++i)
        {
            tempScan.ranges[i]=ranges[i];
            tempScan.intensities[i]=15.0;//这个可能没用
        }

        //===========================================================================
        #if debug_
        //封装数据的可视化的测试
        std::vector<double> angles_temp,ranges_temp;
        for(int i = 0; i < tempScan.ranges.size();i++)
        {
            double lidar_dist = tempScan.ranges[i];
            double lidar_angle = tempScan.angle_min + tempScan.angle_increment * i;

            ranges_temp.push_back(lidar_dist);
            angles_temp.push_back(lidar_angle);
        }
        visual_cloud_scan(ranges_temp,angles_temp,255,255,255);
        #endif
        //===========================================================================
        //发布
        scan_pub_.publish(tempScan);
    }

    //使用点云将激光可视化
    #if debug_
    void visual_cloud_scan(const std::vector<double> &ranges_,const std::vector<double> &angles_,unsigned char r_,unsigned char g_,unsigned char b_)
    {
        unsigned char r = r_, g = g_, b = b_; //变量不要重名 
        for(int i = 0; i < ranges_.size();i++)
        {
            if(ranges_[i] < 0.05 || std::isnan(ranges_[i]) || std::isinf(ranges_[i]))
                continue;

            pcl::PointXYZRGB pt;
            pt.x = ranges_[i] * cos(angles_[i]);
            pt.y = ranges_[i] * sin(angles_[i]);
            pt.z = 1.0;

            // pack r/g/b into rgb
            unsigned int rgb = ((unsigned int)r << 16 | (unsigned int)g << 8 | (unsigned int)b);
            pt.rgb = *reinterpret_cast<float*>(&rgb);

            visual_cloud_.push_back(pt);
        }        
    }
    #endif 
    /**
     * @name getLaserPose()
     * @brief 得到机器人在里程计坐标系中的位姿tf::Pose
     *        得到dt时刻激光雷达在odom坐标系的位姿odom_pose
     * @param odom_pos  机器人的位姿
     * @param dt        dt时刻
     * @param tf_
    */
    bool getLaserPose(tf::Stamped<tf::Pose> &odom_pose,
                      ros::Time dt,
                      tf::TransformListener * tf_)
    {
        odom_pose.setIdentity();

        tf::Stamped < tf::Pose > robot_pose;
        robot_pose.setIdentity();
        robot_pose.frame_id_ = scan_frame_name_;//这里是laser_link
        robot_pose.stamp_ = dt;                 //设置为ros::Time()表示返回最近的转换关系

        // get the global pose of the robot
        try
        {   //解决时间不同步问题
            if(!tf_->waitForTransform(odom_name_, scan_frame_name_, dt, ros::Duration(0.5)))             // 0.15s 的时间可以修改
            {
                ROS_ERROR("LidarMotion-Can not Wait Transform()");
                return false;
            }
            tf_->transformPose(odom_name_, robot_pose, odom_pose);
        }
        catch (tf::LookupException& ex)
        {
            ROS_ERROR("LidarMotion: No Transform available Error looking up robot pose: %s\n", ex.what());
            return false;
        }
        catch (tf::ConnectivityException& ex)
        {
            ROS_ERROR("LidarMotion: Connectivity Error looking up looking up robot pose: %s\n", ex.what());
            return false;
        }
        catch (tf::ExtrapolationException& ex)
        {
            ROS_ERROR("LidarMotion: Extrapolation Error looking up looking up robot pose: %s\n", ex.what());
            return false;
        }

        return true;
    }

    /**
     * @brief Lidar_MotionCalibration
     *        在分段时刻的,激光雷达运动畸变去除;
     *        在此分段函数中,认为机器人是匀速运动;
     * @param frame_base_pose       标定完毕之后的基准坐标系
     * @param frame_start_pose      本分段第一个激光点对应的位姿
     * @param frame_end_pose        本分段最后一个激光点对应的位姿
     * @param ranges                激光数据--距离
     * @param angles                激光数据--角度
     * @param startIndex            本分段第一个激光点在激光帧中的下标
     * @param beam_number           本分段的激光点数量
     */
    void Lidar_MotionCalibration(
            tf::Stamped<tf::Pose> frame_base_pose,//对于每一帧scan,基准坐标系一致
            tf::Stamped<tf::Pose> frame_start_pose,
            tf::Stamped<tf::Pose> frame_end_pose,
            std::vector<double>& ranges,
            std::vector<double>& angles,
            int startIndex,  //每一个分段的激光点起始序号
            int& beam_number)//此分段中,激光点的个数
    {
        //每个位姿进行线性插值时的步长
        double beam_step = 1.0 / (beam_number-1);

        //机器人的起始角度 和 最终角度,四元数表示
        tf::Quaternion start_angle_q =   frame_start_pose.getRotation();
        tf::Quaternion   end_angle_q =   frame_end_pose.getRotation();

        //转换到弧度
        double start_angle_r = tf::getYaw(start_angle_q);
        double base_angle_r = tf::getYaw(frame_base_pose.getRotation());

        //机器人的起始位姿
        tf::Vector3 start_pos = frame_start_pose.getOrigin();//Ps
        start_pos.setZ(0);

        //最终位姿
        tf::Vector3 end_pos = frame_end_pose.getOrigin();    //Pe
        end_pos.setZ(0);

        //基础坐标系
        tf::Vector3 base_pos = frame_base_pose.getOrigin();
        base_pos.setZ(0);

        double mid_angle;
        tf::Vector3 mid_pos;
        tf::Vector3 mid_point;

        double lidar_angle, lidar_dist;
        //插值计算出来每个点对应的位姿
        for(int i = 0; i< beam_number;i++)
        {
            //得到该激光点的角度插值,线性插值需要步长、起始和结束数据
            mid_angle =  tf::getYaw(start_angle_q.slerp(end_angle_q, beam_step * i));

            //得到该激光点的里程计位姿线性插值
            mid_pos = start_pos.lerp(end_pos, beam_step * i);

            //得到激光点在odom 坐标系中的坐标 根据
            double tmp_angle;

            //如果激光雷达不等于无穷,则需要进行矫正.//首先读数据进行判断
            if( tfFuzzyZero(ranges[startIndex + i]) == false)
            {
                //计算对应的激光点在odom坐标系中的坐标

                //得到这帧激光束距离和夹角
                lidar_dist  =  ranges[startIndex+i];
                lidar_angle =  angles[startIndex+i];

                //在激光雷达坐标系下激光点的坐标
                double laser_x,laser_y;
                laser_x = lidar_dist * cos(lidar_angle);
                laser_y = lidar_dist * sin(lidar_angle);

                //在对应的里程计坐标系下激光点的坐标
                double odom_x,odom_y;
                odom_x = laser_x * cos(mid_angle) - laser_y * sin(mid_angle) + mid_pos.x();
                odom_y = laser_x * sin(mid_angle) + laser_y * cos(mid_angle) + mid_pos.y();

                //转换到类型中去
                mid_point.setValue(odom_x, odom_y, 0);
                //得到在基准坐标系下激光点的坐标
                //把在odom坐标系中的激光数据点 转换到 基础坐标系
                //得到那一瞬时,应该测得的激光点的数据
                double x0,y0,a0,s,c;
                x0 = base_pos.x();
                y0 = base_pos.y();
                a0 = base_angle_r;
                s = sin(a0);
                c = cos(a0);
                /*
                 * 把base转换到odom 为[c -s x0;
                 *                     s  c y0;
                 *                     0  0 1 ]
                 * 把odom转换到base为 [c s -x0*c - y0*s;
                 *                    -s c  x0*s - y0*c;
                 *                     0 0  1          ]
                 */
                double tmp_x,tmp_y;
                tmp_x =  mid_point.x()*c  + mid_point.y()*s - x0*c - y0*s;
                tmp_y = -mid_point.x()*s  + mid_point.y()*c  + x0*s - y0*c;
                mid_point.setValue(tmp_x,tmp_y,0);

                //然后计算该激光点以起始坐标为起点的 dist angle
                double dx,dy;
                dx = (mid_point.x());
                dy = (mid_point.y());
                lidar_dist = sqrt(dx*dx + dy*dy);
                lidar_angle = atan2(dy,dx);

                //激光雷达被矫正
                ranges[startIndex+i] = lidar_dist;
                angles[startIndex+i] = lidar_angle;
            }
            //如果等于无穷,则随便计算一下角度
            else
            {
                //激光角度
                lidar_angle = angles[startIndex+i];

                //里程计坐标系的角度
                tmp_angle = mid_angle + lidar_angle;
                tmp_angle = tfNormalizeAngle(tmp_angle);

                //如果数据非法 则只需要设置角度就可以了。把角度换算成start_pos坐标系内的角度
                lidar_angle = tfNormalizeAngle(tmp_angle - start_angle_r);

                angles[startIndex+i] = lidar_angle;
            }
        }
    }

    //激光雷达数据 分段线性进行插值 分段的周期为5ms,可以更改
    //这里会调用Lidar_MotionCalibration()
    /**
     * @name Lidar_Calibration()
     * @brief 激光雷达数据 分段线性进行差值 分段的周期为5ms
     * @param ranges 激光束的距离值集合
     * @param angle 激光束的角度值集合
     * @param startTime 第一束激光的时间戳
     * @param endTime 最后一束激光的时间戳
     * @param *tf_
    */
    void Lidar_Calibration(std::vector<double>& ranges,
                           std::vector<double>& angles,
                           ros::Time startTime,
                           ros::Time endTime,
                           tf::TransformListener * tf_)
    {
        //统计激光束的数量
        int beamNumber = ranges.size();
        if(beamNumber != angles.size())
        {
            ROS_ERROR("Error:ranges not match to the angles");
            return ;
        }

        // 5000us来进行分段
        int interpolation_time_duration = 5 * 1000;//单位us

        tf::Stamped<tf::Pose> frame_base_pose;
        tf::Stamped<tf::Pose> frame_start_pose;
        tf::Stamped<tf::Pose> frame_mid_pose;
        tf::Stamped<tf::Pose> frame_end_pose;

        //起始时间 us
        double start_time = startTime.toSec() * 1000 * 1000;    //转化单位为us
        double end_time = endTime.toSec() * 1000 * 1000;
        double time_inc = (end_time - start_time) / beamNumber; // 每束激光数据的时间间隔,单位us

        //当前插值的段的起始坐标
        int start_index = 0;

        //起始点的位姿 这里要得到起始点位置的原因是 起始点就是我们的base_pose
        //所有的激光点的基准位姿都会改成我们的base_pose
        
        //得到t时刻激光雷达在odom坐标系的位姿frame_start_pose、frame_end_pose
        if(!getLaserPose(frame_start_pose, ros::Time(start_time /1000000.0), tf_))
        {
            ROS_WARN("Not Start Pose,Can not Calib");
            return ;
        }

        if(!getLaserPose(frame_end_pose,ros::Time(end_time / 1000000.0),tf_))
        {
            ROS_WARN("Not End Pose, Can not Calib");
            return ;
        }
        //计数报错使用
        int cnt = 0;
        //基准坐标就是第一个位姿的坐标
        frame_base_pose = frame_start_pose;
        for(int i = 0; i < beamNumber; i++)
        {
            //分段线性,时间段的大小为interpolation_time_duration=5000us
            double mid_time = start_time + time_inc * (i - start_index);//这里的mid_time、start_time多次重复利用
            if(mid_time - start_time > interpolation_time_duration || (i == beamNumber - 1))
            {
                cnt++;
                //得到临时终点frame_mid_pose在里程计中的位姿,对应一个激光束
                if(!getLaserPose(frame_mid_pose, ros::Time(mid_time/1000000.0), tf_))
                {
                    ROS_ERROR("Mid %d Pose Error",cnt);
                    return ;
                }
                //对当前的起点和终点进行插值
                //interpolation_time_duration分段间隔中间有多少个点,算上本分段间隔首尾
                int interp_count = i - start_index + 1;//可以尝试推算一下,通常会有几十个或者上百个激光点
                //对本分段的激光点进行运动畸变的去除
                Lidar_MotionCalibration(frame_base_pose,
                                        frame_start_pose,
                                        frame_mid_pose,
                                        ranges,
                                        angles,
                                        start_index,
                                        interp_count);

                //更新时间
                start_time = mid_time;
                start_index = i;     //为了方便计算分段中激光点个数
                frame_start_pose = frame_mid_pose;
            }
        }
    }

public:
    //声明TF的聆听者、ROS句柄、scan的订阅者、scan的发布者
    tf::TransformListener* tf_;
    ros::NodeHandle nh_;
    ros::Subscriber scan_sub_;
    ros::Publisher  scan_pub_;

    //针对各自的情况需要更改的名字,自行更改
    const std::string scan_frame_name_="laser_link";
    const std::string odom_name_="odom";
    const std::string scan_sub_name_="scan";
    const std::string scan_pub_name_="scan_cal";

    #if debug_
    //可视化点云对象
    pcl::PointCloud<pcl::PointXYZRGB> visual_cloud_;
    #endif
};

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

    tf::TransformListener tf(ros::Duration(10.0));

    LidarMotionCalibrator tmpLidarMotionCalib(&tf);

    ros::spin();
    return 0;
}

3、融合的方法

在这里插入图片描述
参考博客:https://blog.csdn.net/zhao_ke_xue/article/details/105734833

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

激光雷达运动畸变去除方法 的相关文章

  • SIYI AK28 遥控器接收机的SBUS口与STM32通讯

    SBUS介绍 SBUS是Futaba公司定义的一种串口通信协议 xff0c Futaba的产品应用越来越广泛 xff0c 不论是航模 xff0c 无人机 xff0c 还是机器人 xff0c 遥控车 xff0c 总能有它的身影 SBUS是一个
  • 基于STM32F407四旋翼无人机---AK8975磁力计(四)

    基于STM32F407四旋翼无人机 AK8975磁力计 xff08 四 xff09 磁力计基本介绍1 2 磁力计原理图 2 磁力计数据获取3 磁力计椭球拟合校准3 1 简单介绍椭球拟合 磁力计基本介绍 该模块采用高灵敏度霍尔传感器技术 xf
  • 硬链接与软链接

    硬链接 hard link 与软链接 xff08 又称符号链接 xff0c 即 soft link 或 symbolic link xff09 链接为 Linux 系统解决了文件的共享使用 xff0c 还带来了隐藏文件路径 增加权限安全及节
  • 基于STM32F407四旋翼无人机 --- 姿态解算讲解(四元数)(叉积法融合陀螺仪数据和加速度数据)(五)

    基于STM32F407四旋翼无人机 姿态解算讲解 xff08 五 xff09 姿态解算姿态解算定义欧拉角四元数四元数性质 方向余弦矩阵四元数方向余弦矩阵 叉积法融合陀螺仪数据和加速度数据叉积运算 一阶龙格库塔法四元数更新获得欧拉角 姿态解算
  • 基于STM32F407四旋翼无人机---PID算法控制(六)

    基于STM32F407四旋翼无人机 PID算法控制 xff08 六 xff09 PID介绍PID仿真分析 PID介绍 PID介绍 此算法是由P xff08 比例 xff09 I xff08 积分 xff09 和D xff08 微分 xff0
  • 四足机器人(一)----MATLAB simulink对四足机器人物理建模

    四足机器人 xff08 一 xff09 MATLAB simulink对四足机器人物理建模 一 本设计中用的是网上下载的别人已经画好的四足机器狗的3D模型 那么我们就需要将这些3D模型导入到MATLAB的建模中 xff0c 打开MATLAB
  • 四足机器人(二)---运动学逆解和步态规划

    四足机器人 xff08 二 xff09 运动学逆解和步态规划 运动学逆解步态规划MATLAB仿真 运动学逆解 其实运动学分为运动学正解和运动学逆解 xff0c 二者有什么区别呢 xff1f 因为在四足机器人中用的是12个舵机 xff0c 所
  • 四足机器人(三)--- 姿态控制

    四足机器人 xff08 三 xff09 姿态控制 概述姿态表示使用MATLAB实现姿态控制算法效果 概述 四足机器人运动过程中 xff0c 身体部分的姿态会不断地发生变化 假如机器人的足端一直保持与地面接触且相对位置不发生变化 xff0c
  • VSCode+python+opencv搭建过程

    VSCode 43 python 43 opencv搭建过程 python安装VSCode安装安装opencv python安装 首先打开python的官网 www python org xff0c 进入python官网下载页面 xff0c
  • 智能家居之主机---计划筹备

    智能家居之主机 计划筹备 前言绪言前期构思 硬件平台结构平台 前言 绪言 感觉有一年多没发过文章了 xff0c 这一年多太忙了 xff0c 来到新的公司后要学的太多了 xff0c 代码风格 xff0c 架构 xff0c 操作系统 xff0c
  • 智能家居之主机--环境搭建

    智能家居之主机 环境搭建 硬件环境软件环境结构 硬件环境 上节说到硬件平台的搭建 xff0c 之前是在altium designer上面画好的 xff0c 现在要支持国产 xff0c 没办法只能在立创EDA上面重新画了 xff0c 有的人说
  • 智能家居之主机--驱动层搭建

    智能家居之主机 驱动层搭建 bsp 底层驱动bsp gpiobsp adcbsp uartbsp timer 伪调度 bsp 底层驱动 bsp gpio 利用一个config h的配置文件 xff0c 把所有要使用的gpio的属性配置好 x
  • STM32串口自定义数据接收协议

    文章目录 写在前面0 需求1 问题产生1 1 模块 43 上位机实验1 2 电路板串口数据接收实验1 3 问题来了 xff01 2 开始分析2 1 串口数据格式2 2 测一波波形 3 代码分析4 新的问题 xff1a 串口数据累加总结 写在
  • 科普:电池的保护电路

    科普 xff1a 电池的保护电路 http www cnbeta com articles 204504 htm
  • 汇编语言基础知识-寄存器

    汇编语言基础知识 寄存器 寄存器是距离CPU中最近的存储单元 xff0c 对于现代CPU其获取数据的顺序往往是 CPU 61 61 gt 寄存器 61 61 gt 一级缓存 61 61 gt 二级缓存 61 61 gt 三级缓存 61 61
  • ERROR:未定义标识符“cout“、“endl“

    ERROR情况 xff1a 未定义标识符 span class token string 34 cout 34 span 未定义标识符 span class token string 34 endl 34 span 问题原因 xff1a c
  • VSCode格式化快捷键

    Shift 43 Ctrl 43 F
  • Linux内核源代码分析一(Linux0.12)

    Linux内核源代码分析一 xff08 Linux0 12 xff09 Linux 内核主要由 5 个模块构成 xff0c 它们分别是 xff1a 进程调度模块 内存管理模块 文件系统模块 进程间通信模块和网络接口模块 1 引导启动程序 x
  • Qt类继承关系以及所属模块关系一览表

    Qt类关系一览表 高清版下载 xff1a Qt类继承关系以及所属模块关系一览表 更多内容请关注个人博客 xff1a https blog csdn net qq 43148810

随机推荐

  • cartographer主从机rviz订阅地图出错

    参考 xff1a https answers ros org question 261071 rviz client md5sum error ERROR 1563347805 272316618 Client rviz wants top
  • Gooseeker软件使用教程

    因为课程需要使用到Gooseeker软件 xff0c 写个使用教程防止以后忘记 安装好gooseeker软件 xff0c 下图为安装好的界面 xff1a 点击右上方的 MS谋数台 xff0c 弹出如下界面 xff1a 在左上方的网址栏输入想
  • HTTP Auth认证请求(附代理)-代码篇

    2种方式 方式一 xff1a Http请求头上添加Basic Authentication认证 httpPost httpPost addHeader Authorization 34 Basic 34 43 Base64 encodeBa
  • Makefile以及CMakelists的编辑

    1 概念 在linux中不像windows和mac有图形界面 xff0c 怎么快速的用命令行运行大型项目成为一个问题 xff0c 并且像c c 43 43 需要自己添加include文件的位置 xff0c 当文件数目变多的时候 xff0c
  • python爬虫练习1:计算评分平均值

    练习要求 xff1a 抽取某本书的前50条短评内容并计算评分的平均值 提示 xff1a 有的评论中并不包含评分 代码 xff1a span class token comment coding utf 8 span span class t
  • python爬虫练习2:取道指成分股数据

    练习要求 xff1a 在 http money cnn com data dow30 上抓取道指成分股数据并将30家公司的代码 公司名称和最近一次成交价放到一个列表中输出 代码 xff1a span class token comment
  • python中sort函数的使用

    转自https blog csdn net lyy14011305 article details 76148512 我们需要对List进行排序 xff0c Python提供了两个方法 对给定的List L进行排序 xff0c 方法1 用L
  • Python序列与文件编程练习

    练习一 xff1a 使用以下语句存储一个字符串 xff1a string 61 My moral standing is 0 98765 将其中的数字字符串转换成浮点数并输出 代码 xff1a span class token commen
  • 简单平台用户信息管理系统

    要求 xff1a 用字典创建一个平台的用户信息 xff08 包含用户名和密码 xff09 管理系统 xff0c 新用户可以用与现有系统帐号不冲突的用户名创建帐号 xff0c 已存在的老用户则可以用用户名和密码登陆重返系统 实现代码 xff1
  • DataFrame小练习

    练习要求 xff1a 已知有一个列表中存放了一组音乐数据 xff1a music data 61 the rolling stones Satisfaction Beatles Let It Be Guns N Roses Don t Cr
  • nenuacm 2019 新生训练#10 字符串处理 题解

    题目链接 Ultra Fast Mathematician 解题思路 xff1a 题目说辣么多 xff0c 其实就是将两个二进制进行异或运算 xff1a 相同位上的数字相同 xff0c 异或结果为0 xff0c 否则为1 AC代码 span
  • 比较两个字符串大小(C语言)

    比较两个字符串的大小 比较两个字符串的大小 xff0c 不许使用strcmp函数 输入格式 在两行分别输入两个长度小于20的字符串 在字符串中不要出现换行符 xff0c 空格 xff0c 制表符 输出格式 直接输出 gt 或 lt 或 61
  • 海康web3.0使用记录

    项目中集成海康web3 0控制摄像头 近期项目中的业务需求涉及播放控制实时监控 xff0c 采用了海康web3 0的开发包 xff0c 开发包规范是32位IE11浏览器 xff0c 业务前期本来只是播放rtsp流的监控 xff0c 所以采取
  • 字符串与数字范围

    当字符串由数字组成时 xff0c 告诉你字符串的长度是length xff0c 意味着变成数字时最大会达到10length 1 比如告诉字符串长度最长为105 xff0c 那要注意数字可以达到10的105 1次方 xff0c 大约是1010
  • 如何在VS2019上配置OpenCV

    目录 前言正文1 安装Visual Studio2017 20192 配置 OpenCV https so csdn net so search q 61 OpenCV amp spm 61 1001 2101 3001 7020 2 1
  • GCC编译与Makefile基本语法

    写在最前面 xff1a 所有的博文都是为了若干年月以后当我再次翻看可以快速回想起之前的零星知识 学海无涯 xff0c 在看这篇文章的未来的你 xff0c 加油吧 xff01 目录 一 Makefile简介 二 Linux环境下的C语言编译过
  • CMake与CMakeLists是干什么的?

    写在最前面 xff1a 所有的博文都是为了若干年月以后当我再次翻看可以快速回想起之前的零星知识 学海无涯 xff0c 在看这篇文章的未来的你 xff0c 加油吧 xff01 因为之前看到有些软件中使用了CMake xff0c 不太理解为什么
  • 解决vscode智能代码提示快捷键 Ctrl+Space 无效的问题

    背景 vscode 智能代码提示除了输入时通过字符触发 xff0c 还能使用快捷键ctrl 43 space触发 xff0c 但是在 Windows 下会发现没有效果 因为这个快捷键在Windows下是系统的中文 简体 输入法 输入法 非输
  • Ubuntu系统安装、并解决Ubuntu系统网络连接激活失败问题

    Ubuntu系统安装 并解决Ubuntu系统网络连接激活失败问题 前言 1 Ubuntu安装过程中要保证网络连接稳定 2 Ubuntu安装过程要保证U盘不离开插槽 3 第一次安装请先浏览整个过程 xff0c 再进行操作 4 卸载Ubuntu
  • 激光雷达运动畸变去除方法

    文章目录 一 激光雷达运动畸变产生的原因二 为什么需要解决这个问题 xff1f 三 去除运动畸变的原理四 运动去畸变的方法1 纯估计方法 xff08 ICP VICP xff09 2 传感器辅助的方法 xff08 odom xff0c IM