计算机视觉算法使用OPENCV图像处理,摄像头校准,透视变换,颜色口罩,Sobels和多项式拟合来计算道路曲率和车道车辆的偏移。
在识别前方道路的几何形状方面,高级泳道的发现项目离[车道线检测]较远。
使用高速公路驾驶的视频录制,该项目的目标是计算道路曲率的半径。弯曲的道路比直线公路更具挑战性。要正确计算曲率,需要识别车道线,但最重要的是,图像不得不被关注。图像转换对于相机校准和透视转换是必要的,以获得鸟类的眼光。
该项目在Python中实现,并使用OpenCV图像处理库。源代码可以在AdvancedLaneFinding.ipynb中找到
[视频失真]是一种物理现象,发生在图像记录中,当通过摄像机镜头感知时,直线将直线投影为略微弯曲的现象。高速公路驾驶视频是使用汽车上的前面摄像头录制的,并且图像被扭曲。失真系数对每个相机都是特异性的,可以使用已知的几何形式来计算。
用嵌入式相机捕获的棋盘图像在camera_cal文件夹中提供。这些图像的优点是它们具有高对比度和已知的几何形状。提供的图像目前可以使用9 * 6个角落。
# Object points are real world points, here a 3D coordinates matrix is generated
# z coordinates are 0 and x, y are equidistant as it is known that the chessboard is made of identical squares
objp = np.zeros((6*9,3), np.float32)
objp[:,:2] = np.mgrid[0:9,0:6].T.reshape(-1,2)
对象点是基于共同的理解设置的,即在国际象棋板图案中,所有正方形都是相等的。这意味着对象点将具有从网格索引生成的x和y坐标,而z始终为0。图像点代表使用OpenCV的函数findChessboardCorners在图像中找到的相应对象点。
# Convert to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Find the chessboard corners
nx = 9
ny = 6
ret, corners = cv2.findChessboardCorners(gray, (nx, ny), None)
扫描所有图像后,图像点列表具有足够的数据来与对象点进行比较,以计算摄像机矩阵和失真系数。这导致了使用“ CalibrateCamera”功能的精确摄像机矩阵和失真系数识别。
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
undist = cv2.undistort(img, mtx, dist, None, mtx)
OPENCV undistort函数用于使用摄像机矩阵和失真系数转换图像。
比较这些图片时,可见相机校准技术的结果。在棋盘图片上,扭曲更为明显,在路上,它更加微妙。然而,未呈现的图片会导致不正确的道路曲率计算。
为了缩小曲率,理想的视角是鸟类的视野。这意味着从上方感知道路,而不是通过车辆的挡风玻璃角度来看。
这种透视转换是使用直线车道的场景计算的,并且先前的常识是车道线实际上是平行的。源和目标点直接从图像中直接识别出透视转换。
#Source points taken from images with straight lane lines, these are to become parallel after the warp transform
src = np.float32([
(190, 720), # bottom-left corner
(596, 447), # top-left corner
(685, 447), # top-right corner
(1125, 720) # bottom-right corner
])
# Destination points are to be parallel, taking into account the image size
dst = np.float32([
[offset, img_size[1]], # bottom-left corner
[offset, 0], # top-left corner
[img_size[0]-offset, 0], # top-right corner
[img_size[0]-offset, img_size[1]] # bottom-right corner
])
OPENCV提供了透视转换函数,以计算给定源和目标点的图像的转换矩阵。使用warpPerspective功能,执行了鸟的眼睛视图转换。
# Calculate the transformation matrix and it's inverse transformation
M = cv2.getPerspectiveTransform(src, dst)
M_inv = cv2.getPerspectiveTransform(dst, src)
warped = cv2.warpPerspective(undist, M, img_size)
目的是以这样的方式处理图像,以使车道线像素被保留并易于与道路区分开。应用四个转换,然后组合。
第一个转换将x sobel在灰度尺度的图像上拍摄。这代表X方向的导数,并有助于检测倾向于垂直的线。仅保留高于最小阈值的值。
# Transform image to gray scale
gray_img =cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Apply sobel (derivative) in x direction, this is usefull to detect lines that tend to be vertical
sobelx = cv2.Sobel(gray_img, cv2.CV_64F, 1, 0)
abs_sobelx = np.absolute(sobelx)
# Scale result to 0-255
scaled_sobel = np.uint8(255*abs_sobelx/np.max(abs_sobelx))
sx_binary = np.zeros_like(scaled_sobel)
# Keep only derivative values that are in the margin of interest
sx_binary[(scaled_sobel >= 30) & (scaled_sobel <= 255)] = 1
第二个转换选择灰色缩放图像中的白色像素。白色是由200至255之间的值定义的,这些值是在给定图片上使用反复试验选择的。
# Detect pixels that are white in the grayscale image
white_binary = np.zeros_like(gray_img)
white_binary[(gray_img > 200) & (gray_img <= 255)] = 1
第三个转换是使用HLS Colorspace在饱和组件上。这对于在光混凝土路上检测黄线尤为重要。
# Convert image to HLS
hls = cv2.cvtColor(img, cv2.COLOR_BGR2HLS)
H = hls[:,:,0]
S = hls[:,:,2]
sat_binary = np.zeros_like(S)
# Detect pixels that have a high saturation value
sat_binary[(S > 90) & (S <= 255)] = 1
第四个转换在色调分量上,值从10到25,被识别为对应于黄色。
hue_binary = np.zeros_like(H)
# Detect pixels that are yellow using the hue component
hue_binary[(H > 10) & (H <= 25)] = 1
车道线检测是对已经未经扭曲和扭曲的二元阈值图像进行的。最初,在图像上计算直方图。这意味着在每列上求和,以检测左右车道线的最可能的X位置。
# Take a histogram of the bottom half of the image
histogram = np.sum(binary_warped[binary_warped.shape[0]//2:,:], axis=0)
# Find the peak of the left and right halves of the histogram
# These will be the starting point for the left and right lines
midpoint = np.int(histogram.shape[0]//2)
leftx_base = np.argmax(histogram[:midpoint])
rightx_base = np.argmax(histogram[midpoint:]) + midpoint
从图像底部的这些基本位置开始,应用滑动窗口方法向上搜索线像素。当X和Y坐标位于窗口定义的区域内时,请考虑采用车道像素。当检测到足够的像素以确信它们是线路的一部分时,它们的平均位置将被计算并保留为下一个上下窗口的起点。
# Choose the number of sliding windows
nwindows = 9
# Set the width of the windows +/- margin
margin = 100
# Set minimum number of pixels found to recenter window
minpix = 50
# Identify window boundaries in x and y (and right and left)
win_y_low = binary_warped.shape[0] - (window+1)*window_height
win_y_high = binary_warped.shape[0] - window*window_height
win_xleft_low = leftx_current - margin
win_xleft_high = leftx_current + margin
win_xright_low = rightx_current - margin
win_xright_high = rightx_current + margin
# Identify the nonzero pixels in x and y within the window #
good_left_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) &
(nonzerox >= win_xleft_low) & (nonzerox < win_xleft_high)).nonzero()[0]
good_right_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) &
(nonzerox >= win_xright_low) & (nonzerox < win_xright_high)).nonzero()[0]
# Append these indices to the lists
left_lane_inds.append(good_left_inds)
right_lane_inds.append(good_right_inds)
所有这些像素都放在X和Y坐标的列表中。这是在两条车道线上对称完成的。 leftx , lefty , rightx , righty像素位置是从功能返回的,然后在左右侧拟合二级多项式,以找到所选像素的最佳线路拟合。
# Fit a second order polynomial to each with np.polyfit() ###
left_fit = np.polyfit(lefty, leftx, 2)
right_fit = np.polyfit(righty, rightx, 2)
在这里,确定的左和右线像素分别以红色和蓝色标记。第二度多项式在结果图像上追踪。
为了将车道线搜索从一个视频框架加快速度,使用了上一个周期中的信息。下一个图像更有可能具有靠近先前车道线路的车道线。这是对上图像的左线和右线的多项式拟合来定义搜索区域。
仍使用滑动窗口方法,但是搜索是沿着先前的行进行搜索,而不是从直方图的峰值开始,并为窗口的宽度提供了给定的边距。
# Set the area of search based on activated x-values within the +/- margin of our polynomial function
left_lane_inds = ((nonzerox > (prev_left_fit[0]*(nonzeroy**2) + prev_left_fit[1]*nonzeroy +
prev_left_fit[2] - margin)) & (nonzerox < (prev_left_fit[0]*(nonzeroy**2) +
prev_left_fit[1]*nonzeroy + prev_left_fit[2] + margin))).nonzero()[0]
right_lane_inds = ((nonzerox > (prev_right_fit[0]*(nonzeroy**2) + prev_right_fit[1]*nonzeroy +
prev_right_fit[2] - margin)) & (nonzerox < (prev_right_fit[0]*(nonzeroy**2) +
prev_right_fit[1]*nonzeroy + prev_right_fit[2] + margin))).nonzero()[0]
# Again, extract left and right line pixel positions
leftx = nonzerox[left_lane_inds]
lefty = nonzeroy[left_lane_inds]
rightx = nonzerox[right_lane_inds]
righty = nonzeroy[right_lane_inds]
搜索返回leftx , lefty , rightx , righty Pixel坐标,这些坐标拟合了每个左侧和右侧的二级多项式函数。
为了计算半径和车辆在道路上的位置以米为单位,需要从像素转换为缩放因素。相应的缩放值在y方向上为30米至720像素,在x尺寸中为3.7米到700像素。
多项式拟合用于进行转换。使用从每个左右车道线的拟合线的对齐像素的X坐标,应用了转换因子,并且在每个线路上进行多项式拟合。
# Define conversions in x and y from pixels space to meters
ym_per_pix = 30/720 # meters per pixel in y dimension
xm_per_pix = 3.7/700 # meters per pixel in x dimension
left_fit_cr = np.polyfit(ploty*ym_per_pix, left_fitx*xm_per_pix, 2)
right_fit_cr = np.polyfit(ploty*ym_per_pix, right_fitx*xm_per_pix, 2)
# Define y-value where we want radius of curvature
# We'll choose the maximum y-value, corresponding to the bottom of the image
y_eval = np.max(ploty)
# Calculation of R_curve (radius of curvature)
left_curverad = ((1 + (2*left_fit_cr[0]*y_eval*ym_per_pix + left_fit_cr[1])**2)**1.5) / np.absolute(2*left_fit_cr[0])
right_curverad = ((1 + (2*right_fit_cr[0]*y_eval*ym_per_pix + right_fit_cr[1])**2)**1.5) / np.absolute(2*right_fit_cr[0])
曲率的半径是使用图像底部的y点计算的。为了计算车辆的位置,使用像素中的多项式拟合来确定对应于图像底部y的左右车道的X位置。
# Define conversion in x from pixels space to meters
xm_per_pix = 3.7/700 # meters per pixel in x dimension
# Choose the y value corresponding to the bottom of the image
y_max = binary_warped.shape[0]
# Calculate left and right line positions at the bottom of the image
left_x_pos = left_fit[0]*y_max**2 + left_fit[1]*y_max + left_fit[2]
right_x_pos = right_fit[0]*y_max**2 + right_fit[1]*y_max + right_fit[2]
# Calculate the x position of the center of the lane
center_lanes_x_pos = (left_x_pos + right_x_pos)//2
# Calculate the deviation between the center of the lane and the center of the picture
# The car is assumed to be placed in the center of the picture
# If the deviation is negative, the car is on the felt hand side of the center of the lane
veh_pos = ((binary_warped.shape[1]//2) - center_lanes_x_pos) * xm_per_pix
这两个值的平均值给出了图像中车道中心的位置。如果车道的中心通过nbp数量的像素向右移动,则意味着汽车被nbp * xm_per_pix meters向左移动。这是基于假设摄像机安装在车辆中央轴上的假设。