基于OpenCV實(shí)現(xiàn)車道線檢測(cè)(自動(dòng)駕駛 機(jī)器視覺)
0 前言
無人駕駛技術(shù)是機(jī)器學(xué)習(xí)為主的一門前沿領(lǐng)域,在無人駕駛領(lǐng)域中機(jī)器學(xué)習(xí)的各種算法隨處可見,今天學(xué)長(zhǎng)給大家介紹無人駕駛技術(shù)中的車道線檢測(cè)。
1 車道線檢測(cè)
在無人駕駛領(lǐng)域每一個(gè)任務(wù)都是相當(dāng)復(fù)雜,看上去無從下手。那么面對(duì)這樣極其復(fù)雜問題,我們解決問題方式從先嘗試簡(jiǎn)化問題,然后由簡(jiǎn)入難一步一步嘗試來一個(gè)一個(gè)地解決問題。車道線檢測(cè)在無人駕駛中應(yīng)該算是比較簡(jiǎn)單的任務(wù),依賴計(jì)算機(jī)視覺一些相關(guān)技術(shù),通過讀取 camera 傳入的圖像數(shù)據(jù)進(jìn)行分析,識(shí)別出車道線位置,我想這個(gè)對(duì)于 lidar 可能是無能為力。所以今天我們就從最簡(jiǎn)單任務(wù)說起,看看有哪些技術(shù)可以幫助我們檢出車道線。
我們先把問題簡(jiǎn)化,所謂簡(jiǎn)化問題就是用一些條件限制來縮小車道線檢測(cè)的問題。我們先看數(shù)據(jù),也就是輸入算法是車輛行駛的圖像,輸出車道線位置。
更多時(shí)候我們?nèi)绾翁幚硪患容^困難任務(wù),可能有時(shí)候我們拿到任務(wù)時(shí)還沒有任何思路,不要著急也不用想太多,我們先開始一步一步地做,從最簡(jiǎn)單的開始做起,隨著做就會(huì)有思路,同樣一些問題也會(huì)暴露出來。我們先找一段視頻,這段視頻是我從網(wǎng)上一個(gè)關(guān)于車道線檢測(cè)項(xiàng)目中拿到的,也參考他的思路來做這件事。好現(xiàn)在就開始做這件事,那么最簡(jiǎn)單的事就是先讀取視頻,然后將其顯示在屏幕以便于調(diào)試。
2 目標(biāo)
檢測(cè)圖像中車道線位置,將車道線信息提供路徑規(guī)劃。
3 檢測(cè)思路
- 圖像灰度處理
- 圖像高斯平滑處理
- canny 邊緣檢測(cè)
- 區(qū)域 Mask
- 霍夫變換
- 繪制車道線
4 代碼實(shí)現(xiàn)
4.1 視頻圖像加載
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/丹成學(xué)長(zhǎng)車道線檢測(cè).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()
上面代碼學(xué)長(zhǎng)就不多說了,默認(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 格式。
4.2 車道線區(qū)域
現(xiàn)在這個(gè)區(qū)域是我們根據(jù)觀測(cè)圖像繪制出來,
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
效果如下:
4.3 區(qū)域
我們要檢測(cè)車道線位置相對(duì)比較固定,通常出現(xiàn)車的前方,所以我們通過繪制,也就是僅檢測(cè)我們關(guān)心區(qū)域。通過創(chuàng)建 mask 來過濾掉那些不關(guān)心的區(qū)域保留關(guān)心區(qū)域。
4.4 canny 邊緣檢測(cè)
有關(guān)邊緣檢測(cè)也是計(jì)算機(jī)視覺。首先利用梯度變化來檢測(cè)圖像中的邊,如何識(shí)別圖像的梯度變化呢,答案是卷積核。卷積核是就是不連續(xù)的像素上找到梯度變化較大位置。我們知道 sobal 核可以很好檢測(cè)邊緣,那么 canny 就是 sobal 核檢測(cè)上進(jìn)行優(yōu)化。
# 示例代碼,作者丹成學(xué)長(zhǎng):Q746876041 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
4.5 霍夫變換(Hough transform)
霍夫變換是將 x 和 y 坐標(biāo)系中的線映射表示在霍夫空間的點(diǎn)(m,b)。所以霍夫變換實(shí)際上一種由繁到簡(jiǎn)(類似降維)的操作。當(dāng)使用 canny 進(jìn)行邊緣檢測(cè)后圖像可以交給霍夫變換進(jìn)行簡(jiǎn)單圖形(線、圓)等的識(shí)別。這里用霍夫變換在 canny 邊緣檢測(cè)結(jié)果中尋找直線。
# 示例代碼,作者丹成學(xué)長(zhǎng):Q746876041 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 邊緣檢測(cè)結(jié)果應(yīng)用 Hough 變換 # 輸出“線”是一個(gè)數(shù)組,其中包含檢測(cè)到的線段的端點(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)) 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 邊緣檢測(cè) 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
4.6 HoughLinesP 檢測(cè)原理
接下來進(jìn)入代碼環(huán)節(jié),學(xué)長(zhǎng)詳細(xì)給大家解釋一下 HoughLinesP 參數(shù)的含義以及如何使用。
lines = cv2.HoughLinesP(cropped_image,2,np.pi/180,100,np.array([]),minLineLength=40,maxLineGap=5)
第一參數(shù)是我們要檢查的圖片 Hough accumulator 數(shù)組
第二個(gè)和第三個(gè)參數(shù)用于定義我們 Hough 坐標(biāo)如何劃分 bin,也就是小格的精度。我們通過曲線穿過 bin 格子來進(jìn)行投票,我們根據(jù)投票數(shù)量來決定 p 和 theta 的值。2 表示我們小格寬度以像素為單位 。
我們可以通過下圖劃分小格,只要曲線穿過就會(huì)對(duì)小格進(jìn)行投票,我們記錄投票數(shù)量,記錄最多的作為參數(shù)
- 如果定義尺寸過大也就失去精度,如果定義格子尺寸過小雖然精度上來了,這樣也會(huì)打來增長(zhǎng)計(jì)算時(shí)間。
- 接下來參數(shù) 100 表示我們投票為 100 以上的線才是符合要求是我們要找的線。也就是在 bin 小格子需要有 100 以上線相交于此才是我們要找的參數(shù)。
- minLineLength 給 40 表示我們檢查線長(zhǎng)度不能小于 40 pixel
- maxLineGap=5 作為線間斷不能大于 5 pixel
4.6.1 定義顯示車道線方法
def disply_lines(image,lines): pass
通過定義函數(shù)將找到的車道線顯示出來。
line_image = disply_lines(lane_image,lines)
4.6.2 查看探測(cè)車道線數(shù)據(jù)結(jié)構(gòu)
def disply_lines(image,lines): line_image = np.zeros_like(image) if lines is not None: for line in lines: print(line)
先定義一個(gè)尺寸大小和原圖一樣的矩陣用于繪制查找到車道線,我們先判斷一下是否已經(jīng)找到車道線,lines 返回值應(yīng)該不為 None 是一個(gè)矩陣,我們可以簡(jiǎn)單地打印一下看一下效果
[[704 418 927 641]] [[704 426 791 516]] [[320 703 445 494]] [[585 301 663 381]] [[630 341 670 383]]
4.6.3 探測(cè)車道線
看數(shù)據(jù)結(jié)構(gòu)[[x1,y1,x2,y2]] 的二維數(shù)組,這就需要我們轉(zhuǎn)換一下為一維數(shù)據(jù)[x1,y1,x2,y2]
def disply_lines(image,lines): line_image = np.zeros_like(image) if liness is not None: for line in lines: x1,y1,x2,y2 = line.reshape(4) cv2.line(line_image,(x1,y1),(x2,y2),(255,0,0),10) return line_image line_image = disply_lines(lane_image,lines) cv2.imshow('result',line_image)
4.6.4 合成
有關(guān)合成圖片我們是將兩張圖片通過給一定權(quán)重進(jìn)行疊加合成。
4.6.5 優(yōu)化
探測(cè)到的車道線還是不夠平滑,我們需要優(yōu)化,基本思路就是對(duì)這些直線的斜率和截距取平均值然后將所有探測(cè)出點(diǎn)繪制到一條直線上。
def average_slope_intercept(image,lines): left_fit = [] right_fit = [] for line in lines: x1, y1, x2, y2 = line.reshape(4) parameters = np.polyfit((x1,x2),(y1,y2),1) print(parameters)
這里學(xué)長(zhǎng)定義兩個(gè)數(shù)組 left_fit 和 right_fit 分別用于存放左右兩側(cè)車道線的點(diǎn),我們打印一下 lines 的斜率和截距,通過 numpy 提供 polyfit 方法輸入兩個(gè)點(diǎn)我們就可以得到通過這些點(diǎn)的直線的斜率和截距。
[ 1. -286.] [ 1.03448276 -302.27586207] [ -1.672 1238.04 ] [ 1.02564103 -299.
[ 1.02564103 -299. def average_slope_intercept(image,lines): left_fit = [] right_fit = [] for line in lines: x1, y1, x2, y2 = line.reshape(4) parameters = np.polyfit((x1,x2),(y1,y2),1) # print(parameters) slope = parameters[0] intercept = parameters[1] if slope < 0: left_fit.append((slope,intercept)) else: right_fit.append((slope,intercept)) print(left_fit) print(right_fit)
我們輸出一下圖片大小,我們圖片是以其左上角作為原點(diǎn) 0 ,0 來開始計(jì)算的,所以我們直線從圖片底部 700 多向上繪制我們無需繪制全部可以截距一部分即可。
def make_coordinates(image, line_parameters): slope, intercept = line_parameters y1 = image.shape[0] y2 = int(y1*(3/5)) x1 = int((y1 - intercept)/slope) x2 = int((y2 - intercept)/slope) # print(image.shape) return np.array([x1,y1,x2,y2])
所以直線開始和終止我們給定 y1,y2 然后通過方程的斜率和截距根據(jù)y 算出 x。
averaged_lines = average_slope_intercept(lane_image,lines); line_image = disply_lines(lane_image,averaged_lines) combo_image = cv2.addWeighted(lane_image,0.8, line_image, 1, 1,1) cv2.imshow('result',combo_image)
到此這篇關(guān)于基于OpenCV實(shí)現(xiàn)車道線檢測(cè)(自動(dòng)駕駛 機(jī)器視覺)的文章就介紹到這了,更多相關(guān)OpenCV 車道線檢測(cè)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++編程中的或||、與&&、非!邏輯運(yùn)算符基本用法整理
這篇文章主要介紹了C++中的或||、與&&、非!邏輯運(yùn)算符基本用法整理,是C++入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2016-01-01Cocos2d-x學(xué)習(xí)筆記之開發(fā)環(huán)境搭建
這篇文章主要介紹了Cocos2d-x學(xué)習(xí)筆記之開發(fā)環(huán)境搭建,本文使用Visual Studio作為開發(fā)IDE,是不同于其它教程的,需要的朋友可以參考下2014-09-09C語言多維數(shù)組數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn)詳解
對(duì)于數(shù)組想必大家都不陌生首先得要知道的是對(duì)于數(shù)組元素在內(nèi)存存儲(chǔ)是連續(xù)性的,下面這篇文章主要給大家介紹了關(guān)于C語言多維數(shù)組數(shù)據(jù)結(jié)構(gòu)的相關(guān)資料,需要的朋友可以參考下2021-12-12C語言fgetc和fputc函數(shù)用法詳解(以字符形式讀寫文件)
這篇文章主要介紹了C語言fgetc和fputc函數(shù)用法詳解(以字符形式讀寫文件),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01C標(biāo)準(zhǔn)庫<assert.h>的實(shí)現(xiàn)詳解
這篇文章主要介紹了C標(biāo)準(zhǔn)庫<assert.h>的實(shí)現(xiàn),主要包括了<assert.h>的基本概念、實(shí)現(xiàn)及用法等,需要的朋友可以參考下2014-09-09