爱折腾-创客智造实验室 - 无人驾驶汽车系统入门
type: channel
link: https://www.ncnynl.com/tag/无人驾驶汽车系统入门
items: 无人驾驶汽车系统入门（一）——卡尔曼滤波与目标追踪
items: 无人驾驶汽车系统入门（二）——高级运动模型和扩展卡尔曼滤波
items: 无人驾驶汽车系统入门（三）——无损卡尔曼滤波，目标追踪，C++
items: 无人驾驶汽车系统入门（四）——反馈控制入门，PID控制
items: 无人驾驶汽车系统入门（五）——运动学自行车模型和动力学自行车模型
items: 无人驾驶汽车系统入门（六）——基于传统计算机视觉的车道线检测(1)
items: 无人驾驶汽车系统入门（七）——基于传统计算机视觉的车道线检测(2)

无人驾驶汽车系统入门（一）——卡尔曼滤波与目标追踪
type: item
date: 2019-04-17T07:52:00+00:00
description: 无人驾驶汽车系统入门（一）——卡尔曼滤波与目标追踪 说明： 介绍无人驾驶汽车系统感知模块的重要技术——卡尔曼滤波 卡尔曼滤波按如下三个章节说明： 卡尔曼滤波与行人状态估计 扩展卡尔曼滤波（EKF）与传感器融合 处理模型，无损卡尔曼滤波（UKF）与车辆状态轨迹 本节为卡尔曼滤波，主要讲解卡尔曼滤波的具体推导，卡尔曼滤波在行人状态估计中的一个小例子。 为什么要学卡尔曼滤波？ 卡尔曼滤波以及其扩展算法能够应用于目标状态估计，如果这个目标是行人，那么就是行人状态估计（或者说行人追踪） 如果这个目标是自身，那么就是车辆自身的追踪（结合一些地图的先验，GPS等数据的话就是自身的定位）。 在很多的无人驾驶汽车项目中，都能找到卡尔曼滤波的扩展算法的身影（比如说EKF，UKF等等）。 本节我们从最简单的卡尔曼滤波出发，完整的理解一遍卡尔曼滤波的推导过程，并实现一个简单的状态估计Python程序。 卡尔曼滤波是什么？ 我们通常要对一些事物的状态去做估计，为什么要做估计呢？因为我们通常无法精确的知道物体当前的状态。 为了估计一个事物的状态，我们往往会去测量它，但是我们不能完全相信我们的测量，因为我们的测量是不精准的，它往往会存在一定的噪声，这个时候我们就要去估计我们的状态。 卡尔曼滤波就是一种结合预测（先验分布）和测量更新（似然）的状态估计算法。 概率论的知识基础 下面是一些概率论的基础知识，如果之前有这方面的知识储备那当然是最好的，很有利于我们理解整个博客内容 如果没有这方面的基础而且也看不懂下面的内容也没关系，我会以一个相对直观的方式来展现整个理论部分。 先验概率 P(X)P(X)：仅仅依赖主观上的经验，事先根据已有的只是的推断 后验概率 P(X|Z)P(X|Z)：是在相关证据或者背景给定并纳入考虑以后的条件概率 似然 P(Z|X)P(Z|X)：已知结果区推测固有性质的可能性 贝叶斯公式： 后验分布正比于先验分布乘以似然。 卡尔曼滤波完整推导 1.简单的例子 若干年后，我们的可回收火箭要降落到地球，我们比较关心的状态就是我们的飞行器的高度了，飞行器的高度就是我们想要估计的状态，我们会通过一些传感器去测量我们当前的高度信息，比如说使用气压计。 假如我们每一次测量，当前高度都变成上一次测量的 95%，那么我们就可以得到如下关系： 我们可以使用递归来表示这样的公式——为了计算我们当前的高度，我们必须知道我们上一个测量的高度，以此递推，我们就会推到我们的飞行器的初始高度。 但是，由于我们的测量往往来自于一些传感器（比如说GPS，气压计），所以测量的结果总是带有噪声的，这个噪声是有传感器本身引起的，那么我们的表达式就变成了： 这个噪声我们称之为测量噪声 (Measurement Noise)。 通常来说，这种噪声都满足高斯分布。我们用符号描述以上两部分： 第一个式子是我们的 处理模型，是我们的经验（比如说一些运动模型，牛顿力学等等），我们用这种处理模型去 预测 我们考察的事物的状态（在没有任何信息的情况下，或者说在没有任何测量数据的情况下）； 第二个式子是测量的表达式，它大致描述了我们测量的组成成分，我们用这个测量去 更新 我们对状态的估计。 其中 xk 是我们的飞行器当前的状态，xk−1 是我们上一个状态（注意状态和测量的区别） zk 是我们当前对飞行器的测量，vk 是我们当前的测量噪声， a 是一个常数，在我们这个例子里面就是 0.95 很显然，现实中的运动不会像我们这个简单的处理模型一样高度按比例缩小。 在这里为了简化，我们先假设我们的模型挺好的，能够大致描述出这颗“神奇火箭”的运动规律，只是偶尔会存在一定的偏差（比如所空气湍流的影响） 那么我们在计算xkxk 时在加一个噪声来描述我们的处理模型与实际运动的差异，这个噪声我们称之为处理噪声 (ProcessProcess NoiseNoise)，我们用 wt 来表示这种噪声，那么 xk 的计算公式就变成： 为了简化，后面的分析我们会先忽略处理噪声，但是我们在传感器融合的部分会将处理噪声重新考虑进来。 2.状态估计 因为我们是要估计飞行器的状态（高度），所以我们把测量的公式变换成： 计算卡尔曼增益 预测和更新 使用线性代数的方法来表示预测和更新 至此，卡尔曼滤波的完整推导就结束了。 下面，我们来看看卡尔曼滤波在无人汽车的感知模块的应用 卡尔曼滤波算法为什么会叫滤波算法？ 以一维卡尔曼滤波为例，如果我们单纯的相信测量的信号，那么这个信号是包含噪声的，是很毛糙的 但是当我们运行卡尔曼滤波算法去做估计，我们估计的信号会很光滑，看起来似乎滤掉了噪声的影响，所以称之为滤波算法。 实际上，卡尔曼滤波不仅仅过滤掉了测量信号的噪声，它同时也结合了以往的估计，卡尔曼滤波在线性问题中被证明是最优估计。 卡尔曼滤波在无人驾…

无人驾驶汽车系统入门（二）——高级运动模型和扩展卡尔曼滤波
type: item
date: 2019-04-17T08:52:00+00:00
description: 无人驾驶汽车系统入门（二）——高级运动模型和扩展卡尔曼滤波 说明： 介绍高级运动模型和扩展卡尔曼滤波 前言： 上一篇文章的最后我们提到卡尔曼滤波存在着一个非常大的局限性——它仅能对线性的处理模型和测量模型进行精确的估计，在非线性的场景中并不能达到最优的估计效果。 所以之前为了保证我们的处理模型是线性的，我们上一节中使用了恒定速度模型，然后将估计目标的加减速用处理噪声来表示，这一模型用来估算行人的状态其实已经足够了 但是在现实的驾驶环境中，我们不仅要估计行人，我们除了估计行人状态以外，我们还需要估计其他车辆，自行车等等状态，他们的状态估计显然不能使用简单的线性系统来描述 这里，我们介绍非线性系统情况下的一种广泛使用的滤波算法——扩展卡尔曼滤波(Extended Kalman Filter, EKF) 本节讲解非线性系统中广泛使用的扩展卡尔曼滤波算法，我们通常将该算法应用于实际的车辆状态估计（或者说车辆追踪）中。 另外，实际的车辆追踪运动模型显然不能使用简单的恒定速度模型来建模 在本节中会介绍几种应用于车辆追踪的高级运动模型。 并且已经其中的CTRV模型来构造我们的扩展卡尔曼滤波。 最后，在代码实例中，我会介绍如何使用EKF做多传感器融合。 应用于车辆追踪的高级运动模型 首先要明确的一点是，不管是什么运动模型，本质上都是为了帮助我们简化问题，所以我们可以根据运动模型的复杂程度（次数）来给我们常用的运动模型分一下类。 一次运动模型（也别称为线性运动模型）： 恒定速度模型（Constant Velocity, CV） 恒定加速度模型（Constant Acceleration, CA） 这些线性运动模型假定目标是直线运动的，并不考虑物体的转弯。 二次运动模型： 恒定转率和速度模型（Constant Turn Rate and Velocity，CTRV） 恒定转率和加速度模型（Constant Turn Rate and Acceleration，CTRA） CTRV CTRV目前多用于机载追踪系统（飞机），这些二次运动模型大多假定速度 vv 和 偏航角速度（yaw rate） ωω 没有关系，因此，在这类运动模型中，由于偏航角速度测量的扰动（不稳定），即使车辆没有移动，我们的运动模型下的角速度也会发生细微的变化。 为了解决这个问题，速度 vv 和 偏航角速度 ωω 的关联可以通过设定转向角 ΦΦ 恒定来建立，这样就引出了 恒定转向角和速度模型（Constant Steering Angle and Velocity，CSAV）, 另外，速度可以别假定为线性变化的，进而引出了常曲率和加速度模型（Constant Curvature and Acceleration，CCA）。 这些运动模型的关系如图： 运动模型的状态转移公式 由于除CCA以外，以上的运动模型都非常著名，故本文不提供详细的推导过程。本文提供CV和CTRV模型的状态转移公式。 状态转移公式：就是我们的处理模型由上一状态的估计计算下一个状态的先验分布的计算公式，可以理解为我们基于一定的先验 知识总结出来的运动公式。 扩展卡尔曼滤波 雅可比矩阵 处理噪声 测量 将预测映射到激光雷达测量空间： 虽然这个雅可比矩阵看似非常复杂，但是我们待会编程的时候并不需要完整的推导出这个雅可比矩阵的表达式，在本篇中，我们采用numdifftools这个公式来直接求解雅可比矩阵。 综上，EKF的整个过程为： 4.Python实现 和之前一样，为了实现交互式的代码，实例的代码为了便于理解，我们仍然使用大家熟悉的Python来实现 当然，实际无人车项目肯定是需要用C++来实现的，要将下面的示例代码使用C++来改写是非常简单快速的。 首先引入相关的库： from __future__ import print_function import numpy as np import matplotlib.dates as mdates import matplotlib.pyplot as plt from scipy.stats import norm from sympy import Symbol, symbols, Matrix, sin, cos, sqrt, atan2 from sympy import init_printing init_printing(use_latex=True) import numdifftools as nd import math 首先我们读取我们的数据集，该数据集包含了追踪目标的LIDAR和RADAR的测量值，以及测量的时间点 同时为了验证我们追踪目标的精度，该数据还包含了追踪目标的真实坐标。（数据下载链接见文章末尾） 其中第一列表示测量数据来自LIDAR…

无人驾驶汽车系统入门（三）——无损卡尔曼滤波，目标追踪，C++
type: item
date: 2019-04-17T08:52:00+00:00
description: 无人驾驶汽车系统入门（三）——无损卡尔曼滤波，目标追踪，C++ 说明： 介绍前面两篇文章我们了解了卡尔曼滤波以及扩展卡尔曼滤波在目标追踪的应用，我们在上一篇文章中还具体用Python实现了EKF，但是细心的同学会发现，EKF的效率确实很低，计算雅可比矩阵确实是一个很费时的操作，当问题（非线性的）一旦变得复杂，其计算量就变得十分不可控制。在此再向大家接受一种滤波——无损卡尔曼滤波（Unscented Kalman Filter， UKF） UKF 通过上一篇文章，我们已经知道KF不适用于非线性系统，为处理非线性系统，我们通过一阶泰勒展式来近似（用线性函数近似） 这个方案直接的结果就是，我们对于具体的问题都要求解对应的一阶偏导（雅可比矩阵），求解雅可比矩阵本身就是费时的计算，而且我们上一篇还使用了Python而非C++，而且我们图省事还用了一个叫做numdifftools的库来实现雅可比矩阵的求解而不是自行推导，这一切都造成了我们上一次博客的代码执行效率奇低！ 显然现实应用里面我们的无人车是不能接受这种延迟的，我相信很多同学也和我一样讨厌求解雅可比矩阵，那么，就让我们来学习一种相对简单的状态估计算法——UKF。 UKF使用的是统计线性化技术，我们把这种线性化的方法叫做无损变换（unscented transformation）这一技术主要通过n个在先验分布中采集的点（我们把它们叫sigma points）的线性回归来线性化随机变量的非线性函数 由于我们考虑的是随机变量的扩展，所以这种线性化要比泰勒级数线性化（EKF所使用的策略）更准确。 和EKF一样，UKF也主要分为预测和更新。 运动模型 非线性处理/测量模型 我们知道我们在应用KF是面临的主要问题就是非线性处理模型（比如说CTRV）和非线性测量模型（RADAR测量）的处理。 我们从概率分布的角度来描述这个问题： UKF的基本思路就是： 近似非线性函数的概率分布要比近似非线性函数本身要容易！ 那么如何去找一个与真实分布近似的高斯分布呢？——找一个与真实分布有着相同的均值和协方差的高斯分布。 那么如何去找这样的均值和方差呢？——无损变换。 无损变换 计算一堆点（术语叫做sigma point），通过一定的手段产生的这些sigma点能够代表当前的分布， 然后将这些点通过非线性函数（处理模型）变换成一些新的点， 然后基于这些新的sigma点计算出一个高斯分布（带有权重地计算出来）如图： 预测 由一个高斯分布产生sigma point 通常，假定状态的个数为 n ，我们会产生 2n+1 个sigma点， 其中第一个就是我们当前状态的均值 μ ，sigma点集的均值的计算公式为： 预测sigma point 预测均值和方差 测量更新 1.预测测量（将先验映射到测量空间然后算出均值和方差） 这篇博客继续使用上一篇（EKF）中的测量实验数据，那么我们知道，测量更新分为两个部分，LIDAR测量和RADAR测量， 其中LIDAR测量模型本身就是线性的，所以我们重点还是放在RADAR测量模型的处理上面，RADAR的测量映射函数为： 2.更新状态 http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.136.6539&rep=rep1&type=pdf https://www.cse.sc.edu/~terejanu/files/tutorialUKF.pdf http://ais.informatik.uni-freiburg.de/teaching/ws13/mapping/pdf/slam06-ukf-4.pdf UKF实例 我们继续基于上一篇文章的数据来做状态估计的实例，不过，为了更加接近实际实际的应用，我们本节采用C++实现。 由于本节使用的C++代码量相对较大，而且为多文件的项目，代码就不再一一贴出，所有代码我都已经上传至如下地址： http://download.csdn.net/download/adamshan/10041096 运行-效果 首先解压，编译: mkdir build cd build cmake .. make 运行： ./ukf ../data/data_synthetic.txt ../data/output.txt 得到计算的结果： Accuracy - RMSE: 0.0723408 0.0821208 0.342265 0.23017 我们发现，这个UKF要比我们上一篇博客写的EKF的效率要高得多得多。 我们执行可视化的python脚本看一下效果： cd ../data python plot.py 得到如下结果： 放大一点： UKF在我们这个问题中的执行效率和估计精度都高于我们上一篇文章中实现的E…

无人驾驶汽车系统入门（四）——反馈控制入门，PID控制
type: item
date: 2019-04-17T08:52:00+00:00
description: 无人驾驶汽车系统入门（四）——反馈控制入门，PID控制 说明： 前面几篇博客介绍了卡尔曼滤波的一些基本算法，其实目标追踪，定位，传感器融合还有很多问题要处理，这些我们在以后的系列博客中在进一步细讲，现在我想给大家介绍一下无人驾驶汽车系统开发中需要的控制相关的理论和技术，还是和第一篇说的那样，我想到哪就写到哪，追踪和定位等更高级的算法我在后面会继续写。所以感兴趣的同学可以关注我的博客，无人驾驶汽车系统入门系列博客会一直更新下去。 为什么需要控制理论 试想有如下场景，当你驾驶一辆汽车通过这个弯道的时候，假设你已经知道你要开的路线，那么你会怎么去操作控制你的车呢？ 显然，如果你不是专业的选手的话，你无法做到一步到位的控制，你需要一边观察车辆相对于你想要开的路线的相对偏差，一边调整你的方向盘的角度和油门踏板的力度，这种基于环境反馈的控制我们称为 反馈控制 。 反馈控制是现代控制理论的基础，这是反馈控制的一般思路： 我们希望我们控制的对象（无人车）能够按照我们希望（规划好）的路径行驶， 我们会将环境当前给我们的反馈（我们当前的位置）和参考线进行比较，得到我们当前偏离参考线的距离（误差）， 基于这个误差，我们设计一定的算法来产生输出信号，使得这个误差不断的变小，这样的过程就是反馈控制的一般过程。 那么我们如何基于这个误差来产生控制指令呢？ 我们最直观的感觉就是要让误差在我们的控制下逐渐变小直到为0： 0误差就意味着车一直在你想让它开的路径上开。如何减少误差就是我们这几篇博客要向大家介绍的内容。 为了了解反馈控制，我先向大家介绍 PID控制，PID控制是目前利用最为广泛的控制理论，我们以它为出发点讨论控制理论。 比例，积分和导数 PID就是指 比例（proportion）、积分（integral）、导数（derivative）， 这三项表示我们如何使用我们的误差来产生控制指令，整个流程如下： 1.P控制 考虑一个简单的情况，假设我们希望无人车按照图中绿线行驶，但是我们的车在如图所示的位置： 那么我们要转多少度角呢？如果都按照固定的角度转（如下图），那么车的轨迹将如图中所示： 那么显然坐这样的车是不舒服的。一个直观的解决方法就是使用比例控制。 如图所示，当偏差大的时候，我们偏转更多的角度，当偏差小的时候，则偏转小一点。 那么这就是P control（比例控制）这里我们使用 CTE（Cross Track Error） 作为偏差度量 ， CTE就是我们到参考线的距离。那么这个时候转角就变成了： 此时车辆虽然在参考线上，但是并不是我们希望的状态（它在下一刻就会偏离），但是对于P控制而言，这是理想状态，此时控制转角为0,因此，P控制会一次又一次的超过参考线（overshot），为了矫正这种overshot，我们需要考虑一个额外的误差项——CTE变化率。 2.PD控制 CTE的变化率描述了我们的无人车向着参考线方向移动的有多快，如果我们的无人车一直都完美的在参考线上运动的话，那么我们的CTE变化率就为0。那么这一项（描述误差的变化率）就可以用导数来表示，那么，现在我们的控制输出就变成了比例项和导数项求和的形式： PD控制似乎已经能够胜任良好的反馈控制了，但其实还不够，PD控制器可以保证正常的控制的需求，但是当环境存在扰动的时候，比如说下面这种情况： 车在受力发生轻微偏移以后，由于PD控制中下 P 项倾向于向参考线方向运动，而 D 项则尝试抵消这种倾向，造成无人车始终都无法沿着参考线运动，这个问题叫做 steady state error 为了解决这个问题，我们再引入一项—— 积分项。 3.PID控制 我们将积分项也就如到我们的控制输出函数中，这个时候，无人车的转角就可以表示为： 我们发现其实PID实现确实不难，但是三个系数的选择却很难，那么如何选择PID系数呢？ 我们可以在我们的控制循环中通过一定的算法不断尝试，下面我提供给大家一种寻找参数的算法： 具体的算法见我的C++代码实例。 PID C++代码 1.pid.cpp #include <limits> #include <iostream> #include "PID.h" //using namespace std; PID::PID() {} PID::~PID() {} void PID::Init(double Kp, double Ki, double Kd) { parameter.push_back(Kp); parameter.push_back(Ki); parameter.push_back(Kd); this->p_error = 99999999.; this->d_error = 0.0; this->i_error = 0.0; //twiddle parameter…

无人驾驶汽车系统入门（五）——运动学自行车模型和动力学自行车模型
type: item
date: 2019-04-17T08:53:00+00:00
description: 无人驾驶汽车系统入门（五）——运动学自行车模型和动力学自行车模型 说明： 在简要了解了PID控制以后，我们就要接触一些现代的控制算法。 在了解高级的车辆控制算法之前，掌握车辆运动模型是非常有必要的。 车辆运动模型就是一类能够描述我们的车辆的运动规律的模型。 显然，越复杂的模型就越接近现实中的车辆运动规律，本节我们一起了解一下两个广泛使用的车辆模型 运动学自行车模型（Kinematic Bicycle Model） 和 动力学自行车模型（Dynamic Bicycle Model） 无人驾驶系统 无人驾驶系统往往分成感知，决策和控制三个模块， 其中无人车的路径规划和底层控制是工作在不同的层的，路径规划层往往会基于更加高层的（感知层，定位层）的信息和底层的（控制层）的实时信息指定行驶的路径， 那么从路径规划层传来的就是车辆的参考路径，控制系统需要做的就是严格按照这个参考路径（以及速度等控制输入量）去驾驶我们的车辆， 一般来说，我们会用多项式的行驶来描述这个路径曲线，如下所示的三次多项式就可以描述绝大多数的路径了： 自行车模型（Bicycle Model） 首先我们要简化汽车运动，其中自行车模型就是简单且有效的简化方式。自行车模型基于如下几个假设： 车辆在垂直方向的运动被忽略掉了，也就是说我们描述的车辆是一个二维平面上的运动物体（可以等价与我们是站在天空中的俯视视角） 我们假设车辆的结构就像自行车一样，也就是说车辆的前面两个轮胎拥有一直的角度和转速等，同样后面的两个轮胎也是如此，那么前后的轮胎就可以各用一个轮胎来描述 我们假设车辆运动也和自行车一样，这意味着是前面的轮胎控制这车辆的转角 首先我们简单的在一个二维平面上描述一个车辆： 其中 θ是其在 Yaw 方向的偏转角度，它是相对于 x 轴的逆时针方向的角度，v 是 θ 方向的速度，L 是车辆的轴距（前后轮胎的距离）, (x,y) 是车辆的坐标。 下图是该车辆的自行车模型： 运动学自行车模型 然后我们定义我们模型中的状态量，运动学自行车模型使用四个状态量来描述车辆的当前状态： x : 即车辆当前的 x 坐标 y : 即车辆当前的 y 坐标 ψ : 即车辆当前的偏航角（Yaw 方向的偏角，往往用弧度来描述, 逆时针方向为正） v : 即车辆的速度 一个简单的运动学自行车模型如图所示： 由于绝大多数的汽车后轮都不能够偏转，所以我们的自行车模型就假定后轮的转角控制输入 δr=0， 也就是说，方向盘上的控制输入，都反映到了前轮的转角上了。 那么基于这个简单的运动学自行车模型，在给定了一个时刻的控制输入以后，我们可以计算求得 dt 时间以后我们车辆的状态信息（坐标，偏航角以及速度），那么这个模型就可以作为我们模型预测控制下的基础车辆模型了。 动力学自行车模型 前面的车辆运动学自行车模型其实还隐含着一个重要的假设，那就是： 车前轮的方向即是车辆当前的速度方向，在实际车辆运动过程中，当车辆在以相对高的速度行驶时，车轮的方向并不一定车辆当前的速度方向，这个时候，我们引入车辆的动力学自行车模型。 车辆动力学模型通过对轮胎和路面之间的复杂相互作用来描述车辆的运动。在一个动力模型中，我们需要考虑各种各样的力的作用，他们可以大致分为两类： 纵向力(Longitudinal force) 和 侧向力（Lateral force）, 纵向力就是使车辆前后移动的力量，而侧向力则促使车辆在横向移动，在力的相互作用过程中，轮胎起着决定性的作用（根据一定的物理常识，轮胎是车辆运动的一个重要的力的来源）。 运动学自行车模型的Python实现 我们使用Python代码简单实现一个运动学自行车模型类，作为后期我们使用模型预测控制的车辆模型。 from __future__ import print_function import math class KinematicModel(object): def __init__(self, x, y, psi, v, f_len, r_len): self.x = x self.y = y self.psi = psi self.v = v self.f_len = f_len self.r_len = r_len def get_state(self): return self.x, self.y, self.psi, self.v def update_state(self, a, delta, dt): beta = math.atan((self.r_len / (self.r_len + self.f_len)) * math.tan(delta)) self.x = self.x + self.v * math.cos(self.psi * beta) * dt se…

无人驾驶汽车系统入门（六）——基于传统计算机视觉的车道线检测(1)
type: item
date: 2019-04-17T08:53:00+00:00
description: 无人驾驶汽车系统入门（六）——基于传统计算机视觉的车道线检测(1) 说明： 介绍如何进行车道线检测 感知，作为无人驾驶汽车系统中的“眼睛”，是目前无人驾驶汽车量产和商用化的最大障碍之一（技术角度）， 目前，高等级的无人驾驶汽车系统仍然非常依赖于激光雷达的测量，通过激光雷达构造周围环境的3D地图，从而为无人驾驶系统的决策和规划提供准确的环境信息和自身相对的位置信息。 然而，激光雷达雷达在成本，解析度的方面都不理想，所以基于视觉的无人驾驶相关技术（主要在感知方面）近几年发展迅猛， 本节我们从传统的 计算机视觉（Computer Version, CV）出发，来了解并且实践基于传统计算机视觉算法的车道线检测技术。 视觉感知VS激光雷达 激光雷达能够为无人驾驶汽车提供障碍物检测，高精度地图和定位等多方面的支持，在目前各个研究团队广泛使用。 下图是激光雷达的简易构造： 激光雷达以一定的角速度旋转，发射出波长在600nm到1000nm之间的激光射线，同时收集来自反射点的信息，激光雷达每旋转一周，收集到的反射点的坐标构成的一个集合，我们称之为 点云（Point Cloud）。 下图就是一张点云图： 激光雷达具有可靠性高，障碍物细节分辨以及精准测距等优点，同时不受光线条件的影响。但是，激光雷达的价格居高不下，例如目前在各个研究团队中广泛使用的Velodyne HDL-64E 激光雷达的售价就在10万美元左右，这使得成本成为搭载激光雷达的无人驾驶汽车的商用化的障碍。 作为比较，基于计算机视觉（Computer Version, CV）的感知成本相对低，结合视觉，IMU+GPS以及毫米波雷达的方案是目前已经商用驾驶辅助系统中采用的主流方案（例如特斯拉的Autopilot），计算机视觉是指通过图像来感知和理解我们的世界的科学。 对于无人驾驶而言，计算机视觉可以帮助确定车道线的位置，识别车辆，行人以及其他事物，以确保无人车的形式安全，基于深度学习的技术，我们甚至可以实现端到端的自动驾驶（输入图像，输出操作）。 此外，人类驾驶员在做感知的时候只需要一双眼睛（偶尔可能需要用耳朵去听声音），而不需要精确知道距离障碍物有多少厘米，障碍物长宽高分别是多少厘米等，所以我们可以相信——无人驾驶的最终形态或许就像人类驾驶一样仅仅需要视觉感知。 本节我们将使用传统的计算机视觉技术进行车道线的检测，同时基于检测出来的车道线计算车道线的曲率和车辆偏离车道线中心线的距离。在下一届中，我们将使用TensorFlow实现基于深度学习的车道线检测。 相机标定 相机标定（Camera Calibration） 通常是做计算机视觉的第一步，首先，为什么要做相机标定呢？ 因为我们通过相机镜头记录下的图像往往存在一定程度的失真，这种失真往往表现为图像畸变（Image Distortion） 。 畸变分为两类： 径向畸变（radial distortion）: 由于透镜的特性，光线容易在相机镜头的边缘出现较小或者较大幅度的弯曲，称之为径向畸变。 这种畸变在普通廉价的镜头中表现更加明显，径向畸变主要包括桶形畸变和枕形畸变两种。 以下分别是枕形和桶形畸变示意图： 2.切向畸变（tangential distortion）： 是由于透镜本身与相机传感器平面（成像平面）或图像平面不平行而产生的， 这种情况多是由于透镜被粘贴到镜头模组上的安装偏差导致。 畸变说明： 畸变（distortion） 是对直线投影（rectilinear projection）的一种偏移。 简单来说直线投影是场景内的一条直线投影到图片上也保持为一条直线。 那畸变简单来说就是一条直线投影到图片上不能保持为一条直线了，这是一种光学畸变（optical aberration）。 可能由于摄像机镜头的原因，这里不讨论，有兴趣的可以查阅光学畸变的相关的资料。 直接看公式难以理解，我们看如下的图像： 这就是为什么我们标定相机通常使用棋盘图像了，下面我们使用OpenCV来实现一下相机标定。 相机，棋盘图 首先从各个角度对这个棋盘拍照，得到一系列如下的照片： 一般来说，使用相机在各个角度拍摄20张左右的棋盘图即可完成后面的标定工作。 使用OpenCV找出棋盘的对角点 首先读取图像并转为灰度图： from __future__ import print_function import numpy as np import cv2 import matplotlib.pyplot as plt import pickle %matplotlib inline cal = plt.imread('calibration1.jpg') fig1 = plt.figure(1, figsize=(16, 9)) cal_gray = cv2.cvt…

无人驾驶汽车系统入门（七）——基于传统计算机视觉的车道线检测(2)
type: item
date: 2019-04-17T08:53:00+00:00
description: 无人驾驶汽车系统入门（七）——基于传统计算机视觉的车道线检测(2) 说明： 介绍基于视觉的车道线识别 在获得ROI（透视变换）以后，我们对得到的“鸟瞰图”应用色彩阈值化和梯度阈值化，以得到鸟瞰图中“可能为”车道线的像素然后再使用滑动窗口来确定车道线的多项式系数，下面我们来具体讨论这两个步骤。 边缘检测 车道线的一个主要的特征就是它可以看作图像中的边缘（Edge）， 因此我们可以使用一定的 边缘检测(Edge detection) 算法将之从原图像中提取出来。 边缘检测的结果就是标识出了数字图像中亮度变化明显的点。 有许多用于边缘检测的方法，他们大致可分为两类：基于搜索和基于零交叉。 基于搜索的边缘检测方法首先计算边缘强度，通常用一阶导数表示，例如梯度模; 然后，用计算估计边缘的局部方向，通常采用梯度的方向，并利用此方向找到局部梯度模的最大值。 基于零交叉的方法找到由图像得到的二阶导数的零交叉点来定位边缘.通常用拉普拉斯算子或非线性微分方程的零交叉点。 import cv2 import numpy as np from matplotlib import pyplot as plt img = cv2.imread('test_images/shudu.jpeg', 0) laplacian = cv2.Laplacian(img, cv2.CV_64F) sobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3) sobely = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3) plt.subplot(2, 2, 1), plt.imshow(img, cmap='gray') plt.title('Original'), plt.xticks([]), plt.yticks([]) plt.subplot(2, 2, 2), plt.imshow(laplacian, cmap='gray') plt.title('Laplacian'), plt.xticks([]), plt.yticks([]) plt.subplot(2, 2, 3), plt.imshow(sobelx, cmap='gray') plt.title('Sobel X'), plt.xticks([]), plt.yticks([]) plt.subplot(2, 2, 4), plt.imshow(sobely, cmap='gray') plt.title('Sobel Y'), plt.xticks([]), plt.yticks([]) plt.show() 检测的结果： 我们分别使用OpenCV实现以上的内容： def abs_sobel_thresh(img, sobel_kernel=3, orient='x', thresh=(0, 255)): gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) if orient == 'x': sobel = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=sobel_kernel) else: sobel = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=sobel_kernel) abs_sobel = np.absolute(sobel) scaled_sobel = np.uint8(255 * abs_sobel / np.max(abs_sobel)) sxbinary = np.zeros_like(scaled_sobel) sxbinary[(scaled_sobel >= thresh[0]) & (scaled_sobel <= thresh[1])] = 1 return sxbinary def mag_thresh(img, sobel_kernel=3, mag_thresh=(0, 255)): # Convert to grayscale gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) # Take both Sobel x and y gradients sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=sobel_kernel) sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=sobel_kernel) # Calculate the gradient magnitude gradmag = np.sqrt(sobelx**2 + sob…