个性化阅读
专注于IT技术分析

机器人编程入门教程

本文概述

编者注:2018年10月16日, 本文经过全面修改以使用最新技术。

面对现实, 机器人很棒。他们也有一天会统治世界, 希望那时他们会怜惜他们那可怜的, 柔软多肉的创作者(又名机器人开发者), 并帮助我们建立一个充满很多东西的乌托邦空间。我当然是在开玩笑, 但这只是一种。

为了对这个问题产生很小的影响, 我去年参加了自主机器人控制理论课程, 最终以我构建的基于Python的机器人模拟器为基础, 使我可以在简单, 移动, 可编程的机器人上实践控制理论。 。

在本文中, 我将展示如何使用Python机器人框架来开发控制软件, 描述我为模拟机器人开发的控制方案, 说明其如何与环境互动并实现其目标, 并讨论其中的一些内容。我在此过程中遇到的机器人编程的基本挑战。

为了按照本入门教程学习机器人技术, 你应该对两件事有基本的了解:

  • 数学—我们将使用一些三角函数和向量
  • Python(由于Python是较为流行的基本机器人编程语言之一), 我们将利用基本的Python库和函数

此处显示的代码片段只是整个模拟器的一部分, 该模拟器依赖于类和接口, 因此, 要直接阅读代码, 你可能需要一些Python和面向对象编程的经验。

最后, 可以帮助你更好地遵循本教程的可选主题是了解什么是状态机以及范围传感器和编码器如何工作。

可编程机器人的挑战:感知与现实以及控制的脆弱性

所有机器人技术的基本挑战是:永远不可能知道环境的真实状态。机器人控制软件只能根据其传感器返回的测量值来猜测现实世界的状态。它只能尝试通过生成控制信号来更改现实世界的状态。

此图演示了练习Python机器人编程时物理机器人和计算机控件之间的交互。

机器人控制软件只能根据其传感器返回的测量值来猜测现实世界的状态。

因此, 控制设计的第一步就是提出对现实世界的抽象, 即模型, 用它来解释我们的传感器读数并做出决定。只要现实世界按照模型的假设行事, 我们就可以做出很好的猜测并发挥控制作用。但是, 一旦现实世界背离了这些假设, 我们将不再能够做出正确的猜测, 并且控制也将丢失。通常, 一旦失去控制权, 就永远无法重获控制权。 (除非有一些仁慈的外来力量将其还原。)

这是机器人编程如此困难的主要原因之一。我们经常看到实验室中最新研究机器人的视频, 这些视频表现出了出色的敏捷性, 导航性或团队合作精神, 我们很想问:”为什么在现实世界中不使用这种技术?”好吧, 下次你看到这样的视频时, 请看一看如何高度控制实验室环境。在大多数情况下, 只要环境条件保持在其内部模型的狭窄范围内, 这些机器人就只能执行这些令人印象深刻的任务。因此, 机器人技术发展的一个关键是开发更加复杂, 灵活和健壮的模型, 而这种发展受可用计算资源的限制。

机器人技术发展的关键之一是开发更加复杂, 灵活和健壮的模型。

[旁注:哲学家和心理学家都将注意到, 活物还受其对自己的感觉所传达的内在感知的依赖。机器人技术的许多进步来自观察活物并观察它们对意外刺激的反应。想一想。你的世界内部模型是什么?它与蚂蚁和鱼不同吗? (希望如此。)但是, 就像蚂蚁和鱼一样, 它有可能过分简化了世界的某些现实。当你对世界的假设不正确时, 可能会使你失去对事物的控制权。有时我们称此为”危险”。我们的小机器人为在未知的宇宙中生存而奋斗的方式也一样, 我们所有人也是如此。对于机器人专家来说, 这是一个有力的见解。]

可编程机器人模拟器

我构建的模拟器是用Python编写的, 被巧妙地称为Sobot Rimulator。你可以在GitHub上找到v1.0.0。它虽然没有太多的花招, 但是却可以很好地完成一件事:为移动机器人提供准确的模拟, 并为有抱负的机器人专家提供一个简单的框架, 以练习机器人软件编程。尽管拥有一个真正的机器人总是更好, 但是一个好的Python机器人模拟器更易于使用, 并且是一个很好的起点。

在现实世界中的机器人中, 需要生成控制信号的软件(“控制器”)以很高的速度运行并进行复杂的计算。这会影响最佳使用哪种机器人编程语言的选择:通常, C ++用于此类情况, 但是在更简单的机器人应用程序中, Python是执行速度与易于开发和测试之间的很好折衷方案。

我写的软件模拟了一个称为Khepera的现实生活中的研究机器人, 但它可以适应各种尺寸和传感器不同的移动机器人。由于我试图对模拟器进行编程, 使其与真实机器人的功能尽可能相似, 因此可以将控制逻辑以最小的重构量加载到真实的Khepera机器人中, 并且其性能与模拟机器人相同。实施的特定功能涉及Khepera III, 但可以轻松地适应新的Khepera IV。

换句话说, 对模拟机器人进行编程类似于对真实机器人进行编程。如果要将模拟器用于开发和评估不同的控制软件方法, 则这至关重要。

在本教程中, 我将描述Sobot Rimulator v1.0.0随附的机器人控制软件体系结构, 并提供Python源代码段(为清晰起见, 进行了一些修改)。但是, 我鼓励你深入了解源代码并进行修改。该模拟器已经过分叉, 可用于控制不同的移动机器人, 包括iRobot的Roomba2。同样, 请随时分叉该项目并进行改进。

机器人的控制逻辑仅限于以下Python类/文件:

  • models / supervisor.py-此类负责围绕机器人的模拟世界与机器人本身之间的交互。它改进了我们的机器人状态机, 并触发了控制器以计算所需的行为。
  • models / supervisor_state_machine.py-此类表示机器人可以处于的不同状态, 具体取决于其对传感器的解释。
  • models / controllers目录中的文件-这些类在已知的环境状态下实现了机器人的不同行为。特别地, 根据状态机选择特定的控制器。

目标

机器人像人一样, 需要生活中的目标。我们控制该机器人的软件的目标将非常简单:它将尝试使其达到预定的目标点。从无人驾驶汽车到机器人吸尘器, 这通常是任何移动机器人都应具备的基本功能。在激活机器人之前, 已将目标坐标编程到控制软件中, 但可以通过监视机器人运动的其他Python应用程序生成目标坐标。例如, 考虑通过多个航路点行驶。

然而, 使事情复杂化的是, 机器人的环境可能布满障碍物。机器人在通往目标的途中不得与障碍物碰撞。因此, 如果机器人遇到障碍物, 它将不得不找到自己的出路, 以便继续前进到目标。

可编程机器人

每个机器人都具有不同的功能和控制方面的考虑。让我们熟悉我们的模拟可编程机器人。

首先要注意的是, 在本指南中, 我们的机器人将是自动移动机器人。这意味着它可以在太空中自由移动, 并且可以在自己的控制下移动。例如, 这与远程控制机器人(非自治)或工厂机械手(非移动)相反。我们的机器人必须自己弄清楚如何实现目标并在环境中生存。对于新手机器人程序员来说, 这被证明是一个令人惊讶的困难挑战。

控制输入​​:传感器

机器人可以通过多种不同的方式来监视其环境。这些可以包括接近传感器, 光传感器, 保险杠, 照相机等中的任何东西。此外, 机器人可能会与外部传感器通信, 从而为外部机器人提供自身无法直接观察的信息。

我们的参考机器人配备了9个红外传感器(较新的模型具有8个红外传感器和5个超声波接近传感器), 每个方向都以”裙边”形式布置。面对机器人正面的传感器要多于面对机器人背面的传感器, 因为通常知道机器人正面和背面的传感器对于机器人来说更为重要。

除了接近传感器之外, 机器人还具有一对车轮跟踪器, 可跟踪车轮的运动。这些使你可以跟踪每个轮子旋转了多少圈, 一个轮子向前转动一整圈为2, 765个滴答声。向相反的方向倒转计数, 减少而不是增加刻度数。你不必担心本教程中的特定数字, 因为我们将编写的软件使用以米为单位的行进距离。稍后, 我将向你展示如何使用简单的Python函数从滴答中计算出来。

控制输出:移动性

一些机器人在腿上四处走动。有些像球一样滚动。有些甚至像蛇一样滑行。

我们的机器人是差动驱动机器人, 这意味着它可以在两个轮子上滚动。当两个轮子以相同的速度旋转时, 机器人将直线移动。当轮子以不同的速度运动时, 机器人转动。因此, 控制该机器人的运动归结为适当地控制这两个轮子的每一个转动的速率。

API

在Sobot Rimulator中, 机器人”计算机”与(模拟的)物理世界之间的分隔由文件robot_supervisor_interface.py体现, 该文件定义了用于与”真实的机器人”传感器和电机进行交互的整个API:

  • read_proximity_sensors()以传感器的本机格式返回一个包含9个值的数组
  • read_wheel_encoders()返回两个值组成的数组, 该值表示自开始以来的总滴答声
  • set_wheel_drive_rates(v_l, v_r)取两个值(以弧度/秒为单位), 并将车轮的左右速度设置为这两个值

此接口在内部使用机器人对象, 该对象提供来自传感器的数据以及移动电动机或车轮的可能性。如果要创建其他机器人, 则只需提供可被同一接口使用的其他Python机器人类, 其余代码(控制器, 主管和模拟器)即可使用!

模拟器

就像你在现实世界中使用真实的机器人而无需过多关注所涉及的物理定律一样, 你可以忽略机器人的仿真方式, 而直接跳过控制器软件的编程方式, 因为它几乎是一样的在现实世界和模拟之间。但是, 如果你感到好奇, 我将在这里简单介绍一下。

文件world.py是一个Python类, 代表模拟的世界, 内部有机器人和障碍物。此类中的step函数通过以下步骤来完善我们的简单世界:

  • 将物理规则应用于机器人的运动
  • 考虑与障碍物的碰撞
  • 为机器人传感器提供新的价值

最后, 它调用负责执行机器人大脑软件的机器人主管。

step函数在循环中执行, 以便robot.step_motion()使用主管在上一个模拟步骤中计算出的车轮速度移动机器人。

# step the simulation through one time interval
def step( self ):
dt = self.dt
# step all the robots
for robot in self.robots:
# step robot motion
robot.step_motion( dt )

# apply physics interactions
self.physics.apply_physics()

# NOTE: The supervisors must run last to ensure they are observing the "current" world
# step all of the supervisors
for supervisor in self.supervisors:
supervisor.step( dt )

# increment world time
self.world_time += dt

apply_physics()函数在内部更新机器人接近传感器的值, 以便主管可以在当前模拟步骤中估计环境。相同的概念适用于编码器。

一个简单的模型

首先, 我们的机器人将具有非常简单的模型。它将对世界做出许多假设。一些重要的方面包括:

  • 地形总是平坦甚至平坦
  • 障碍永无止境
  • 车轮永不打滑
  • 什么也不会推动机器人
  • 传感器永远不会失败或提供错误的读数
  • 告诉他们车轮总是转动

尽管这些假设中的大多数在类似房屋的环境中都是合理的, 但仍可能存在圆形障碍。我们的避障软件具有简单的实现方式, 并遵循障碍的边界以绕过障碍。我们将通过额外的检查来避免圆形障碍, 向读者提示如何改进机器人的控制框架。

控制回路

现在, 我们将进入控制软件的核心, 并说明我们要在机器人内部进行编程的行为。可以将其他行为添加到此框架中, 阅读完后, 你应该尝试自己的想法!基于行为的机器人技术是20多年前提出的, 它仍然是移动机器人的强大工具。例如, 在2007年的DARPA城市挑战赛中, 使用了一系列行为, 这是首届自动驾驶汽车竞赛!

机器人是动态系统。机器人的状态, 传感器的读数及其控制信号的影响是恒定的。控制事件的播放方式涉及以下三个步骤:

  1. 施加控制信号。
  2. 测量结果。
  3. 生成计算出的新控制信号, 使我们更接近目标。

重复这些步骤, 直到我们实现目标。我们每秒执行此操作的次数越多, 我们对系统的控制就越精细。 Sobot Rimulator机器人每秒重复20次(20 Hz)20次, 但是许多机器人必须每秒执行数千或数百万次才能获得足够的控制。请记住我们之前关于不同机器人系统语言和速度要求的不同机器人编程语言的介绍。

通常, 我们的机器人每次使用其传感器进行测量时, 都会使用这些测量值来更新其对世界状态的内部估算, 例如距目标的距离。它将状态与期望状态的参考值进行比较(对于距离, 它希望其为零), 并计算期望状态与实际状态之间的误差。一旦知道了这些信息, 就可以减少产生新的控制信号的问题, 从而最大限度地减少了误差, 最终将使机器人移向目标。

一个聪明的把戏:简化模型

为了控制我们要编程的机器人, 我们必须向左轮发送一个信号, 告知其旋转速度, 向右轮发送一个单独的信号, 告知其旋转速度。我们称这些信号为vL和vR。但是, 不断地根据vL和vR进行思考非常麻烦。而不是问”我们希望左轮转动多快, 我们希望右轮转动多快?”人们自然会问:”我们希望机器人前进多快?我们希望它转动多快或改变方向?”我们将这些参数称为速度v和角(旋转)速度ω(读作”Ω”)。事实证明, 我们可以基于v和ω而不是vL和vR来建立整个模型, 只有确定了我们希望编程的机器人如何移动并将这两个值数学地转换为vL和vR后, 我们才需要实际控制机器人轮子。这被称为单轮控制模型。

在机器人编程中,了解单轮驱动模型和差动驱动模型之间的区别很重要。

这是在supervisor.py中实现最终转换的Python代码。请注意, 如果ω为0, 则两个车轮将以相同的速度旋转:

# generate and send the correct commands to the robot
def _send_robot_commands( self ):
  # ...
  v_l, v_r = self._uni_to_diff( v, omega )
  self.robot.set_wheel_drive_rates( v_l, v_r )

def _uni_to_diff( self, v, omega ):
  # v = translational velocity (m/s)
  # omega = angular velocity (rad/s)

  R = self.robot_wheel_radius
  L = self.robot_wheel_base_length

  v_l = ( (2.0 * v) - (omega*L) ) / (2.0 * R)
  v_r = ( (2.0 * v) + (omega*L) ) / (2.0 * R)

  return v_l, v_r

估计状态:机器人, 了解自己

机器人必须使用其传感器来尝试估计环境状态及其自身状态。这些估计值永远不会是完美的, 但它们必须相当不错, 因为机器人将根据这些估计值做出所有决策。单独使用其接近传感器和车轮转向器, 它必须尝试猜测以下内容:

  • 障碍的方向
  • 距障碍物的距离
  • 机器人的位置
  • 机器人的前进方向

前两个属性由接近传感器的读数确定, 非常简单。 API函数read_proximity_sensors()返回一个九个值的数组, 每个传感器一个。我们提前知道例如第七个读数对应于指向机器人右侧75度的传感器。

因此, 如果此值显示的读数对应于0.1米的距离, 那么我们知道在距离0.1米(向左75度)处存在障碍物。如果没有障碍物, 传感器将返回其最大距离0.2米的读数。因此, 如果我们在传感器7上读取0.2米, 我们将假定在该方向上实际上没有障碍物。

由于红外传感器的工作方式(测量红外反射), 它们返回的数字是检测到的实际距离的非线性变换。因此, 用于确定指示距离的Python函数必须将这些读数转换为米。可以在supervisor.py中执行以下操作:

# update the distances indicated by the proximity sensors
def _update_proximity_sensor_distances( self ):
    self.proximity_sensor_distances = [ 0.02-( log(readval/3960.0) )/30.0 for
        readval in self.robot.read_proximity_sensors() ]

同样, 在此Python机器人框架中, 我们有一个特定的传感器模型, 而在现实世界中, 传感器随附了随附的软件, 该软件应提供类似的从非线性值到仪表的转换功能。

确定机器人的位置和方向(在机器人编程中统称为姿势)要更具挑战性。我们的机器人使用测距法估算其姿势。这是车轮滴答声进入的地方。通过测量自控制循环的最后一次迭代以来每个车轮转了多少圈, 可以很好地估计机器人的姿势如何发生变化, 但前提是变化很小。

这是在实际机器人中非常频繁地迭代控制回路的重要原因之一, 在实际机器人中, 移动轮子的电机可能并不完美。如果我们等待太久才测量车轮行情指示器, 那么两个车轮本可以做很多事情, 并且无法估计我们最终的位置。

使用当前的软件模拟器, 我们可以承受以20 Hz(与控制器相同的频率)运行里程计计算的费用。但是, 最好有一个单独的Python线程运行得更快, 以捕捉股票行情的小幅度波动。

以下是supervisor.py中的完整里程表功能, 用于更新机器人姿势估计。请注意, 机器人的姿态由坐标x和y以及航向角theta组成, 该航向角是从x轴正方向开始的弧度。正x位于东方, 正y位于北方。因此, 朝向0表示机器人正向东。机器人始终假定其初始姿势为(0, 0), 0。

# update the estimated position of the robot using it's wheel encoder readings
def _update_odometry( self ):
  R = self.robot_wheel_radius
  N = float( self.wheel_encoder_ticks_per_revolution )
  
  # read the wheel encoder values
  ticks_left, ticks_right = self.robot.read_wheel_encoders()
  
  # get the difference in ticks since the last iteration
  d_ticks_left = ticks_left - self.prev_ticks_left
  d_ticks_right = ticks_right - self.prev_ticks_right
  
  # estimate the wheel movements
  d_left_wheel = 2*pi*R*( d_ticks_left / N )
  d_right_wheel = 2*pi*R*( d_ticks_right / N )
  d_center = 0.5 * ( d_left_wheel + d_right_wheel )
  
  # calculate the new pose
  prev_x, prev_y, prev_theta = self.estimated_pose.scalar_unpack()
  new_x = prev_x + ( d_center * cos( prev_theta ) )
  new_y = prev_y + ( d_center * sin( prev_theta ) )
  new_theta = prev_theta + ( ( d_right_wheel - d_left_wheel ) / self.robot_wheel_base_length )
  
  # update the pose estimate with the new values
  self.estimated_pose.scalar_update( new_x, new_y, new_theta )
  
  # save the current tick count for the next iteration
  self.prev_ticks_left = ticks_left
  self.prev_ticks_right = ticks_right

既然我们的机器人能够对现实世界做出很好的估算, 那么我们就可以使用这些信息来实现我们的目标。

相关:视频游戏物理教程-固体物体的碰撞检测

Python机器人编程方法:Go-to-Goal行为

本编程教程中我们的小机器人的存在的最高目的是达到目标。那么我们如何使车轮转动到那里呢?首先, 我们稍微简化一下我们的世界观, 并假设没有障碍。

然后, 这成为一个简单的任务, 并且可以使用Python轻松编程。如果我们在实现目标的同时前进, 我们将实现目标。多亏了里程表, 我们才能知道当前的坐标和航向。我们还知道目标的坐标是什么, 因为它们已预先编程。因此, 使用一些线性代数, 我们可以确定从位置到目标的向量, 如go_to_goal_controller.py:

# return a go-to-goal heading vector in the robot's reference frame
def calculate_gtg_heading_vector( self ):
  # get the inverse of the robot's pose
  robot_inv_pos, robot_inv_theta = self.supervisor.estimated_pose().inverse().vector_unpack()
  
  # calculate the goal vector in the robot's reference frame
  goal = self.supervisor.goal()
  goal = linalg.rotate_and_translate_vector( goal, robot_inv_theta, robot_inv_pos )
  
  return goal

请注意, 我们是在机器人的参考系中获得矢量到目标的, 而不是在世界坐标系中。如果目标位于机器人参考系中的X轴上, 则意味着它位于机器人的正前方。因此, 此向量与X轴的夹角就是我们的航向与我们要航向的航向之间的差。换句话说, 这是我们当前状态与我们希望当前状态之间存在的误差。因此, 我们想要调整转弯速率ω, 以使航向和目标之间的角度变为0。我们希望将误差最小化:

# calculate the error terms
theta_d = atan2( self.gtg_heading_vector[1], self.gtg_heading_vector[0] )

# calculate angular velocity
omega = self.kP * theta_d

上面的控制器Python实现片段中的self.kP是控制增益。它是一个系数, 它决定我们与目标所处的距离成正比。如果标题中的错误为0, 则转弯率也为0。在文件go_to_goal_controller.py中的真实Python函数中, 你会看到更多类似的收益, 因为我们使用了PID控制器而不是简单的比例系数。

现在我们有了角速度ω, 如何确定前进速度v?一个好的一般经验法则可能是你本能地知道的:如果我们不转弯, 我们可以全速前进, 然后我们转得越快, 我们应放慢的速度就越大。通常, 这可以帮助我们保持系统稳定并在模型的范围内发挥作用。因此, v是ω的函数。在go_to_goal_controller.py中, 公式为:

# calculate translational velocity
# velocity is v_max when omega is 0, # drops rapidly to zero as |omega| rises
v = self.supervisor.v_max() / ( abs( omega ) + 1 )**0.5

对此公式进行详细说明的建议是, 考虑到我们通常会在接近目标时放慢速度, 以便以零速度达到目标。这个公式将如何改变?它必须以某种方式包括将v_max()替换为与距离成比例的东西。好的, 我们几乎完成了一个控制循环。剩下要做的唯一一件事就是将这两个独轮车模型参数转换为车轮差速, 然后将信号发送至车轮。这是在目标控制下机器人无障碍的轨迹示例:

这是编程的机器人轨迹的示例。

如我们所见, 目标向量是我们进行控制计算的有效参考。它是”我们想去的地方”的内部表示。正如我们将看到的, ” go-to-goal”行为与其他行为之间的唯一主要区别是有时朝着目标迈进是一个坏主意, 因此我们必须计算一个不同的参考向量。

Python机器人编程方法:避免障碍行为

当障碍物朝那个方向前进时, 就是一个典型的例子。让我们尝试编写控制规则以使机器人避开它们, 而不是像我们那样费力地闯入事物。

为了简化方案, 现在让我们完全忘记目标, 仅将以下目标作为我们的目标:当我们面前没有障碍时, 继续前进。当遇到障碍物时, 请转离它, 直到它不再在我们面前。

因此, 当我们面前没有障碍时, 我们希望参考矢量简单地指向前方。则ω为零, v为最大速度。但是, 一旦我们使用接近传感器检测到障碍物, 就希望参考矢量指向远离障碍物的任何方向。这将导致ω猛增以使我们远离障碍物, 并使v下降以确保我们在过程中不会意外遇到障碍物。

生成所需参考矢量的一种巧妙方法是将我们的九个接近度读数转换为矢量, 然后进行加权求和。当未检测到障碍物时, 矢量将对称求和, 从而生成一个参考矢量, 该参考矢量会根据需要指向正前方。但是, 如果右侧的传感器捡起障碍物, 它将为总和贡献较小的矢量, 结果将是向左偏移的参考矢量。

对于具有不同传感器位置的普通机器人, 可以应用相同的想法, 但是当传感器在机器人的前后对称时, 由于加权和可能变为零, 因此可能需要更改重量和/或进行额外的护理。 。

正确编程后,机器人可以避免这些复杂的障碍。

这是在void_obstacles_controller.py中执行此操作的代码:

# sensor gains (weights)
self.sensor_gains = [ 1.0+( (0.4*abs(p.theta)) / pi )
                      for p in supervisor.proximity_sensor_placements() ]

# ...

# return an obstacle avoidance vector in the robot's reference frame
# also returns vectors to detected obstacles in the robot's reference frame
def calculate_ao_heading_vector( self ):
  # initialize vector
  obstacle_vectors = [ [ 0.0, 0.0 ] ] * len( self.proximity_sensor_placements )
  ao_heading_vector = [ 0.0, 0.0 ]             
  
  # get the distances indicated by the robot's sensor readings
  sensor_distances = self.supervisor.proximity_sensor_distances()
  
  # calculate the position of detected obstacles and find an avoidance vector
  robot_pos, robot_theta = self.supervisor.estimated_pose().vector_unpack()
  
  for i in range( len( sensor_distances ) ):
    # calculate the position of the obstacle
    sensor_pos, sensor_theta = self.proximity_sensor_placements[i].vector_unpack()
    vector = [ sensor_distances[i], 0.0 ]
    vector = linalg.rotate_and_translate_vector( vector, sensor_theta, sensor_pos )
    obstacle_vectors[i] = vector   # store the obstacle vectors in the robot's reference frame
    
    # accumulate the heading vector within the robot's reference frame
    ao_heading_vector = linalg.add( ao_heading_vector, linalg.scale( vector, self.sensor_gains[i] ) )
                                 
  return ao_heading_vector, obstacle_vectors

使用所得的ao_heading_vector作为我们尝试进行匹配的机器人的参考, 以下是仅使用避免障碍控制器在模拟中运行机器人软件的结果, 而完全忽略了目标点。机器人会漫无目的地反弹, 但不会与障碍物碰撞, 甚至可以在一些非常狭窄的空间中导航:

该机器人成功地避免了Python机器人模拟器中的障碍。

Python机器人编程方法:混合自动机(行为状态机)

到目前为止, 我们已经分别描述了两种行为:”实现目标”和”避免障碍”。两者的功能都令人钦佩, 但为了在充满障碍的环境中成功实现目标, 我们需要将它们结合起来。

我们将开发的解决方案在于一类具有混合自动机的超酷外观的机器。混合自动机具有几种不同的行为或模式以及监督状态机。监督状态机会在不连续的时间(当达到目标或环境突然变化太多时)从一种模式切换到另一种模式, 而每种行为都使用传感器和滚轮对环境变化进行连续反应。该解决方案之所以称为”混合”, 是因为它既可以离散又可以连续发展。

我们的Python机器人框架在文件supervisor_state_machine.py中实现了状态机。

配备了我们的两种便捷行为后, 一个简单的逻辑就会提示自己:当未检测到障碍物时, 请使用” go-to-goal”行为。当检测到障碍物时, 切换到避免障碍物行为, 直到不再检测到障碍物为止。

然而, 事实证明, 这种逻辑会产生很多问题。当遇到障碍物时, 该系统将倾向于做的事情是先离开它, 然后一旦它离开它, 立即右转回去并再次进入它。结果是无休止的快速切换循环, 使机器人毫无用处。在最坏的情况下, 机器人可能会在控制循环的每次迭代之间在行为之间切换, 这种状态称为Zeno条件。

这个问题有多种解决方案, 正在寻求更深入知识的读者应检查例如DAMN软件体系结构。

我们对简单的模拟机器人的需求是一个更简单的解决方案:一种专门针对绕过障碍物并到达另一侧的行为。

Python机器人编程方法:追随行为

这就是想法:当我们遇到障碍物时, 请获取最接近障碍物的两个传感器读数, 然后使用它们来估算障碍物的表面。然后, 只需将我们的参考矢量设置为与此表面平行即可。一直沿着这堵墙, 直到A)障碍不再在我们与目标之间, 并且B)我们比开始时更接近目标。然后我们可以确定我们已经正确地越过了障碍。

由于信息有限, 我们无法确定绕过左​​侧或右侧障碍物的速度会更快。为了下定决心, 我们选择了可以立即使我们更接近目标的方向。为了弄清楚哪种方式, 我们需要知道目标运行行为和避免障碍行为的参考向量, 以及可能的跟随壁参考向量。这是最终决策方式的说明(在这种情况下, 机器人将选择向左走):

通过使用几种类型的行为,已编程的机器人会避开障碍物并继续前进。

确定后墙参考向量比避免障碍或目标参考向量要复杂得多。查看follow_wall_controller.py中的Python代码, 了解其操作方式。

最终控制设计

最终控制设计将追随行为用于几乎所有遇到的障碍物。但是, 如果机器人发现自己处于狭窄的地方, 很危险地接近碰撞, 它将切换到纯躲避障碍物模式, 直到距离更安全为止, 然后返回跟随墙。成功解决障碍后, 机器人将切换至目标。这是最终状态图, 已在supervisor_state_machine.py中编程:

该图说明了机器人编程行为之间的切换,以实现目标和避免障碍。

这是机器人使用此控制方案成功在拥挤的环境中导航的过程:

机器人模拟器已经成功地使机器人软件避免了障碍并实现了其原始目的。

你可以尝试实现的状态机的另一个功能是一种避免圆形障碍的方法, 可以尽快切换为通向目标, 而不是遵循障碍边界直到终点(圆形对象不存在!)。 )

调整, 调整, 调整:尝试和错误

Sobot Rimulator随附的控制方案非常精细。花了许多小时在这里调整一个小变量, 在那儿调整另一个方程, 以使其以我满意的方式工作。机器人编程通常涉及很多普通的反复试验。机器人非常复杂, 几乎没有任何捷径可让它们在机器人模拟器环境中发挥最佳性能……至少, 与机器学习完全无关, 但这完全是蠕虫。

机器人技术通常涉及很多普通的反复试验。

我鼓励你在Sobot Rimulator中使用控制变量, 并观察并尝试解释结果。进行以下所有更改都会对模拟机器人的行为产生深远影响:

  • 每个控制器的误差增益kP
  • 避免障碍控制器使用的传感器增益
  • 在每个控制器中v作为ω的函数的计算
  • 随行墙控制器使用的障碍物距离
  • supervisor_state_machine.py使用的切换条件
  • 几乎其他任何东西

可编程机器人失败时

为了达到这一点, 我们已经做了很多工作, 而且这款机器人看起来非常聪明。但是, 如果你通过多个随机地图运行Sobot Rimulator, 很快就会找到该机器人无法处理的地图。有时, 它直接将自己驱动到狭窄的角落并发生碰撞。有时, 它只是在障碍物的反面不断地来回摆动。有时, 它被合法地监禁, 没有达到目标的可能途径。经过所有测试和调整后, 有时我们必须得出一个结论, 即所使用的模型无法胜任工作, 我们必须更改设计或添加功能。

在移动机器人世界中, 我们的小机器人的”大脑”处于光谱的最简单的一端。可以通过添加一些更高级的软件来克服它遇到的许多失败案例。更先进的机器人利用地图等技术来记住它的去向, 并避免一遍又一遍地尝试相同的事情;启发式, 当找不到完美的决策时产生可接受的决策;和机器学习, 以更完美地调整控制机器人行为的各种控制参数。

即将发生的事的样本

机器人已经在为我们做很多事情, 而且将来只会做更多的事情。尽管即使是基本的机器人程序设计也是一个艰巨的研究领域, 都需要极大的耐心, 但它也是一个引人入胜的巨大收获。

在本教程中, 我们学习了如何使用高级编程语言Python为机器人开发反应控制软件。但是有许多更高级的概念可以通过类似于我们在此处原型化的Python机器人框架快速学习和测试。希望你会考虑参与即将发生的事情的塑造!


致谢:我要感谢佐治亚理工学院的Magnus Egerstedt博士和Jean-Pierre de la Croix教授了我所有这些知识, 以及他们对我在Sobot Rimulator上所做的工作的热情。

相关:OpenCV教程:在iOS中使用MSER进行实时对象检测

赞(1)
未经允许不得转载:srcmini » 机器人编程入门教程

评论 抢沙发

评论前必须登录!