机器人技术是一个很久以前就离开科幻小说领域的领域,以推动工业自动化,医疗保健,太空探索和其他应用程序的发展。 软件机器人模拟器不仅简化了机器人工程师的开发工作,而且还为研究人员提供了研究人工智能(AI)算法和机器学习的工具。 这样一种以研究为重点的模拟器是基于Java 3D技术构建的开源Simbad项目(请参阅参考资料 )。 本文展示了如何使用Simbad工具包对虚拟机器人进行编程,以实现包含架构的机器人设计理念。
本文从对机器人技术的简要概述开始,并解释了包含架构的概念。 然后介绍了Simbad工具包,并演示了如何在Simbad中实现包含架构。 然后,您将使用该体系结构编写一个简单的机器人。 最后,您将看到令人着迷的迷宫世界,并对第二台机器人进行编程,该机器人不同于荷马·辛普森(参见参考资料 ),可以从迷宫中摆脱出来。 您的机器人不会是物理的,但会“生活”在Simbad虚拟世界中。
机器人一词没有公认的定义。 出于本文的目的,请考虑将机器人分为三个部分:
在传统的机器人技术(即1986年之前的机器人技术)中,机器人具有中央的“大脑”,该“大脑”构建和维护世界的“地图”,并根据该地图制定计划。 首先,机器人的传感器(例如,触摸传感器,光传感器和超声波传感器)从其环境中获取信息。 机器人的大脑融合了传感器收集的所有信息,并更新了世界地图。 然后,机器人决定要采取的行动。 它通过其执行器和效应器起作用。 执行器基本上是电动机,并连接到执行器,执行器与机器人的环境相互作用。 效应器的例子是轮子和手臂。 ( 执行器一词通常宽松地指执行器或效应器。)
简而言之,传统的机器人从可能的多个传感器获取输入,融合该传感器信息,更新其世界地图,基于其当前的世界视图制定计划,然后采取行动。 但是,此方法有问题。 一方面,它是高度计算密集型的。 另外,由于外部世界总是在变化,因此保持世界地图最新是很困难的。 此外,许多生物,例如昆虫,在没有外部世界地图甚至记忆的情况下依然繁衍生息。 尝试模仿它们会更好吗? 这些问题导致了一种新型的机器人技术,称为基于行为的机器人技术 (BBR)。 BBR可能是当今机器人实验室中的主导哲学。
可以使用包含架构来实现BBR。 包容体系结构的发明者—现在是MIT AI实验室负责人的Rodney A. Brooks —在其1986年的开创性论文“大象不玩象棋”中介绍了它(请参阅参考资料 )。 基于行为的机器人是由一组独立的简单行为组成的 。 行为由触发它们的原因(通常是传感器读数)和所采取的动作(通常涉及效应器)定义。 行为相互叠加。 当两种行为发生冲突时,中央仲裁员将决定哪个优先。 根据BBR支持者的说法,机器人的整体行为是紧急的 ,并且可以大于其各个部分的总和。 出现的较高级别的行为包含较低级别的行为。 无需尝试设计机器人,您只需添加行为并查看出现的情况即可。
Simbad允许您在软件中模拟机器人。 根据该项目的网站,该网站“使程序员能够编写自己的机器人控制器,修改环境并使用可用的传感器。它主要面向希望简单学习基础人工智能,机器学习,在自治机器人和自治代理的情况下,更一般地说是AI算法。”
Simbad由Louis Hugues和Nicolas Bredeche用Java语言编写。 在SourceForge.net上托管的项目是免费的,您可以在GNU通用公共许可证的条件下使用和修改。
Simbad世界可以包含特工(机器人)和无生命的对象(盒子,墙壁,灯光等)。 Simbad世界中的时间分为离散的滴答声。 Simbad计划在特工之间共享时间。 与物理机器人一样,Simbad Agents既具有传感器(距离,触摸,光线等)又具有执行器(通常是轮子)。 在每个滴答声中,机器人都可以行动。
代理会覆盖performBehavior()
方法来确定其行为。 在performBehavior()
,机器人可以记录传感器读数并设置其平移和旋转速度。 performBehavior()
会立即发生,因此它无法发出“前进一米”之类的命令。 为了解决此限制,通常必须跟踪机器人的状态。 您还可以使用计时器变量来跟踪您处于当前状态的时钟滴答声。
对于本文的练习,您将主要关注两个Simbad API程序包:
simbad.sim
:该软件包的类既代表您的机器人,也代表它所生活的世界。它们包括(以及其他):
Agent
: Agent
是机器人。 Arch
:机器人可以在周围或下方行驶的拱门。 Box
:可用作机器人世界中的障碍物。 CameraSensor
:让您从机器人的角度看它的世界。 EnvironmentDescription
:代表您向其中添加机器人和诸如墙壁或盒子之类的对象的“世界”。 LampActuator
:您可以添加到机器人中的灯。 LightSensor
:感应光的强度。 RangeSensorBelt
:包含围绕机器人的一组距离传感器。 RobotFactory
:使用它可以向您的机器人添加传感器。 Wall
:机器人的另一种障碍。 simbad.gui
:该软件包的类显示您的机器人世界,并让您对其进行控制。 它们包括(以及其他):
Simbad
:显示机器人世界,传感器输入和控件的框架。 要开始在Simbad中实现包含架构,您需要定义一个Agent
的子类,称为BehaviorBasedAgent
。 BehaviorBasedAgent
包含的阵列Behavior
S和boolean
矩阵指定哪些Behavior
小号抑制哪些其他Behavior
S:
private Behavior[] behaviors;
private boolean suppresses[][];
BehaviorBasedAgent
充当Behavior
的调度程序。 清单1显示了遍历行为的代码(使用currentBehaviorIndex
类变量来跟踪接下来应该转向哪种行为)并在其中进行仲裁:
protected void performBehavior() {
boolean isActive[] = new boolean[behaviors.length];
for (int i = 0; i < isActive.length; i++) {
isActive[i] = behaviors[i].isActive();
}
boolean ranABehavior = false;
while (!ranABehavior) {
boolean runCurrentBehavior = isActive[currentBehaviorIndex];
if (runCurrentBehavior) {
for (int i = 0; i < suppresses.length; i++) {
if (isActive[i] && suppresses[i][currentBehaviorIndex]) {
runCurrentBehavior = false;
break;
}
}
}
if (runCurrentBehavior) {
if (currentBehaviorIndex < behaviors.length) {
Velocities newVelocities = behaviors[currentBehaviorIndex].act();
this.setTranslationalVelocity(newVelocities
.getTranslationalVelocity());
this
.setRotationalVelocity(newVelocities
.getRotationalVelocity());
}
ranABehavior = true;
}
if (behaviors.length > 0) {
currentBehaviorIndex = (currentBehaviorIndex + 1)
% behaviors.length;
}
}
}
请注意, performBehavior()
会覆盖simbad.sim.Agent.performBehavior()
。
Behavior
有两种abstract
方法:
isActive()
根据给定的传感器当前状态是否应该激活返回一个布尔值。 (所有Behaviors
共享一组传感器。) act()
返回一对速度(平移和旋转(按此顺序)),它们显示了它希望电动机采取的动作。 现在,您将创建一个示例机器人,该机器人具有四个Behavior
(按降序排列),如清单2至5所示。( 下载本文使用的源代码。)
Avoidance
:碰撞后改变方向或避免碰撞。 LightSeeking
光:朝着光移动。 Wandering
:偶尔随机改变方向。 StraightLine
:沿直线移动。 Avoidance
类(基于Simbad SingleAvoiderDemo.java演示代码) public boolean isActive() {
return getSensors().getBumpers().oneHasHit()
|| getSensors().getSonars().oneHasHit();
}
public Velocities act() {
double translationalVelocity = 0.8;
double rotationalVelocity = 0;
RangeSensorBelt sonars = getSensors().getSonars();
double rotationalVelocityFactor = Math.PI / 32;
if (getSensors().getBumpers().oneHasHit()) {
// if ran into something
translationalVelocity = -0.1;
rotationalVelocity = Math.PI / 8
- (rotationalVelocityFactor * Math.random());
} else if (sonars.oneHasHit()) {
// reads the three front quadrants
double left = sonars.getFrontLeftQuadrantMeasurement();
double right = sonars.getFrontRightQuadrantMeasurement();
double front = sonars.getFrontQuadrantMeasurement();
// if obstacle near
if ((front < 0.7) || (left < 0.7) || (right < 0.7)) {
double maxRotationalVelocity = Math.PI / 4;
if (left < right)
rotationalVelocity = -maxRotationalVelocity
- (rotationalVelocityFactor * Math.random());
else
rotationalVelocity = maxRotationalVelocity
- (rotationalVelocityFactor * Math.random());
translationalVelocity = 0;
} else {
rotationalVelocity = 0;
translationalVelocity = 0.6;
}
}
return new Velocities(translationalVelocity, rotationalVelocity);
}
LightSeeking
类(基于Simbad LightSearchDemo.java演示代码) public boolean isActive() {
float llum = getSensors().getLightSensorLeft().getAverageLuminance();
float rlum = getSensors().getLightSensorRight().getAverageLuminance();
double luminance = (llum + rlum) / 2.0;
// Seek light if it's near.
return luminance > LUMINANCE_SEEKING_MIN;
}
public Velocities act() {
// turn towards light
float llum = getSensors().getLightSensorLeft().getAverageLuminance();
float rlum = getSensors().getLightSensorRight().getAverageLuminance();
double translationalVelocity = 0.5 / (llum + rlum);
double rotationalVelocity = (llum - rlum) * Math.PI / 4;
return new Velocities(translationalVelocity, rotationalVelocity);
}
Wandering
类 public boolean isActive() {
return random.nextDouble() < WANDERING_PROBABILITY;
}
public Velocities act() {
return new Velocities(0.8, random.nextDouble() * 2 * Math.PI);
}
StraightLine
类 public boolean isActive() {
return true;
}
public Velocities act() {
return new Velocities(0.8, 0.0);
}
清单6指定了哪些行为抑制了其他行为:
private void initBehaviorBasedAgent(BehaviorBasedAgent behaviorBasedAgent) {
Sensors sensors = behaviorBasedAgent.getSensors();
Behavior[] behaviors = { new Avoidance(sensors),
new LightSeeking(sensors), new Wandering(sensors),
new StraightLine(sensors), };
boolean subsumes[][] = { { false, true, true, true },
{ false, false, true, true }, { false, false, false, true },
{ false, false, false, false } };
behaviorBasedAgent.initBehaviors(behaviors, subsumes);
}
请注意,尽管此示例中的Behavior
具有总优先级(抑制),但是不必那样。
作为练习,您可能想尝试以下方法:
“最后!我知道我们可以使用Tremaux的算法解决这个迷宫!” 莉萨·辛普森(Lisa Simpson)
在现有的几种迷宫求解算法中,有两种常用的算法会消耗恒定数量的内存,而与迷宫的大小无关。 它们是墙跟随和Pledge算法 (由英国埃克塞特的Jon Pledge于12岁时发明)。 Tremaux的算法 (Lisa Simpson的首选算法)是一种出色的算法,但为简单起见,本文重点介绍了墙跟踪和Pledge算法。
跟随墙是您可能在孩提时就已经学过的简单迷宫算法。 使用此算法解决迷宫的所有工作是将左手放在左墙上(或右手放在右墙上),然后一直跟随直到退出迷宫。 很容易看出,如果您所在的迷宫在其边界上具有入口和出口,则该算法始终有效。 但是,如果目标是在岛内 (迷宫的一部分与迷宫的其余部分断开连接),则此算法找不到解决方案,因为它无法“跳到”岛上。
质押算法比墙跟踪更复杂,并且解决了更大的迷宫问题,因为它可以在孤岛之间跳转。 质押算法的基本思想是选择一个绝对方向(例如北,南,东或西),并始终尝试朝该方向前进。 我将这个方向称为您偏爱的方向 。 碰到墙时,您会向右转,然后沿墙左转,直到您面对自己喜欢的方向, 并且转弯的总和为零(其中顺时针转为负,逆时针转为正)。 届时,您将继续朝着自己喜欢的方向继续前进,依此类推。 必须将转弯总和为零,以防止您陷入某些循环中,例如形状像大写字母G的循环(尝试在纸上看一下我的意思)。
现在是时候让您的朋友惊奇(原谅双关语),并建造一个名为Algernon的迷宫解决机器人。
要实施跟踪墙或Pledge算法,您需要知道机器人何时到达交叉点以及何时到达交叉点-通道朝哪个方向前进。
完成此操作的方法可能不止一种,但是您可以通过在机器人的左侧安装声纳传感器来实现。 当迷宫的通道向左倾斜时,此传感器会提醒您。 要知道机器人何时向下移动通道(即当它“撞入”墙壁时),您需要在机器人的正面添加一个触摸传感器。
您可以使用algernon.subsumption
包对Algernon进行algernon.subsumption
( 下载源代码)。 就机器人而言,Algernon非常简单,您可以以直接的“过程”方式对他进行编程。 但是,即使在像这样简单的机器人中,使用包含编程也可以使代码更简洁,模块化,易于理解。
为了简化算法的实现,我将假定墙以直角布置。 因此,机器人的所有转弯都是向左旋转90度或向右旋转90度。
如果考虑(左侧)跟踪墙算法,您会发现可以将其分解为四种行为:
您需要确定四种行为的优先级。 前面的列表按优先级递减的顺序是正确的。 您将获得四个类,每个类都扩展Behavior
:
GoStraight
TurnRight
TurnLeft
ReachGoal
清单7是GoStraight
代码。 TRANSLATIONAL_VELOCITY
是设置为0.4
的常量:
public boolean isActive() {
return true;
}
public Velocities act() {
double rotationalVelocity = 0.0;
return new Velocities(TRANSLATIONAL_VELOCITY, rotationalVelocity);
}
清单8是TurnRight
代码。 getRotationCount()
返回以您使用的旋转速度旋转90度所需的时钟的滴答数。
public boolean isActive() {
if (turningRightCount > 0) {
return true;
}
RangeSensorBelt bumpers = getSensors().getBumpers();
// Check the front bumper.
if (bumpers.hasHit(0)) {
backingUpCount = 10;
turningRightCount = getRotationCount();
return true;
} else {
return false;
}
}
public Velocities act() {
if (backingUpCount > 0) {
// We back up a bit (we just ran into a wall) before turning right.
backingUpCount--;
return new Velocities(-TRANSLATIONAL_VELOCITY, 0.0);
} else {
turningRightCount--;
return new Velocities(0.0, -Math.PI / 2);
}
}
当Algernon左转时,他先往前走一点,这样他的后部就离开了他左端的墙壁。 然后他向左旋转。 最后,他需要进一步前进,以便再次在他的左边留下一堵墙。 清单9是TurnLeft
代码:
public boolean isActive() {
if (postGoingForwardCount > 0) {
return true;
}
RangeSensorBelt sonars = getSensors().getSonars();
// Check the sonar on the left.
if (sonars.getMeasurement(1) > 1.0) {
// There is a passageway to the left.
preGoingForwardCount = 20;
postGoingForwardCount = 40;
turnLeftCount = getRotationCount();
return true;
} else {
return false;
}
}
public Velocities act() {
if (preGoingForwardCount > 0) {
preGoingForwardCount--;
return new Velocities(TRANSLATIONAL_VELOCITY, 0.0);
} else if (turnLeftCount > 0) {
turnLeftCount--;
return new Velocities(0.0, Math.PI / 2);
} else {
postGoingForwardCount--;
return new Velocities(TRANSLATIONAL_VELOCITY, 0.0);
}
}
清单10是ReachGoal
代码:
public boolean isActive() {
RangeSensorBelt sonars = getSensors().getSonars();
// Is there open space all around us? That is, are we out of the maze?
double clearDistance = 1.2;
return sonars.getMeasurement(0) > clearDistance
&& sonars.getMeasurement(1) > clearDistance
&& sonars.getMeasurement(3) > clearDistance
&& sonars.getMeasurement(2) > clearDistance;
}
public Velocities act() {
// Stop
return new Velocities(0.0, 0.0);
}
清单11是Algernon的主要行为代码:
private void initBehaviorBasedAgent(
algernon.subsumption.BehaviorBasedAgent behaviorBasedAgent) {
algernon.subsumption.Sensors sensors = behaviorBasedAgent.getSensors();
algernon.subsumption.Behavior[] behaviors = { new ReachGoal(sensors),
new TurnLeft(sensors), new TurnRight(sensors),
new GoStraightAlways(sensors) };
boolean subsumes[][] = { { false, true, true, true },
{ false, false, true, true }, { false, false, false, true },
{ false, false, false, false } };
behaviorBasedAgent.initBehaviors(behaviors, subsumes);
}
图1显示了Algernon在迷宫中导航:
请注意,即使其组成部分对迷宫(甚至墙壁)一无所知,您的机器人也可以解决迷宫问题。 阿尔杰农(Algernon)没有中央大脑,无法计算出迷宫的出路。 这就是从包容体系结构中得到的结果:一组简单的分层行为中的复杂的,看似有目的的行为。
在本文中,您对模拟机器人进行了编程。 对真正的物理机器人进行编程要困难得多。 物理世界以各种混乱的方式侵入。 例如,让跟随墙壁的机器人平行于左边的墙壁移动非常容易。 在表面不完美的现实世界中,让您的机器人不要转向左侧的墙或转向距离墙太远的墙本身就是一个挑战。 如果您喜欢编程,那并不意味着您一定会喜欢制造机器人。 制作有趣的机器人可能需要比编程技能更多的机械技能。
如果您对自己的机器人的构建和编程感兴趣,LEGO Mindstorms是一个出色的机器人套件。 替代Mindstorms的一种低成本替代方案是构建生物电子美学力学(BEAM)机器人。 BEAM将基于行为的机器人技术的想法再进一步一步,并且完全消除了编程。 整体行为来自硬连线的模拟反射响应行为。 对于$ 30或更少,你可以用BEAM套件建立你的第一个机器人,或通过计划,你可以在书籍,如加雷思·布兰温的绝对新手指南制作机器人找到(见相关主题 )。 或者,您可以购买Roomba并对其进行破解。
在对机器人进行编程并仅在短时间内查看其他人的机器人代码后,我注意到的一件令人惊讶的事情是,您只需花很少的代码就可以使机器人做很多事情。 (但是,可能需要大量修改和尝试使用常量才能正确地获取少量代码。)使用LEGO Mindstorms套件,您可以在一个下午的指令中构建您的第一个简单机器人。
您可以探索整个蓬勃发展的业余机器人亚文化:机器人书籍,机器人竞赛,机器人视频,以及您所在地区的机器人俱乐部。
翻译自: https://www.ibm.com/developerworks/java/library/j-robots/index.html