甜品
為什么喜歡無人駕駛?cè)蝿?wù)淋淀,主要原因無人駕駛?cè)蝿?wù)是機(jī)器學(xué)習(xí)為主的一門前沿領(lǐng)域,在無人駕駛領(lǐng)域中機(jī)器學(xué)習(xí)的各種算法隨處可見够挂,其實(shí)這么說也不算嚴(yán)謹(jǐn)顷啼。而于其他機(jī)器學(xué)習(xí)應(yīng)用的場景中,機(jī)器學(xué)習(xí)多半是一種輔助或者錦上添花的技術(shù)七兜。
分享內(nèi)容
以后分享主要分幾個(gè)部分
- 目標(biāo)
- 原理(做任務(wù)基本思路)
- TODO 也就是隨后要做的事以及要優(yōu)化的內(nèi)容
- 難點(diǎn)丸凭,關(guān)于這個(gè)任務(wù)難點(diǎn),我們可以一起討論
車道線檢測
在無人駕駛領(lǐng)域每一個(gè)任務(wù)都是相當(dāng)復(fù)雜腕铸,看上去無從下手惜犀。那么面對(duì)這樣極其復(fù)雜問題,我們解決問題方式從先嘗試簡化問題狠裹,然后由簡入難一步一步嘗試來一個(gè)一個(gè)地解決問題虽界。車道線檢測在無人駕駛中應(yīng)該算是比較簡單的任務(wù),依賴計(jì)算機(jī)視覺一些相關(guān)技術(shù)涛菠,通過讀取 camera 傳入的圖像數(shù)據(jù)進(jìn)行分析莉御,識(shí)別出車道線位置撇吞,我想這個(gè)對(duì)于 lidar 可能是無能為力。所以今天我們就從最簡單任務(wù)說起礁叔,看看有哪些技術(shù)可以幫助我們檢出車道線牍颈。
我們先把問題簡化,所謂簡化問題就是用一些條件限制來縮小車道線檢測的問題琅关。我們先看數(shù)據(jù)煮岁,也就是輸入算法是車輛行駛的圖像,輸出車道線位置涣易。
更多時(shí)候我們?nèi)绾翁幚硪患容^困難任務(wù)画机,可能有時(shí)候我們拿到任務(wù)時(shí)還沒有任何思路,不要著急也不用想太多都毒,我們先開始一步一步地做色罚,從最簡單的開始做起,隨著做就會(huì)有思路账劲,同樣一些問題也會(huì)暴露出來戳护。我們先找一段視頻,這段視頻是我從網(wǎng)上一個(gè)關(guān)于車道線檢測項(xiàng)目中拿到的瀑焦,也參考他的思路來做這件事腌且。好現(xiàn)在就開始做這件事,那么最簡單的事就是先讀取視頻榛瓮,然后將其顯示在屏幕以便于調(diào)試铺董。
目標(biāo)
檢測圖像中車道線位置,將車道線信息提供路徑規(guī)劃禀晓。
思路
- 圖像灰度處理
- 圖像高斯平滑處理
- canny 邊緣檢測
- 區(qū)域 Mask
- 霍夫變換
- 繪制車道線
import cv2
import numpy as np
import sys
import pygame
from pygame.locals import *
class Display(object):
def __init__(self,Width,Height):
pygame.init()
pygame.display.set_caption('Drive Video')
self.screen = pygame.display.set_mode((Width,Height),0,32)
def paint(self,draw):
self.screen.fill([0,0,0])
draw = cv2.transpose(draw)
draw = pygame.surfarray.make_surface(draw)
self.screen.blit(draw,(0,0))
pygame.display.update()
if __name__ == "__main__":
solid_white_right_video_path = "test_videos/solidWhiteRight.mp4"
cap = cv2.VideoCapture(solid_white_right_video_path)
Width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
Height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
display = Display(Width,Height)
while True:
ret, draw = cap.read()
draw = cv2.cvtColor(draw,cv2.COLOR_BGR2RGB)
if ret == False:
break
display.paint(draw)
for event in pygame.event.get():
if event.type == QUIT:
sys.exit()
上面代碼我就不多說了精续,默認(rèn)您對(duì) python 是有所了解,關(guān)于如何使用 opencv 讀取圖片網(wǎng)上代碼示例也很多粹懒,大家一看就懂重付。這里因?yàn)槲矣玫氖?mac 有時(shí)候顯示視頻圖像可能會(huì)有些問題,所以我們用 pygame 來顯示 opencv 讀取圖像凫乖。這個(gè)大家根據(jù)自己實(shí)際情況而定吧确垫。值得說一句的是 opencv 讀取圖像是 BGR 格式,要想在 pygame 中正確顯示圖像就需要將 BGR 轉(zhuǎn)換為 RGB 格式帽芽。
車道線區(qū)域
現(xiàn)在這個(gè)區(qū)域是我們根據(jù)觀測圖像繪制出來删掀,
<img src="images/drive_on_road.jpg">
顏色選擇
def color_select(img,red_threshold=200,green_threshold=200,blue_threshold=200):
ysize,xsize = img.shape[:2]
color_select = np.copy(img)
rgb_threshold = [red_threshold, green_threshold, blue_threshold]
thresholds = (img[:,:,0] < rgb_threshold[0]) \
| (img[:,:,1] < rgb_threshold[1]) \
| (img[:,:,2] < rgb_threshold[2])
color_select[thresholds] = [0,0,0]
return color_select
<img src="images/color_select.jpg">
draw = color_select(draw)
區(qū)域
我們要檢測車道線位置相對(duì)比較固定,通常出現(xiàn)車的前方导街,所以我們通過繪制披泪,也就是僅檢測我們關(guān)心區(qū)域。通過創(chuàng)建 mask 來過濾掉那些不關(guān)心的區(qū)域保留關(guān)心區(qū)域搬瑰。
canny 邊緣檢測
有關(guān)邊緣檢測也是計(jì)算機(jī)視覺付呕。首先利用梯度變化來檢測圖像中的邊计福,如何識(shí)別圖像的梯度變化呢,答案是卷積核徽职。卷積核是就是不連續(xù)的像素上找到梯度變化較大位置。我們知道 sobal 核可以很好檢測邊緣佩厚,那么 canny 就是 sobal 核檢測上進(jìn)行優(yōu)化姆钉。
def canny_edge_detect(img):
gray = cv2.cvtColor(img,cv2.COLOR_RGB2GRAY)
kernel_size = 5
blur_gray = cv2.GaussianBlur(gray,(kernel_size, kernel_size),0)
low_threshold = 180
high_threshold = 240
edges = cv2.Canny(blur_gray, low_threshold, high_threshold)
return edges
<img src="images/canny_edge_detect.jpg"/>
霍夫變換(Hough transform)
霍夫變換是將 x 和 y 坐標(biāo)系中的線映射表示在霍夫空間的點(diǎn)(m,b)。所以霍夫變換實(shí)際上一種由繁到簡(類似降維)的操作抄瓦。當(dāng)使用 canny 進(jìn)行邊緣檢測后圖像可以交給霍夫變換進(jìn)行簡單圖形(線潮瓶、圓)等的識(shí)別。這里用霍夫變換在 canny 邊緣檢測結(jié)果中尋找直線钙姊。
mask = np.zeros_like(edges)
ignore_mask_color = 255
# 獲取圖片尺寸
imshape = img.shape
# 定義 mask 頂點(diǎn)
vertices = np.array([[(0,imshape[0]),(450, 290), (490, 290), (imshape[1],imshape[0])]], dtype=np.int32)
# 使用 fillpoly 來繪制 mask
cv2.fillPoly(mask, vertices, ignore_mask_color)
masked_edges = cv2.bitwise_and(edges, mask)
# 定義Hough 變換的參數(shù)
rho = 1
theta = np.pi/180
threshold = 2
min_line_length = 4 # 組成一條線的最小像素?cái)?shù)
max_line_gap = 5 # 可連接線段之間的最大像素間距
# 創(chuàng)建一個(gè)用于繪制車道線的圖片
line_image = np.copy(img)*0
# 對(duì)于 canny 邊緣檢測結(jié)果應(yīng)用 Hough 變換
# 輸出“線”是一個(gè)數(shù)組毯辅,其中包含檢測到的線段的端點(diǎn)
lines = cv2.HoughLinesP(masked_edges, rho, theta, threshold, np.array([]),
min_line_length, max_line_gap)
# 遍歷“線”的數(shù)組來在 line_image 上繪制
for line in lines:
for x1,y1,x2,y2 in line:
cv2.line(line_image,(x1,y1),(x2,y2),(255,0,0),10)
color_edges = np.dstack((edges, edges, edges))
<img src="images/simple_lane_detector.jpg">
import math
import cv2
import numpy as np
"""
Gray Scale
Gaussian Smoothing
Canny Edge Detection
Region Masking
Hough Transform
Draw Lines [Mark Lane Lines with different Color]
"""
class SimpleLaneLineDetector(object):
def __init__(self):
pass
def detect(self,img):
# 圖像灰度處理
gray_img = self.grayscale(img)
print(gray_img)
#圖像高斯平滑處理
smoothed_img = self.gaussian_blur(img = gray_img, kernel_size = 5)
#canny 邊緣檢測
canny_img = self.canny(img = smoothed_img, low_threshold = 180, high_threshold = 240)
#區(qū)域 Mask
masked_img = self.region_of_interest(img = canny_img, vertices = self.get_vertices(img))
#霍夫變換
houghed_lines = self.hough_lines(img = masked_img, rho = 1, theta = np.pi/180, threshold = 20, min_line_len = 20, max_line_gap = 180)
# 繪制車道線
output = self.weighted_img(img = houghed_lines, initial_img = img, alpha=0.8, beta=1., gamma=0.)
return output
def grayscale(self,img):
return cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
def canny(self,img, low_threshold, high_threshold):
return cv2.Canny(img, low_threshold, high_threshold)
def gaussian_blur(self,img, kernel_size):
return cv2.GaussianBlur(img, (kernel_size, kernel_size), 0)
def region_of_interest(self,img, vertices):
mask = np.zeros_like(img)
if len(img.shape) > 2:
channel_count = img.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)
return masked_image
def draw_lines(self,img, lines, color=[255, 0, 0], thickness=10):
for line in lines:
for x1,y1,x2,y2 in line:
cv2.line(img, (x1, y1), (x2, y2), color, thickness)
def slope_lines(self,image,lines):
img = image.copy()
poly_vertices = []
order = [0,1,3,2]
left_lines = []
right_lines = []
for line in lines:
for x1,y1,x2,y2 in line:
if x1 == x2:
pass
else:
m = (y2 - y1) / (x2 - x1)
c = y1 - m * x1
if m < 0:
left_lines.append((m,c))
elif m >= 0:
right_lines.append((m,c))
left_line = np.mean(left_lines, axis=0)
right_line = np.mean(right_lines, axis=0)
for slope, intercept in [left_line, right_line]:
rows, cols = image.shape[:2]
y1= int(rows)
y2= int(rows*0.6)
x1=int((y1-intercept)/slope)
x2=int((y2-intercept)/slope)
poly_vertices.append((x1, y1))
poly_vertices.append((x2, y2))
self.draw_lines(img, np.array([[[x1,y1,x2,y2]]]))
poly_vertices = [poly_vertices[i] for i in order]
cv2.fillPoly(img, pts = np.array([poly_vertices],'int32'), color = (0,255,0))
return cv2.addWeighted(image,0.7,img,0.4,0.)
def hough_lines(self,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)
line_img = np.zeros((img.shape[0], img.shape[1], 3), dtype=np.uint8)
line_img = self.slope_lines(line_img,lines)
return line_img
def weighted_img(self,img, initial_img, alpha=0.1, beta=1., gamma=0.):
lines_edges = cv2.addWeighted(initial_img, alpha, img, beta, gamma)
return lines_edges
def get_vertices(self,image):
rows, cols = image.shape[:2]
bottom_left = [cols*0.15, rows]
top_left = [cols*0.45, rows*0.6]
bottom_right = [cols*0.95, rows]
top_right = [cols*0.55, rows*0.6]
ver = np.array([[bottom_left, top_left, top_right, bottom_right]], dtype=np.int32)
return ver
TODO
- 車道線區(qū)域
困難
- 復(fù)雜路況,城市路況
- 車道線被前車覆蓋
- 雨雪天氣