目录
五.真实自动驾驶小车开发(树莓派小车+神经网络计算棒NCS2)
一 . 基本介绍
众所周知,自动驾驶技术已成为汽车圈一个风口浪尖的热词。相较于传统汽车,自动驾驶汽车拥有诸多显著优势,一是自动驾驶汽车能够缓解道路拥堵、提升交通安全;二是自动驾驶汽车能为用户提供更便利更好的出行体验;三是自动驾驶技术将带动整个汽车产业链发生巨变,催生交通出行新生态。如今,无论是科技巨头还是传统车企都在钻研自动驾驶的相关技术,国外典型代表有特斯拉、谷歌、Uber,国内的有百度、蔚来、华为,小鹏,滴滴,小米等。
自动驾驶汽车是一项艰巨而复杂的任务,涉及硬件、软件、AI算法于一体,开发一套完整的自动驾驶汽车系统需要多个领域的工程师、研究者协同合作才可能完成。那么我们个人是否能够搭建一套类似的简易自动驾驶小车,领略下自动驾驶的乐趣呢? 接下来的一系列内容就围绕这个任务来实现。本文将会一步步的讲解如何搭建一套比较真实的、基于深度学习算法驱动的自动驾驶小车。
本文使用纯视觉方案实现一个端到端的能够在规定道路上行驶的自动驾驶小车,通过USB摄像头拍摄路面图像,实时分析并规划车辆转向角度。
考虑到成本因素,本文会分别使用模拟驾驶平台和真实的树莓派小车来实现。如果没有树莓派小车的读者可以通过模拟平台来学习。实际上不管是平台仿真还是真实树莓派小车环境,整个实现思路和方法都是一样的,只不过在树莓派小车上实现难度要大一些,因为需要额外的硬件支持,并且需要自己去调试、组装小车和布置道路环境。
为了开发方便,本文使用 Python语言进行全流程开发,如果对Python语言不熟悉的读者可以先学习下Python基础语法再来学习本文内容。
本文使用 OpenCV作为基本的图像处理工具,使用 Pytorch作为深度学习框架。同样的,如果不熟悉这两个工具的读者可以先去官网学习下OpenCV和Pytorch的基本教程。
本文所有代码和数据均开源,下载地址(包含了模拟器、所有训练数据、完整代码):
链接:https://pan.baidu.com/s/1yhIQpKCJlUqbU6xWzHiY9A
提取码:ln2a
具体效果如下视频所示(第1个是在模拟环境中通过深度学习实现,第2个是真实的使用树莓派搭建的深度学习自动驾驶小车):
基于深度学习的自动驾驶小车
基于纯视觉的端到端自动驾驶小车,使用pytorch和神经网络计算棒实现 完整教程请在csdn上搜索”钱彬的博客 一文掌握基于深度学习的自动驾驶小车开发”
二、模拟平台安装和基本使用
下载地址:Releases · tawnkramer/gym-donkeycar · GitHub
该地址中提供的模拟器是基于Unity开发的,是经过删减过后的可执行程序,不再需要额外安装unity,下载下来后就可以直接运行。目前覆盖windows、Linux、Mac共3个版本。本文为了简单教学,只讲解如何在windows平台上运行和使用该模拟器。
具体的,下载Windows平台对应的DonkeySimWin.zip压缩包,解压后内容如下所示:
双击运行其中的donkey_sim.exe即可启动模拟器。
主界面如下所示:
该模拟器中提供了很多不同的赛道,在模拟器左侧是相关设置,可以设置不同的视角等。这里我们选择最简单的赛道generated road,因为这个赛道没有障碍物且跟我们真实高速公路环境比较像,上手比较容易。我们先设置左侧的Settings如下:
注意我们设置了paceCar并且勾选了manualDriving,这样我们就可以自己手动操作小车了,不需要使用内置的自动驾驶模式。
接下来单击generated road,进入具体的场景:
在场景中,如果我们前面主界面使用了手工模式(paceCar处勾选manualDriving),那么我们就可以通过键盘来操控小车进行体验了。与一般的赛车游戏类似(qq飞车、跑跑卡丁车等),W键表示前进,A表示左转,D表示右转,S表示后退。
在该模拟器中,控制小车的主要是两个参数:油门(W和S键)和转向角度(A和D键),这个与我们真实驾驶的汽车基本一致:挂挡+踩油门来控制前进动力,打方向盘控制车辆转向。为了能够实现自动驾驶,我们首先要能够根据这两个参数去控制模拟器里面小车的运行。我们怎么样通过Python代码来控制这个模拟器呢?
这个模拟器的好处就在于预留了Python控制接口,我们只需要安装一个驱动库就可以直接驱动模拟器里面的小车运行(提前安装好Git工具):
pip install git+https://github.com/tawnkramer/gym-donkeycar
安装好以后我们可以运行下面的python代码来实现小车的控制(注意:运行下面的代码前先启动模拟器,并停留在模拟器主界面上):
导入库
import gym
import gym_donkeycar
import numpy as np
import cv2
设置模拟器环境
env = gym.make("donkey-generated-roads-v0")
重置当前场景
obv = env.reset()
运行100帧
for t in range(100):
# 定义控制动作
action = np.array([0.3,0.5]) # 动作控制,0.3表示转向,0.5表示油门
# 执行动作
obv, reward, done, info = env.step(action)
# 取一张图像保存
if t == 20:
img = cv2.cvtColor(obv,cv2.COLOR_RGB2BGR)
cv2.imwrite('test.jpg',img)
运行完以后重置当前场景
obv = env.reset()
我们先分析下这段代码。下面这行代码用于设置模拟器环境,简单来说就是启用哪张地图:
env = gym.make("donkey-generated-roads-v0")
在这个模拟器里面我们可以用到的地图如下所示:
- “donkey-warehouse-v0”
- “donkey-generated-roads-v0”
- “donkey-avc-sparkfun-v0”
- “donkey-generated-track-v0”
- “donkey-roboracingleague-track-v0”
- “donkey-waveshare-v0”
- “donkey-minimonaco-track-v0”
- “donkey-warren-track-v0”
- “donkey-thunderhill-track-v0”
- “donkey-circuit-launch-track-v0”
接下来的代码里面,我们运行了100帧,每帧都用固定的控制参数来执行:右转0.3、前进0.5。这两个字段就是我们前面提到的转向和油门值。下面给出这两个值的具体定义:
油门值取值范围是[-1,1],负值代表倒退,正值代表前进。转向值取值范围也是[-1,1],负值代表向左,正值代表向右。
接下来使用np.array封装这两个参数,然后通过env.step来执行单步动作。执行完动作以后会返回一些信息,其中我们需要重点关注obs这个返回参数,这个参数表示当前位于小车正中间行车记录仪摄像头返回的一帧图像 ,图像宽160像素,高120像素,3通道RGB图像。如下图所示:
由于本文主要使用摄像头图像数据来控制小车运行,因此上述代码中我们抽取了一张图像并保存到本地用来分析并测试算法。
通过上述代码,我们就可以使用python调整两个参数[油门值、转向值]来控制小车的运行,并且可以得到小车每次运行后的图像数据。实现了这样一个逻辑,我们自然就可以通过建立自动驾驶模型,逐帧分析图像,然后控制小车的这两个参数来实现小车的自动驾驶。
本小节内容重点使读者重新熟悉下python基本使用方法,同时熟悉下这个小车驾驶模拟器,接下来我们将正式进入自动驾驶算法研发环节。
三、基于OpenCV的自动驾驶控制
在学习自动驾驶前,我们先看看传统算法是怎么解决上面这个任务的。只有综合比较了传统算法和深度学习算法,我们才能真正体会到深度学习的强大能力。
本小节,我们将使用传统图像处理算法进行行道线检测等步骤来控制小车运行在行道线内。一方面帮助读者巩固下基本的opencv图像处理技术,另一方面可以更清晰的认识这个任务难点,为后面实现基于深度学习的自动驾驶做好铺垫。
具体的,我们希望通过算法来控制小车,最终让这个小车稳定运行在行车道内。这里面涉及到两方面: 感知和动作规划。 感知部分我们主要通过行道线检测来实现,动作规划通过操控转向角度来实现。行道线检测的目的就是希望能够根据检测到的行道线位置来计算最终应该转向的角度,从而控制小车始终运行在当前车道线内。
由于道路环境比较简单,针对我们这个任务,我们进一步简化我们的控制变量,我们只控制转向角度,对于油门值我们在运行时保持低匀速,这样我们的重点就可以放在一个变量上面— 转向角度。
3.1基于HSV空间的特定颜色区域提取
颜色过滤是目前经常被使用到的图像处理技巧之一,例如天气预报抠像等,经常会使用绿幕作为背景进行抠图。本小节使用颜色过滤来初步提取出行道线。
从模拟平台的图像数据上进行分析,小车左侧是黄实线,右侧是白实线。我们希望小车一直运行在这两根线之间。因此,我们首先要定位出这两根线。我们可以通过颜色空间变换来定位这两根线。
为了方便将黄色线和白色线从图像中过滤出来,我们需要将图像从RGB空间转换到HSV空间再处理。
这里首先我们解释下RGB和HSV颜色空间的区别。
RGB 是我们接触最多的颜色空间,由三个通道表示一幅图像,分别为红色(R),绿色(G)和蓝色(B)。这三种颜色的不同组合可以形成几乎所有的其他颜色。RGB 颜色空间是图像处理中最基本、最常用、面向硬件的颜色空间,比较容易理解。RGB 颜色空间利用三个颜色分量的线性组合来表示颜色,任何颜色都与这三个分量有关,而且这三个分量是高度相关的,所以连续变换颜色时并不直观,想对图像的颜色进行调整需要更改这三个分量才行。自然环境下获取的图像容易受自然光照、遮挡和阴影等情况的影响,即对亮度比较敏感。而 RGB 颜色空间的三个分量都与亮度密切相关,即只要亮度改变,三个分量都会随之相应地改变,而没有一种更直观的方式来表达。但是人眼对于这三种颜色分量的敏感程度是不一样的,在单色中,人眼对红色最不敏感,蓝色最敏感,所以 RGB 颜色空间是一种均匀性较差的颜色空间。如果颜色的相似性直接用欧氏距离来度量,其结果与人眼视觉会有较大的偏差。对于某一种颜色,我们很难推测出较为精确的三个分量数值来表示。所以,RGB 颜色空间适合于显示系统,却并不适合于图像处理。
基于上述理由,在图像处理中使用较多的是 HSV 颜色空间,它比 RGB 更接近人们对彩色的感知经验。非常直观地表达颜色的色调、鲜艳程度和明暗程度,方便进行颜色的对比。在 HSV 颜色空间下,比 BGR 更容易跟踪某种颜色的物体,常用于分割指定颜色的物体。HSV 表达彩色图像的方式由三个部分组成:
- Hue(色调、色相)
- Saturation(饱和度、色彩纯净度)
- Value(明度)
其中Hue用角度度量,取值范围为0~360°,表示色彩信息,即所处的光谱颜色的位置,如下图所示。
Hue色调取值图
如果我们想要过滤出黄色线,那么我们就可以将色调范围控制在[30~90]之间即可。注意,在OpenCV中色调范围是[0~180],因此上述黄色范围需要缩小1倍,即[15~45]。检测白色行道线也是采用类似的原理。
下面我们用代码实现一下:
import cv2
import numpy as np
#读取图像并转换到HSV空间
frame = cv2.imread('test.jpg')
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
黄色线检测
lower_blue = np.array([15, 40, 40])
upper_blue = np.array([45, 255, 255])
yellow_mask = cv2.inRange(hsv, lower_blue, upper_blue)
cv2.imwrite('yellow_mask.jpg',yellow_mask)
白色线检测
lower_blue = np.array([0, 0, 200])
upper_blue = np.array([180, 30, 255])
white_mask = cv2.inRange(hsv, lower_blue, upper_blue)
cv2.imwrite('white_mask.jpg',white_mask)
效果如下图所示:
可以看到,黄色区域和白色区域基本都检测出来了,当然还存在不少干扰区域,需要进一步处理。
3.2基于canny算子的边缘轮廓提取
目前我们仅获得了行道线区域,为了后续能够方便的计算行道线角度,我们需要得到行道线具体的轮廓/线段信息,即从区域中提取出线段。这里我们使用Canny算法实现。
Canny边缘检测是从不同视觉对象中提取有用的结构信息并大大减少要处理的数据量的一种技术,于1986年被提出,目前已广泛应用于各种计算机视觉系统。
Canny算法具体包括5个步骤:
1) 使用高斯滤波器,以平滑图像,滤除噪声。
2) 计算图像中每个像素点的梯度强度和方向。
3) 应用非极大值(Non-Maximum Suppression)抑制,以消除边缘检测带来的杂散响应。
4) 应用双阈值(Double-Threshold)检测来确定真实的和潜在的边缘。
5) 通过抑制孤立的弱边缘最终完成边缘检测。
具体实现细节我们不再详细剖析,在OpenCV中集成了canny算法,只需要一行代码即可实现canny边缘检测。
黄色线边缘提取
yellow_edge = cv2.Canny(yellow_mask, 200, 400)
cv2.imwrite('yellow_edge.jpg', yellow_edge)
白色线边缘提取white
white_edge = cv2.Canny(white_mask, 200, 400)
cv2.imwrite('white_edge.jpg', white_edge)
代码中200和400这两个参数表示canny算子的低、高阈值,按照opencv教程一般可以不用修改。
最终效果如下所示:
3.3感兴趣区域定位
在利用OpenCV对图像进行处理时,通常会遇到一个情况,就是只需要对部分感兴趣区域(Region Of Interest, ROI)进行处理。例如针对我们这个模拟平台上的智能小车任务来说,对于黄色行道线,我们只关注图像左下部分,而对于白色行道线,我们只关注图像右下部分即可。至于图像其他部分因为我们通过人工分析知道,这些区域我们并不需要处理。因此,针对黄色边缘我们只需要提取图像左下块区域,针对白色边缘我们只需要提取图像右下块区域。
def region_of_interest(edges, color='yellow'):
'''
感兴趣区域提取
'''
height, width = edges.shape
mask = np.zeros_like(edges)
# 定义感兴趣区域掩码轮廓
if color == 'yellow':
polygon = np.array([[(0, height * 1 / 2),
(width * 1 / 2, height * 1 / 2),
(width * 1 / 2, height),
(0, height)]], np.int32)
else:
polygon = np.array([[(width * 1 / 2, height * 1 / 2),
(width, height * 1 / 2),
(width, height),
(width * 1 / 2, height)]], np.int32)
# 填充感兴趣区域掩码
cv2.fillPoly(mask, polygon, 255)
# 提取感兴趣区域
croped_edge = cv2.bitwise_and(edges, mask)
return croped_edge
最终效果如下图所示:
到这里我们看到行道线区域基本定位的比较”干净”了。
3.4基于霍夫变换的线段检测
到目前,我们抽取出了比较精确的行道线轮廓,但是对于实际的自动驾驶任务来说还没有完成目标任务要求,我们要对行道线轮廓再进一步处理,得到行道线的具体线段信息(每条线段的起始点坐标)。本小节我们使用霍夫变换来完成这个任务。霍夫变换,英文名称Hough Transform,作用是用来检测图像中的直线或者圆等几何图形的。
具体的,一条直线的表示方法有好多种,最常见的是 _y=mx+b_的形式。结合我们这个任务,对于最终检测出的感兴趣区域,怎么把图片中的直线提取出来。基本的思考流程是:如果直线 _y=mx+b_在图片中,那么图片中,必需有N多点在直线上(像素点代入表达式成立),只要有这条直线上的两个点,就能确定这条直线。该问题可以转换为:求解所有的(m,b)组合。
设置两个坐标系,左边的坐标系表示的是(x,y)值,右边的坐标系表达的是(m,b)的值,即直线的参数值。那么一个(x,y)点在右边对应的就是一条线,左边坐标系的一条直线就是右边坐标系中的一个点。这样,右边左边系中的交点就表示有多个点经过(k,b)确定的直线。但是,该方法存在一个问题,(m,b)的取值范围太大。
Original: https://blog.csdn.net/qianbin3200896/article/details/119832583
Author: 钱彬 (Qian Bin)
Title: 一文掌握基于深度学习的自动驾驶小车开发(Pytorch实现,含完整数据和源码,树莓派+神经计算棒)
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/624470/
转载文章受原作者版权保护。转载请注明原作者出处!