在这篇文章中,我将介绍如何从视频中查找并标记车道。被标记的车道会显示到视频上,并得到当前路面的曲率以及车辆在该车道内的位置。首先我们需要对图像进行相机失真校正,这里就不作详细介绍了。我们的关键任务是识别图片中属于车道的像素,为此我们使用了“颜色阈值”的概念。
梯度阈值
在Canny Edge Detection中,我们采用了整体梯度,这有助于我们检测强度或颜色急剧变化的区域。为此,canny边缘检测使用Sobel算子,该算子近似于在一个方向上获取图像的导数。运算符由一对卷积内核组成。
总梯度的大小由以下公式给出:
而渐变的方向是:
让我们尝试分离出“幅度”和“梯度方向”,而不是采用整体梯度。在某些情况下,这可以提供更大的优势。车道线,如果车道不太弯曲,则与图像中的垂直线更接近。因此,ax方向梯度比ay方向更有意义。采取单独的x、y梯度大小或方向,都有相应的优点。我们可以应用不同的阈值以达到期望的结果。
Sobel X,Y阈值
OpenCV具有sobel函数,可沿x,y方向获取梯度,该函数还可用于使用上述公式创建仅幅度和方向的阈值。完全不需要将图形转换为灰度,就可以提供很好的视觉效果。阈值只是创建二进制图像的一种方法,其中将满足条件的每个像素更改为1,将其他像素设置为0。
代码语言:javascript复制import numpy as np
import cv2
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import pickle
# Read in an image and grayscale it
image = mpimg.imread('straight_lines1.jpg')
# Define a function that applies Sobel x or y,
# then takes an absolute value and applies a threshold.
# Note: calling your function with orient='x', thresh_min=5, thresh_max=100
def abs_sobel_thresh(img, orient='x', thresh_min=0, thresh_max=255):
# Apply the following steps to img
# 1) Convert to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
# 2) Take the derivative in x or y given orient = 'x' or 'y'
sobel = cv2.Sobel(gray, cv2.CV_64F, orient=='x', orient=='y')
# 3) Take the absolute value of the derivative or gradient
abs_sobel = np.absolute(sobel)
# 4) Scale to 8-bit (0 - 255) then convert to type = np.uint8
scaled_sobel = np.uint8(255*abs_sobel/np.max(abs_sobel))
# 5) Create a mask of 1's where the scaled gradient magnitude
# is > thresh_min and < thresh_max
sxbinary = np.zeros_like(scaled_sobel)
sxbinary[(scaled_sobel >= thresh_min) & (scaled_sobel <= thresh_max)] = 1
# 6) Return this mask as your binary_output image
binary_output = sxbinary # Remove this line
return binary_output
# Run the function
grad_binary_x = abs_sobel_thresh(image, orient='x', thresh_min=20, thresh_max=100)
grad_binary_y = abs_sobel_thresh(image, orient='y', thresh_min=20, thresh_max=100)
# Plot the result
f, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(24, 9))
f.tight_layout()
ax1.imshow(image)
ax1.set_title('Original Image', fontsize=30)
ax2.imshow(grad_binary_x, cmap='gray')
ax2.set_title('Thresholded Gradient in X', fontsize=30)
ax3.imshow(grad_binary_y, cmap='gray')
ax3.set_title('Thresholded Gradient in Y', fontsize=30)
plt.subplots_adjust(left=0., right=1, top=0.9, bottom=0.)
上面代码的输出显示了不同阈值之间的差异。请注意,X梯度阈值看起来似乎更好一些,可以满足我们的需求。
索贝尔阈值
类似地,使用整体梯度的幅值作为阈值可以组合一些单独的X,Y梯度特征。
梯度幅度阈值
同样,我们可以在梯度方向上应用阈值。这些图中的车道线在45至60度范围内。可以在该角度范围内使用适当的正切值。
梯度方向阈值
色彩空间
色彩空间是分析图像的非常有用的工具。有多种颜色空间模型可用于定义图像中的颜色。最简单的RGB(红色绿色蓝色)模型根据红色,绿色和蓝色成分定义颜色。每个分量可以取0到255之间的值,其中[0,0,0]代表黑色,[255,255,255]代表白色。RGB被认为是“加法”颜色空间,可以将颜色想象成红色,绿色和蓝色的不同组合。OpenCV具有多种功能以利用不同的色彩空间。不过要注意的另一件事是,OpenCV默认会读取BGR中的图像,该图像可以转换为RGB。
RGB通道
请注意,在蓝色通道中,黄色车道线在红色通道中最亮时却不可见。因此,这里红色通道可能是查找车道线最有用的通道。请注意,我使用了灰度图来显示不同的颜色通道。除RGB外,还有其他多种颜色空间模型,例如CMYK,HLS,HSV,LAB等。HSV和HLS 代表色相,饱和度和亮度/亮度,这对于识别图像的对比度特别有用。
HSV颜色空间
色相是不同的颜色,饱和度是颜色的强烈程度,值是亮度值。大家可以尝试不同的色彩空间和色彩通道,以查看适合当前程序的是哪一种形式。一旦知道正确的色彩空间和色彩通道,就可以应用阈值设置。就当前程序而言最适合HLS色彩空间的S通道。
HLS色彩空间
我们使用以下代码进行颜色阈值分割:
代码语言:javascript复制import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np
import cv2
# Read in an image, you can also try test1.jpg or test4.jpg
image = mpimg.imread('straight_lines1.jpg')
# Define a function that thresholds the S-channel of HLS
# Use exclusive lower bound (>) and inclusive upper (<=)
def hls_select(img, thresh=(0, 255)):
# 1) Convert to HLS color space
hls = cv2.cvtColor(img, cv2.COLOR_RGB2HLS)
# 2) Apply a threshold to the S channel
binary_output = np.zeros_like(hls[:,:,2])
binary_output[(hls[:,:,2] > thresh[0]) & (hls[:,:,2] <= thresh[1])] = 1
# 3) Return a binary image of threshold result
#binary_output = np.copy(img) # placeholder line
return binary_output
hls_binary = hls_select(image, thresh=(180, 255))
# Plot the result
f, (ax1, ax2) = plt.subplots(1, 2, figsize=(24, 9))
f.tight_layout()
ax1.imshow(image)
ax1.set_title('Original Image', fontsize=50)
ax2.imshow(hls_binary, cmap='gray')
ax2.set_title('Thresholded S', fontsize=50)
plt.subplots_adjust(left=0., right=1, top=0.9, bottom=0.)
S阈值分割
得出正确的阈值并不总是那么容易。一种方法是使用3D散点图。我们可以绘制图片的各个通道,然后近似我们可能感兴趣的值。
HLS散点图
一旦知道要使用的渐变,色彩空间和通道,就可以组合各种阈值。对于这个特定的项目,我在HLS色彩空间中使用了X方向梯度和S通道来应用阈值。
透视变换(如前一篇文章中所述)被应用于生成的二进制图像以获得鸟瞰图。在2D图像中,对象距视点越远显得越小。因此,最好对未变形的阈值图像执行透视变换,以鸟瞰车道线,以便以后可以准确地完成通过它们的曲线拟合。
透视变换
由于matplotlib和opencv读取图像的方式不同(RGB与BGR),因此颜色在图片中看起来也有所不同。下一步是沿车道线拟合曲线。
线查找方法:直方图中的峰
在对道路图像应用校准,阈值和透视变换后,大家应该拥有一个二进制图像,其中车道线清晰可见。但是仍然需要明确确定哪些像素是线条的一部分,哪些像素属于左线条,哪些像素属于右线条。对此图像绘制二进制激活在何处发生的直方图是一种可能的解决方案。 沿着图像下半部分的所有列获取直方图,如下所示:
该直方图中的两个最突出的峰将很好地指示车道线底部的x位置。我们可以将其用作在哪里搜索线的起点。从这一点开始,我们可以使用围绕线心放置的滑动窗口来查找并跟随线直到框架的顶部。
滑动窗算法
遵循以下算法:
1-在图像中识别所有非零像素
2-接着,在泳道的x位置处定义滑动窗口,并且识别出现在窗口内的所有非零像素。
3-滑动窗口沿Y方向移动,以查找更多非零像素,并在X偏移其平均值的情况下,以防我们发现超过设定的数量。
4-一旦我们拥有了整个图像中所有适合车道的像素候选,就通过它们拟合二阶多项式f(y)= Ay2 By C
5-分别对左右车道线重复上述步骤。
拟合车道线
一旦知道线条在哪里,就很合适!在视频的下一帧中,我们无需再次进行盲目搜索,而只需在前一行位置周围的空白处搜索即可。
测量曲率
一旦多项式通过车道线拟合,就可以使用Curvdist()函数计算其曲率半径。我们可以在曲线的局部区域上绘制一个与附近点非常契合的圆。
曲线y = f(x)的任意点x的曲率半径的公式为
为了将像素值覆盖到道路单位中,使用以下转换
ym_per_pix = 30/720 xm_per_pix = 3.7 / 700
它们以米/像素为单位
为了计算到中心的距离,假定摄像机安装在汽车的中心。左车道和右车道的平均值在图像的底部获取,然后从图像的中心减去。然后,将距离乘以xm_per_pix乘以将其转换为米。一旦车道线被识别,就使用在透视变换步骤中计算出的矩阵的逆矩阵将整个车道弯回到原始图像上。最后,对每一帧重复上述步骤,以识别视频中的车道线:它标记了车道,左上角的文字告诉您车道的曲率和车辆在该车道中的位置。该管道对于给定的视频效果很好。但是,在车道曲率更大的情况下,它会遇到困难。为了解决这个问题,最好将拟合的所有系数存储为一帧到另一帧的历史记录,并查找任何重大偏离。考虑到较大的曲率,更新滑动窗口也可能很有用。
代码链接:https://github.com/architras/Advanced_Lane_Lines