智慧交通day03-车道线检测实现06:车道线定位及拟合+代码实现
學(xué)習目標
- 了解直方圖確定車道線位置的思想
我們根據(jù)前面檢測出的車道線信息,利用直方圖和滑動窗口的方法,精確定位車道線,并進行擬合。
1. 定位思想
下圖是我們檢測到的車道線結(jié)果:
沿x軸方向統(tǒng)計每一列中白色像素點的個數(shù),橫坐標是圖像的列數(shù),縱坐標表示每列中白色點的數(shù)量,那么這幅圖就是“直方圖”,如下圖所示:
對比上述兩圖,可以發(fā)現(xiàn)直方圖左半邊最大值對應(yīng)的列數(shù),即為左車道線所在的位置,直方圖右半邊最大值對應(yīng)的列數(shù),是右車道線所在的位置。
確定左右車道線的大致位置后,使用”滑動窗口“的方法,在圖中對左右車道線的點進行搜索。
滑動窗口的搜索過程:
-  設(shè)置搜索窗口大小(width和height):一般情況下width為手工設(shè)定,height為圖片大小除以設(shè)置搜索窗口數(shù)目計算得到。 
-  以搜尋起始點作為當前搜索的基點,并以當前基點為中心,做一個網(wǎng)格化搜索。 
-  對每個搜索窗口分別做水平和垂直方向直方圖統(tǒng)計,統(tǒng)計在搜索框區(qū)域內(nèi)非零像素個數(shù),并過濾掉非零像素數(shù)目小于50的框。 
-  計算非零像素坐標的均值作為當前搜索框的中心,并對這些中心點做一個二階的多項式擬合,得到當前搜尋對應(yīng)的車道線曲線參數(shù)。 
2.實現(xiàn)
使用直方圖和滑動窗口檢測車道線的代碼如下:
def cal_line_param(binary_warped):# 1.確定左右車道線的位置# 統(tǒng)計直方圖histogram = np.sum(binary_warped[:, :], axis=0)# 在統(tǒng)計結(jié)果中找到左右最大的點的位置,作為左右車道檢測的開始點# 將統(tǒng)計結(jié)果一分為二,劃分為左右兩個部分,分別定位峰值位置,即為兩條車道的搜索位置midpoint = np.int(histogram.shape[0] / 2)leftx_base = np.argmax(histogram[:midpoint])rightx_base = np.argmax(histogram[midpoint:]) + midpoint# 2.滑動窗口檢測車道線# 設(shè)置滑動窗口的數(shù)量,計算每一個窗口的高度nwindows = 9window_height = np.int(binary_warped.shape[0] / nwindows)# 獲取圖像中不為0的點nonzero = binary_warped.nonzero()nonzeroy = np.array(nonzero[0])nonzerox = np.array(nonzero[1])# 車道檢測的當前位置leftx_current = leftx_baserightx_current = rightx_base# 設(shè)置x的檢測范圍,滑動窗口的寬度的一半,手動指定margin = 100# 設(shè)置最小像素點,閾值用于統(tǒng)計滑動窗口區(qū)域內(nèi)的非零像素個數(shù),小于50的窗口不對x的中心值進行更新minpix = 50# 用來記錄搜索窗口中非零點在nonzeroy和nonzerox中的索引left_lane_inds = []right_lane_inds = []# 遍歷該副圖像中的每一個窗口for window in range(nwindows):# 設(shè)置窗口的y的檢測范圍,因為圖像是(行列),shape[0]表示y方向的結(jié)果,上面是0win_y_low = binary_warped.shape[0] - (window + 1) * window_heightwin_y_high = binary_warped.shape[0] - window * window_height# 左車道x的范圍win_xleft_low = leftx_current - marginwin_xleft_high = leftx_current + margin# 右車道x的范圍win_xright_low = rightx_current - marginwin_xright_high = rightx_current + margin# 確定非零點的位置x,y是否在搜索窗口中,將在搜索窗口內(nèi)的x,y的索引存入left_lane_inds和right_lane_inds中g(shù)ood_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]left_lane_inds.append(good_left_inds)right_lane_inds.append(good_right_inds)# 如果獲取的點的個數(shù)大于最小個數(shù),則利用其更新滑動窗口在x軸的位置if len(good_left_inds) > minpix:leftx_current = np.int(np.mean(nonzerox[good_left_inds]))if len(good_right_inds) > minpix:rightx_current = np.int(np.mean(nonzerox[good_right_inds]))# 將檢測出的左右車道點轉(zhuǎn)換為arrayleft_lane_inds = np.concatenate(left_lane_inds)right_lane_inds = np.concatenate(right_lane_inds)# 獲取檢測出的左右車道點在圖像中的位置leftx = nonzerox[left_lane_inds]lefty = nonzeroy[left_lane_inds]rightx = nonzerox[right_lane_inds]righty = nonzeroy[right_lane_inds]# 3.用曲線擬合檢測出的點,二次多項式擬合,返回的結(jié)果是系數(shù)left_fit = np.polyfit(lefty, leftx, 2)right_fit = np.polyfit(righty, rightx, 2)return left_fit, right_fit車道線的檢測的擬合結(jié)果如下圖所示:
其中綠色的方框是滑動窗口的結(jié)果,中間的黃線是車道線擬合的結(jié)果。
下面我們將車道區(qū)域繪制處理,即在檢測出的車道線中間繪制多邊形,代碼如下:
def fill_lane_poly(img, left_fit, right_fit):# 獲取圖像的行數(shù)y_max = img.shape[0]# 設(shè)置輸出圖像的大小,并將白色位置設(shè)為255out_img = np.dstack((img, img, img)) * 255# 在擬合曲線中獲取左右車道線的像素位置left_points = [[left_fit[0] * y ** 2 + left_fit[1] * y + left_fit[2], y] for y in range(y_max)]right_points = [[right_fit[0] * y ** 2 + right_fit[1] * y + right_fit[2], y] for y in range(y_max - 1, -1, -1)]# 將左右車道的像素點進行合并line_points = np.vstack((left_points, right_points))# 根據(jù)左右車道線的像素位置繪制多邊形cv2.fillPoly(out_img, np.int_([line_points]), (0, 255, 0))return out_img效果如下圖所示:
其中兩個方法給大家介紹下:
np.vstack:按垂直方向(行順序)堆疊數(shù)組構(gòu)成一個新的數(shù)組
np.dstack:按水平方向(列順序)堆疊數(shù)組構(gòu)成一個新的數(shù)組
np.hstack(),np.vstack()解讀_lllxxq141592654的博客-CSDN博客_np.vstack()https://blog.csdn.net/lllxxq141592654/article/details/84260091https://blog.csdn.net/lllxxq141592654/article/details/84260091https://blog.csdn.net/lllxxq141592654/article/details/84260091
示例如下:
將檢測出的車道逆投影到原始圖像,直接調(diào)用透視變換的方法即可:
transform_img_inverse = img_perspect_transform(result, M_inverse)效果如下圖所示:
最后將其疊加在原圖像上,則有:
總結(jié):
知道車道線精確定位的思想:
首先利用直方圖的方法確定左右車道線的位置,然后利用滑動窗口的方法,搜索車道線位置,并進行擬合,然后繪制車道區(qū)域,并進行反投影,得到原始圖像中的車道區(qū)域。
代碼實現(xiàn):
# encoding:utf-8from matplotlib import font_manager my_font = font_manager.FontProperties(fname="/System/Library/Fonts/PingFang.ttc")import cv2 import numpy as np import matplotlib.pyplot as plt #遍歷文件夾 import glob from moviepy.editor import VideoFileClip import sys reload(sys) sys.setdefaultencoding('utf-8')"""參數(shù)設(shè)置""" nx = 9 ny = 6 #獲取棋盤格數(shù)據(jù) file_paths = glob.glob("./camera_cal/calibration*.jpg")#相機矯正使用opencv封裝好的api #目的:得到內(nèi)參、外參、畸變系數(shù) def cal_calibrate_params(file_paths):#存儲角點數(shù)據(jù)的坐標object_points = [] #角點在真實三維空間的位置image_points = [] #角點在圖像空間中的位置#生成角點在真實世界中的位置objp = np.zeros((nx*ny,3),np.float32)#以棋盤格作為坐標,每相鄰的黑白棋的相差1objp[:,:2] = np.mgrid[0:nx,0:ny].T.reshape(-1,2)#角點檢測for file_path in file_paths:img = cv2.imread(file_path)#將圖像灰度化gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)#角點檢測rect,coners = cv2.findChessboardCorners(gray,(nx,ny),None)#若檢測到角點,則進行保存 即得到了真實坐標和圖像坐標if rect == True :object_points.append(objp)image_points.append(coners)# 相機較真ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(object_points, image_points, gray.shape[::-1], None, None)return ret, mtx, dist, rvecs, tvecs# 圖像去畸變:利用相機校正的內(nèi)參,畸變系數(shù) def img_undistort(img, mtx, dist):dis = cv2.undistort(img, mtx, dist, None, mtx)return dis#車道線提取 #顏色空間轉(zhuǎn)換--》邊緣檢測--》顏色閾值--》并且使用L通道進行白色的區(qū)域進行抑制 def pipeline(img,s_thresh = (170,255),sx_thresh=(40,200)):# 復(fù)制原圖像img = np.copy(img)# 顏色空間轉(zhuǎn)換hls = cv2.cvtColor(img,cv2.COLOR_RGB2HLS).astype(np.float)l_chanel = hls[:,:,1]s_chanel = hls[:,:,2]#sobel邊緣檢測sobelx = cv2.Sobel(l_chanel,cv2.CV_64F,1,0)#求絕對值abs_sobelx = np.absolute(sobelx)#將其轉(zhuǎn)換為8bit的整數(shù)scaled_sobel = np.uint8(255 * abs_sobelx / np.max(abs_sobelx))#對邊緣提取的結(jié)果進行二值化sxbinary = np.zeros_like(scaled_sobel)#邊緣位置賦值為1,非邊緣位置賦值為0sxbinary[(scaled_sobel >= sx_thresh[0]) & (scaled_sobel <= sx_thresh[1])] = 1#對S通道進行閾值處理s_binary = np.zeros_like(s_chanel)s_binary[(s_chanel >= s_thresh[0]) & (s_chanel <= s_thresh[1])] = 1# 結(jié)合邊緣提取結(jié)果和顏色通道的結(jié)果,color_binary = np.zeros_like(sxbinary)color_binary[((sxbinary == 1) | (s_binary == 1)) & (l_chanel > 100)] = 1return color_binary#透視變換-->將檢測結(jié)果轉(zhuǎn)換為俯視圖。 #獲取透視變換的參數(shù)矩陣【二值圖的四個點】 def cal_perspective_params(img,points):# x與y方向上的偏移offset_x = 330offset_y = 0#轉(zhuǎn)換之后img的大小img_size = (img.shape[1],img.shape[0])src = np.float32(points)#設(shè)置俯視圖中的對應(yīng)的四個點 左上角 右上角 左下角 右下角dst = np.float32([[offset_x, offset_y], [img_size[0] - offset_x, offset_y],[offset_x, img_size[1] - offset_y], [img_size[0] - offset_x, img_size[1] - offset_y]])## 原圖像轉(zhuǎn)換到俯視圖M = cv2.getPerspectiveTransform(src, dst)# 俯視圖到原圖像M_inverse = cv2.getPerspectiveTransform(dst, src)return M, M_inverse#根據(jù)透視變化矩陣完成透視變換 def img_perspect_transform(img,M):#獲取圖像大小img_size = (img.shape[1],img.shape[0])#完成圖像的透視變化return cv2.warpPerspective(img,M,img_size)# 精確定位車道線 #傳入已經(jīng)經(jīng)過邊緣檢測的圖像閾值結(jié)果的二值圖,再進行透明變換 def cal_line_param(binary_warped):#定位車道線的大致位置==計算直方圖histogram = np.sum(binary_warped[:,:],axis=0) #計算y軸# 將直方圖一分為二,分別進行左右車道線的定位midpoint = np.int(histogram.shape[0]/2)#分別統(tǒng)計左右車道的最大值midpoint = np.int(histogram.shape[0] / 2)leftx_base = np.argmax(histogram[:midpoint]) #左車道rightx_base = np.argmax(histogram[midpoint:]) + midpoint #右車道#設(shè)置滑動窗口#對每一個車道線來說 滑動窗口的個數(shù)nwindows = 9#設(shè)置滑動窗口的高window_height = np.int(binary_warped.shape[0]/nwindows)#設(shè)置滑動窗口的寬度==x的檢測范圍,即滑動窗口的一半margin = 100#統(tǒng)計圖像中非0點的個數(shù)nonzero = binary_warped.nonzero()nonzeroy = np.array(nonzero[0])#非0點的位置-x坐標序列nonzerox = np.array(nonzero[1])#非0點的位置-y坐標序列#車道檢測位置leftx_current = leftx_baserightx_current = rightx_base#設(shè)置閾值:表示當前滑動窗口中的非0點的個數(shù)minpix = 50#記錄窗口中,非0點的索引left_lane_inds = []right_lane_inds = []#遍歷滑動窗口for window in range(nwindows):# 設(shè)置窗口的y的檢測范圍,因為圖像是(行列),shape[0]表示y方向的結(jié)果,上面是0win_y_low = binary_warped.shape[0] - (window + 1) * window_height #y的最低點win_y_high = binary_warped.shape[0] - window * window_height #y的最高點# 左車道x的范圍win_xleft_low = leftx_current - marginwin_xleft_high = leftx_current + margin# 右車道x的范圍win_xright_low = rightx_current - marginwin_xright_high = rightx_current + margin# 確定非零點的位置x,y是否在搜索窗口中,將在搜索窗口內(nèi)的x,y的索引存入left_lane_inds和right_lane_inds中g(shù)ood_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]left_lane_inds.append(good_left_inds)right_lane_inds.append(good_right_inds)# 如果獲取的點的個數(shù)大于最小個數(shù),則利用其更新滑動窗口在x軸的位置=修正車道線的位置if len(good_left_inds) > minpix:leftx_current = np.int(np.mean(nonzerox[good_left_inds]))if len(good_right_inds) > minpix:rightx_current = np.int(np.mean(nonzerox[good_right_inds]))# 將檢測出的左右車道點轉(zhuǎn)換為arrayleft_lane_inds = np.concatenate(left_lane_inds)right_lane_inds = np.concatenate(right_lane_inds)# 獲取檢測出的左右車道x與y點在圖像中的位置leftx = nonzerox[left_lane_inds]lefty = nonzeroy[left_lane_inds]rightx = nonzerox[right_lane_inds]righty = nonzeroy[right_lane_inds]# 3.用曲線擬合檢測出的點,二次多項式擬合,返回的結(jié)果是系數(shù)left_fit = np.polyfit(lefty, leftx, 2)right_fit = np.polyfit(righty, rightx, 2)return left_fit, right_fit#填充車道線之間的多邊形 def fill_lane_poly(img,left_fit,right_fit):#行數(shù)y_max = img.shape[0]#設(shè)置填充之后的圖像的大小 取到0-255之間out_img = np.dstack((img,img,img))*255#根據(jù)擬合結(jié)果,獲取擬合曲線的車道線像素位置left_points = [[left_fit[0] * y ** 2 + left_fit[1] * y + left_fit[2], y] for y in range(y_max)]right_points = [[right_fit[0] * y ** 2 + right_fit[1] * y + right_fit[2], y] for y in range(y_max - 1, -1, -1)]# 將左右車道的像素點進行合并line_points = np.vstack((left_points, right_points))# 根據(jù)左右車道線的像素位置繪制多邊形cv2.fillPoly(out_img, np.int_([line_points]), (0, 255, 0))return out_imgif __name__ == "__main__":#透視變換#獲取原圖的四個點img = cv2.imread('./test/straight_lines2.jpg')points = [[601, 448], [683, 448], [230, 717], [1097, 717]]#將四個點繪制到圖像上 (文件,坐標起點,坐標終點,顏色,連接起來)img = cv2.line(img, (601, 448), (683, 448), (0, 0, 255), 3)img = cv2.line(img, (683, 448), (1097, 717), (0, 0, 255), 3)img = cv2.line(img, (1097, 717), (230, 717), (0, 0, 255), 3)img = cv2.line(img, (230, 717), (601, 448), (0, 0, 255), 3)#透視變換的矩陣M,M_inverse = cal_perspective_params(img,points)#車道線檢測演示ret, mtx, dist, rvecs, tvecs = cal_calibrate_params(file_paths)undistort_img = img_undistort(img,mtx,dist)#提取車道線pipeline_img = pipeline(undistort_img)#透視變換trasform_img = img_perspect_transform(pipeline_img,M)#計算車道線的擬合結(jié)果left_fit,right_fit = cal_line_param(trasform_img)#進行填充result = fill_lane_poly(trasform_img,left_fit,right_fit)plt.figure()# 反轉(zhuǎn)CV2中BGR 轉(zhuǎn)化為matplotlib的RGBplt.imshow(result[:, :, ::-1])plt.title("vertical view:FULL")plt.show()#透視變換的逆變換trasform_img_inv = img_perspect_transform(result,M_inverse)plt.figure()# 反轉(zhuǎn)CV2中BGR 轉(zhuǎn)化為matplotlib的RGBplt.imshow(trasform_img_inv[:, :, ::-1])plt.title("Original drawing:FULL")plt.show()#與原圖進行疊加res = cv2.addWeighted(img,1,trasform_img_inv,0.5,0)plt.figure()# 反轉(zhuǎn)CV2中BGR 轉(zhuǎn)化為matplotlib的RGBplt.imshow(res[:, :, ::-1])plt.title("safe work")plt.show()輸出:
?
總結(jié)
以上是生活随笔為你收集整理的智慧交通day03-车道线检测实现06:车道线定位及拟合+代码实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 【Pytorch神经网络实战案例】10
- 下一篇: AttributeError: type
