【ROS】costmap之(1)costmap_math

本文详细介绍了ROS中costmap_2d库中的几个关键数学函数,包括sign函数、distance函数、distanceToLine函数以及intersects函数。这些函数涉及点到点、点到线段的距离计算,以及判断点是否在多边形内部或多边形间是否相交的算法,对于理解ROS中的路径规划和避障功能至关重要。



costmap_2d: costmap_math.cpp

costmap_2d: costmap_math.h

1、内联函数[double sign(double x)]

功能:输入x如果小于0返回-1,否则返回1。

2、内联函数[double sign0(double x)]

功能:输入x如果小于0返回-1,如果大于0返回1,否则返回0。

3、内联函数[double distance(double x0, double y0, double x1, double y1)]

功能:输入坐标点P0(x0,y0)、P1(x1,y1),返回|P0P1|距离。

4、函数[double distanceToLine(double pX, double pY, double x0, double y0, double x1, double y1)]

功能:输入坐标点P0(pX,pY)与线段L的坐标点L0(x0,y0)、L1(x1,y1),返回P0到线段的距离。

设垂点LD(xx,yy),dot为向量L0P0·L0L1(即|L0L1|和L0P0的投影长度的乘积),len_sq为向量L0L1·L0L1(即|L0L1|和L0L1的投影长度的乘积,其实就是向量L0L1模的平方),param即为L0LD与L0L1模长比例。

当L0P0·L0L1的夹角大于90°时param<0,此时P0距离线段L最近点为L0;

当L0LD的模比L0L1的模大时param>1,此时P0距离线段L最近点为L1;

剩下的情况通过比例可求得垂点为最近点;

由此可求得垂点LD,通过求垂点LD与P0的距离可得P0到直线得距离。

5、函数[bool intersects(std::vector<geometry_msgs::Point>& polygon, float testx, float testy)]

功能:输入多边形顶点集和test点,返回该点是否与多边形相交

判断一个点是否在多边形内部,可以使用引射线的方法

测试点Ptest(testx,testy)

条件1:(yi > testy) != (yj > testy)

只有当yi<esty<yj或者yj<testy<yi时条件成立。

条件2:testx < (xj - xi) * (testy - yi) / (yj - yi) + xi

只有当测试点Ptest在线段i,j下方时,即过测试点Ptest向上画射线(Y轴),条件1、2同时成立时射线与线段i,j相交。

遍历多边形的边,每次条件1和条件2同时成立时(即射线与边相交)把布尔量取反;当相交次数为奇数时,测试点Ptest在多边形内,否则不在多边形内。(参考PNPoly算法)

6、函数[bool intersects(std::vector<geometry_msgs::Point>& polygon1, std::vector<geometry_msgs::Point>& polygon2)]

功能:输入两个多边形顶点集,返回两个多边形是否相交

分别取polygon1的每个顶点判断是否在多边形polygon2内部,然后再分别取polygon2的每个顶点判断是否在多边形polygon1内部。

<think>我们正在为基于ROS的四轮麦轮小车实现完整的导航功能。已知雷达驱动启动命令为`roslaunch ydlidar_ros_driver x3_lidar_view.launch`,功能包路径为`~/ydlidar_ws/src`。 完整的导航功能通常包括以下几个部分: 1. 建立机器人模型(URDF) 2. 搭建移动底盘驱动(包括电机控制和里程计计算) 3. 激光雷达驱动(已提供) 4. SLAM建图(如gmapping) 5. 地图保存(map_server) 6. 导航(move_base) 由于四轮麦轮小车采用全向移动方式,因此我们需要在移动底盘驱动中实现全向运动的控制,并在导航配置中考虑全向移动的特性。 步骤概述: 1. 创建机器人URDF模型,包含底盘、轮子、雷达等。 2. 编写电机驱动节点,订阅`cmd_vel`话题,将速度指令转换为四个麦轮的速度,并发布里程计。 3. 启动雷达驱动(已提供)。 4. 使用gmapping(或其他SLAM算法)进行建图。 5. 保存地图。 6. 配置move_base,使用保存的地图进行导航。 由于代码量较大,这里将分步骤提供关键代码和配置。 第一步:创建URDF模型 假设我们的小车是一个矩形,四个麦轮分别位于四个角。每个麦轮由两个滚轮组成,但我们在URDF中通常用一个圆柱体表示一个麦轮。注意麦轮的安装角度(通常为45度)。 例如,创建一个名为`mecanum_bot.urdf`的文件,存放在功能包(如`mecanum_bot_description`)中: ```xml <?xml version="1.0"?> <robot name="mecanum_bot"> <link name="base_link"> <visual> <geometry> <box size="0.4 0.3 0.1"/> </geometry> </visual> </link> <link name="front_left_wheel_link"> <visual> <geometry> <cylinder length="0.02" radius="0.05"/> </geometry> </visual> </link> <!-- 类似定义front_right_wheel_link, rear_left_wheel_link, rear_right_wheel_link --> <joint name="front_left_wheel_joint" type="continuous"> <parent link="base_link"/> <child link="front_left_wheel_link"/> <origin xyz="0.2 0.15 0" rpy="0 0 0"/> <axis xyz="0 1 0"/> </joint> <!-- 其他三个轮子类似,注意位置和方向 --> <!-- 注意:麦轮需要调整安装角度,例如前左轮为45度,则使用rpy="0 0 0.7854"(45度的弧度) --> <!-- 雷达 --> <link name="laser_frame"> <visual> <geometry> <cylinder length="0.05" radius="0.05"/> </geometry> </visual> </link> <joint name="laser_joint" type="fixed"> <parent link="base_link"/> <child link="laser_frame"/> <origin xyz="0 0 0.1" rpy="0 0 0"/> </joint> <!-- 其他传感器(可选) --> </robot> ``` 第二步:编写电机驱动和里程计节点 我们需要一个节点来订阅`cmd_vel`(类型为`geometry_msgs/Twist`),并将其转换为四个麦轮的速度。同时,根据轮子的速度计算里程计。 麦轮运动学模型(以常见的四轮布局为例): 设小车中心为原点,轮子位置分别为(前左, 前右, 后左, 后右) = ( (l, w), (l, -w), (-l, w), (-l, -w) ),其中l为前后轮距的一半,w为左右轮距的一半。 速度转换公式(逆运动学): 设小车线速度在x、y方向的分量为$v_x$、$v_y$,角速度为$\omega$,则四个轮子的速度$v_1, v_2, v_3, v_4$(分别对应前左、前右、后左、后右)为: $$ v_1 = v_x - v_y - \omega \cdot (l + w) $$ $$ v_2 = v_x + v_y + \omega \cdot (l + w) $$ $$ v_3 = v_x + v_y - \omega \cdot (l + w) $$ $$ v_4 = v_x - v_y + \omega \cdot (l + w) $$ 注意:这个公式是其中一种布局的推导,具体取决于轮子的安装方向。实际应用中需要根据轮子的实际安装方向调整正负号。 里程计计算(正运动学): 根据四个轮子的速度(通过编码器获取)可以反推小车速度: $$ v_x = \frac{v_1 + v_2 + v_3 + v_4}{4} $$ $$ v_y = \frac{-v_1 + v_2 + v_3 - v_4}{4} $$ $$ \omega = \frac{-v_1 + v_2 - v_3 + v_4}{4(l+w)} $$ 然后通过积分得到位置和姿态。 由于我们这里没有实际的电机,所以假设我们已经有一个可以控制电机的底层驱动,并且可以通过串口或CAN总线发送轮速指令。这里我们用一个节点模拟,即接收`cmd_vel`,计算轮速(这里我们只是发布计算后的轮速,实际控制需要根据具体硬件编写驱动),并发布里程计。 创建一个新的功能包(如`mecanum_bot_driver`),然后在`src`下创建`mecanum_driver.cpp`(或使用Python,这里以Python为例)。 注意:在ROS中,通常使用`nav_msgs/Odometry`消息类型发布里程计,并使用`tf`发布坐标变换(从`odom`到`base_link`)。 以下是一个简化的节点代码(`mecanum_driver.py`): ```python #!/usr/bin/env python import rospy import tf from math import sin, cos from geometry_msgs.msg import Twist from nav_msgs.msg import Odometry from tf.broadcaster import TransformBroadcaster # 定义机器人的物理参数 l = 0.15 # 前后轮轴距的一半(单位:米) w = 0.10 # 左右轮距的一半(单位:米) class MecanumDriver: def __init__(self): rospy.init_node('mecanum_driver') self.odom_pub = rospy.Publisher('/odom', Odometry, queue_size=10) self.odom_broadcaster = TransformBroadcaster() rospy.Subscriber('/cmd_vel', Twist, self.cmd_vel_cb) # 初始位置和姿态 self.x = 0.0 self.y = 0.0 self.theta = 0.0 self.last_time = rospy.Time.now() # 设置更新频率 self.rate = rospy.Rate(10) # 10Hz def cmd_vel_cb(self, msg): # 获取当前时间 current_time = rospy.Time.now() dt = (current_time - self.last_time).to_sec() self.last_time = current_time # 从Twist消息中获取速度 vx = msg.linear.x vy = msg.linear.y vth = msg.angular.z # 更新位置(积分) delta_x = (vx * cos(self.theta) - vy * sin(self.theta)) * dt delta_y = (vx * sin(self.theta) + vy * cos(self.theta)) * dt delta_th = vth * dt self.x += delta_x self.y += delta_y self.theta += delta_th # 发布tf变换:odom->base_link odom_quat = tf.transformations.quaternion_from_euler(0, 0, self.theta) self.odom_broadcaster.sendTransform( (self.x, self.y, 0.), odom_quat, current_time, "base_link", "odom" ) # 发布里程计消息 odom = Odometry() odom.header.stamp = current_time odom.header.frame_id = "odom" odom.child_frame_id = "base_link" odom.pose.pose.position.x = self.x odom.pose.pose.position.y = self.y odom.pose.pose.position.z = 0.0 odom.pose.pose.orientation.x = odom_quat[0] odom.pose.pose.orientation.y = odom_quat[1] odom.pose.pose.orientation.z = odom_quat[2] odom.pose.pose.orientation.w = odom_quat[3] odom.twist.twist.linear.x = vx odom.twist.twist.linear.y = vy odom.twist.twist.angular.z = vth self.odom_pub.publish(odom) def run(self): while not rospy.is_shutdown(): self.rate.sleep() if __name__ == '__main__': try: driver = MecanumDriver() driver.run() except rospy.ROSInterruptException: pass ``` 注意:上述里程计计算是近似的,实际中应该使用编码器反馈的轮速来计算。这里为了简化,我们直接使用`cmd_vel`积分得到里程计,实际应用中需要根据轮速反馈计算。 第三步:启动雷达驱动 已知命令:`roslaunch ydlidar_ros_driver x3_lidar_view.launch` 第四步:SLAM建图 使用gmapping进行建图。需要启动gmapping节点,并配置相关参数。 创建一个启动文件,例如`slam.launch`: ```xml <launch> <!-- 启动雷达驱动 --> <include file="$(find ydlidar_ros_driver)/launch/x3_lidar_view.launch" /> <!-- 启动机器人模型 --> <param name="robot_description" command="$(find xacro)/xacro '$(find mecanum_bot_description)/urdf/mecanum_bot.urdf'" /> <!-- 启动机器人状态发布 --> <node pkg="robot_state_publisher" type="robot_state_publisher" name="robot_state_publisher" /> <!-- 启动gmapping --> <node pkg="gmapping" type="slam_gmapping" name="slam_gmapping" output="screen"> <param name="base_frame" value="base_link"/> <param name="odom_frame" value="odom"/> <param name="map_update_interval" value="5.0"/> <param name="maxUrange" value="16.0"/> <param name="sigma" value="0.05"/> <param name="kernelSize" value="1"/> <param name="lstep" value="0.05"/> <param name="astep" value="0.05"/> <param name="iterations" value="5"/> <param name="lsigma" value="0.075"/> <param name="ogain" value="3.0"/> <param name="lskip" value="0"/> <param name="srr" value="0.01"/> <param name="srt" value="0.02"/> <param name="str" value="0.01"/> <param name="stt" value="0.02"/> <param name="linearUpdate" value="0.5"/> <param name="angularUpdate" value="0.436"/> <param name="temporalUpdate" value="-1.0"/> <param name="resampleThreshold" value="0.5"/> <param name="particles" value="80"/> <param name="xmin" value="-50.0"/> <param name="ymin" value="-50.0"/> <param name="xmax" value="50.0"/> <param name="ymax" value="50.0"/> <param name="delta" value="0.05"/> <param name="llsamplerange" value="0.01"/> <param name="llsamplestep" value="0.01"/> <param name="lasamplerange" value="0.005"/> <param name="lasamplestep" value="0.005"/> </node> </launch> ``` 第五步:保存地图 建图完成后,使用map_server保存地图: ```bash rosrun map_server map_saver -f mymap ``` 第六步:导航 使用move_base进行导航。需要配置move_base的参数,包括全局代价地图、局部代价地图、全局路径规划器(如NavFn)、局部路径规划器(如TebLocalPlanner,支持全向移动)等。 由于全向移动机器人,我们推荐使用TebLocalPlanner,因为它支持全向运动。 首先,安装TebLocalPlanner: ```bash sudo apt-get install ros-$ROS_DISTRO-teb-local-planner ``` 然后,创建配置文件(在功能包`mecanum_bot_navigation`的`config`文件夹下): - `costmap_common_params.yaml` - `global_costmap_params.yaml` - `local_costmap_params.yaml` - `teb_local_planner_params.yaml` 由于配置内容较多,这里只给出关键部分,具体参数需要根据机器人调整。 `teb_local_planner_params.yaml`中需要设置机器人类型为全向: ```yaml robot_type: omni ``` 然后,创建启动文件`navigation.launch`: ```xml <launch> <!-- 启动雷达驱动 --> <include file="$(find ydlidar_ros_driver)/launch/x3_lidar_view.launch" /> <!-- 启动机器人模型 --> <param name="robot_description" command="$(find xacro)/xacro '$(find mecanum_bot_description)/urdf/mecanum_bot.urdf'" /> <!-- 启动机器人状态发布 --> <node pkg="robot_state_publisher" type="robot_state_publisher" name="robot_state_publisher" /> <!-- 启动地图服务器 --> <arg name="map_file" default="$(find mecanum_bot_navigation)/maps/mymap.yaml"/> <node pkg="map_server" type="map_server" name="map_server" args="$(arg map_file)" /> <!-- 启动AMCL节点 --> <include file="$(find mecanum_bot_navigation)/launch/amcl.launch" /> <!-- 启动move_base --> <node pkg="move_base" type="move_base" respawn="false" name="move_base" output="screen"> <rosparam file="$(find mecanum_bot_navigation)/config/costmap_common_params.yaml" command="load" ns="global_costmap" /> <rosparam file="$(find mecanum_bot_navigation)/config/costmap_common_params.yaml" command="load" ns="local_costmap" /> <rosparam file="$(find mecanum_bot_navigation)/config/local_costmap_params.yaml" command="load" /> <rosparam file="$(find mecanum_bot_navigation)/config/global_costmap_params.yaml" command="load" /> <rosparam file="$(find mecanum_bot_navigation)/config/teb_local_planner_params.yaml" command="load" /> <!-- 设置全局规划器 --> <param name="base_global_planner" value="global_planner/GlobalPlanner" /> <!-- 设置局部规划器 --> <param name="base_local_planner" value="teb_local_planner/TebLocalPlannerROS" /> <remap from="cmd_vel" to="/cmd_vel" /> </node> </launch> ``` 注意:AMCL也需要配置,这里省略了AMCL的启动文件内容。 由于这是一个复杂的系统,实际中需要调整大量参数并进行测试。 终端指令总结: 1. 启动建图: ```bash roslaunch mecanum_bot_navigation slam.launch ``` 然后使用键盘或遥控控制机器人移动,完成建图后保存地图。 2. 启动导航: ```bash roslaunch mecanum_bot_navigation navigation.launch ``` 注意:以上代码和配置仅为示例,实际使用时需要根据具体硬件和需求调整。 由于篇幅限制,无法提供所有细节,但希望这个指南能帮助你开始实现。 §§相关问题§§ 1. 如何调整gmapping参数以提高建图质量? 2. 在ROS中如何为全向移动机器人配置TebLocalPlanner? 3. 如何验证里程计的准确性?</think>### ROS四轮麦轮小车完整导航功能实现指南 为实现基于ROS的四轮麦轮小车的完整导航功能,需要完成以下核心模块: #### 1. 系统架构 ``` [雷达传感器] -> [SLAM建图] -> [导航地图] | | [底盘驱动] <- [运动控制] <- [路径规划] | | [IMU/编码器] -> [定位] -> [避障] ``` #### 2. 核心功能包结构 ```bash ~/ydlidar_ws/src/ ├── mecanum_bot_description/ # 机器人模型 ├── mecanum_bot_control/ # 底盘驱动 ├── mecanum_bot_navigation/ # 导航配置 └── ydlidar_ros_driver/ # 雷达驱动 ``` #### 3. 关键代码实现 **A. 麦轮运动学模型(mecanum_bot_control/src/kinematics.cpp)** ```cpp #include <ros/ros.h> #include <geometry_msgs/Twist.h> #include <sensor_msgs/JointState.h> // 麦轮运动学参数 const double WHEEL_RADIUS = 0.05; // 轮半径(m) const double LX = 0.15; // 轮距x方向(m) const double LY = 0.10; // 轮距y方向(m) void cmdVelCallback(const geometry_msgs::Twist::ConstPtr& msg) { // 运动学逆解: 将Twist转换为四个轮速 double vx = msg->linear.x; double vy = msg->linear.y; double wz = msg->angular.z; // 麦轮运动学方程 double w1 = (vx - vy - wz*(LX+LY)) / WHEEL_RADIUS; // 前左轮 double w2 = (vx + vy + wz*(LX+LY)) / WHEEL_RADIUS; // 前右轮 double w3 = (vx + vy - wz*(LX+LY)) / WHEEL_RADIUS; // 后左轮 double w4 = (vx - vy + wz*(LX+LY)) / WHEEL_RADIUS; // 后右轮 // 发布到电机控制器 sensor_msgs::JointState wheel_speeds; wheel_speeds.velocity = {w1, w2, w3, w4}; // ... [发布到硬件接口] } ``` **B. 导航配置(mecanum_bot_navigation/config/teb_params.yaml)** ```yaml TebLocalPlannerROS: robot_type: omni # 全向移动机器人 max_vel_x: 0.5 # 最大线速度(m/s) max_vel_y: 0.5 # 横向最大速度 max_vel_theta: 1.0 # 最大角速度(rad/s) acc_lim_x: 0.5 # 线加速度限制 acc_lim_y: 0.5 acc_lim_theta: 1.0 min_turning_radius: 0.0 # 全向机器人无最小转弯半径 footprint_model: # 机器人轮廓 type: "polygon" vertices: [[-0.2, -0.15], [0.2, -0.15], [0.2, 0.15], [-0.2, 0.15]] ``` #### 4. 完整终端指令流程 **步骤1:启动雷达驱动** ```bash roslaunch ydlidar_ros_driver x3_lidar_view.launch ``` **步骤2:启动机器人模型** ```bash roslaunch mecanum_bot_description display.launch ``` **步骤3:启动底盘驱动** ```bash roslaunch mecanum_bot_control base_controller.launch ``` **步骤4:SLAM建图(使用gmapping)** ```bash roslaunch mecanum_bot_navigation slam_gmapping.launch # 使用键盘控制建图 rosrun teleop_twist_keyboard teleop_twist_keyboard.py ``` **步骤5:保存地图** ```bash rosrun map_server map_saver -f ~/maps/mecanum_map ``` **步骤6:启动导航(使用teb_local_planner)** ```bash roslaunch mecanum_bot_navigation navigation.launch ``` **步骤7:发送导航目标(使用Rviz)** ```bash rosrun rviz rviz -d `rospack find mecanum_bot_navigation`/rviz/nav.rviz ``` #### 5. 关键配置文件 **导航启动文件(navigation.launch)** ```xml <launch> <!-- 加载地图 --> <node pkg="map_server" type="map_server" name="map_server" args="$(find mecanum_bot_navigation)/maps/mecanum_map.yaml"/> <!-- AMCL定位 --> <include file="$(find mecanum_bot_navigation)/launch/amcl.launch"/> <!-- MoveBase导航 --> <node pkg="move_base" type="move_base" respawn="false" name="move_base"> <rosparam file="$(find mecanum_bot_navigation)/config/costmap_common_params.yaml" command="load" ns="global_costmap"/> <rosparam file="$(find mecanum_bot_navigation)/config/costmap_common_params.yaml" command="load" ns="local_costmap"/> <rosparam file="$(find mecanum_bot_navigation)/config/local_costmap_params.yaml" command="load"/> <rosparam file="$(find mecanum_bot_navigation)/config/global_costmap_params.yaml" command="load"/> <rosparam file="$(find mecanum_bot_navigation)/config/teb_params.yaml" command="load"/> <param name="base_local_planner" value="teb_local_planner/TebLocalPlannerROS"/> <param name="controller_frequency" value="10.0"/> </node> </launch> ``` #### 6. 调试技巧 1. 检查TF树是否正确: ```bash rosrun tf view_frames evince frames.pdf ``` 2. 监控导航状态: ```bash rostopic echo /move_base/status ``` 3. 可视化代价地图: ```bash roslaunch mecanum_bot_navigation costmap_viewer.launch ``` ### 注意事项 1. 麦轮机器人需使用支持全向移动的局部规划器(如teb_local_planner) 2. 运动学参数需根据实际机器人尺寸精确配置 3. 导航性能取决于雷达精度和地图质量 4. 全向移动时需增大避障安全距离
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值