ROS入门学习记录


代码引用:https://github.com/DroidAITech/ROS-Academy-for-Beginners

工程架构

创建工作空间及编译

  1. mkdir catkin_ws创建一个catkin编译的工作空间
  2. cd catkin_ws进入到catkin_ws
  3. mkdir src再创建一个src的文件夹,这个是必须的
  4. cd ../..回到最外层的目录
  5. sudo chmod -R 777 catkin_ws对整个工作空间及子目录进行可读可执行可操作的赋权
  6. catkin_make在工作空间内执行catkin_make生成所需要的基本框架
  7. cd src进入src
  8. catkin_create_pkg test1创建一个test1的package
  9. catkin_create_pkg test2 roscpp rospy std_msgs nav_msgs创建一个带有依赖的test2的package,roscpp用来cpp,rospy用于python脚本,std_msgs消息,以及导航专门用的nav_msgs
  10. git clone git@github.com:DroidAITech/ROS-Academy-for-Beginners.git克隆一个别人的package
  11. ssh-keygen -t rsa -C "974782852@qq.com"生成git的公钥,否则git没有权限
  12. cd catkin_ws回到catkin_ws目录
  13. rosinstall --from-paths src --ignore-src --rosdistro=melodic -y安装所有package会用到的依赖,rosdistro需要和ubuntu版本对应,所有用到的依赖可以在每个package中的package.xml的build中找到
  14. source ~/catkin_ws/devel/setup.bash加载当前的环境
  15. echo "source ~/catkin_ws/devel/setup.bash " >> ~/.bashrc这样就不用每次再加载一次环境,打开一个bash自动加载好
  16. rospack find test2rospack find命令可以直接找到相应的package
  17. rosls topic_demonrosls 可以直接列出package下的内容

通信架构

master和node

  • master, 节点管理器
    • 每个节点启动时要向它注册
    • 管理node之间的通信
    • roscore启动master,rosout日志输出(warn,error等), parameter server参数服务器
  • node, ros的进程
    • 一个package可以有多个可执行文件,运行后就加载到内存中,成了一个node
    • 一般来说一个功能一个node
    • rosrun [pkg_name] [node_name]启动一个节点
    • rosnode list列出当前运行的node的信息
    • rosnode info [node_name]显示某个node的详细信息
    • rosnode kill [node_name]结束某个node
    • 一次启动多个node,用roslaunch [pkg_name] [file_name.launch], 可以在pkg中建立一个launch文件夹,xx.launch 专门用来启动多个节点,一般命名为xx_bringup, 不用死记硬背,找别人的模板看着改

打开robot sim仿真

  1. roslaunch robot_sim_demo robot_spawn.launch启动sim仿真,比较卡,应该看看网上有没有什么优化的策略
  2. rosnode list列出当前有哪些node
  3. rosnode info xxx列出xxx节点的具体信息
  4. rosrun robot_sim_demo robot_keyboard_teleop.py按ijkl就可以控制仿真中机器人的前后左右运动
  5. rosrun image_view image_view image:=camera/rgb/image_raw可以看到摄像头看到的画面。

topics和msgs

  • 异步通信方式,node a只管publish, 不用管有没有人接收,node b有消息来了就去处理,没有就做别的;可以有多个node向同一个topic发布,也可以有多个node订阅同一个topic
  • node之间通过publish-subscribe机制通信, 接收者收到数据会回调
  • node a向node b发送一个信息,发送到topic频道,然后node b从频道中取出数据
  • Message是topic的数据类型,定义在package里面的msg文件夹中的*.msg文件中
    • bool, int8, int16, int32, int64, float32, float64, string, time, duration, header, 可变长数组[], 固定长度数组[C]
  • rostopic list列出当前所有topic
  • rostopic info /topic_mame显示某个topic的属性信息
  • rostopic echo /topic_name显示某个topic的内容
  • rostopic pub /topic_name ...向某个topic发布内容
  • rosmsg list列出系统上所有的msg
  • rosmsg show /msg_name显示某msg的内容

service和srv

  • 同步通信方式,node a调用node b的函数, 并且会阻塞等待返回一个结果,request-reply的方式,node b作为server, node a为client;远程过程调用RPC
  • 偶尔调用的功能、具体任务
  • srv定义在srv文件夹中*.srv文件
  • 返回的数组嵌套一个msg文件
  • 修改package.xml,添加依赖
  • 修改CMakeList.txt,添加相应的srv和msg
  • rosservice list列出当前所有活跃的service
  • rosservice info service_name显示某个service的属性信息
  • rosservice call service_name args调用某个service,eg:rosservice call /go/delte_light 'light_name: 'sun'',删除gazebo中的sun灯光
  • rossrv list列出系统上所有的srv
  • rossrv show srv_name显示某个srv内容

parameter server

  • roscore时候已经启动parameter server
  • 存储各种参数的字典,可以用命令行,launch文件(两个标签和)和node api读写
  • key和value的形式
  • rosparam list列出当前所有的参数
  • rosparam get param_key显示某个参数的值
  • rosparam set param_key param_value设置某个参数的值
  • rosparam dump file_name保存参数到文件中
  • rosparam load file_name从文件中读取参数,yaml格式:key:value
  • rosparam delete param_key删除参数

action

  • 类似service, 带有状态反馈的通信方式
  • 通常用在长时间、可抢占的任务中
  • action定义在action文件夹中的*.action
  • 发送一个goal,实时返回feedback,最终完成返回result

常用工具

gazebo

  • 和ros是用一个团队开发,可以用来仿真导航、路径规划等,有很多现成的模型可以使用。如桌子,方块,圆球等动态的障碍物,测试避障等功能。

RViz

  • The Robot Visualization tool可视化工具
  • 方便调试和监控
  • rviz可以启动
  • 添加一个robotmodel可以把xbot的模型加载到界面中,如果显示的是一片白色,把左侧选项栏中的framebase换成机器人即可。
  • 添加一个camera,在其属性中可以添加imagetopic,可以实时的显示摄像头看到的画面
  • 添加一个laser
  • 添加一个pointclould2点云,由深度摄像头得到的,一个个小方格,不仅有颜色rgb,还有x,y,z信息
  • 左边的每个插件都类似一个subscriber,订阅了这些传感器的topic,用来显示

Rqt

  • rqt_graph显示通信架构,当前有哪些node,哪些topic,数据流
  • rqt_plot绘制曲线,比如odo的速度,imu的数据等。

rosbag

  • 记录和回访数据
  • *.bag
  • rosbag record <topic_names>记录某些topic到bag中
  • rosbag record -a记录所有的topic到bag中
  • rosbag play <bag_files>回放bag

roscpp

  • Client Library提供ROS编程的库,在API上面又封装了一层,如roscpp, rospy, roslisp
  • void ros::init();解析ROS参数,为本node命名
  • ros::NodeHandle是一个类
    • ros::Publisher advertise(const string &topic, uint32_t queue_size);创建话题的publisher
    • ros::Subscriber subscribe(const string &topic, uint32_t queue_size, void(*)(M));创建话题的subscriber
    • ros::ServiceServer advertiseService(const string &service, bool(*srv_func)(Mreq &, Mres &));创建服务的server
    • rosServiceClient serviceClient(const string &service_name, bool persistent=false);创建服务的client
    • bool getParam(const string &key, void &val);查询某个参数的值
    • bool setParam(const string &key, void val);给某个参数赋值
  • ros::masters是一个namespace
    • bool check();检查master是否启动
    • const string& getHost();返回master所处的Hostname
    • bool getNodes(V_string &nodes);从master返回已知的node名称列表
    • bool getTopics(V_Topicinfo &topics);返回所有正在被发布的topic列表
    • bool getURl();返回到master的URl地址,如http://host:port/
    • uint32_t getPort();返回master运行在的端口
  • ros::this_node是一个namespace
    • void getAdvertisedTopics(V_string &topics);返回本node发布的topic
    • const string &getName();返回当前node的名称
    • const string &getNamespace();返回当前node的命名空间
    • void getSubscribedTopics(V_string &topics);返回当前node订阅的topic
  • ros::service是一个namespace
    • bool call(const string &service, Service &service);调用一个RPC服务
    • ServiceClient createClient(const string& service_name, bool persistent=false, const M_string &header_value=M_string());创建一个服务的client
    • bool exists(const string& service_name, bool print_failure_reason);确认服务可调用
    • bool waitForService(const string &service_name, int32_t timeout);等待一个服务,直到它可以调用

topic_demo

  1. package
    • catkin_create_pkg topic_demo roscpp rospy std_msgs
  2. msg
    • mkdir msg
    • gedit gps.msg
  3. talker.cpp
  4. listener.cpp
  5. CMakeList.txt&Package.xml
    • 添加依赖,msg等,具体看详细
talker
//ROS头文件
#include <ros/ros.h>
//自定义msg产生的头文件
#include <topic_demo/gps.h>

int main(int argc, char **argv)
{
  //用于解析ROS参数,第三个参数为本节点名
  ros::init(argc, argv, "talker");

  //实例化句柄,初始化node
  ros::NodeHandle nh;

  //自定义gps msg
  topic_demo::gps msg;
  msg.x = 1.0;
  msg.y = 1.0;
  msg.state = "working";

  //创建publisher
  ros::Publisher pub = nh.advertise<topic_demo::gps>("gps_info", 1);

  //定义发布的频率 
  ros::Rate loop_rate(1.0);
  //循环发布msg
  while (ros::ok())
  {
    //以指数增长,每隔1秒更新一次
    msg.x = 1.03 * msg.x ;
    msg.y = 1.01 * msg.y;
    ROS_INFO("Talker: GPS: x = %f, y = %f ",  msg.x ,msg.y);
    //以1Hz的频率发布msg
    pub.publish(msg);
    //根据前面定义的频率, sleep 1s
    loop_rate.sleep();//根据前面的定义的loop_rate,设置1s的暂停
  }

  return 0;
} 

listener

//ROS头文件
#include <ros/ros.h>
//包含自定义msg产生的头文件
#include <topic_demo/gps.h>
//ROS标准msg头文件
#include <std_msgs/Float32.h>

void gpsCallback(const topic_demo::gps::ConstPtr &msg)
{  
    //计算离原点(0,0)的距离
    std_msgs::Float32 distance;
    distance.data = sqrt(pow(msg->x,2)+pow(msg->y,2));
    //float distance = sqrt(pow(msg->x,2)+pow(msg->y,2));
    ROS_INFO("Listener: Distance to origin = %f, state: %s",distance.data,msg->state.c_str());
}

int main(int argc, char **argv)
{
  ros::init(argc, argv, "listener");
  ros::NodeHandle n;
  ros::Subscriber sub = n.subscribe("gps_info", 1, gpsCallback);
  //ros::spin()用于调用所有可触发的回调函数。将进入循环,不会返回,类似于在循环里反复调用ros::spinOnce()。
  ros::spin(); 
  return 0;
}

service_demo

  1. package
    • catkin_create_pkg service_demo roscpp rospy std_msgs
  2. msg
    • mkdir srv
    • gedit Greeeting.srv
  3. talker.cpp
  4. listener.cpp
  5. CMakeList.txt&Package.xml
    • 添加依赖,msg等,具体看详细
server
// This is the C++ version server file of the service demo
//

// 加载必要文件,注意Service_demo的加载方式
# include "ros/ros.h"
# include "service_demo/Greeting.h"
# include "string"

// 定义请求处理函数
bool handle_function(service_demo::Greeting::Request &req,
					service_demo::Greeting::Response &res)
{
	// 此处我们对请求直接输出
	ROS_INFO("Request from %s with age %d ", req.name.c_str(), req.age);
	
	// 返回一个反馈,将response设置为"..."
	res.feedback = "Hi " + req.name + ". I'm server!";
	return true;
}

int main(int argc, char **argv)
{
	// 初始化节点,命名为"greetings_server"
	ros::init(argc, argv, "greetings_server");
	
	// 定义service的server端,service名称为“greetings”,收到request请求之后传递给handle_function进行处理
	ros::NodeHandle nh;
	ros::ServiceServer service = nh.advertiseService("greetings", handle_function);
	
	// 调用可
	ros::spin();

	return 0;
}

client
// This is client of the service demo
// 包含必要文件,注意Service文件的包含方式,我们定义的srv文件为Greeting.srv,在包含时需要写成Greeting.h
# include "ros/ros.h"
# include "service_demo/Greeting.h"

int main(int argc, char **argv)
{
	// 初始化,节点命名为"greetings_client"
	ros::init(argc, argv, "greetings_client");
	
	// 定义service客户端,service名字为“greetings”,service类型为Service_demo
	ros::NodeHandle nh;
	ros::ServiceClient client = nh.serviceClient<service_demo::Greeting>("greetings");
	
	// 实例化srv,设置其request消息的内容,这里request包含两个变量,name和age,见Greeting.srv
	service_demo::Greeting srv;
	srv.request.name = "HAN";
	srv.request.age = 20;

	if (client.call(srv))
	{
		// 注意我们的response部分中的内容只包含一个变量response,另,注意将其转变成字符串
		ROS_INFO("Response from server: %s", srv.response.feedback.c_str());
	}
	else
	{
		ROS_ERROR("Failed to call service Service_demo");
		return 1;
	}
	return 0;
}

param_demo

  1. cpp写法
    #include<ros/ros.h>

int main(int argc, char **argv){
	ros::init(argc, argv, "param_demo");
	ros::NodeHandle nh;
	int parameter1, parameter2, parameter3, parameter4, parameter5;
	
	//Get Param的三种方法
	//① ros::param::get()获取参数“param1”的value,写入到parameter1上
	bool ifget1 = ros::param::get("param1", parameter1);
	
	//② ros::NodeHandle::getParam()获取参数,与①作用相同
	bool ifget2 = nh.getParam("param2",parameter2);
	
	//③ ros::NodeHandle::param()类似于①和②
	//但如果get不到指定的param,它可以给param指定一个默认值(如33333)
        nh.param("param3", parameter3, 33333);
	
	if(ifget1)
		ROS_INFO("Get param1 = %d", parameter1);
	else
		ROS_WARN("Didn't retrieve param1");
	if(ifget2)
		ROS_INFO("Get param2 = %d", parameter2);
	else
		ROS_WARN("Didn't retrieve param2");
	if(nh.hasParam("param3"))
		ROS_INFO("Get param3 = %d", parameter3);
	else
		ROS_WARN("Didn't retrieve param3");


    //Set Param的两种方法
	//① ros::param::set()设置参数
	parameter4 = 4;
	ros::param::set("param4", parameter4);

	//② ros::NodeHandle::setParam()设置参数
	parameter5 = 5;
	nh.setParam("param5",parameter5);
	
	ROS_INFO("Param4 is set to be %d", parameter4);
	ROS_INFO("Param5 is set to be %d", parameter5);


	//Check Param的两种方法
	//① ros::NodeHandle::hasParam()
	bool ifparam5 = nh.hasParam("param5");

	//② ros::param::has()
	bool ifparam6 = ros::param::has("param6");

	if(ifparam5) 
		ROS_INFO("Param5 exists");
	else
		ROS_INFO("Param5 doesn't exist");
	if(ifparam6) 
		ROS_INFO("Param6 exists");
	else
		ROS_INFO("Param6 doesn't exist");


	//Delete Param的两种方法
	//① ros::NodeHandle::deleteParam()
	bool ifdeleted5 = nh.deleteParam("param5");

	//② ros::param::del()
	bool ifdeleted6 = ros::param::del("param6");
	

	if(ifdeleted5)
		ROS_INFO("Param5 deleted");
	else
		ROS_INFO("Param5 not deleted");
	if(ifdeleted6)
		ROS_INFO("Param6 deleted");
	else
		ROS_INFO("Param6 not deleted");


	ros::Rate rate(0.3);
	while(ros::ok()){
		int parameter = 0;
		
		ROS_INFO("=============Loop==============");
		//roscpp中尚未有ros::param::getallparams()之类的方法
		if(ros::param::get("param1", parameter))
			ROS_INFO("parameter param1 = %d", parameter);
		if(ros::param::get("param2", parameter))
			ROS_INFO("parameter param2 = %d", parameter);
		if(ros::param::get("param3", parameter))
			ROS_INFO("parameter param3 = %d", parameter);
		if(ros::param::get("param4", parameter))
			ROS_INFO("parameter param4 = %d", parameter);
		if(ros::param::get("param5", parameter))
			ROS_INFO("parameter param5 = %d", parameter);
		if(ros::param::get("param6", parameter))
			ROS_INFO("parameter param6 = %d", parameter);
		rate.sleep();
	}
}
  1. launch写法
<launch>
	<!--param参数配置-->
	<param name="param1" value="1" />
	<param name="param2" value="2" />
	<!--param name="table_description" command="$(find xacro)/xacro.py $(find gazebo_worlds)/objects/table.urdf.xacro" /-->

	<!--rosparam参数配置-->
	<rosparam>   
        param3: 3
        param4: 4
        param5: 5
    </rosparam>
	<!--以上写法将参数转成YAML文件加载,注意param前面必须为空格,不能用Tab,否则YAML解析错误-->
	<!--rosparam file="$(find robot_sim_demo)/config/xbot-u_control.yaml" command="load" /-->
	<node pkg="param_demo" type="param_demo" name="param_demo" output="screen" />
</launch>

rospy

  1. script文件夹中放py

api

http://docs.ros.org/api/rospy/html/

  • wait_for_message(topic, topic_type, time_out=None)等待指定topic的一个message,一般用于只执行一次,如按下摄像头的拍照,等待一次信息即可。

topic_demo

pytalker
#!/usr/bin/env python
#coding=utf-8
import rospy
#倒入自定义的数据类型
from topic_demo.msg import gps

def talker():
    #Publisher 函数第一个参数是话题名称,第二个参数 数据类型,现在就是我们定义的msg 最后一个是缓冲区的大小
    #queue_size: None(不建议)  #这将设置为阻塞式同步收发模式!
    #queue_size: 0(不建议)#这将设置为无限缓冲区模式,很危险!
    #queue_size: 10 or more  #一般情况下,设为10 。queue_size太大了会导致数据延迟不同步。
    pub = rospy.Publisher('gps_info', gps , queue_size=10)
    rospy.init_node('pytalker', anonymous=True)
    #更新频率是1hz
    rate = rospy.Rate(1) 
    x=1.0
    y=2.0
    state='working'
    while not rospy.is_shutdown():
        #计算距离
        rospy.loginfo('Talker: GPS: x=%f ,y= %f',x,y)
        pub.publish(gps(state,x,y))
        x=1.03*x
        y=1.01*y
        rate.sleep()

if __name__ == '__main__':
    talker()
pylistener
#!/usr/bin/env python
#coding=utf-8
import rospy
import math
#导入mgs到pkg中
from topic_demo.msg import gps

#回调函数输入的应该是msg
def callback(gps):
    distance = math.sqrt(math.pow(gps.x, 2)+math.pow(gps.y, 2)) 
    rospy.loginfo('Listener: GPS: distance=%f, state=%s', distance, gps.state)

def listener():
    rospy.init_node('pylistener', anonymous=True)
    #Subscriber函数第一个参数是topic的名称,第二个参数是接受的数据类型 第三个参数是回调函数的名称
    rospy.Subscriber('gps_info', gps, callback)
    rospy.spin()

if __name__ == '__main__':
    listener()

service_demo

server
#!/usr/bin/env python
# coding:utf-8

# 上面指定编码utf-8,使python能够识别中文

# 加载必需模块,注意service模块的加载方式,from 包名.srv import *
# 其中srv指的是在包根目录下的srv文件夹,也即srv模块
import rospy
from service_demo.srv import *

def server_srv():
    # 初始化节点,命名为 "greetings_server"
    rospy.init_node("greetings_server")
    # 定义service的server端,service名称为"greetings", service类型为Greeting
    # 收到的request请求信息将作为参数传递给handle_function进行处理
    s = rospy.Service("greetings", Greeting, handle_function)
    rospy.loginfo("Ready to handle the request:")
    # 阻塞程序结束
    rospy.spin()

# Define the handle function to handle the request inputs
def handle_function(req):
    # 注意我们是如何调用request请求内容的,与前面client端相似,都是将其认为是一个对象的属性,通过对象调用属性,在我们定义
    # 的Service_demo类型的service中,request部分的内容包含两个变量,一个是字符串类型的name,另外一个是整数类型的age
    rospy.loginfo( 'Request from %s with age %d', req.name, req.age)
    # 返回一个Service_demoResponse实例化对象,其实就是返回一个response的对象,其包含的内容为我们再Service_demo.srv中定义的
    # response部分的内容,我们定义了一个string类型的变量,因此,此处实例化时传入字符串即可
    return GreetingResponse("Hi %s. I' server!"%req.name)

# 如果单独运行此文件,则将上面定义的server_srv作为主函数运行
if __name__=="__main__":
    server_srv()
client
#!/usr/bin/env python
# coding:utf-8

# 上面的第二句指定编码类型为utf-8,是为了使python能够识别中文

# 加载所需模块
import rospy
from service_demo.srv import *

def client_srv():
    rospy.init_node('greetings_client')
    # 等待有可用的服务 "greetings"
    rospy.wait_for_service("greetings")
    try:
        # 定义service客户端,service名称为“greetings”,service类型为Greeting
        greetings_client = rospy.ServiceProxy("greetings",Greeting)

        # 向server端发送请求,发送的request内容为name和age,其值分别为"HAN", 20
        # 注意,此处发送的request内容与service文件中定义的request部分的属性是一致的
        #resp = greetings_client("HAN",20)
        resp = greetings_client.call("HAN",20)

        # 打印处理结果,注意调用response的方法,类似于从resp对象中调取response属性
        rospy.loginfo("Message From server:%s"%resp.feedback)
    except rospy.ServiceException, e:
        rospy.logwarn("Service call failed: %s"%e)

# 如果单独运行此文件,则将上面函数client_srv()作为主函数运行
if __name__=="__main__":
    client_srv() 

TF&URDF

TF

  • TransForm 坐标系转换
  • 机器人的部件叫做link,对应一个frame坐标系
  • TF树
    • 每两个link之间的联通,broadcaster,需要有一个node来发布给broadcaster,来联通他们之间的关系
  • rosrun tf_view_frames根据当前的tf树创建一个pdf图,监听/tf这个topic 5s
  • rosrun rqt_tf_tree rqt_tf_tree查看当前的tf树
  • rosrun tf tf_echo [reference_frame] [target_frame]查看两个frame之间的变换关系

urdf

  • Unified Robot Description Format
  • 每个link之间的链接叫做joint,
  • 定义一个urdf文件就是描述了整个机器人的组成关系,遵循xml格式

SLAM

  • 同步定位与建图Simutaneous Localization And Mapping
  • Mapping算法:
    • Gmapping
    • Karto
    • Hector
    • Cartographer
  • Localization算法:
    • AMCL
  • Path Planning算法:
    • Navigation
      • Global:
        • Digkstra、A*
      • Locaal:
        • DWA
  • Map:
# This represents a 2-D grid map, in which each cell represents the probability of
# occupancy.

Header header 

#MetaData for the map
MapMetaData info

# The map data, in row-major order, starting with (0,0).  Occupancy
# probabilities are in the range [0,100].  Unknown is -1.
int8[] data
# This hold basic information about the characterists of the OccupancyGrid

# The time at which the map was loaded
time map_load_time
# The map resolution [m/cell]
float32 resolution
# Map width [cells]
uint32 width
# Map height [cells]
uint32 height
# The origin of the map [m, m, rad].  This is the real-world pose of the
# cell (0,0) in the map.
geometry_msgs/Pose origin
  • 一个topic:/map,type:nav_msgs/OccupancyGrid
  • resolution单位:m/pixed 米每像素,一个珊格就是一个像素,resolution代表一个像素是多少米, 一般为0.05、0.025
  • width、height单位:pixed
  • data数组的长度就是width*height

slam演示

  1. roscore
  2. roslaunch robot_sim_demo robot_spawn.launch
  3. roslaunch slam_sim_demo gmapping_demo.launch
  4. roslaunch slam_sim_demo view_slam.launch

Navigation

ros官方link

movebase

  • 以下三个插件都是继承于nav_core
  1. 全局规划,只考虑静态障碍物, Base Local Planner
    1. base_local_planner
    2. dwa_local_planner
  2. 局部规划,动态障碍物, Base Global Planner
    1. carrot_planner
    2. navfn
    3. global_planner
  3. 处理异常行为, Recovery Behavior
    1. clear_costmap_recovery
    2. rotate_recovery
    3. move_slow_and_clear

costmap

  • 代价地图,珊格地图
  • 主要给导航使用,有两张(global_costmap, local_costmap), 多层
    1. static_layer(map),静态不变的
    2. obstacle layer, 动态的,由激光扫到的地图,加到static_layer,支持3D点云的投影
    3. inflation layer, 膨胀, 把障碍物进行膨胀一圈,防止机器人碰撞
  • 以上3层可通过YAML配置

MapServer

  • 提供地图,直接发布/map这个topic
  • 地图格式:*.pgm
  • 表现障碍物有无:occ = (255 - color_avg) / 255.0
    • 255减去某一像素上的RGB平均值/255,比如一个地方是白色的[255,255,255],那么occ为0,说明该像素点为空,没有障碍
  • rosrun map_server map_server my_map.yaml发布地图
  • rosrun map_server map_saver [-f my_map]保存地图
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值