日本无码免费高清在线|成人日本在线观看高清|A级片免费视频操逼欧美|全裸美女搞黄色大片网站|免费成人a片视频|久久无码福利成人激情久久|国产视频一二国产在线v|av女主播在线观看|五月激情影音先锋|亚洲一区天堂av

  • 手機站
  • 小程序

    汽車測試網(wǎng)

  • 公眾號
    • 汽車測試網(wǎng)

    • 在線課堂

    • 電車測試

詳解車道線檢測算法之傳統(tǒng)圖像處理

2020-10-08 22:44:15·  來源:智車科技  作者:白墨  
 
/導(dǎo)讀/車道線檢測算法分為傳統(tǒng)圖像處理方法和深度學(xué)習(xí)方法。本文詳細(xì)介紹用傳統(tǒng)圖像處理方法來解決車道線檢測問題,后文的算法源于Udacity無人駕駛課程的項目(h
/ 導(dǎo)讀 /
車道線檢測算法分為傳統(tǒng)圖像處理方法和深度學(xué)習(xí)方法。本文詳細(xì)介紹用傳統(tǒng)圖像處理方法來解決車道線檢測問題,后文的算法源于Udacity無人駕駛課程的項目(https://www.udacity.com/),僅做參考。
 
計算機視覺基礎(chǔ)
在正式開始算法之前,我們先簡單補充些計算機視覺的基礎(chǔ)知識。已有基礎(chǔ)的童鞋,可以略過直接看算法。
像素
像素Pixel,是picture和element的合成詞,表示圖像元素的意思,可以理解為構(gòu)成圖像的最小單位。像素的大小與圖像的分辨率有關(guān),分辨率越高,像素就越小,圖像就越清晰。
灰度圖像
灰度化,即對彩色圖像進(jìn)行灰度化處理。由于很多算法對顏色的依賴性不高,通常會對彩色圖像進(jìn)行灰度化預(yù)處理,去除彩色信息,不僅能提高魯棒性,還可以提高處理速度。
灰度圖像的每個像素信息是由一個量化的灰度級來描述的,沒有彩色信息;而彩色圖像(例如RGB)的每個像素信息由RGB三原色構(gòu)成的,RGB分別通過三個不同的灰度級來描述。
二值圖像
二值化就是將彩色圖像變成二值圖像。由于二值圖像的數(shù)據(jù)足夠簡單,只有兩個值,所以算法都依賴二值圖像。
二值圖像,即黑白圖像。二值圖像的每個像素只能是黑或白,沒有中間的灰度級過渡,像素值為0、1。最常用的就是采用閾值的方法進(jìn)行二值化。最常用的二值化處理簡單來說,就是選取一個閾值,大于它的像素就視為白,小于它就視為黑。
色彩空間
不同色彩空間對顏色采用不同的表示方式,我們最常用的是RGB色彩空間,除此之外還有HLS、HSV、YUV、Lab色彩空間等。我們可以針對個人需求,轉(zhuǎn)換到適合的彩色空間中處理。
 
例如,RGB色彩空間中的R表示紅色Red,G表示綠色Green,B表示藍(lán)色Blue,這三種顏色以不同的量進(jìn)行疊加,就可以顯示出所有顏色。HSL色彩空間即色相Hue、飽和度Saturation、亮度Lightness;HSV色彩空間則是由色相Hue、飽和度Saturation、明度Value組成。YUV色彩空間是由一個亮度Y和兩個色度UV決定的。而在Lab色彩空間中,L表示亮度Lightness,A表示從綠色到紅色的漸變;B表示從藍(lán)色到黃色的漸變。
顏色閾值過濾
閾值過濾需要設(shè)定一個數(shù)值,像素值高于此值的像素點變?yōu)榘咨?,低于此值則變?yōu)楹谏>唧w來說,例如在RGB色彩空間中,R紅色是表示在一個區(qū)間內(nèi)的連續(xù)數(shù)值;我們通過設(shè)置[min, max]值區(qū)間,將大小位于該區(qū)間內(nèi)的數(shù)值保留,不在該區(qū)間的數(shù)值統(tǒng)一設(shè)置為0。通過這種操作,實現(xiàn)對圖像顏色特征的過濾。
邊緣檢測
這里要介紹的是使用圖像濾波的方式實現(xiàn)邊緣檢測。濾波,其實就是通過放大圖像中的某些頻段,同時濾掉或減弱某些頻段的方法。例如,低通濾波器的作用是消除圖像中的高頻部分,高通濾波器的作用是消除圖像中的低頻部分。
Sobel算法
Sobel算法是通過放大圖像中高頻部分,再對圖像進(jìn)行二值化,可以實現(xiàn)邊緣檢測。雖然它只對垂直或水平方向的圖像頻率起作用,能夠分別得出x和y方向上的亮度函數(shù)的梯度近似值。x方向(水平方向)的Sobel濾波傾向于檢測垂直方向的邊緣,y方向(垂直方向)的Sobel濾波則傾向于檢測水平方向的邊緣。而在車道線檢測中,車道線是傾向于垂直方向的線,所以采用x方向的Sobel算法較為有效。
Canny算法
Canny算法通常是基于梯度算法,用兩個不同的閾值(高閾值和低閾值)來判斷那個點屬于邊緣,雙閾值可以有效降低邊緣的漏檢率。雖然Canny算法能夠精度高,能清晰檢測出所有邊緣,但在車道線檢測中,除了車道線還會檢測出各種陰影。因此相比之下Sobel單方向的檢測也許效果更好。
霍夫變換
霍夫變換是一種一般用于檢測直線(也可檢測圓形)的經(jīng)典算法??梢越Y(jié)合上文的Canny算法檢測直線車道線。經(jīng)過Canny算法得到邊緣分布圖像后,構(gòu)建一個霍夫參數(shù)空間,利用點與線的對偶性,將原始圖像空間的直線變換為參數(shù)空間的一個點。這樣就把原始圖像中直線檢測問題轉(zhuǎn)變?yōu)閷ふ覅?shù)空間中的峰值問題。
簡單解釋下,元空間的點都對應(yīng)霍夫參數(shù)空間的線,原空間的直線都對應(yīng)參數(shù)空間中曲線的交點。即可以通過參數(shù)空間曲線的交點個數(shù)來尋找原空間中在一條直線上的點。
 
圖片來自Udacity無人駕駛課程
感興趣區(qū)域
感興趣區(qū)域,即ROI。在圖像處理過程中,我們可能會對圖像的某一個特定區(qū)域感興趣,只對該區(qū)域?qū)嵤┎僮?。對于車道線檢測來說,由于相機是固定安裝,可以選取一個區(qū)域以排除其他噪聲干擾。感興趣區(qū)域可以是任意圖形,例如四邊形或其他任意多邊形。
透視變換
透視變換是將成像投影到一個新的視平面(鳥瞰圖)。車載相機拍攝到的圖像中,由于受相機角度和高度的影響往往會將平行的車道線拍成梯形,這樣的圖像會給后續(xù)檢測帶來較大難度,所以需要我們對原圖進(jìn)行透視變換,從而得到一張鳥瞰圖。
具體思路是,先讀取兩組坐標(biāo)(例如原圖像的四點坐標(biāo)和目標(biāo)圖像的四點坐標(biāo)),計算變換矩陣;然后根據(jù)變換矩陣對圖像進(jìn)行透視變換,并輸出到目標(biāo)圖像。
相機標(biāo)定
由于一般相機都多少存在畸變,而自動駕駛需要準(zhǔn)確的車道曲率以執(zhí)行正確的方向控制,因此需要對相機進(jìn)行校正。首先讀取兩個坐標(biāo)數(shù)組,計算變換矩陣;然后根據(jù)變換矩陣對原圖進(jìn)行透視變換,并輸出到目標(biāo)畫布。
確定相機內(nèi)參和畸變參數(shù)的過程就叫做相機標(biāo)定。一般只需要一幅圖像(國際象棋棋盤圖像較為常用)就可以進(jìn)行相機標(biāo)定。標(biāo)定時只需要從不同角度拍攝棋盤圖案,通過檢測棋盤圖案中的角點,得到角點像素,并且我們已知真實世界中的對象角點坐標(biāo) ,便可進(jìn)行相機校正。若要進(jìn)行精確的標(biāo)定,通常從不同角度對同一棋盤拍攝多張照片(10-20張)。
直方圖濾波
簡單來說,就是沿著X軸統(tǒng)計每列像素的數(shù)值,并用直方圖表示。其中峰值位置的x坐標(biāo)則對應(yīng)左右兩側(cè)的車道線。通過這種方式,來獲取車道線位置信息。
 
圖片來自Udacity無人駕駛課程
 
初級車道線檢測算法--直線
顏色閾值處理
讀取圖片,對顏色RGB分別設(shè)置閾值,將高于所設(shè)閾值的像素(較亮的白色部分)分離出來。
# 讀取圖片
image = mpimg.imread('test.jpg')
ysize = image.shape[0]
xsize = image.shape[1]
color_select = np.copy(image)
# 對RGB通道分別設(shè)置閾值
red_threshold = 200 #Tuning
green_threshold = 200 #Tuning
blue_threshold = 200 #Tuning
# 將所設(shè)閾值以下的任何像素都設(shè)置為0
rgb_threshold = [red_threshold, green_threshold, blue_threshold]
thresholds = (image[:,:,0] < rgb_threshold[0]) \
| (image[:,:,1] < rgb_threshold[1]) \
| (image[:,:,2] < rgb_threshold[2])
color_select[thresholds] = [0,0,0]
提取ROI
Python
# 首先定義一個空圖像
mask = np.zeros_like(image)
# 設(shè)置ROI區(qū)域坐標(biāo)(任意多邊形)
vertices = np.array([[image.shape[1]*.12, image.shape[0]],\
[image.shape[1]*.25,(image.shape[0]+image.shape[0]*.65)/2],\
[image.shape[1]*.42, image.shape[0]*.63],\
[image.shape[1]*.6,image.shape[0]*.63],\
[image.shape[1]*.8,(image.shape[0]+image.shape[0]*.65)/2],\
[image.shape[1]*.95,image.shape[0]]]) #Tuning
# 設(shè)置顏色通道,并填充ROI
if len(image.shape) > 2:
channel_count = image.shape[2] # i.e. 3 or 4 depending on your image
ignore_mask_color = (255,) * channel_count
else:
ignore_mask_color = 255
cv2.fillPoly(mask, vertices, ignore_mask_color)
# 合并得到ROI,其余部分為零
masked_image = cv2.bitwise_and(image, mask)
 
Canny邊緣檢測
# 將圖像轉(zhuǎn)換為灰度圖
gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
# 進(jìn)行高斯模糊,去除噪聲
kernel_size = 3 #Tuning
blur_gray = cv2.GaussianBlur(gray,(kernel_size, kernel_size), 0) # 參數(shù)kernel_size可以是任何奇數(shù)
# 計算梯度(Canny邊緣檢測)
low_threshold = 50 #Tuning
high_threshold = 150 #Tuning
edges = cv2.Canny(gray,low_threshold,higy_threshold) # 參數(shù):灰度圖,低閾值,高閾值。輸出為邊緣圖。
 
霍夫變換
rho = 2
theta = np.pi/180
threshold = 15
min_line_length = 50
max_line_gap = 20
# rho和theta是霍夫空間的距離和夾角
# threshold是通過同一交點的曲線個數(shù)最小值
# min_line_length是直線的最小像素長度
# max_line_gap是線與線之間的最大像素長度。輸出為多條直線
lines = cv2.HoughLinesP(masked_edges, rho, theta, threshold, np.array([]), min_line_length, max_line_gap)
 
繪制車道線
# 定義左右車道線的點集合
right_list = []
left_list = []
# 根據(jù)霍夫變換得到的線段,繪制左右各一條車道線
if lines is not None:
for line in lines:
for x1,y1,x2,y2 in line:
if (y1 - y2) / (x1 - x2) > 0:# right side
right_list.append([x1,y1,x2,y2,(y2-y1)/(x2-x1),(x1*y2-x2*y1)/(x1-x2)])
else:# left side
left_list.append([x1,y1,x2,y2,(y2-y1)/(x2-x1),(x1*y2-x2*y1)/(x1-x2)])
# 通過計算斜率,篩選得到最優(yōu)車道線
if len(right_list) != 0:
[k_right_mean,b_right_mean,x_list,y_list] = filter_lines(right_list)
y1_right = img.shape[0]-60
x1_right = (y1_right - b_right_mean) / k_right_mean
y2_right = img.shape[0]*.65 #min(y_right)
x2_right = (y2_right - b_right_mean) / k_right_mean
cv2.line(img, (np.int32(x1_right), np.int32(y1_right)), (np.int32(x2_right), np.int32(y2_right)), [255,0,0], thickness)
if len(left_list) != 0:
[k_left_mean,b_left_mean,x_list,y_list] = filter_lines(left_list)
y1_left = img.shape[0]-60
x1_left = (y1_left - b_left_mean) / k_left_mean
y2_left = img.shape[0]*.65 #min(y_left)
x2_left = (y2_left - b_left_mean) / k_left_mean
cv2.line(img, (np.int32(x1_left), np.int32(y1_left)), (np.int32(x2_left), np.int32(y2_left)), color, thickness)
def filter_lines(xy_kb_list):
n = 3
k_list = [xy_kb[4] for xy_kb in xy_kb_list]
mean = np.mean(k_list)
std = np.std(k_list)
good_k_list = []
good_b_list = []
good_x_list = []
good_y_list = []
for [x1, y1, x2, y2, k, b] in xy_kb_list:
if k < mean + n*std:
good_k_list.append(k)
good_b_list.append(b)
good_x_list.append(x1)
good_x_list.append(x2)
good_y_list.append(y1)
good_y_list.append(y2)
if not good_x_list:
good_k_list = k_list
good_b_list = [xy_kb[5] for xy_kb in xy_kb_list]
good_x_list = [xy_kb[0] for xy_kb in xy_kb_list] + [xy_kb[2] for xy_kb in xy_kb_list]
good_y_list = [xy_kb[1] for xy_kb in xy_kb_list] + [xy_kb[3] for xy_kb in xy_kb_list]
k_mean = np.median(good_k_list)
b_mean = np.median(good_b_list)
return k_mean,b_mean,good_x_list,good_y_list
 
將算法應(yīng)用于視頻
output = 'test_video_output.mp4'
from moviepy.editor import VideoFileClip
clip1 = VideoFileClip("test_video.mp4")
white_clip = clip1.fl_image(process_image)
%time white_clip.write_videofile(output , audio=False)
 
高級車道線檢測算法--曲線
計算相機校正矩陣和失真系數(shù)
# 使用提供的一組棋盤格圖片計算相機校正矩陣(camera calibration matrix)和失真系數(shù)(distortion coefficients).
objpoints = []
imgpoints = []
for ny in [5,6]:
for nx in [6,7,8,9]:
objp = np.zeros((ny*nx,3), np.float32)
objp[:,:2] = np.mgrid[0:nx, 0:ny].T.reshape(-1,2)
for idx, fname in enumerate(images):
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 使用cv2自動尋找棋盤corners
ret, corners = cv2.findChessboardCorners(gray, (nx,ny), None)
if ret == True:
objpoints.append(objp)
imgpoints.append(corners)
# 繪制corners
cv2.drawChessboardCorners(img, (nx,ny), corners, ret)
image_name=os.path.split(fname)[1]
write_name = out_dir_cam+'corners_found_ny'+str(ny)+'_nx'+str(nx)+'_'+image_name
cv2.imwrite(write_name, img)
print(fname)
plt.imshow(img)
# Test undistortion on an image
img = cv2.imread('camera_cal/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)
# 保存相機校正矩陣和失真系數(shù)結(jié)果
dist_pickle = {}
dist_pickle["mtx"] = mtx
dist_pickle["dist"] = dist
pickle.dump(dist_pickle, open( "camera_dist_pickle.p", "wb" ) )
校正車道線圖片
使用計算好的相機校正矩陣(camera calibration matrix)和失真系數(shù)(distortion coefficients)校正車道線圖片。
# 打開相機校正矩陣和失真系數(shù)結(jié)果
with open('camera_dist_pickle.p', mode='rb') as f:
dist_pickle = pickle.load(f)
mtx = dist_pickle["mtx"]
dist = dist_pickle["dist"]
# 應(yīng)用于車道線圖片
images = glob.glob('test_images/*.jpg')
for idx, fname in enumerate(images):
img = cv2.imread(fname)
dst = cv2.undistort(img, mtx, dist, None, mtx)
image_name=os.path.split(fname)[1]
write_name = out_dir_img+'undistorted_'+image_name
cv2.imwrite(write_name,dst)
print(write_name)
 
組合閾值處理
# 轉(zhuǎn)換到HLS顏色空間(也可嘗試其他顏色空間)
hls = cv2.cvtColor(img, cv2.COLOR_RGB2HLS).astype(np.float)
l_channel = hls[:,:,1]
s_channel = hls[:,:,2]
# 使用Sobel算法在x方向進(jìn)行閾值處理
sobelx = cv2.Sobel(l_channel, cv2.CV_64F, 1, 0)
abs_sobelx = np.absolute(sobelx)
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])] = 1
# Threshold S channel of HLS
s_binary = np.zeros_like(s_channel)
s_binary[(s_channel >= s_thresh[0]) & (s_channel <= s_thresh[1])] = 1
# Threshold L channel of HLS
l_binary = np.zeros_like(l_channel)
l_binary[(l_channel >= l_thresh[0]) & (l_channel <= l_thresh[1])] = 1
# 將各種處理方式進(jìn)行融合,得到車道線的二進(jìn)制圖。
color_binary = 255*np.dstack(( l_binary, sxbinary, s_binary)).astype('uint8')
combined_binary = np.zeros_like(sxbinary)
combined_binary[((l_binary == 1) & (s_binary == 1) | (sxbinary==1))] = 1
combined_binary = 255*np.dstack((combined_binary,combined_binary,combined_binary)).astype('uint8')
 
透視變換和ROI提取
# 使用透視變換(perspective transform)得到二進(jìn)制圖(binary image)的鳥瞰圖(birds-eye view).
img=plt.imread('test_image.jpg')
corners = np.float32([[190,720],[580,460],[705,460],[1120,720]])# tuning
imshape = img.shape
new_top_left=np.array([corners[0,0],0])
new_top_right=np.array([corners[3,0],0])
offset=[150,0]
src = np.float32([corners[0],corners[1],corners[2],corners[3]])
dst = np.float32([corners[0]+offset,new_top_left+offset,new_top_right-offset ,corners[3]-offset])
M = cv2.getPerspectiveTransform(src, dst)
warped = cv2.warpPerspective(img, M, img_size , flags=cv2.INTER_LINEAR)
# ROI提取
shape = warped.shape
vertices = np.array([[(0,0),(shape[1],0),(shape[1],0),(6*shape[1]/7,shape[0]),
(shape[1]/7,shape[0]), (0,0)]],dtype=np.int32)
mask = np.zeros_like(warped)
if len(shape) > 2:
channel_count = shape[2]
ignore_mask_color = (255,) * channel_count
else:
ignore_mask_color = 255
cv2.fillPoly(mask, vertices, ignore_mask_color)
masked_image = cv2.bitwise_and(img, mask)
利用直方圖濾波和滑動窗口進(jìn)行曲線擬合
# 對二進(jìn)制圖片的像素進(jìn)行直方圖統(tǒng)計,統(tǒng)計左右兩側(cè)的峰值點作為左右車道線的起始點坐標(biāo)進(jìn)行曲線擬合。(利用前幀圖像探索后幀曲線)
def find_peaks(img,thresh):
img_half=img[img.shape[0]//2:,:,0]
data = np.sum(img_half, axis=0)
filtered = scipy.ndimage.filters.gaussian_filter1d(data,20)
xs = np.arange(len(filtered))
peak_ind = signal.find_peaks_cwt(filtered, np.arange(20,300))
peaks = np.array(peak_ind)
peaks = peaks[filtered[peak_ind]>thresh]
return peaks,filtered
def get_next_window(img,center_point,width):
ny,nx,_ = img.shape
mask = np.zeros_like(img)
if (center_point <= width/2): center_point = width/2
if (center_point >= nx-width/2): center_point = nx-width/2
left = center_point - width/2
right = center_point + width/2
vertices = np.array([[(left,0),(left,ny), (right,ny),(right,0)]], dtype=np.int32)
ignore_mask_color=(255,255,255)
cv2.fillPoly(mask, vertices, ignore_mask_color)
masked = cv2.bitwise_and(mask,img)
hist = np.sum(masked[:,:,0],axis=0)
if max(hist>10000):
center = np.argmax(hist)
else:
center = center_point
return masked,center
def lane_from_window(binary,center_point,width):
n_zones=6
ny,nx,nc = binary.shape
zones = binary.reshape(n_zones,-1,nx,nc)
zones = zones[::-1]
window,center = get_next_window(zones[0],center_point,width)
for zone in zones[1:]:
next_window,center = get_next_window(zone,center,width)
window = np.vstack((next_window,window))
return window
left_binary = lane_from_window(warped_binary,380,300)
right_binary = lane_from_window(warped_binary,1000,300)
計算車道曲率
ym_per_pix = 30/720 # meters per pixel in y dimension
xm_per_pix = 3.7/700 # meters per pixel in x dim
lane_width = 3.7
def cal_curvature(line):
fit_coeffs_curv = np.polyfit(y*ym_per_pix, x*xm_per_pix, 2)
radius_of_curvature = ((1 + (2*fit_coeffs_curv[0]*y_eval*ym_per_pix + fit_coeffs_curv[1])**2)**1.5) /np.absolute(2*fit_coeffs_curv[0])
return radius_of_curvature
left_curvature= cal_curvature(left_line)
right_curvature = cal_curvature(right_line)
curvature = 0.5*(round(right_curvature,1) + round(left_curvature,1))
將曲線逆透視到原圖片
# 將完成車道線標(biāo)記的鳥瞰圖反透視變換為初始圖像視角
newwarp = cv2.warpPerspective(color_warp, Minv, (img.shape[1], img.shape[0]))
將算法應(yīng)用于視頻
output = 'test_video_output.mp4'
clip = VideoFileClip("test_video.mp4")
out_clip = clip.fl_image(process_image)
%time out_clip.write_videofile(output, audio=False)
 
 
分享到:
 
反對 0 舉報 0 收藏 0 評論 0
滬ICP備11026917號-25