本篇为本入门系列的最后一篇,一共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是否遇到free
和blocked
的情况就会做出相应的行为。
加载训练模型
我们假设你已经按照训练实例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)
预处理功能
现在我们加载了模型,但有一个小问题,就是我们的摄像头的图像格式要与训练模型时的图像格式完全相同。要做到这一点,我们需要做一些预处理。分如下几个步骤:
- 从BGR转换为RGB模式
- 从HWC布局转换为CHW布局
- 使用与训练期间相同的参数进行标准化(我们的摄像机提供[0,255]范围内的值,并在[0,1]范围内训练加载的图像,因此我们需要缩放255.0
- 将数据从CPU内存传输到GPU内存
- 批量添加维度
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()
接下来,我们创建一个函数,只要相机的值发生变化,就会调用该函数。 此功能将执行以下步骤
- 预处理相机图像
- 执行神经网络
- 当神经网络输出表明我们被阻挡时,我们将向左转,否则我们继续前进。
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变得更好:)