linux生成地图,ROS中利用V-rep进行地图构建仿真

V-rep中显示激光扫描点

在VREP自带的场景中找到practicalPathPlanningDemo.ttt文件,删除场景中多余的物体只保留静态的地图。然后在Model browser→components→sensors中找到SICK TiM310 Fast激光雷达,拖入场景中:

f488641112bd536a0b64e37cf727b23e.png

打开脚本参数修改器,可以修改雷达扫描范围(默认为270°),是否显示雷达扫描线(true),以及最大探测距离(默认为4m)这三个参数。地图大小为5m×5m,我们将雷达最大探测距离改为2m

ee49736f0cd1069ca68b26e632501c79.png

将激光雷达放到地图中任意位置,点击仿真按钮可以看到扫描光线(如果电脑比较卡可以将showLaserSegments这个参数设为false,就不会显示扫描线)如下图所示:

fda4117b8d35eeea7ace09c38158d273.png

SICK_TiM310激光雷达在V-rep中是由两个视角为135°的视觉传感器模拟的,这两个视觉传感器可以探测深度信息:

7baf87bc825238ae9d48087b32b71c46.png

双击视觉传感器图标,修改Filter中Coordinate Extraction的参数与传感器X/Y方向分辨率一致。X方向默认值为135,即会返回135个数据点,这里要改为256。

7b616c208509b6b566efbda1d17c47e5.png

我们可以在V-rep中绘制出激光扫描图:在场景中添加一个Graph,将其设为显示处理(Explicit handling),然后添加用户自定义数据x和y:

4f0d9f49a745f9bd636f266a65ab12f3.png

然后点击Edit XY graphs按钮,在弹出的对话框中添加一个新的曲线。X-value选择我们之前自定义的数据x,Y-value选择自定义的数据y,并去掉Link points选项:

e48dd503bd5f9c53067e1305870de803.png

将SICK_TiM310_fast的lua脚本代码修改如下:

5340a8effb8e821c700bb24a43f3938a.gif

if (sim_call_type==sim_childscriptcall_initialization) thenvisionSensor1Handle=simGetObjectHandle("SICK_TiM310_sensor1")

visionSensor2Handle=simGetObjectHandle("SICK_TiM310_sensor2")

joint1Handle=simGetObjectHandle("SICK_TiM310_joint1")

joint2Handle=simGetObjectHandle("SICK_TiM310_joint2")

sensorRefHandle=simGetObjectHandle("SICK_TiM310_ref")

graphHandle= simGetObjectHandle("Graph")

maxScanDistance=simGetScriptSimulationParameter(sim_handle_self,'maxScanDistance')if maxScanDistance>1000 then maxScanDistance=1000 end

if maxScanDistance<0.1 then maxScanDistance=0.1 endsimSetObjectFloatParameter(visionSensor1Handle,sim_visionfloatparam_far_clipping,maxScanDistance)

simSetObjectFloatParameter(visionSensor2Handle,sim_visionfloatparam_far_clipping,maxScanDistance)

maxScanDistance_=maxScanDistance*0.9999scanningAngle=simGetScriptSimulationParameter(sim_handle_self,'scanAngle')if scanningAngle>270 then scanningAngle=270 end

if scanningAngle<2 then scanningAngle=2 endscanningAngle=scanningAngle*math.pi/180simSetObjectFloatParameter(visionSensor1Handle,sim_visionfloatparam_perspective_angle,scanningAngle/2)

simSetObjectFloatParameter(visionSensor2Handle,sim_visionfloatparam_perspective_angle,scanningAngle/2)

simSetJointPosition(joint1Handle,-scanningAngle/4)

simSetJointPosition(joint2Handle,scanningAngle/4)

red={1,0,0}

lines=simAddDrawingObject(sim_drawing_lines,1,0,-1,1000,nil,nil,nil,red)if (simGetInt32Parameter(sim_intparam_program_version)<30004) thensimDisplayDialog("ERROR","This version of the SICK sensor is only supported from V-REP V3.0.4 and upwards.&&nMake sure to update your V-REP.",sim_dlgstyle_ok,false,nil,{0.8,0,0,0,0,0},{0.5,0,0,1,1,1})end

end

if (sim_call_type==sim_childscriptcall_cleanup) thensimRemoveDrawingObject(lines)

simResetGraph(graphHandle)end

if (sim_call_type==sim_childscriptcall_sensing) thenmeasuredData={}if notFirstHere then

--We skip the very first reading

simAddDrawingObjectItem(lines,nil)

showLines=simGetScriptSimulationParameter(sim_handle_self,'showLaserSegments')

r,t1,u1=simReadVisionSensor(visionSensor1Handle)

r,t2,u2=simReadVisionSensor(visionSensor2Handle)

m1=simGetObjectMatrix(visionSensor1Handle,-1)

m01=simGetInvertedMatrix(simGetObjectMatrix(sensorRefHandle,-1))

m01=simMultiplyMatrices(m01,m1)

m2=simGetObjectMatrix(visionSensor2Handle,-1)

m02=simGetInvertedMatrix(simGetObjectMatrix(sensorRefHandle,-1))

m02=simMultiplyMatrices(m02,m2)if u1 thenp={0,0,0}

p=simMultiplyVector(m1,p)

t={p[1],p[2],p[3],0,0,0}for j=0,u1[2]-1,1 do

for i=0,u1[1]-1,1 dow=2+4*(j*u1[1]+i)

v1=u1[w+1]

v2=u1[w+2]

v3=u1[w+3]

v4=u1[w+4]if (v4

p=simMultiplyVector(m01,p)table.insert(measuredData,p[1])table.insert(measuredData,p[2])table.insert(measuredData,p[3])end

if showLines thenp={v1,v2,v3}

p=simMultiplyVector(m1,p)

t[4]=p[1]

t[5]=p[2]

t[6]=p[3]

simAddDrawingObjectItem(lines,t)end

end

end

end

if u2 thenp={0,0,0}

p=simMultiplyVector(m2,p)

t={p[1],p[2],p[3],0,0,0}for j=0,u2[2]-1,1 do

for i=0,u2[1]-1,1 dow=2+4*(j*u2[1]+i)

v1=u2[w+1]

v2=u2[w+2]

v3=u2[w+3]

v4=u2[w+4]if (v4

p=simMultiplyVector(m02,p)table.insert(measuredData,p[1])table.insert(measuredData,p[2])table.insert(measuredData,p[3])end

if showLines thenp={v1,v2,v3}

p=simMultiplyVector(m2,p)

t[4]=p[1]

t[5]=p[2]

t[6]=p[3]

simAddDrawingObjectItem(lines,t)end

end

end

end

endnotFirstHere=true

--stringData = simPackFloatTable(measuredData) -- Packs a table of floating-point numbers into a string

--simSetStringSignal("UserData", stringData)

simResetGraph(graphHandle)for i=1,#measuredData/3,1 dosimSetGraphUserData(graphHandle,'x',measuredData[3*(i-1)+1])

simSetGraphUserData(graphHandle,'y',measuredData[3*(i-1)+2])

simHandleGraph(graphHandle,0)end

end

点击仿真按钮,可以在X/Y graph窗口中看到激光扫描结果如下:

b809bd11b3421ae3c70f9c50f568a079.png

V-rep中的视觉传感器可以探测到障碍物的坐标以及与其距离,上面的X-Y图就是直接采用坐标点画出的。然而一般激光雷达只能探测障碍物距离,不能直接获取其坐标,我们可以将距离画成与角度对应的极坐标图。将距离数据保存为CSV文件,用Mathematica读入并画出极坐标图:

ranges = Flatten[Import["C:\\Users\\Administrator\\Desktop\\distance.csv"]];

ListPolarPlot[ranges, DataRange -> {-135 Degree, 135 Degree}]

41c57c1af092d0611969df24a743c771.png

发布LaserScan消息

下面的代码将激光雷达扫描数据按照LaserScan的消息格式发布出去:

5340a8effb8e821c700bb24a43f3938a.gif

if (sim_call_type==sim_childscriptcall_initialization) thenvisionSensor1Handle=simGetObjectHandle("SICK_TiM310_sensor1")

visionSensor2Handle=simGetObjectHandle("SICK_TiM310_sensor2")

joint1Handle=simGetObjectHandle("SICK_TiM310_joint1")

joint2Handle=simGetObjectHandle("SICK_TiM310_joint2")

sensorRefHandle=simGetObjectHandle("SICK_TiM310_ref")

maxScanDistance=simGetScriptSimulationParameter(sim_handle_self,'maxScanDistance')if maxScanDistance>1000 then maxScanDistance=1000 end

if maxScanDistance<0.1 then maxScanDistance=0.1 endsimSetObjectFloatParameter(visionSensor1Handle,sim_visionfloatparam_far_clipping,maxScanDistance)

simSetObjectFloatParameter(visionSensor2Handle,sim_visionfloatparam_far_clipping,maxScanDistance)

maxScanDistance_=maxScanDistance*0.9999scanningAngle=simGetScriptSimulationParameter(sim_handle_self,'scanAngle')if scanningAngle>270 then scanningAngle=270 end

if scanningAngle<2 then scanningAngle=2 endscanningAngle=scanningAngle*math.pi/180simSetObjectFloatParameter(visionSensor1Handle,sim_visionfloatparam_perspective_angle,scanningAngle/2)

simSetObjectFloatParameter(visionSensor2Handle,sim_visionfloatparam_perspective_angle,scanningAngle/2)

simSetJointPosition(joint1Handle,-scanningAngle/4)

simSetJointPosition(joint2Handle,scanningAngle/4)

red={1,0,0}

lines=simAddDrawingObject(sim_drawing_lines,1,0,-1,1000,nil,nil,nil,red)if (simGetInt32Parameter(sim_intparam_program_version)<30004) thensimDisplayDialog("ERROR","This version of the SICK sensor is only supported from V-REP V3.0.4 and upwards.&&nMake sure to update your V-REP.",sim_dlgstyle_ok,false,nil,{0.8,0,0,0,0,0},{0.5,0,0,1,1,1})end

--Enable an LaserScan publisher:

pub = simExtRosInterface_advertise('/scan', 'sensor_msgs/LaserScan')--After calling this function, this publisher will treat uint8 arrays as string. Using strings should be in general much faster that using int arrays in Lua.

simExtRosInterface_publisherTreatUInt8ArrayAsString(pub) --treat uint8 arrays as strings (much faster, tables/arrays are kind of slow in Lua)

angle_min= -135 * (math.pi/180); --angle correspond to FIRST beam in scan ( in rad)

angle_max= 135 * (math.pi/180) --angle correspond to LAST beam in scan ( in rad)

angle_increment = 270*(math.pi/180)/512 --Angular resolution i.e angle between 2 beams

--sensor scans every 50ms with 512 beams. Each beam is measured in (50 ms/ 512 )

time_increment = (1 / 20) / 512range_min= 0.05range_max= maxScanDistance --scan can measure upto this range

end

if (sim_call_type==sim_childscriptcall_cleanup) thensimRemoveDrawingObject(lines)

simExtRosInterface_shutdownPublisher(pub)end

if (sim_call_type==sim_childscriptcall_sensing) thenmeasuredData={}

distanceData={}if notFirstHere then

--We skip the very first reading

simAddDrawingObjectItem(lines,nil)

showLines=simGetScriptSimulationParameter(sim_handle_self,'showLaserSegments')

r,t1,u1=simReadVisionSensor(visionSensor1Handle)

r,t2,u2=simReadVisionSensor(visionSensor2Handle)

m1=simGetObjectMatrix(visionSensor1Handle,-1)

m01=simGetInvertedMatrix(simGetObjectMatrix(sensorRefHandle,-1))

m01=simMultiplyMatrices(m01,m1)

m2=simGetObjectMatrix(visionSensor2Handle,-1)

m02=simGetInvertedMatrix(simGetObjectMatrix(sensorRefHandle,-1))

m02=simMultiplyMatrices(m02,m2)if u1 thenp={0,0,0}

p=simMultiplyVector(m1,p)

t={p[1],p[2],p[3],0,0,0}for j=0,u1[2]-1,1 do

for i=0,u1[1]-1,1 dow=2+4*(j*u1[1]+i)

v1=u1[w+1]

v2=u1[w+2]

v3=u1[w+3]

v4=u1[w+4]table.insert(distanceData,v4)if (v4

p=simMultiplyVector(m01,p)table.insert(measuredData,p[1])table.insert(measuredData,p[2])table.insert(measuredData,p[3])end

if showLines thenp={v1,v2,v3}

p=simMultiplyVector(m1,p)

t[4]=p[1]

t[5]=p[2]

t[6]=p[3]

simAddDrawingObjectItem(lines,t)end

end

end

end

if u2 thenp={0,0,0}

p=simMultiplyVector(m2,p)

t={p[1],p[2],p[3],0,0,0}for j=0,u2[2]-1,1 do

for i=0,u2[1]-1,1 dow=2+4*(j*u2[1]+i)

v1=u2[w+1]

v2=u2[w+2]

v3=u2[w+3]

v4=u2[w+4]table.insert(distanceData,v4)if (v4

p=simMultiplyVector(m02,p)table.insert(measuredData,p[1])table.insert(measuredData,p[2])table.insert(measuredData,p[3])end

if showLines thenp={v1,v2,v3}

p=simMultiplyVector(m2,p)

t[4]=p[1]

t[5]=p[2]

t[6]=p[3]

simAddDrawingObjectItem(lines,t)end

end

end

end

endnotFirstHere=true

--populate the LaserScan message

scan={}

scan['header']={seq=0,stamp=simExtRosInterface_getTime(), frame_id="SICK_TiM310_ref"}

scan['angle_min']=angle_min

scan['angle_max']=angle_max

scan['angle_increment']=angle_increment

scan['time_increment']=time_increment

scan['scan_time']=simExtRosInterface_getTime() --Return the current ROS time i.e. the time returned by ros::Time::now()

scan['range_min']=range_min

scan['range_max']=range_max

scan['ranges'] =distanceData

scan['intensities']={}

simExtRosInterface_publish(pub, scan)end

注意代码中发布的距离是相对于视觉传感器坐标系的,因为模型中视觉传感器坐标系与激光雷达坐标系(SICK_TiM310_ref)在X、Y方向的位置是一致的,而Z坐标只存在一点高度差异,并不会影响X-Y平面内障碍物相对于SICK_TiM310_ref参考坐标系的位置坐标。如果这两个坐标系在X、Y方向存在偏差,就需要将采集到的数据点转换到SICK_TiM310_ref坐标系中。

另外代码中变量v4为激光雷达探测到的距物体的距离,如果在最大扫描范围内没有探测到物体,则会返回最大值。由于这个距离与扫描角度是一一对应的,因此要注意table.insert函数的使用,不能放在下一句的if语句之中,否则在超过最大扫描范围的地方不会向列表内插入距离数据,这样会造成距离与角度不匹配,可能导致激光图像出现歪斜。

点击仿真按钮,程序运行没问题后在rviz中可以添加LaserScan进行查看:

43399019f98658974218fea70048afbf.png

输入rostopic hz /scan可以查看消息发布的频率:

556a6992fc7513aa0701ac6427d453a0.png

这里有一个小问题,从上图可以看出激光雷达信息发布的频率约为43Hz,但是V-rep仿真的时间步���为50ms,消息发布的频率应该为20Hz。这是因为V-rep中默认情况下仿真并不是以实际时间在运行,在工具栏上点击real-time mode按钮,开始实时模式:

d3b5435cbfaa641ada2db33783f77dca.png

现在再查看消息发布的频率,可以看到频率和我们设定的一样了:

ff55b8dad4e10e01c4f6fe928ded390d.png

另外,通过rostopic echo /scan命令可以查看消息的具体内容(方便我们检查出可能存在的错误:我在虚拟机下运行得到的数据很奇怪,但是换到实体系统上就没有问题):

2961b745cbf5308146245afac5a232c7.png

发布nav_msgs/Odometry里程计信息及tf变换

在V-rep中进行地图构建仿真时可以用键盘控制机器人的位置(这里直接简化为控制激光雷达),那么机器人相对于初始时刻odom坐标系的位置和姿态等信息可以通过航迹推算(使用里程计或惯性传感器根据机器人运动学模型计算)获得。然后需要将其按照nav_msgs/Odometry消息的格式包装好,发布到/odom话题上;并且还要发布机器人坐标系base_link相对于odom坐标系的tf变换。The nav_msgs/Odometry message stores an estimate of the position and velocity of a robot in free space.The "tf" software library is responsible for managing the relationships between coordinate frames relevant to the robot in a transform tree. Therefore, any odometry source must publish information about the coordinate frame that it manages.

V-rep脚本中发布tf变换主要用下面这两个函数,区别在于simExtRosInterface_sendTransform调用一次只能发送一对变换,而simExtRosInterface_sendTransforms则可以一次发送多对变换,函数参数是变换的列表:

d540cc8f700da4e12b573e97488e4467.png

根据V-rep中物体的句柄和名称发布坐标系变换的代码如下:

functiongetTransformStamped(objHandle, name, relTo, relToName)--This function retrieves the stamped transform for a specific object

t = simExtRosInterface_getTime()

p= simGetObjectPosition(objHandle, relTo)

o= simGetObjectQuaternion(objHandle, relTo)return{

header= {stamp=t, frame_id=relToName},

child_frame_id= name,

transform= {

translation={x=p[1],y=p[2],z=p[3]},

rotation={x=o[1],y=o[2],z=o[3],w=o[4]}

}

}end----------------------------------------------------------------------------------------------------------------------

simExtRosInterface_sendTransforms({getTransformStamped(sensorRefHandle,'SICK_TiM310_ref',baseLinkHandle,'base_link'),

getTransformStamped(baseLinkHandle,'base_link',odomHandle,'odom')})--simExtRosInterface_sendTransform(getTransformStamped(sensorRefHandle,'SICK_TiM310_ref',baseLinkHandle,'base_link')

--simExtRosInterface_sendTransform(getTransformStamped(baseLinkHandle,'base_link',odomHandle,'odom'))

我们在V-rep的脚本程序中向ros系统发布了坐标系之间的变换,有时可能会出现许多错误。为了方便排查错误,ros提供了一系列tf调试工具。下面两种命令都可以以图形化的方式查看坐标系之间的tf关系:

$ rosrun tf view_frames

$rosrun rqt_tf_tree rqt_tf_tree

打开生成的pdf文件或在弹出的rqt窗口中,可以很清楚的看出里程计坐标系odom,机器人坐标系base_link,以及激光雷达坐标系SICK_TiM310_ref之间的关系:

1dff2e8415bc58af64f1d287f169f1fe.png

tf_echo命令可以用于查看两个坐标系之间具体的变换关系(注意输出的是target_frame相对于reference_frame的关系):

$ rosrun tf tf_echo reference_frame target_frame

如下图所示,会输出激光传感器坐标系SICK_TiM310_ref相对于机器人坐标系base_link的变换(V-rep模型中这两个坐标系是重合的):

d959d0a83bfb62f342f06d428030f3cb.png

在/odom话题上发布nav_msgs/Odometry消息的代码如下(注意这里直接调用函数获取到相对于odom的位置和姿态,省去了航迹推算的过程。如果在真实的小车上进行测试,就需要根据里程计数据来推算小车的位置和姿态等信息,然后再发送出去):

odomPub = simExtRosInterface_advertise('/odom', 'nav_msgs/Odometry')local pos =simGetObjectPosition(baseLinkHandle, odomHandle)local ori =simGetObjectQuaternion(baseLinkHandle, odomHandle)

odom={}

odom.header= {seq=0,stamp=simExtRosInterface_getTime(), frame_id="odom"}

odom.child_frame_id= 'base_link'odom.pose= { pose={position={x=pos[1],y=pos[2],z=pos[3]}, orientation={x=ori[1],y=ori[2],z=ori[3],w=ori[4]} } }

simExtRosInterface_publish(odomPub, odom)

使用gmapping构建地图

gmaping包是用来生成地图的,它需要从ROS系统监听多个Topic,并输出map。The slam_gmapping node takes in sensor_msgs/LaserScan messages and builds a map (nav_msgs/OccupancyGrid)

Subscribed Topics:

Transforms necessary to relate frames for laser, base, and odometry

Laser scans to create the map from

Required tf Transforms:

 → base_link:usually a fixed value, broadcast periodically by a robot_state_publisher, or a tf static_transform_publisher.

base_link → odom:usually provided by the odometry system (e.g., the driver for the mobile base)

Provided tf Transforms:

map → odom:the current estimate of the robot's pose within the map frame

使用记录下的tf以及laser scan data构建地图的步骤如下:

1. 键盘或手柄控制机器人在空间中运动时,使用rosbag记录激光及tf数据包,记录完成后按Ctrl+C键结束。

$ rosbag record -O my_scan_data /scan /tf

76c1c34fda7b485d82cc4744b7b8b0f8.gif

2. 设置参数,确保在任何节点使用前use_sim_time参数为true。我们重播一个记录历史文件时,里面记录的是历史时间,所以我们需要告诉ROS从现在起开始启用模拟时间。This basically tells nodes on startup to use simulated time (ticked here by rosbag) instead of wall-clock time (as in a live system). It avoids confusing time-dependent components like tf, which otherwise would wonder why messages are arriving with timestamps far in the past. 关于时钟问题可以参考

Normally, the ROS client libraries will use your computer's system clock as a time source, also known as the "wall-clock" or "wall-time" (like the clock on the wall of your lab). When you are running a simulation or playing back logged data, however, it is often desirable to instead have the system use a simulated clock so that you can have accelerated, slowed, or stepped control over your system's perceived time. For example, if you are playing back sensor data into your system, you may wish to have your time correspond to the timestamps of the sensor data.

$ rosparam set use_sim_time true

下图是use_sim_time参数为false时的情况:

d47a5c17ceaf0d7a61a54e9edc92a6ef.png

设置use_sim_time为true,rosbag回放开始后ROS Time与Bag Time一致:

2e04a14d65e8fc06d6c55b36a16df646.png

3. 运行slam_gmapping节点,它将在scan主题上监听激光扫描数据并创建地图(可以在命令行中设置建图参数:比如地图分辨率、粒子数目、迭代次数、地图更新间隔等参数)

$ rosrun gmapping slam_gmapping scan:=scan _xmin:=-2.5 _xmax:=2.5 _ymin:=-2.5 _ymax:=2.5 ...

比较重要的几个参数有:

particles (int, default: 30) gmapping算法中的粒子数,因为gmapping使用的是粒子滤波算法,粒子在不断地迭代更新,所以选取一个合适的粒子数可以让算法在保证比较准确的同时有较高的速度。

minimumScore (float, default: 0.0) 最小匹配得分,这个参数很重要,它决定了对激光的一个置信度,越高说明对激光匹配算法的要求越高,激光的匹配也越容易失败而转去使用里程计数据,而设的太低又会使地图中出现大量噪声,所以需要权衡调整。在V-rep仿真中里程计数据是直接通过函数获取的,没有误差,因此可以将这个值调高一点,让地图的匹配更多依赖里程计数据。

lskip(int, default: 0)的值如果为0,则所有的激光数据帧都会用来进行scan matching,如果lskip的值大于0则会跳过几帧来进行scan matching。有时激光数据的噪声会比较大,对所有数据帧进行匹配的效果可能会不好,这时可以加大lskip的值。

...

slam_gmapping节点用到的参数相当多,有很多参数需要在实际中测试多次来确定其值。如果参数太多在命令行中输入会不太方便,可以写成launch文件来运行:

4. 在新终端中启动bag包回放,将数据提供给slam_gmapping节点

$ rosbag play my_scan_data.bag

在数据回放过程中也可以打开rviz进行查看:启动rviz,在左下方点击add按钮,然后选择map,创建一副空地图;接着制定rviz的topic为/map可以监听到地图数据。下图是rosbag回放过程中建图的动态过程:

51e1ec97e9512fbaa32373bb72a18ecc.gif

5. 使用map_server生成地图

$ rosrun map_server map_saver -f my_map

使用map_saver命令后会生成两个文件。my_map.pgm是地图的PGM格式的图片,PGM格式是便携式灰度图像格式(portable graymap file format)。my_map.yaml文件描述地图元数据。

d1770d78bb8ff87ce3bfe1244968f766.png

my_map.yaml文件内容如下:

image: my_map.pgm

resolution: 0.050000

origin: [-12.200000, -12.200000, 0.000000]

negate: 0

occupied_thresh: 0.65

free_thresh: 0.196

image:图像文件的路径;可以是绝对的,或相对于YAML文件的位置

resolution:地图的分辨率,米/像素

origin:地图中左下角像素的位置和姿态(x,y,yaw),偏航为逆时针旋转(yaw = 0表示无旋转)

occupancy_thresh:概率大于该阈值的像素被认为完全占用

free_thresh:概率小于该阈值的像素被认为是完全自由的

negate:“白/黑”对应“自由/占用”语义是否应该被反转

最终生成的地图如下图所示,图中越亮/白的像素表示没有障碍物(free)的概率越大,越暗/黑的像素表示被障碍物占据(occupied)的概率越大,灰色表示状态未知。

4489e7bb0eea8694dc5df0032e4855cb.png

用屏幕测量工具测量图片上的像素间的距离,再乘以分辨率可以得到实际尺寸。例如,左下角点和右下角点的像素间距测量结果为102(可能点取的不精确),对应的实际距离为5.1m,这与真实地图大小一致。

a4aeb85d84d65f1d3954a40ae64286dc.png

6. 在建图结束后不要忘记重置use_sim_time参数

$ rosparam set use_sim_time false

0b1331709591d260c1c78e86d0c51c18.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/288392.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python环境搭建与连接SQL Server类MyDBase的实现

一、开发环境搭建 1、下载所需版本的软件包(点击下载),点击安装即可,注意x86与x64的选择。 2、配置环境:复制python安装目录,粘贴到Path中 3、打开cmd,输入python并回车,看到如下信息,则安装成功!

如何隐藏地址栏中的真实地址_Firefox推出Private Relay插件:可隐藏真实邮箱地址...

Mozilla 正在为 Firefox 开发一项名为Private Relay 的新服务&#xff0c;它能够用来一键随机生成电子邮件别名&#xff0c;以隐藏真实邮箱地址&#xff0c;使用户免受广告商和垃圾邮件侵害。Private Relay 将作为 Firefox插件提供。它于上个月开始测试&#xff0c;目前仍处于封…

使用VS开发C++ 控制台程序或其他项目出现 ‘ LINK : fatal error LNK1104: 无法打开文件“LIBCD.lib” ’ 常规解决办法

原因&#xff1a;大多是由低版本例如VC6(本人是)开发项目&#xff0c;然后在高版本上再开发该项目导致LIBCD.lib出现更改由此出现错误提示。 本人VS版本2017&#xff1b; 一般解决办法&#xff1a; 第一步&#xff1a;右键项目属性 第二步&#xff1a;在忽略特定库位置加上要忽…

不同步节点在线使用Remix开发以太坊Dapp及solidity学习入门 ( 一 ):智能合约HelloWorld

有问题可以点击–>加群互相学习 本人本来想自己写公链&#xff0c;结果发现任重道远&#xff1b; 遂&#xff0c;开始写Dapp&#xff0c;顺便写的时候搞个教程吧。。。 通过系列教程学习将会&#xff1a; 1.基本使用solidity 语言开发智能合约 2.知道怎么发自己的以太坊的to…

Blazor University (16)渲染树 — 使用 @key 优化

原文链接&#xff1a;https://blazor-university.com/components/render-trees/optimising-using-key/使用 key 优化源代码[1]提示&#xff1a; 对于在运行时循环生成的组件&#xff0c;始终使用 key。前面的示例运行良好&#xff0c;因为 Blazor 能够轻松地将虚拟 DOM 元素与浏…

苹果的热榜:积分墙背后的隐秘世界

电影《楚门的世界》中描述过这样的故事&#xff1a;楚门这个快乐单纯的青年&#xff0c;一直以为自己是平凡小镇上普通的保险推销员。直到有一天他发现这世界上的一切都是为他精心安排的。他会遇到谁、在他身上将要发生什么事件&#xff0c;都是按照剧本被人操纵的。甚至连他的…

solidity编写eth智能合约之contract 创建合约(二)

环境说明&#xff1a; Ide&#xff1a;在线remix Solidity IDE 语言&#xff1a;solidity solidity 版本号&#xff1a;0.4.20 Tip&#xff1a;如果一点都不懂的建议从头开始看 运行结果截图我不赘述&#xff0c;所有合约代码均通过个人检测。请按照标准进行操作&#xff0c;如…

为什么HttpContextAccessor要这么设计?

前言周五在群里面有小伙伴问&#xff0c;ASP.NET Core这个HttpContextAccessor为什么改成了这个样子&#xff1f;在印象中&#xff0c;这已经是第三次遇到有小伙伴问这个问题了&#xff0c;特意来写一篇记录&#xff0c;来回答一下这个问题。聊一聊历史关于HttpContext其实我们…

元素周期表排列的规律_中考化学:金属活动性顺序表和元素周期表规律总结

在我们初三学年的化学学习中&#xff0c;有两大重要规律需要同学们牢牢记住&#xff0c;这也是贯穿我们化学始终的化学规律&#xff0c;那就是金属活动性顺序表和化学元素周期表规律。一、金属活动性顺序表:金属活动性顺序由强至弱: K Ca na Mg Al Zn Fe Sn Pb(H)Cu Hg Ag Pt A…

solidity modifier函数修改器 智能合约开发知识浅学(三)

环境说明&#xff1a; Ide&#xff1a;在线remix Solidity IDE 语言&#xff1a;solidity solidity 版本号&#xff1a;0.4.20 Tip&#xff1a;如果一点都不懂的建议从头开始看 运行结果截图我不赘述&#xff0c;所有合约代码均通过个人检测。请按照标准进行操作&#xff0c;如…

百度网盘超级会员,年卡低至198元!百度官方直充,会员实时生效!

大家都喜欢用百度网盘来存储文件、照片&#xff0c;还用百度网盘分享文档&#xff0c;但没有会员的&#xff0c;容量就太小&#xff0c;传输速度也受限&#xff0c;还是咬牙充个会员吧&#xff01;幻海优品是一家正规的会员充值平台&#xff0c;价格很实惠&#xff01;百度网盘…

C#服务器编程:WebService、Ajax与回调函数(一)

目 录 1、结果展示 2、WebService 3、回调函数 本实例演示借助WebService、Ajax技术和回调函数,从MSSQL数据库中获取所需数据,并用JavaScript语言将数据结果显示到网页地图上。 1、结果展示 2、WebService (1)在工具箱的Ajax Extentions下面找到ScriptManager控件,拖…

SyntaxHighlighter行号显示错误问题解决方案

SyntaxHighlighter是根据代码中的换行符分配行号的。但是&#xff0c;如果一行代码或者注释比较长&#xff0c;在页面显示时需要分成多行显示&#xff0c;会出现行号对不上的问题&#xff0c;像这样&#xff1a; 通过设置CSS强制不换行&#xff0c;可以保证行号显示正常&#x…

mysql 一对多 关联一条最新的数据_不得不会的mysql锁

6. 多表之间的关系如图&#xff0c;实际业务数据库中的表之间都是有关系的&#xff0c;我们接下来主要要学习的就是如何分析表关系及建立表关系。分类表create table category( cid varchar(32) primary key, cname varchar(100) );商品表create table product( pid varchar(3…

C语言实现万年历记事本,简单实用的layui日历标注记事本代码

一款简单实用的layui日历标注记事本代码&#xff0c;响应式自适应电脑、平板跟手机移动端&#xff0c;可以在日历上设置每日事项标注记录&#xff0c;支持撤销、添加、修改标注记录。查看演示下载资源&#xff1a;52次 下载资源下载积分&#xff1a;20积分js代码 layui.use([la…

围棋经典棋谱_秀秀老师:茶艺师也要学好围棋

“引清风&#xff0c;邀明月&#xff0c;去来兮。省多少闲是闲非。临山近水&#xff0c;近些松竹向些梅。书院茶香几多般&#xff0c;诗酒琴棋。无萦无烦恼&#xff0c;无别离。于中国文人雅士而言&#xff0c;茶与棋&#xff0c;皆是清雅之物事。曹臣《舌花录》中&#xff0c;…

基于 Dapr 和 .NET 开发云原生应用(奉上视频+资料)

点击蓝字/关注我序言&#xff1a;今年是.NET20周年&#xff0c;为了传播.NET和营造.NET技术氛围举办了此次云原生开发挑战赛&#xff0c;请来众多业界大咖来给大家分享技术&#xff0c;为大家参赛做预热&#xff0c;参赛的朋友都可获得51Aspx 500积分和微软亲签证书&#xff0c…

【Spring-AOP-学习笔记-3】@Before前向增强处理简单示例

项目结构程序代码HelloImpl.javaWorldImpl.java定义切面类package org.crazyit.app.aspect;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;// 定义一个切面Aspectpublic class AuthAspect{ // 匹配org.crazyit.app.service.impl包下所…

sklearn 线性回归_使用sklearn库做线性回归拟合

背景资料随着海拔高度的上升&#xff0c;温度越来越低&#xff0c;经过气象专家的研究&#xff0c;在一定的海拔高度范围内&#xff0c;高度和温度呈线性关系。现有一组实测资料&#xff0c;我们需要对这些数据进行处理拟合&#xff0c;获得此线性关系。解决思路采用sklearn库中…

VS2022之DebuggerVisualizer

在Debug程序时&#xff0c;面对一些大集合&#xff0c;之前是这样查看的&#xff0c;如下图&#xff0c;这样看起来不直观&#xff0c;集合中的数据只能一个一个实体查看&#xff1a;VS2022预览版带来一个新功能&#xff0c;集合表格可视化&#xff0c;比如下面这样一段代码&am…