基於python的車道線檢測
最近在開源社群下載了一份使用opencv在python環境中實現車道線檢測的程式碼,研究了一下,終於有點兒看懂了,尋思著寫下來,免得以後忘記了。
這個車道線檢測專案原本是優達學城裡無人駕駛課程中的第一個上手的專案,原始碼應該是一個外國人寫的吧,反正大家傳來傳去,我覺得挺有意思。說說這個程式碼實現車道線檢測的過程吧。
(1)對視訊流進行處理
主要使用了moviepy.editor中的VideoFileClip,利用裡面的fl_image()將輸入的視訊流轉化成了一幀幀的影象。
(2)影象轉換為灰度影象
這個主要是使用opencv中的函式cv2.cvtColor()將影象轉化為灰度影象
(3)使用高斯模糊對影象進行去噪聲處理
(4)使用cv2.canny()提取影象的輪廓
(5)模板影象
其實就是隻保留影象的感興趣區域,減少計算時間,因為實際中攝像頭的固定在車上,所拍攝的影象中特定的部分包含車道線,一般都位於圖片的中下部,所以只需要對這個區域進行處理即可。
(6)使用霍夫直線檢測,提取輪廓圖中的直線
這裡使用的依然是opencv中的函式:cv2.HoughLinesP()它是一個基於概率的直線檢測演算法,可以直接輸出檢測到的直線的點集(一般採用兩點表示一條直線),關於這個演算法的詳情可以參考這個部落格:
(7)對提取後的直線集合進行處理
上一步中提取到了影象中所有直線特徵的點集,包含了很多的直線,但是很多事我們不需要的直線,這就需要特別處理–去掉冗餘的直線。怎麼做呢?作者給出了一個好的辦法:首先對直線點集根據斜率的正負分成左右兩類直線集;然後在分別對左右直線集進行處理,方法為:每次計算直線集的平均斜率和每條直線的斜率與平均斜率的差值的絕對值,求出其中差值最大的直線,判斷該直線的斜率是否大於閾值,如果大於閾值,就將其彈出,然後對剩下的直線繼續進行相同的操作,指導滿足條件為止。
(8)畫出車道線
上一步中得到的直線集依然很多(可以看做是點集),但是範圍就已經很小了,這裡採用最小二乘擬合的方式,將這些到用直線擬合,得到最後的左右車道線。之後在圖上繪製出車道線就可以了。
下面將我註釋過的程式碼貼出來:
from moviepy.editor import VideoFileClip
import matplotlib.pyplot as plt
import matplotlib.image as mplimg
import numpy as np
import cv2
blur_ksize = 5 # Gaussian blur kernel size
canny_lthreshold = 50 # Canny edge detection low threshold
canny_hthreshold = 150 # Canny edge detection high threshold
# Hough transform parameters
rho = 1#rho的步長,即直線到影象原點(0,0)點的距離
theta = np.pi / 180#theta的範圍
threshold = 15#累加器中的值高於它時才認為是一條直線
min_line_length = 40#線的最短長度,比這個短的都被忽略
max_line_gap = 20#兩條直線之間的最大間隔,小於此值,認為是一條直線
def roi_mask(img, vertices):#img是輸入的影象,verticess是興趣區的四個點的座標(三維的陣列)
mask = np.zeros_like(img)#生成與輸入影象相同大小的影象,並使用0填充,影象為黑色
#defining a 3 channel or 1 channel color to fill the mask with depending on the input image
if len(img.shape) > 2:
channel_count = img.shape[2] # i.e. 3 or 4 depending on your image
mask_color = (255,) * channel_count#如果 channel_count=3,則為(255,255,255)
else:
mask_color = 255
cv2.fillPoly(mask, vertices, mask_color)#使用白色填充多邊形,形成蒙板
masked_img = cv2.bitwise_and(img, mask)#img&mask,經過此操作後,興趣區域以外的部分被矇住了,只留下興趣區域的影象
return masked_img
def draw_roi(img, vertices):
cv2.polylines(img, vertices, True, [255, 0, 0], thickness=2)
def draw_lines(img, lines, color=[255, 0, 0], thickness=2):
for line in lines:
for x1, y1, x2, y2 in line:
cv2.line(img, (x1, y1), (x2, y2), color, thickness)
def hough_lines(img, rho, theta, threshold, min_line_len, max_line_gap):
lines = cv2.HoughLinesP(img, rho, theta, threshold, np.array([]), minLineLength=min_line_len, maxLineGap=max_line_gap)#函式輸出的直接就是一組直線點的座標位置(每條直線用兩個點表示[x1,y1],[x2,y2])
line_img = np.zeros((img.shape[0], img.shape[1], 3), dtype=np.uint8)#生成繪製直線的繪圖板,黑底
# draw_lines(line_img, lines)
draw_lanes(line_img, lines)
return line_img
def draw_lanes(img, lines, color=[255, 0, 0], thickness=8):
left_lines, right_lines = [], []#用於儲存左邊和右邊的直線
for line in lines:#對直線進行分類
for x1, y1, x2, y2 in line:
k = (y2 - y1) / (x2 - x1)
if k < 0:
left_lines.append(line)
else:
right_lines.append(line)
if (len(left_lines) <= 0 or len(right_lines) <= 0):
return img
clean_lines(left_lines, 0.1)#彈出左側不滿足斜率要求的直線
clean_lines(right_lines, 0.1)#彈出右側不滿足斜率要求的直線
left_points = [(x1, y1) for line in left_lines for x1,y1,x2,y2 in line]#提取左側直線族中的所有的第一個點
left_points = left_points + [(x2, y2) for line in left_lines for x1,y1,x2,y2 in line]#提取左側直線族中的所有的第二個點
right_points = [(x1, y1) for line in right_lines for x1,y1,x2,y2 in line]#提取右側直線族中的所有的第一個點
right_points = right_points + [(x2, y2) for line in right_lines for x1,y1,x2,y2 in line]#提取右側側直線族中的所有的第二個點
left_vtx = calc_lane_vertices(left_points, 325, img.shape[0])#擬合點集,生成直線表示式,並計算左側直線在影象中的兩個端點的座標
right_vtx = calc_lane_vertices(right_points, 325, img.shape[0])#擬合點集,生成直線表示式,並計算右側直線在影象中的兩個端點的座標
cv2.line(img, left_vtx[0], left_vtx[1], color, thickness)#畫出直線
cv2.line(img, right_vtx[0], right_vtx[1], color, thickness)#畫出直線
#將不滿足斜率要求的直線彈出
def clean_lines(lines, threshold):
slope=[]
for line in lines:
for x1,y1,x2,y2 in line:
k=(y2-y1)/(x2-x1)
slope.append(k)
#slope = [(y2 - y1) / (x2 - x1) for line in lines for x1, y1, x2, y2 in line]
while len(lines) > 0:
mean = np.mean(slope)#計算斜率的平均值,因為後面會將直線和斜率值彈出
diff = [abs(s - mean) for s in slope]#計算每條直線斜率與平均值的差值
idx = np.argmax(diff)#計算差值的最大值的下標
if diff[idx] > threshold:#將差值大於閾值的直線彈出
slope.pop(idx)#彈出斜率
lines.pop(idx)#彈出直線
else:
break
#擬合點集,生成直線表示式,並計算直線在影象中的兩個端點的座標
def calc_lane_vertices(point_list, ymin, ymax):
x = [p[0] for p in point_list]#提取x
y = [p[1] for p in point_list]#提取y
fit = np.polyfit(y, x, 1)#用一次多項式x=a*y+b擬合這些點,fit是(a,b)
fit_fn = np.poly1d(fit)#生成多項式物件a*y+b
xmin = int(fit_fn(ymin))#計算這條直線在影象中最左側的橫座標
xmax = int(fit_fn(ymax))#計算這條直線在影象中最右側的橫座標
return [(xmin, ymin), (xmax, ymax)]
def process_an_image(img):
roi_vtx = np.array([[(0, img.shape[0]), (460, 325), (520, 325), (img.shape[1], img.shape[0])]])#目標區域的四個點座標,roi_vtx是一個三維的陣列
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)#影象轉換為灰度圖
blur_gray = cv2.GaussianBlur(gray, (blur_ksize, blur_ksize), 0, 0)#使用高斯模糊去噪聲
edges = cv2.Canny(blur_gray, canny_lthreshold, canny_hthreshold)#使用Canny進行邊緣檢測
roi_edges = roi_mask(edges, roi_vtx)#對邊緣檢測的影象生成影象蒙板,去掉不感興趣的區域,保留興趣區
line_img = hough_lines(roi_edges, rho, theta, threshold, min_line_length, max_line_gap)#使用霍夫直線檢測,並且繪製直線
res_img = cv2.addWeighted(img, 0.8, line_img, 1, 0)#將處理後的影象與原圖做融合
return res_img
img = mplimg.imread("lane.jpg")
print("start to process the image....")
res_img=process_an_image(img)
print("show you the image....")
plt.imshow(res_img)
plt.show()
print("start to process the video....")
output = 'video_2_xlt.mp4'#ouput video
clip = VideoFileClip("video_2.mp4")#input video
out_clip = clip.fl_image(process_an_image)#對視訊的每一幀進行處理
out_clip.write_videofile(output, audio=True)#將處理後的視訊寫入新的視訊檔案
視訊檔案和原始碼都在這個連結裡面:
https://download.csdn.net/download/tomhard/10128952
處理前的圖片:
處理後的圖片:
當然,這個專案只是簡單的車道線檢測,並沒有做進一步的擴充套件,比如相機的標定,根據車道線的位置判斷車輛偏移車道的情況等。