2020 無人駕駛(6)之車道線檢測

tesla_001.jpg

甜品

為什么喜歡無人駕駛?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ù)觀測圖像繪制出來删掀,


drive_on_road.jpg

<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">


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"/>


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
simple_lane_detector.jpg

TODO

  • 車道線區(qū)域

困難

  • 復(fù)雜路況,城市路況
  • 車道線被前車覆蓋
  • 雨雪天氣
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末煞额,一起剝皮案震驚了整個(gè)濱河市思恐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌膊毁,老刑警劉巖胀莹,帶你破解...
    沈念sama閱讀 221,888評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異婚温,居然都是意外死亡描焰,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門栅螟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來荆秦,“玉大人,你說我怎么就攤上這事力图〔匠瘢” “怎么了?”我有些...
    開封第一講書人閱讀 168,386評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵搪哪,是天一觀的道長靡努。 經(jīng)常有香客問我,道長晓折,這世上最難降的妖魔是什么惑朦? 我笑而不...
    開封第一講書人閱讀 59,726評(píng)論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮漓概,結(jié)果婚禮上漾月,老公的妹妹穿的比我還像新娘。我一直安慰自己胃珍,他們只是感情好梁肿,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評(píng)論 6 397
  • 文/花漫 我一把揭開白布蜓陌。 她就那樣靜靜地躺著,像睡著了一般吩蔑。 火紅的嫁衣襯著肌膚如雪钮热。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,337評(píng)論 1 310
  • 那天烛芬,我揣著相機(jī)與錄音隧期,去河邊找鬼。 笑死赘娄,一個(gè)胖子當(dāng)著我的面吹牛仆潮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播遣臼,決...
    沈念sama閱讀 40,902評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼性置,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了揍堰?” 一聲冷哼從身側(cè)響起鹏浅,我...
    開封第一講書人閱讀 39,807評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎个榕,沒想到半個(gè)月后篡石,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,349評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡西采,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評(píng)論 3 340
  • 正文 我和宋清朗相戀三年凰萨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片械馆。...
    茶點(diǎn)故事閱讀 40,567評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡胖眷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出霹崎,到底是詐尸還是另有隱情珊搀,我是刑警寧澤,帶...
    沈念sama閱讀 36,242評(píng)論 5 350
  • 正文 年R本政府宣布尾菇,位于F島的核電站境析,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏派诬。R本人自食惡果不足惜劳淆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望默赂。 院中可真熱鬧沛鸵,春花似錦、人聲如沸缆八。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至栏妖,卻和暖如春乱豆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背底哥。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評(píng)論 1 272
  • 我被黑心中介騙來泰國打工咙鞍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人趾徽。 一個(gè)月前我還...
    沈念sama閱讀 48,995評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像翰守,于是被迫代替她去往敵國和親孵奶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評(píng)論 2 359