在Webots机器人仿真平台上,使用NAO机器人在自建的场馆中完成寻路以及避障等功能。
Nao类变量
ZERO_ANGLE
:零度角设定IS_GO
:是否前进标志位(默认为前进)Flag
:go函数启动标志位(防止多个go函数同时启动造成冲突 )ARRIVED
:到达检测标志位ISDETECING
:是否进行转向检测标志位Destinations
:目标区域预设,每一行代表一个目标区域,前两个为x轴线,后两个为z轴参数 PHALANX_MAX = 8
ZERO_ANGLE = 1.5828
IS_GO = True
Flag = True
ARRIVED = False
ISDETECTING = False
Destinations = [
[[-2.6, -2.00], [3.4, 2.62]],
[[-2.6, -2.00], [0.4, -0.4]],
[[-2.6, -2.00], [-2.4, -3.2]],
[[-1.8, -1.4], [-3.2, -3.47]],
[[1.4, 1.8], [-3.2, -3.47]],
[[2.1, 2.4], [-2.4, -3.2]],
[[2.1, 2.4], [0.4, -0.4]],
[[2.1, 2.4], [3.4, 2.62]]
]
loadMotionsFiles
handWave
:挥手forwards
:前进backwards
:后退sideStepLeft
:向左横向移动sideStepRight
:向右横向移动turnLeft60
:左转60度turnRight60
:右转60度def loadMotionFiles(self):
self.handWave = Motion('../../motions/HandWave.motion')
self.forwards = Motion('../../motions/Forwards50.motion')
self.backwards = Motion('../../motions/Backwards.motion')
self.sideStepLeft = Motion('../../motions/SideStepLeft.motion')
self.sideStepRight = Motion('../../motions/SideStepRight.motion')
self.turnLeft60 = Motion('../../motions/TurnLeft60.motion')
self.turnRight60 = Motion('../../motions/TurnRight60.motion')
startMotion
def startMotion(self, motion):
if self.currentlyPlaying:
self.currentlyPlaying.stop()
motion.play()
self.currentlyPlaying = motion
# 前进动作休眠1.5秒
if motion == self.forwards:
time.sleep(1.5)
# 横向移动休眠2秒
if motion == self.sideStepLeft or \
motion == self.sideStepRight:
time.sleep(2)
self.IS_GO = True
self.currentlyPlaying = None
findAndEnableDevices
getCenter
def getCenter(self, destID):
# 计算x坐标
x = (self.Destinations[destID - 1][0][0] + \
self.Destinations[destID - 1][0][1]) / 2
# 计算y坐标(z坐标)
y = (self.Destinations[destID - 1][1][0] + \
self.Destinations[destID - 1][1][1]) / 2
center = [x, y]
return center
go
def go(self, destID):
print("Go to painting {}".format(destID))
self.IS_GO = True
self.Flag = True
self.ARRIVED = False
self.ISDETECTING = False
# 新建到达检测
_thread.start_new_thread(self.isDest, (destID, ))
# 新建转向
_thread.start_new_thread(self.turnAround, (destID, ))
# 新建前进
_thread.start_new_thread(self.goDest, (destID, ))
def isDest(self, destID):
while True:
# 通过gps获取当前位置
pos = self.gps.getValues()
# 判断是否到达目标区域内
if (pos[0] > self.Destinations[destID - 1][0][0]) and \
(pos[0] < self.Destinations[destID - 1][0][1]) and \
(pos[2] < self.Destinations[destID - 1][1][0]) and \
(pos[2] > self.Destinations[destID - 1][1][1]):
# 停止当前动作
if self.currentlyPlaying:
self.currentlyPlaying.stop()
self.currentlyPlaying = None
# 标志到达
self.ARRIVED = True
# 允许下一个go函数运行
self.Flag = True
# 打印到达信息
print("Arrive painting {}".format(destID))
# 挥手示意
self.handWave.setLoop(True)
self.handWave.play()
self.handWave.setLoop(False)
return
def avoid(self):
# 检测到左侧有障碍物
while self.us[0].getValue() < 0.5:
self.startMotion(self.sideStepRight)
# 检测到右侧有障碍物
while self.us[1].getValue() < 0.5:
self.startMotion(self.sideStepLeft)
getAngle
alpha
:当前朝向与目标点夹角beta
:当前目标点与0度角夹角Angle
:旋转所需角度def getAngle(self, destCenter, Pos, RollPitchYaw):
x = destCenter[1] - Pos[2]
y = destCenter[0] - Pos[0]
sin = y / math.sqrt(x ** 2 + y ** 2)
alpha = math.asin(sin)
# 根据x轴坐标修正alpha
if alpha < 0:
if x > 0:
alpha = - alpha - 3.14
else:
if x > 0:
alpha = 3.14 - alpha
# 获取beta角
beta = RollPitchYaw[2] - self.ZERO_ANGLE
Angle = alpha + beta
# Angle的绝对值要小于3.14,即转角小于180°
if Angle < -3.14:
Angle += 6.28
if Angle > 3.14:
Angle -= 6.28
return Angle
detectAngle
def detectAngle(self, before, angle):
while True:
now = self.inertialUnit.getRollPitchYaw()
# 修正角度
temp = abs(before[2] - now[2] - angle)
if temp > 6:
temp -= 6
if temp < 0.4:
# 停止当前动作
if self.currentlyPlaying:
self.currentlyPlaying.stop()
self.currentlyPlaying = None
# 允许前进
self.IS_GO = True
# 允许下一个检测线程开启
self.ISDETECTING = False
return
turnAround
def turnAround(self, destID):
while not self.ARRIVED:
Pos = self.gps.getValues()
before = self.inertialUnit.getRollPitchYaw()
destCenter = self.getCenter(destID)
angle = self.getAngle(destCenter, Pos, before)
if abs(angle) >= 0.4:
# 停止前进
self.IS_GO = False
# 检测是否已开启转向检测进程
if not self.ISDETECTING:
self.ISDETECTING = True
_thread.start_new_thread(
self.detectAngle,
(before, angle, )
)
# 当偏差角度小于0时左转
if angle < 0:
if not self.currentlyPlaying:
self.startMotion(self.turnLeft60)
# 当偏差角度大于0时右转
else:
if not self.currentlyPlaying:
self.startMotion(self.turnRight60)
def goDest(self, destID):
while not self.ARRIVED:
if self.IS_GO:
# 等待当前动作停止
if not self.currentlyPlaying:
# 避障
self.avoid()
# 前进
self.startMotion(self.forwards)
__init__
def __init__(self):
Robot.__init__(self)
self.currentlyPlaying = False
self.findAndEnableDevices()
self.loadMotionFiles()
run
def run(self):
print("Ready!")
key = -1
while robot.step(self.timeStep) != -1:
key = self.keyboard.getKey()
if key > 0:
break
# 实时检测键盘输入
while True:
key = self.keyboard.getKey()
# 当输入目标地点1-8时执行go,并置标志位为False防止多次执行
if key > 48 and key < 57:
destID = key - 48
if self.Flag == True:
self.go(destID)
self.Flag = False
if robot.step(self.timeStep) == -1:
break
Nao机器人可以连续前往多个目标点,在途中可以进行简单的避障(详细效果见附件视频)。
本次实验所设计的Nao机器人最大的优点在于实时检测,只需规定0度角以及目标区域即可完成寻路,并且途中可以规避障碍物,不受场馆限制,可移植性强。同时,也存在着一定缺点,首先则是无法中途修改目标地点,其次则是对于正前方的障碍物尚不能做出很好的应对策略。
实验最难的部分就是开始,刚开始的时候对Webots平台十分陌生,让机器人动起来都十分困难,后来通过修改nao_demp.py成功地让机器人动了起来,但思维又局限于怎样修改motion来转需要角度。经过一段时间的思考,思维跳脱了出来,可以在转到需要角度时停下,而不是通过修改motion这种治标不治本的办法。
由于对Webots平台的陌生,导致线程爆炸,计算资源过度浪费,程序极度卡顿,经过反复地调试终于控制住了线程的数量。在避障方面,很遗憾没能写出更加优秀的避障算法,只完成了基本的避障功能,有机会的话会继续加以改进。
本次实验给我最大的感悟就在于多角度思考问题,修改不了motion就在满足时停下,同时适当的误差可以接受不必强求,没有办法做到绝对的精准。
感谢老师给了我了解Webots平台的机会,希望以后能有更多有趣的实验。