玩转JetBot自动驾驶 (七)实现自动避障

本篇为本入门系列的最后一篇,一共7篇教程,能完整地让JetBot通过摄像头实时自动避障。

原版翻译notebook可在Github下载:
https://github.com/ling3ye/jetbot/blob/master/notebooks/collision_avoidance/live_demo.ipynb

现在,JetBot需要改为移动电源供电。

开机后,通过浏览器输入如下网址:
http://<你JetBot的IP地址>:8888

会看到Jupyter Lab的界面提示登陆:

默认的账户密码都是:jetbot

在Jupyter Lab的文件浏览器中,找到collision_avoidance文件夹,点开live_demo.ipynb开始实时避障吧。

实时避障

在这个notebook,我们将会使用我们上次训练的模型,测试Jetbot是否遇到freeblocked的情况就会做出相应的行为。

加载训练模型

我们假设你已经按照训练实例notebook中训练模型,并下载到你的工作平台上。现在,你需要把模型上传到此notebook的相同目录中。在Jupyter Lab的文件浏览器上有上传的按钮,点击就能把文件上传上去。

请在执行下一个单元格的代码之前,请确保该训练好的模型已上传完成。

执行以下代码,初始化PyTorch模型

import torch
import torchvision

model = torchvision.models.alexnet(pretrained=False)
model.classifier[6] = torch.nn.Linear(model.classifier[6].in_features, 2)

接下来,加载您上传的,已经被训练过的best_model.pth的模型

model.load_state_dict(torch.load('best_model.pth'))

目前,模型权重计算位于CPU内存上,执行下面的代码以使用到GPU。

device = torch.device('cuda')
model = model.to(device)

预处理功能

现在我们加载了模型,但有一个小问题,就是我们的摄像头的图像格式要与训练模型时的图像格式完全相同。要做到这一点,我们需要做一些预处理。分如下几个步骤:

  1. 从BGR转换为RGB模式
  2. 从HWC布局转换为CHW布局
  3. 使用与训练期间相同的参数进行标准化(我们的摄像机提供[0,255]范围内的值,并在[0,1]范围内训练加载的图像,因此我们需要缩放255.0
  4. 将数据从CPU内存传输到GPU内存
  5. 批量添加维度
import cv2
import numpy as np

mean = 255.0 * np.array([0.485, 0.456, 0.406])
stdev = 255.0 * np.array([0.229, 0.224, 0.225])

normalize = torchvision.transforms.Normalize(mean, stdev)

def preprocess(camera_value):
    global device, normalize
    x = camera_value
    x = cv2.cvtColor(x, cv2.COLOR_BGR2RGB)
    x = x.transpose((2, 0, 1))
    x = torch.from_numpy(x).float()
    x = normalize(x)
    x = x.to(device)
    x = x[None, ...]
    return x

非常好! 我们现在定义了我们的预处理功能,可以将图像从相机格式转换为神经网络输入的格式。

现在,让我们显示我们的摄像头。 你现在应该对此非常熟悉。 我们还将创建一个滑块,用于显示机器人被阻挡的概率。

import traitlets
from IPython.display import display
import ipywidgets.widgets as widgets
from jetbot import Camera, bgr8_to_jpeg

camera = Camera.instance(width=224, height=224)
image = widgets.Image(format='jpeg', width=224, height=224)
blocked_slider = widgets.FloatSlider(description='blocked', min=0.0, max=1.0, orientation='vertical')

camera_link = traitlets.dlink((camera, 'value'), (image, 'value'), transform=bgr8_to_jpeg)

display(widgets.HBox([image, blocked_slider]))

我们还创建需要驱动电机的robot实例。

from jetbot import Robot

robot = Robot()

接下来,我们创建一个函数,只要相机的值发生变化,就会调用该函数。 此功能将执行以下步骤

  1. 预处理相机图像
  2. 执行神经网络
  3. 当神经网络输出表明我们被阻挡时,我们将向左转,否则我们继续前进。
import torch.nn.functional as F
import time

def update(change):
    global blocked_slider, robot
    x = change['new'] 
    x = preprocess(x)
    y = model(x)
    
    # we apply the `softmax` function to normalize the output vector so it sums to 1 (which makes it a probability distribution)
    y = F.softmax(y, dim=1)
    
    prob_blocked = float(y.flatten()[0])
    
    blocked_slider.value = prob_blocked
    
    if prob_blocked < 0.5:
        robot.forward(0.4)
    else:
        robot.left(0.4)
    
    time.sleep(0.001)
        
update({'new': camera.value})  # we call the function once to intialize

很好! 我们已经创建了神经网络执行功能,但现在我们需要将它附加到相机进行处理。

我们用observe函数完成了这个处理。

警告:此代码将移动机器人! 请确保你的Jetbot安全。

camera.observe(update, names='value')  # this attaches the 'update' function to the 'value' traitlet of our camera

真棒! 如果您的以运行上面这代码块,它现在应该为每个检测到的照片生成新命令。 也许首先将机器人放在地上,看看它遇到障碍物时的反应。

如果要停止此行为,可以通过执行以下代码来取消。

camera.unobserve(update, names='value')
robot.stop()

也许您希望Jetbot在没有流式传输视频的情况下运行,这样会减少JetBot的运算负担。 您可以取消摄像头的连接,执行如下代码。
只是不推流到浏览器上,但在Jetbot上摄像头仍然是工作状态中的。

camera_link.unlink()  # don't stream to browser (will still run camera)

又如果要继续在浏览器显示视频,请执行以下代码。

camera_link.link()  # stream to browser (wont run camera)

总结

非常有趣,现在你的机器人可以智能地避开障碍!

如果您的机器人没有很好地避免碰撞,请尝试找出失败的位置。 美妙之处在于我们可以为这些故障情况收集更多数据并使Jetbot变得更好:)

发表评论

BACK TO TOP