檢測步驟:
- 相機標定
- 圖片失真校正
- 圖像閾值化
- 透視變換
- 檢測車道像素並擬合邊界
- 計算車道的曲率和車輛相對位置
- 車道邊界彎曲回原始圖像
一、相機標定
1.1 角點檢測
我從准備object points
開始,它將是世界棋盤角落的(x, y, z)坐標。這里我假設棋盤固定在z=0的(x, y)平面上,這樣每個校准圖像的目標點都是相同的。因此objp
只是一個復制的坐標數組,每當我成功地檢測到測試圖像中的所有棋盤角時,objpoints
將附加一個它的副本。imgpoints
將與(x, y)像素位置的每一個角落在圖像平面與每一個成功的棋盤檢測。
# prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
objp = np.zeros((6*9,3), np.float32)
objp[:,:2] = np.mgrid[0:9,0:6].T.reshape(-1,2)
# Arrays to store object points and image points from all the images.
objpoints = [] # 3d points in real world space
imgpoints = [] # 2d points in image plane.
其中imgpoints
的獲得,通過cv2.findChessboardCorners()
函數。
1.2 標定
然后,我使用輸出objpoints
和imgpoints
使用cv2.calibrateCamera()
函數計算相機校准和失真系數。我使用 cv2. undistort()
函數對測試圖像進行失真校正,
# Test undistortion on an image
img = cv2.imread('./calibration.jpg')
img_size = (img.shape[1], img.shape[0])
# Do camera calibration given object points and image points
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, img_size,None,None)
dst = cv2.undistort(img, mtx, dist, None, mtx)
得到如下結果:
Figure 1 去畸變
二、圖片的Pipeline
2.1 圖片獲取
將一張原始圖片通過之前標定的參數進行矯正,再對矯正后的圖片進行余下操作,矯正的函數為cv2.undistort()
,結果如Figure 2
所示,
Figure 2 車道線矯正
2.2 圖像閾值化
我使用顏色和梯度閾值的組合來生成一個二進制圖像。下面是這個步驟的輸出示例。我使用binary_select()
函數對測試圖像應用了顏色變換和漸變,並得到了這個結果。
# Define a function that thresholds the S-channel of HLS
def binary_select(image, s_thresh=(170, 255), sx_thresh=(20, 100)):
hls = cv2.cvtColor(image, cv2.COLOR_RGB2HLS) #Convert to HLS color space
s_channel = hls[:,:,2] #Apply a threshold to the S channel
# Threshold color channel
s_binary = np.zeros_like(s_channel)
s_binary[(s_channel >= s_thresh[0]) & (s_channel <= s_thresh[1])] = 255
# Sobel x
sobelx = cv2.Sobel(s_channel, cv2.CV_64F, 1, 0) # Take the derivative in x
abs_sobelx = np.absolute(sobelx) # Absolute x derivative to accentuate lines away from horizontal
scaled_sobel = np.uint8(255*abs_sobelx/np.max(abs_sobelx))
# Threshold x gradient
sxbinary = np.zeros_like(scaled_sobel)
sxbinary[(scaled_sobel >= sx_thresh[0]) & (scaled_sobel <= sx_thresh[1])] = 255
#the threshold result of combine s_binary and sxbinary
binary_output = np.zeros_like(s_channel)
binary_output[(s_binary >= 255) & (sxbinary >= 255)] = 255
return s_channel, s_binary, sxbinary, binary_output
Figure 3 閾值化效果
2.3 透視變換
我的透視圖轉換代碼包含一個名為birds_eye_warp()
的函數。函數birds_eye_warp()
接收圖像(img
)、Source(src
)和 Destination(dst
)作為輸入。 其中Source(src)為圖片上的像素坐標,而Destination(dst)是實際場景的比例關系估算獲得。
Source(src) | Destination(dst) |
---|---|
575,464 | 150,0 |
707,464 | 1130,0 |
1049,682 | 1130,720 |
258,682 | 150,720 |
接着通過函數cv2.getPerspectiveTransform()
獲得Source點和Destination的變換關系,
# the function of "birds-eye view"
def birds_eye_warp(img, src, dst):
h,w = img.shape[:2]
M = cv2.getPerspectiveTransform(src, dst)
Minv = cv2.getPerspectiveTransform(dst, src)
# use cv2.warpPerspective() to warp your image to a top-down view
warped = cv2.warpPerspective(img, M, (w,h), flags=cv2.INTER_LINEAR)
return warped, M, Minv
最后,我將src
和dst
點繪制到測試圖像及其翹曲的對應圖像上,驗證了透視圖轉換如預期的那樣工作,以及這些車道線經過透視圖轉換后看起來是平行的。
Figure 4 鳥瞰圖
2.4 擬合車道線像素
首先,通過代碼hist()
函數查找屬於車道線的像素。我選擇了兩個最強的峰作為初始車道線的起點。其次,使用Sliding Windows
策略,為每段選擇車道線像素。
def hist(img): #img是歸一化處理的單通道圖片
# TO-DO: Grab only the bottom half of the image
# Lane lines are likely to be mostly vertical nearest to the car
bottom_half = None
bottom_half = img[img.shape[0]//2:,]
# TO-DO: Sum across image pixels vertically - make sure to set `axis`
# i.e. the highest areas of vertical lines should be larger values
histogram = None
histogram = np.sum(bottom_half, axis = 0)
return histogram
Figure 5 統計頻數直方圖
最后,我借助函數numpy.polyfit()
通過一個二階多項式來擬合車道線像素。使用它們的x和y像素位置來擬合一個二階多項式曲線\(f(y) = Ay^2 + By + C\),
Figure 6 擬合曲線
2.5 車道曲率和車輛位置
通過曲率半徑的參考公式
編寫函數measure re_ature_real()
計算曲線x=f(y)
任意點x處曲率半徑的直線
def measure_curvature_real(left_fitx, right_fitx, ploty, left_fit, right_fit):
# Define conversions in x and y from pixels space to meters
ym_per_pix = 16.0/720 # meters per pixel in y dimension
xm_per_pix = 3.7/1000 # meters per pixel in x dimension
leftx = left_fitx*xm_per_pix
rightx = right_fitx*xm_per_pix
ploty = ploty*ym_per_pix
left_fit_cr = np.polyfit(ploty, leftx, 2)
right_fit_cr = np.polyfit(ploty, rightx, 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)
# Implement the 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])
return left_curverad, right_curverad
- \(car_{p}\) 是汽車在圖片中的位置,因為相機安裝在汽車的中心,這就相當於取圖像寬度的一半
car_position = img.shape[1]/2
。 - \(xm_{pp}\) 是圖像像素與實際距離之間的關系,即代碼中的
xm_per_pix = 3.7/1000
,其中3.7是車道線實際寬度3.7米,1000是圖片像素的像素距離。 - \(lanecenter_{p}\) 是車道線中心,通過檢測圖片中沿x軸的車道線來計算的,即
lane_center_position = (r_fit_x_int + l_fit_x_int) /2
。
最后,車輛相對於車道中心的位置\(center_{d}\)計算公式如下:
\(center_{d} = (car_{p} - lanecenter_{p}) * xm_{pp}\)
2.6 檢測效果
Figure 6 檢測結果
三、視頻的Pipeline
將圖片的檢測方法運用到車輛行駛的視頻上,可以獲得輕微抖動的檢測效果,沒有會導致汽車駛離道路的災難性故障。