天天看點

車道線檢測-從單車道到多車道的車道線檢測(一)車道線檢測

車道線檢測

····在參加udacity的課程後,對于車道線有了一些直覺的認識,後來在實驗室學習和外出實習過程中,也接觸過不少車道線方面的檢測工作,這邊将對這些日子裡的工作做一個總結。

····這也是我寫的第一篇部落格(以前都習慣整理在自己電腦裡面,但想想還是沒有blog繪聲繪色)。

····車道線檢測,從易到難:單直線車道檢測–>單彎道車道檢測–>多直線車道檢測–>多彎道車道檢測(事實上能檢測彎道就能夠檢測直道,相當于曲率半徑極大的彎道)

····我更希望用例子去說明,多按圖說話。本篇分三個内容:

···· 1.講解Udacity的CarND-LaneLines-P1-master項目

···· 2.講解Udacity的CarND-Advanced-Lane-Lines-master項目

···· 3.講解我在這基礎上改進的multi-lane-lines-detection項目

CarND-LaneLines-P1-master

····隻能針對直線車道檢測,結果穩定,實時性高,但波動大,魯棒性差,有待改進。

車道線檢測-從單車道到多車道的車道線檢測(一)車道線檢測

····如圖所示,為項目結果圖,從單車道檢測而言,效果不錯而且實時性高,那麼具體是怎麼做的呢!

····主要思路:對輸入圖像通過灰階化、高斯濾波、canny邊緣檢測;再框選出感興趣區域,對感興趣區域内的二值化點集進行hough變換,得到目标直線集;對直線集進行分類、合并得到穩定輸出結果。

思路講解

····我們通過一幀圖檔來說明處理過程。

車道線檢測-從單車道到多車道的車道線檢測(一)車道線檢測

····如圖[image]所示為圖像原圖,此時為(540x860x3)的數組矩陣,即3層R,G,B通道色彩資料,canny算子變換隻關注色值,即(540x860)矩陣中的數值,同一張圖檔,三通道色值差異不大,通過灰階化,壓縮3通道為單通道,減少資料量,使用函數cv2.cvtColor(image, cv2.COLOR_RGB2GRAY),通常效果較好的是權重平均灰階化,其他具體處理方式見1,裡面通過圖例,簡單直覺說明其原理和方法。;

····對比[gray]、[blur_gray],明顯覺得[blur]更模糊一些,這是由于[blur]進行了高斯濾波,設計一個計算核,大小為3x3或者5x5,類似權重平均的方法,權值滿足正态分布,周遊圖檔,去掉可能存在的椒鹽噪聲,為hough變換做好準備,這裡使用cv2.GaussianBlur(gray, (kernel_size, kernel_size), 0),實作高斯濾波,kernel_size就是核的大小,一般為基數,有疑惑的童鞋請見轉載2,裡面通過圖例,直覺說明常用圖像處理濾波器;

車道線檢測-從單車道到多車道的車道線檢測(一)車道線檢測

····此時,我們轉移注意力到圖[edges],[edges]通過邊緣檢測得到,邊緣檢測算子常見的有Canny,Roberts,Prewitt,簡而言之,通過算子基于差分原理得到一個數值,代表差別度大小,再基于兩個門檻值對圖像進行濾波,去除過多噪點,得到high_threimg和low_threimg,由于high_threshold門檻值更大,必然去掉了部分有效特征,再與low_threimg圖像進行比對、拼接,得到穩定的圖像輸出結果,這裡使用cv2.Canny(blur_gray, low_threshold, high_threshold),通過high_threshold和low_threshold對差別度進行限制,實作理想的二值化圖[edges],這裡的threshold皆為經驗調參值,關于Canny的詳細見轉載3,

關于其他算子綜述見4.

車道線檢測-從單車道到多車道的車道線檢測(一)車道線檢測

····我們可以看到對于一張圖檔的二值化處理,除了我們需要的車道資訊,還會有周圍環境特征,如天空、山、周邊荒草環境等,為了提高算法魯棒性,通過框選感興趣區域,是一個不錯的選擇,[masked_edges]即為結果。這裡我們隻針對單車道線檢測,劃出圖像中關于自身車位的前方及左右半條車道範圍延伸至滅點,如上圖所示,如針對多車道線測,那在框選時需要更加慎重,除了地平線以上剔除外,地平線以下根據拍攝品質好壞(像素較低時不足以得到一個穩定結果)和有效車道數設定(不要求全檢測,隻檢測左右三車道,更合适用于實車運作),所用函數見下文代碼解析;

····之後就是基于hough變換提取出若幹線段,把二值化後的像素坐标點集(X,Y)轉化到栅格化的極坐标(r,θ),基于投票選舉法,統計得到若幹條機率最高的直線,針對這些直線進行合并和剔除,得到最終車道線,如圖[line_image],完工!所用函數見下文代碼解析,關于hough變換的具體細節見轉載5,裡面通過圖例,直覺說明常用圖像處理濾波器(轉載自其他作者,如有侵權請私戳)

代碼講解

····上面講解了思路,這裡說明一下代碼吧。主要是基于每個函數進行講解,最後再說明優化方向。

詳見:https://github.com/wisdom-bob/CarND-LaneLines-P1-master

# importing some useful packages
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np
import math
import cv2

# define some functions
def grayscale(img):
    return cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
#     return cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # use BGR2GRAY if you read an image with cv2.imread()
    
def canny(img, low_threshold, high_threshold):
	#  Image Binarization with canny operator
    return cv2.Canny(img, low_threshold, high_threshold)

def gaussian_blur(img, kernel_size):
	#	gaussian filter by (kernel_size,kernel_size) operator
    return cv2.GaussianBlur(img, (kernel_size, kernel_size), 0)

def region_of_interest(img, vertices):
    #defining a blank mask to start with
    mask = np.zeros_like(img)   
    
    #defining a 3 channel or 1 channel color to fill the mask, depending on the input image
    if len(img.shape) > 2:
        channel_count = img.shape[2]  # i.e. 3 or 4 depending on your image
        ignore_mask_color = (255,) * channel_count
    else:
        ignore_mask_color = 255
        
    #filling pixels inside the polygon defined by "vertices" with the fill color    
    cv2.fillPoly(mask, vertices, ignore_mask_color)
    
    #returning the image only where mask pixels are nonzero
    masked_image = cv2.bitwise_and(img, mask)
    return masked_image

def draw_lines(img, lines, color=[255, 0, 0], thickness=8):
    for line in lines:
        for x1,y1,x2,y2 in line:
            cv2.line(img, (x1, y1), (x2, y2), color, thickness)

def Ltopend(arr,imshape):
	#	return the top point(x0,y0) and end point(x1,y1) of the line
    Arr = np.concatenate(arr, axis=0)
    k = sum(Arr[:,1]-Arr[:,3])/sum(Arr[:,0]-Arr[:,2])
    b = Arr[0][1]-k*Arr[0][0]
    
    x0 = int((imshape[0]-b)//k)
    y0 = imshape[0]
    for i in Arr:
        if i[3] == min(min(Arr[:,1]),min(Arr[:,3])):
            x1,y1 = i[3],i[2]
        elif i[1] == min(min(Arr[:,1]),min(Arr[:,3])):
            x1,y1 = i[1],i[0]
    return ((x0,y0),(y1,x1))

def draw_lines2(img, lines, color=[255, 0, 0], thickness=14):
	# updated draw_lines function
    le,ri= [],[]
    for line in lines:
        for x1,y1,x2,y2 in line:
            if (y2-y1)/(x2-x1)<0:
                le.append(line)
            else:
                ri.append(line)
    le = np.array(le)
    ri = np.array(ri)
    points = Ltopend(le,img.shape)
    cv2.line(img, points[0], points[1], color, thickness)
    points = Ltopend(ri,img.shape)
    cv2.line(img, points[0], points[1], color, thickness)    
            
def hough_lines(img, rho, theta, threshold, min_line_len, max_line_gap,flag=0):
	#	according to the HoughLinesP to get the lines, then draw them
    lines = cv2.HoughLinesP(img, rho, theta, threshold, np.array([]), minLineLength=min_line_len, maxLineGap=max_line_gap)
    line_img = np.zeros((img.shape[0], img.shape[1], 3), dtype=np.uint8)
    if flag:
        draw_lines2(line_img, lines, img.shape)
    else:
        draw_lines(line_img, lines)
    return line_img

def weighted_img(img, initial_img, α=0.8, β=1., γ=0.):
	#	image blending
    return cv2.addWeighted(initial_img, α, img, β, γ)
           

····通過以上函數,我們設定了若幹個函數接口用于接入灰階化、高斯濾波、canny二值化、hough變換等,那麼基于以上函數,我們可以自己先嘗試得到圖像處理結果(上圖的結果)。

def showimg(subplace, title, _img):
	#	design a subplot_imshow funciton
    plt.subplot(*subplace)
    plt.title(title)
    if len(_img.shape) == 3:
        plt.imshow(_img)
    else:
        plt.imshow(_img, cmap='gray')

#	from loca upload some images
names = []
for file in os.listdir("test_images/"):
    names.append(str(file[:file.rfind('.')]))
    
#	image->grayscale->gaussian_blur->canny->edges
name = names[1]
image = files[name]
gray = grayscale(image)
blur_gray = gaussian_blur(gray,7)
edges = canny(blur_gray, 50, 150)

#	Next we'll create a masked edges image using cv2.fillPoly() to get the region of interest
mask = np.zeros_like(edges)   
ignore_mask_color = 255   
imshape = image.shape

vertices = np.array([[(0,imshape[0]),(410, 330), (550, 330), (imshape[1],imshape[0])]], dtype=np.int32)
masked_edges = region_of_interest(edges,vertices)

#	Define the Hough transform parameters
rho = 1                             # distance resolution in pixels of the Hough grid
theta = np.pi/180                   # angular resolution in radians of the Hough grid
threshold = 15                      # minimum number of votes (intersections in Hough grid cell)
min_line_length = 25                 # minimum number of pixels making up a line
max_line_gap = 25                   # maximum gap in pixels between connectable line segments
line_image = np.copy(image)*0       # creating a blank to draw lines on

#	Run Hough on edge detected image
line_image = hough_lines(masked_edges, rho, theta, threshold, min_line_length, max_line_gap,flag=1)

#	Create the combined image, show and save it
color_edges = np.dstack((edges, edges, edges)) 
finalimg = weighted_img(line_image,image)
# plt.savefig("out"+name)

#	debug with images to show
plt.figure(figsize=[16, 9])
for i, imgname in enumerate(['image', 'gray', 'blur_gray', 'edges', 'masked_edges','line_image']):
    showimg((2, 3, i+1), imgname, eval(imgname))
           

····在此基礎之上,我們已經基本摸清楚了這些函數的具體用途和使用效果,那麼我們可以基于上段函數來建構一個function,寫定各參數,實作面向實時的單車道線檢測處理。

def process_image(image, ignore_mask_color = 255, rho = 1, theta = np.pi/180, threshold = 15, min_line_length = 25,max_line_gap = 25):
	# gray
    gray = grayscale(image)
    
    # blur_gray
    blur_gray = gaussian_blur(gray,7)
    
    # edges
    edges = canny(blur_gray, 50, 150) 
    
    # region-of-interest
    imshape = image.shape
    mask = np.zeros_like(edges)   
    vertices = np.array([[(0,imshape[0]),(410, 330), (550, 330), (imshape[1],imshape[0])]], dtype=np.int32)
    masked_edges = region_of_interest(edges,vertices)

	# creating a blank to draw lines on
    line_image = np.copy(image)*0       

    # Run Hough on edge detected image
    line_image = hough_lines(masked_edges, rho, theta, threshold, min_line_length, max_line_gap,flag=0)

    # Create the combined image, show and save it
    color_edges = np.dstack((edges, edges, edges)) 
    finalimg = weighted_img(line_image,image)
    return finalimg
           

····此時接入視訊,檢視結果.

# Import everything needed to edit/save/watch video clips
from moviepy.editor import VideoFileClip
from IPython.display import HTML

white_output = 'test_videos_output/solidWhiteRight.mp4'
clip1 = VideoFileClip("test_videos/solidWhiteRight.mp4")
white_clip = clip1.fl_image(process_image) #NOTE: this function expects color images!!
%time white_clip.write_videofile(white_output, audio=False)
           
車道線檢測-從單車道到多車道的車道線檢測(一)車道線檢測

總結和拓展

····至此基于hough的單車道線檢測已經完成了,無需gpu,隻在cpu上跑可以達到40幀/s,滿足實時性要求,算是非常好的結果,而且車道線檢測結果非常穩定,隻是偶爾會有錯幀,而且hough變換隻能基于直線來進行,彎道檢測必須用其他方法。

····從優化角度去考慮,至少可以再加個平滑器,這樣圖像效果更棒,提高魯棒性,不易受偶爾的錯幀影響;無法應對複雜場景,可以考慮增加滅點特征,可以減少部分錯誤結果,提高算法魯棒性;可以考慮增加跟蹤器,提高算法結果的可靠性。

····快去試試吧~~

如有侵權,請私戳~~感謝。
  1. https://www.cnblogs.com/finlay/p/3665302.html ↩︎
  2. https://blog.csdn.net/eastmount/article/details/82216380 ↩︎
  3. https://www.cnblogs.com/techyan1990/p/7291771.html ↩︎
  4. https://www.cnblogs.com/wlzy/p/7283579.html ↩︎
  5. https://blog.csdn.net/yuyuntan/article/details/80141392 ↩︎

繼續閱讀