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,一经查实,立即删除!

相关文章

微软 改变 开源【几个站点】

codeplex切换到目前的主流开源中心https://github.com/microsoft 几个核心技术的站点https://github.com/Microsoft/dotnethttps://github.com/aspnethttps://github.com/microsoft/vscode 基础站点https://opensource.microsoft.com/https://open.microsoft.com/ 本地化翻译ht…

剑指offer之中判断二叉树是不是对称二叉树(递归和非递归实现)

1 问题 判断二叉树是不是对称&#xff08;递归和非递归实现&#xff09; 如下二叉树&#xff0c;就是对称的二叉树 23 3 1 4 4 1 如下二叉树&#xff0c;就是非对称的二叉树 23 3 1 4 4 2 2 代码实现 #include <iostream> #in…

如何使用 C# 生成随机并唯一的数字?

咨询区 Christian Peut我的项目中需要生成若干个并且唯一的随机数&#xff0c;我用的是 System.Random&#xff0c;种子给的是 DateTime.Now.Ticks &#xff0c;参考如下代码&#xff1a;private void NewNumber() {Random a new Random(DateTime.Now.Ticks.GetHashCode());My…

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;在忽略特定库位置加上要忽…

linux算法设计,嵌入式Linux平台下随机序列算法设计.doc

嵌入式Linux平台下随机序列算法设计嵌入式Linux平台下随机序列算法设计【摘 要】本文以多媒体播放器的随机不重复播放机能为切入点&#xff0c;针对嵌入式平台实时性要求高&#xff0c;处理速度不够快&#xff0c;但系统存储歌曲量大的特点&#xff0c;进行随机序列产生算法的设…

解决windows系统80端口被占用问题

在windows下部署web应用(80端口)&#xff0c;启动时提示bind 80端口失败 检查端口占用&#xff1a; netstat -ano | findstr 0.0.0.0:80 发现System进程 (pid4) 占用了端口 然而本机也没有安装iis&#xff0c;于是排除IIS服务的问题。解决方式 管理员身份运行cmdnet stop http …

[MySQL] 分组排序取前N条记录以及生成自动数字序列,类似group by后 limit

前言&#xff1a;同事的业务场景是&#xff0c;按照cid、author分组&#xff0c;再按照id倒叙&#xff0c;取出前2条记录出来。oracle里面可以通过row_number() OVER (PARTITION BY cid,author ORDER BY id DESC) 表示根据cid,author分组&#xff0c;在分组内部根据id排序&…

picturebox 图片自适应

picturebox控件共有两种载入图片方式&#xff0c;分别为&#xff1a; pictureBox1.BackgroundImage Image&#xff0c;pictureBox1.load(url) 为使加载的图片自使用控件尺寸&#xff0c;可以分别对pictureBox控件设置BackGroundImageLayoutStretch&#xff0c;SizeModeStretch…

编译C程序提示之'for' loop initial declaration used outside C99 mode

1 问题 再gcc编译一个c程序的时候&#xff0c;错误提示如下 for loop initial declaration used outside C99 mode 2 原因 c99是允许在for循环中声明变量的&#xff0c;但是如果使用的标准为c99之下的话&#xff0c;则不允许这么做&#xff0c;这里我是在for循环里面定义了变…

不同步节点在线使用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 元素与浏…

Python趣味编程---Python也会讲笑话

笑话从哪里来?自己写肯定是不现实的。在这个“云”的时代,各种云都有,自然是不缺开放API的(大部分都是免费的)。随意一搜,果然被我找到一个接口:易源_笑话大全http://apistore.baidu.com/apiworks/servicedetail/864.html。下面写一个用Python写的例子,其实不止python语…

python闭环最短路径_深度学习经典算法 | 蚁群算法解析

蚁群算法基本思想蚁群算法的基本原理来源于自然界中蚂蚁觅食的最短路径问题。根据昆虫学家的观察&#xff0c;发现自然界的蚂蚁虽然视觉不发达&#xff0c;但它可以在没有任何提示的情况下找到从食物源到巢穴的最短路径&#xff0c;并且能在环境发生变化(如原有路径上有了障碍物…

c语言整形除法是五舍六入吗,四舍六入五成双 - C/C++论坛 - 51CTO技术论坛_中国领先的IT技术社区...

复制内容到剪贴板代码:#includeusing namespace std;#define FLENGTH 1000; //宏定义保留3位小数double fun(double ldBuf); //四舍六入五成双函数int main(void){double ldTemp9.824963;double ldRet;//大于5ldTemp9.824963;ldRet fun(ldTemp);printf("%.…

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

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

剑指offer之字符串的全排列

1 问题 求字符串的全排列&#xff0c;比如字符串abc&#xff0c;它的全排列如下 abc, acb, bac, bca, cad, cba 2 思路 我们先固定第一个字符&#xff0c;这里的第一个字符肯定是这个字符串里面字符串的全子集&#xff08;不包含重复&#xff09;&#xff0c;abc字符串&…

OC基础学习

1.OC简介 ➢ C语言的基础上&#xff0c;增加了一层最小的面向对象语法 ➢ 完全兼容C语言 ➢ 可以在OC代码中混入C语言代码&#xff0c;甚至是C代码 ➢ 可以使用OC开发Mac OS X平台和iOS平台的应用程序 2.OC关键字 ➢ 基本上所有关键字都是以开头 ➢ 下面列举一些常见的关键字&a…

关中断是否禁止任务调度?关中断能作为互斥吗?

本文引自链接http://www.52rd.com/Blog/Detail_RD.Blog_imjacob_16830.html?WebShieldDRSessionVerifyCOR6tnpfsOXExxEAO6Z6 今天再看《嵌入式软件系统教程》&#xff08;&#xff08;美&#xff09;西蒙 著&#xff0c;陈向群 等译&#xff09; &#xff0c;里面讲到关中断会…