7.做出决策

我们已经建立了感知步骤Perception.py,接下来要构建决策函数decision.py 

我们利用一个基础的人工智能模型——决策树模型。其结构如下:

在这里插入图片描述

由于我们采用一个非常简单的模型,所以我们只需要编码一系列可能的场景与每种场景相关的决策。

我们在这个项目中会接收很多信息,比如漫游者号当前状态的信息,比如当前位置,速度、方向、转向角等等。我们只需要给出相应的条件,然后给出如何进行下一步即可。

现在定义一个名为RoverState() 的Python类,它可以跟踪漫游者号的变化状态,下面是默认值:

class RoverState():
    def __init__(self):
        self.start_time = None # 记录导航开始的时间
        self.total_time = None # 记录导航的总持续时间
        self.img = None # 当前的相机图像
        self.pos = None # 当前的位置 (x, y)
        self.yaw = None # 当前的偏航角
        self.pitch = None # 当前的俯仰角
        self.roll = None # 当前的旋转角
        self.vel = None # 当前的速度
        self.steer = 0 # 当前的转向角
        self.throttle = 0 # 当前的油门值
        self.brake = 0 # 当前的制动值
        self.nav_angles = None # 可导航地形像素的角度
        self.nav_dists = None # 可导航地形像素的距离
        self.ground_truth = ground_truth_3d # 真实的世界地图
        self.mode = 'forward' # 当前模式 (可以是前进或者停止)
        self.throttle_set = 0.2 # 加速时的节流设定
        self.brake_set = 10 # 刹车时的刹车设定
        # 下面的stop_forward和go_forward字段表示可导航地形像素的总数。
        # 这是一种非常粗糙的形式来表示漫游者号当何时可以继续前进或者何时应该停止。
        # 可以随意添加新字段或修改这些字段。
        self.stop_forward = 50 # T启动停止时的阈值
        self.go_forward = 500 # 再次前进的阈值
        self.max_vel = 2 # 最大速度 (m/s)
        # 感知步骤的图像输入
        # 更新此图像以显示中间分析步骤
        # 在自主模式下的屏幕上
        self.vision_image = np.zeros((160, 320, 3), dtype=np.float) 
        # 世界地图
        # 使用可导航的位置更新此图像
        # 障碍物和岩石样本
        self.worldmap = np.zeros((200, 200, 3), dtype=np.float) 
        self.samples_pos = None # 存储实际样本的位置
        self.samples_found = 0 # 计算找到的样本数
        self.near_sample = False # 如果在岩石样本范围内
        self.pick_up = False # 设置为True来拾取样本

然后创建此类的实例:

Rover= RoverState()

我们可以使用Rover对每组新的遥测值进行更新。例如

Rover.vel = new_velocity_from_telemetry
Rover.yaw = new_yaw_from_telemetry

现在我们可以使用条件语句创建一个类似上图所示的决策树,以根据当前的遥测来分析决定下一步做什么,例如

if Rover.vel >= Rover.max_vel:
    Rover.throttle = 0
else:
Rover.throttle = Rover.throttle_set

以下给出可行的漫游者号使用决策树的决策方案,可将其保存为decision.py

import numpy as np

# 命令基于perception_step()函数的输出
def decision_step(Rover):

    if Rover.nav_angles is not None:
        # 检查 Rover.mode 状态
        if Rover.mode == 'forward':
            # 检查可导航地形的范围
            if len(Rover.nav_angles) >= Rover.stop_forward:  
                # 如果为forward模式,可导航地图是好的,速度如果低于最大值,则加速
                if Rover.vel < Rover.max_vel:
                    # 设置油门值
                    Rover.throttle = Rover.throttle_set
                else:
                    Rover.throttle = 0
                Rover.brake = 0
                # 将转向设置为平均夹角,范围为 +/- 15
                Rover.steer = np.clip(np.mean(Rover.nav_angles * 180/np.pi), -15, 15)
            # 如果缺少地形图,则设置为停止模式
            elif len(Rover.nav_angles) < Rover.stop_forward:
                    # 设置到停止模式并刹车
                    Rover.throttle = 0
                    # 设置制动值
                    Rover.brake = Rover.brake_set
                    Rover.steer = 0
                    Rover.mode = 'stop'

        # 如果我们已经处于“停止”模式,那么做出不同的决定
        elif Rover.mode == 'stop':
            # 如果我们处于停止模式但仍然继续制动
            if Rover.vel > 0.2:
                Rover.throttle = 0
                Rover.brake = Rover.brake_set
                Rover.steer = 0
            # 如果我们没有移动(vel <0.2),那就做点别的
            elif Rover.vel <= 0.2:
                # 现在为停止状态,依靠视觉数据,判断是否有前进的道路
                if len(Rover.nav_angles) < Rover.go_forward:
                    Rover.throttle = 0
                    # 松开制动器以便转动
                    Rover.brake = 0
                    # 转弯范围为+/- 15度,当停止时,下一条线将引起4轮转向
                    Rover.steer = -15
                #如果停下来但看到前面有足够的可导航地形,那就启动
                if len(Rover.nav_angles) >= Rover.go_forward:
                    # 将油门设置回存储值
                    Rover.throttle = Rover.throttle_set
                    Rover.brake = 0
                    # 将转向设置为平均角度
                    Rover.steer = np.clip(np.mean(Rover.nav_angles * 180/np.pi), -15, 15)
                    Rover.mode = 'forward'
    else:
        Rover.throttle = Rover.throttle_set
        Rover.steer = 0
        Rover.brake = 0
        
    # 在可拾取岩石的状态下发送拾取命
    if Rover.near_sample and Rover.vel == 0 and not Rover.picking_up:
        Rover.send_pickup = True
    
    return Rover

8.控制漫游者号

我们使用名为send_control() 的函数,来控制漫游者号并将图像显示到模拟器屏幕。

def send_control(commands, image_string1, image_string2):
    # 定义需要发送给漫游者号的命令
    data={
        'throttle': commands[0].__str__(),
        'brake': commands[1].__str__(),
        'steering_angle': commands[2].__str__(),
        'inset_image1': image_string1,
        'inset_image2': image_string2,
        }
    # 通过socketIO服务器发送命令
    sio.emit("data", data, skip_sid=True)

参数commands是一个包含油门、刹车和转向值的元组,其储存在Rover.throttle, Rover.brake 和 Rover.steer中

其他参数被称为image_string1与image_string2,它们包含Rover.vision_image和Rover.worldmap转换,同时在自主导航模式将base64字符串的图像呈现在屏幕上。

该send_control() 函数创建一个带有所有这些值的字典data,然后将该字典传递给该 sio.emit() 函数,以将命令和图像发送到漫游者号。不需要担心这个被究竟是如何实现的,只是一旦已经更新Rover.throttle,Rover.brake和Rover.steer,commands输入send_control()将被更新,并在每次更新的时候两个显示图像:Rover.vision_image和Rover.worldmap 将被更新。