让车辆“学会”识别车道:使用计算机视觉进行车道检测
2017年10月20日 由 xiaoshan.xiang 发表
648790
0
所有人在开车时都要注意识别车道,确保车辆行驶时在车道的限制范围内,保证交通顺畅,并尽量减少与附近车道上其他车辆相撞的几率。对于自动驾驶车辆来说,这是一个关键任务。事实证明,使用计算机视觉技术可以识别道路上的车道标记。我们将介绍如何使用各种技术来识别和绘制车道的内部,计算车道的曲率,甚至估计车辆相对于车道中心的位置。
为了检测和绘制一个多边形(采用汽车当前所在车道的形状),我们构建了一个管道,由以下步骤组成:
- 一组棋盘图像的摄像机标定矩阵和畸变系数的计算
- 图像失真去除;
- 在车道线路上应用颜色和梯度阈值;
- 通过透视变换制作鸟瞰图;
- 使用滑动窗口寻找热车道线像素(hot lane line pixels);
- 二度多项式拟合,以确定构成车道的左右线;
- 车道曲率和车道中心偏离的计算;
- 图像中车道边界的变形和绘制,以及车道曲率信息。
我相信一张图胜过千言万语,所以这里是:
摄像机标定和图像失真去除
第一步是找到校准矩阵,以及用于拍摄道路照片的相机的畸变系数。这是非常必要的,因为相机镜头的凸形曲线在进入针孔时弯曲光线,从而造成了真实图像的扭曲。因此,真实世界里的直线在我们的照片中可能不再是直线了。
为了计算相机的变换矩阵和畸变系数,我们在同一相机拍摄的平面上使用了多个棋盘图像。OpenCV有一种简便的方法叫做findChessboardCorners ,它可以识别出黑白方块相交的点,并以这种方式逆向工程畸变矩阵。下图显示了示例图像上所标识的棋盘角:
我们可以看到,角点被很好地识别了。接下来,我们在从不同角度拍摄的多个棋盘图像上运行棋盘查找算法,通过识别图像和物体点来校准相机。前者是指二维映射中的坐标,后者表示三维空间中的图像点的真实坐标(棋盘图像的z轴,或深度= 0)。这些映射使我们能够正确的消除由同一照相机拍摄造成的图像失真。你可以在下图中看到它的效果:
我们现在可以反转所有图像失真,如下图所示:
阈值
我们在这一节中应用颜色和边缘阈值来更好地检测线,更容易找到最好的描述左右通道的多项式。
我们首先开始探索应该采用哪些颜色空间来增加探测车道的机会,并促进梯度阈步骤的任务。
颜色阈值
我们用不同的颜色空间进行实验,看看在最有效的车道线路分离上应该使用哪些颜色空间和通道:
在RGB组件上,我们看到蓝色通道在识别黄线时最差,而红色通道似乎给出了最佳效果。
对于HLS和HSV,颜色相通道产生非常杂乱的输出,而HLS的饱和通道似乎给出了不错的结果; 它优于HSV的饱和通道。 相反,HSV的亮度通道给出了非常清晰的灰度图像,特别是在黄线上,比HLS的亮度通道好得多。
LAB的A通道表现差强人意,而B通道在识别黄线方面很强。但它是识别黄线和白线的亮度通道(没有双关语)。
在这个阶段,我们面临各种有利弊的选择。我们的目标是在给定的颜色通道上找到正确的阈值,以突显车道的黄线和白线。实际上有很多方法可以实现这个结果,我们选择使用HLS,是因为我们已经知道如何设置项目1:简单通道检测中的黄色和白色通道线的阈值。
简单通道检测地址:
https://github.com/kenshiro-o/CarND-LaneLines-P1
下面的代码展示了我们如何在HLS上为白色和黄色(我们的车道颜色)设置阈值,并产生二进制图像:
def compute_hls_white_yellow_binary(rgb_img):
"""
Returns a binary thresholded image produced retaining only white and yellow elements on the picture
The provided image should be in RGB format
"""
hls_img = to_hls(rgb_img)
# Compute a binary thresholded image where yellow is isolated from HLS components
img_hls_yellow_bin = np.zeros_like(hls_img[:,:,0])
img_hls_yellow_bin[((hls_img[:,:,0] >= 15) & (hls_img[:,:,0] <= 35))
& ((hls_img[:,:,1] >= 30) & (hls_img[:,:,1] <= 204))
& ((hls_img[:,:,2] >= 115) & (hls_img[:,:,2] <= 255))
] = 1
# Compute a binary thresholded image where white is isolated from HLS components
img_hls_white_bin = np.zeros_like(hls_img[:,:,0])
img_hls_white_bin[((hls_img[:,:,0] >= 0) & (hls_img[:,:,0] <= 255))
& ((hls_img[:,:,1] >= 200) & (hls_img[:,:,1] <= 255))
& ((hls_img[:,:,2] >= 0) & (hls_img[:,:,2] <= 255))
] = 1
# Now combine both
img_hls_white_yellow_bin = np.zeros_like(hls_img[:,:,0])
img_hls_white_yellow_bin[(img_hls_yellow_bin == 1) | (img_hls_white_bin == 1)] = 1
return img_hls_white_yellow_bin
结果如下:
正如在上面看到的,HLS颜色阈值在图像上取得了很好的效果。阈值不能确定前面树的阴影下的黄线。我们相信在这种情况下,梯度阈值可以起到帮助作用。
梯度阈值
我们使用Sobel运算符来识别梯度,它表示图像中颜色强度的变化。较高的值表示强烈的颜色变化。
我们已经决定使用LAB的L通道作为我们的单通道图像,作为下面的sobel函数的输入。
我们尝试了许多不同的参数和不同的Sobel操作,并得出了最终的结果:
Sobel操作地址:
https://github.com/kenshiro-o/CarND-Advanced-Lane-Lines/blob/master/notebook.ipynb
我们选取底部的第二幅图像作为最佳结果。请注意,我们在选择的图像上应用了一个15x15像素的内核,从而有效地平滑了像素,生成了一个更干净的二进制图像。
结合两种方法
我们结合了颜色和Sobel阈值二进制图像,并得到以下结果:
在左边的图像中,所有的绿色像素都被Sobel阈值所保留,而蓝色的像素被HLS颜色阈值识别。结果非常令人鼓舞,我们似乎找到了正确的参数去检测车道。我们把视线转换到我们的图像上,并产生车道鸟瞰图。
透视转换
我们现在需要在二维图像中定义一个梯形区域,它将经过一个透视转换,转换成鸟的视角,如下图所示:
然后我们定义4个额外的点,将其组成一个矩形,这将映射到源梯形中的像素上:
dst_pts = np.array([[200, bottom_px], [200, 0], [1000, 0], [1000, bottom_px]], np.float32)
透视变换产生以下类型的图像:
将所有的东西结合在一起
我们可以看到,我们的视角转换将保持直线,这是一个必要的完整性检查。然而,以上示例的曲线并不完美,但它们不应该为我们的算法带来无法克服的问题。
我们现在可以将阈值应用到我们的鸟瞰图上:
直方图
然后,我们在图像的下半部分计算y方向的二进制阈值图像的直方图,以识别像素强度最高的x位置:
找到线路并绘制车道区域
滑动窗口
由于我们现在知道像素的起始x位置(从图像的底部)最有可能产生一条车道线,我们将运行一个滑动窗口搜索,试图“捕获”车道线的像素坐标。
滑动窗口搜索地址:
https://www.coursera.org/learn/machine-learning/lecture/bQhq3/sliding-windows
我们通过numpy的polyfit简单地计算二级多项式,以找到最适合左右线路的曲线系数。
polyfit地址:
https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.polyfit.html
改进算法的一种方法是将保存先前计算过的帧t - 1的系数,并尝试从这些系数中找到我们的车道像素。当我们没有找到足够的车道像素(少于85%的非零像素)时,我们就会回到滑动窗口搜索,以帮助我们在车道上拟合更好的曲线。
车道曲率
我们也通过计算最小圆的半径来计算车道曲率,该圆可以与我们的车道线相切——在直行车道上,半径将会很大。我们通过定义出适当的像素高度对车道长度和像素宽对车道宽度的比率,将像素空间转换为米(又称实际单位):
# Height ratio: 32 meters / 720 px
self.ym_per_px = self.real_world_lane_size_meters[0] / self.img_dimensions[0]
# Width ratio: 3.7 meters / 800 px
self.xm_per_px = self.real_world_lane_size_meters[1] / self.lane_width_px
我试图通过参考资源(资源地址:
https://www.psychologicalscience.org/news/motr/lines-on-the-road-are-longer-than-you-think.html)的数据来手动估算鸟瞰图上车道的长度:每次一辆汽车行驶超过40英尺(约12.2米)时。样本图像中的鸟瞰图大概覆盖了32米。按照美国高速公路的标准,宽度仍为3.7米。你可以通过该链接(链接地址:
https://www.intmath.com/applications-differentiation/8-radius-curvature.php)在曲率半径的数学基础上找到更多的信息。
我们也可以通过抵消车道左、右线路的起始(即底部)坐标的平均值,减去中间点作为偏移量,再乘以车道的象素与真实世界的宽度比,来计算出汽车与车道中心的距离。
展开绘制的车道区域
最后,我们将车道的内部绘制成绿色并展开图像,从鸟瞰图到原始的无失真图像。此外,我们将这个大图像与我们的车道检测算法的小图像叠加在一起,以更好地感知框架上发生了什么。我们还添加了关于车道曲率和车辆中心位置的文本信息:
最终结果
下面的gif显示我们已经构建了一个强大的车道探测管道。
另外,我在Youtube上上传了一段视频,在视频中我绘制了车道,并添加了额外的信息,比如车道曲率近似值。视频地址
https://www.youtube.com/watch?v=fJBHd5S6jgo&feature=youtu.be
[video width="1280" height="720" mp4="https://www.atyun.com/uploadfile/2017/10/videoplayback-4.mp4"][/video]
结论
我们已经介绍了如何执行摄像机标定,颜色和梯度阈值,以及透视变换和滑动窗口来识别车道线。
我们相信这个项目需要很多改进,比如:
- 用LAB和YUV颜色空间进行实验来决定我们是否能产生更好的颜色阈值;
- 使用卷积代替滑动窗口来识别热像素;
- 产生一个前帧的线系数的指数移动平均值,当我们的像素检测失败时使用它;
- 更好地检测像素“捕获”的异常(例如,一些非零像素完全脱离了线路)并拒绝它们;
- 应用其他本项目未涵盖的相关计算机视觉技术。