在介紹計(jì)算機(jī)視覺技術(shù)前,我想先討論一下這次分享的輸入和輸出。
輸入
一張攝像機(jī)拍攝到的道路圖片,圖片中需要包含車道線。如下圖所示。
輸出
圖像坐標(biāo)系下的左右車道線的直線方程和有效距離。將左右車道線的方程繪制到原始圖像上,應(yīng)如下圖所示。
在輸入和輸出都定義清楚后,我們就開始使用計(jì)算機(jī)視覺技術(shù),一步步完成對原始圖像的處理。
原始圖像
認(rèn)識圖像前,我們需要先回顧一下在初中所學(xué)的物理知識——光的三原色,光的三原色分別是紅色(Red)、綠色(Green)和藍(lán)色(Blue)。通過不同比例的三原色組合形成不同的可見光色。如下圖所示。
圖片出處:https://zhidao.baidu.com/question/197911511.html
圖像中的每個(gè)像素點(diǎn)都是由RGB(紅綠藍(lán))三個(gè)顏色通道組成。為了方便描述RGB顏色模型,在計(jì)算機(jī)中約束了每個(gè)通道由暗到亮的范圍是0~255。
當(dāng)某個(gè)像素點(diǎn)的R通道數(shù)值為255,G和B通道數(shù)值為0時(shí),實(shí)際表現(xiàn)出的顏色就是最亮的紅色;當(dāng)某個(gè)像素點(diǎn)的RGB三通道都為255時(shí),所表示的是最亮的白色;當(dāng)某個(gè)像素點(diǎn)的RGB三通道都為0時(shí),就會顯示最暗的黑色。在RGB顏色模型中,不會有比[255,255,255]的組合更亮的顏色了。
根據(jù)以上理論基礎(chǔ),一幅彩色圖像,其實(shí)就是由三幅單通道的圖像疊加,如下圖所示。
以基于python的OpenCV為例,讀取名為test_img.jpg的圖片到計(jì)算機(jī)內(nèi)存中的代碼如下:
import cv2
img = cv2.imread('image_name.jpg')
讀取圖像后,我們可以將圖像看做一個(gè)二維數(shù)組,每個(gè)數(shù)組元素中存了三個(gè)值,分別是RGB三個(gè)通道所對應(yīng)的數(shù)值。
OpenCV定義了,圖像的原點(diǎn)(0,0)在圖片的左上角,橫軸為X,朝右,縱軸為Y,朝下,如下圖所示。
需要注意的是,由于OpenCV的早期開發(fā)者習(xí)慣于使用BGR順序的顏色模型,因此使用OpenCV的imread()讀到的像素,每個(gè)像素的排列是按BGR,而不是常見的RGB,代碼編寫時(shí)需要注意。
灰度處理
考慮到處理三個(gè)通道的數(shù)據(jù)比較復(fù)雜,我們先將圖像進(jìn)行灰度化處理,灰度化的過程就是將每個(gè)像素點(diǎn)的RGB值統(tǒng)一成同一個(gè)值。灰度化后的圖像將由三通道變?yōu)閱瓮ǖ溃瑔瓮ǖ赖臄?shù)據(jù)處理起來就會簡單許多。
通常這個(gè)值是根據(jù)RGB三通道的數(shù)值進(jìn)行加權(quán)計(jì)算得到。人眼對RGB顏色的敏感度不同,對綠色最敏感,權(quán)值較高,對藍(lán)色最不敏感,權(quán)值較低。
坐標(biāo)為(x,y)的像素點(diǎn)進(jìn)行灰度化操作的具體計(jì)算公式如下:
調(diào)用OpenCV中提供的cvtColor()函數(shù),能夠方便地對圖像進(jìn)行灰度處理
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 由于使用cv2.imread()讀到的img的數(shù)據(jù)排列為BGR,因此這里的參數(shù)為BGR2GRAY
灰度處理后的圖像如下圖所示:
邊緣提取
為了突出車道線,我們對灰度化后的圖像做邊緣處理。“邊緣”就是圖像中明暗交替較為明顯的區(qū)域。車道線通常為白色或黃色,地面通常為灰色或黑色,因此車道線的邊緣處會有很明顯的明暗交替。
常用的邊緣提取算法有Canny算法和Sobel算法,它們只是計(jì)算方式不同,但實(shí)現(xiàn)的功能類似。可以根據(jù)實(shí)際要處理的圖像,選擇算法。哪種算法達(dá)到的效果更好,就選哪種。
以Canny算法為例,選取特定的閾值后,對灰度圖像進(jìn)行處理,即可得到的邊緣提取的效果圖。
low_threshold = 40
high_threshold = 150
canny_image = cv2.Canny(gray, low_threshold, high_threshold)
感興趣區(qū)域選擇
邊緣提取完成后,需要檢測的車道線被凸顯出來了。為了實(shí)現(xiàn)自車所在車道的車道線檢測,我們需要將感興趣的區(qū)域(Region of Interest)提取出來。提取感興趣區(qū)域最簡單的方式就是“截取”。
首先選定一個(gè)感興趣區(qū)域,比如下圖所示的藍(lán)色三角形區(qū)域。對每個(gè)像素點(diǎn)的坐標(biāo)值進(jìn)行遍歷,如果發(fā)現(xiàn)當(dāng)前點(diǎn)的坐標(biāo)不在三角區(qū)域內(nèi),則將該點(diǎn)涂“黑”,即將該點(diǎn)的像素值置為0。
為了實(shí)現(xiàn)截取功能,可以封裝一下OpenCV的部分函數(shù),定義一個(gè)region_of_interest函數(shù):
def region_of_interest(img, vertices):
#定義一個(gè)和輸入圖像同樣大小的全黑圖像mask,這個(gè)mask也稱掩膜
#掩膜的介紹,可參考:https://www.cnblogs.com/skyfsm/p/6894685.html
mask = np.zeros_like(img)
#根據(jù)輸入圖像的通道數(shù),忽略的像素點(diǎn)是多通道的白色,還是單通道的白色
if len(img.shape) > 2:
channel_count = img.shape[2] # i.e. 3 or 4 depending on your image
ignore_mask_color = (255,) * channel_count
else:
ignore_mask_color = 255
#[vertices]中的點(diǎn)組成了多邊形,將在多邊形內(nèi)的mask像素點(diǎn)保留,
cv2.fillPoly(mask, [vertices], ignore_mask_color)
#與mask做"與"操作,即僅留下多邊形部分的圖像
masked_image = cv2.bitwise_and(img, mask)
return masked_image
源碼出自:https://github.com/udacity/CarND-LaneLines-P1/blob/master/P1.ipynb
封裝完函數(shù)后,我們將感興趣的區(qū)域輸入,實(shí)現(xiàn)邊緣提取后的圖像的截取。
#圖像像素行數(shù) rows = canny_image .shape[0] 540行
#圖像像素列數(shù) cols = canny_image .shape[1] 960列
left_bottom = [0, canny_image .shape[0]]
right_bottom = [canny_image .shape[1], canny_image .shape[0]]
apex = [canny_image .shape[1]/2, 310]
vertices = np.array([ left_bottom, right_bottom, apex ], np.int32)
roi_image = region_of_interest(canny_image, vertices)
截取后的圖像入下圖所示:
霍夫變換
經(jīng)過灰度處理、邊緣檢測、感興趣區(qū)域截取后,我們終于將左右車道線從復(fù)雜的圖像中提取出來了。接下來,我們使用霍夫變換來提取圖像中的直線(段)。
霍夫變換是一種特征檢測方法,其原理和推導(dǎo)過程可以參看經(jīng)典霍夫變換(Hough Transform)https://blog.csdn.net/yuyuntan/article/details/80141392。
在圖像中使用霍夫變換不僅能夠識別圖像中的直線,還能識別出圖像中的圓、橢圓等特征。OpenCV為我們提供了霍夫變換檢測直線的函數(shù),可以通過設(shè)置不同的參數(shù),檢測不同長度的線段。由于車道線存在虛線的可能,因此線段的檢測長度不能設(shè)置地太長,否則短線段會被忽略掉。
OpenCV的霍夫變換直線檢測函數(shù)使用方法如下:
rho = 2 # distance resolution in pixels of the Hough grid
theta = np.pi/180 # angular resolution in radians of the Hough grid
threshold = 15 # minimum number of votes (intersections in Hough grid cell)
min_line_length = 40 #minimum number of pixels making up a line
max_line_gap = 20 # maximum gap in pixels between connectable line segments
# Hough Transform 檢測線段,線段兩個(gè)端點(diǎn)的坐標(biāo)存在lines中
lines = cv2.HoughLinesP(roi_image, rho, theta, threshold, np.array([]),
min_line_length, max_line_gap)
封裝一個(gè)繪圖函數(shù),實(shí)現(xiàn)把線段繪制在圖像上的功能,以實(shí)現(xiàn)線段的可視化
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) # 將線段繪制在img上
將得到線段繪制在原始圖像上
import numpy as np
line_image = np.copy(img) # 復(fù)制一份原圖,將線段繪制在這幅圖上
draw_lines(line_image, lines, [255, 0, 0], 6)
結(jié)果如下圖:
可以看出,雖然右車道線的線段不連續(xù),但已經(jīng)很接近我們想要的輸出結(jié)果了。
數(shù)據(jù)后處理
霍夫變換得到的一系列線段結(jié)果跟我們的輸出結(jié)果還是有些差異。為了解決這些差異,需要對我們檢測到的數(shù)據(jù)做一定的后處理操作。
實(shí)現(xiàn)以下兩步后處理,才能真正得到我們的輸出結(jié)果。
1.計(jì)算左右車道線的直線方程
根據(jù)每個(gè)線段在圖像坐標(biāo)系下的斜率,判斷線段為左車道線還是右車道線,并存于不同的變量中。隨后對所有左車道線上的點(diǎn)、所有右車道線上的點(diǎn)做一次最小二乘直線擬合,得到的即為最終的左、右車道線的直線方程。
2.計(jì)算左右車道線的上下邊界
考慮到現(xiàn)實(shí)世界中左右車道線一般都是平行的,所以可以認(rèn)為左右車道線上最上和最下的點(diǎn)對應(yīng)的y值,就是左右車道線的邊界。
基于以上兩步數(shù)據(jù)后處理的思路,我們重新定義draw_lines()函數(shù),將數(shù)據(jù)后處理過程寫入該函數(shù)中。
def draw_lines(img, lines, color=[255, 0, 0], thickness=2):
left_lines_x = []
left_lines_y = []
right_lines_x = []
right_lines_y = []
line_y_max = 0
line_y_min = 999
for line in lines:
for x1,y1,x2,y2 in line:
if y1 > line_y_max:
line_y_max = y1
if y2 > line_y_max:
line_y_max = y2
if y1 < line_y_min:
line_y_min = y1
if y2 < line_y_min:
line_y_min = y2
k = (y2 - y1)/(x2 - x1)
if k < -0.3:
left_lines_x.append(x1)
left_lines_y.append(y1)
left_lines_x.append(x2)
left_lines_y.append(y2)
elif k > 0.3:
right_lines_x.append(x1)
right_lines_y.append(y1)
right_lines_x.append(x2)
right_lines_y.append(y2)
#最小二乘直線擬合
left_line_k, left_line_b = np.polyfit(left_lines_x, left_lines_y, 1)
right_line_k, right_line_b = np.polyfit(right_lines_x, right_lines_y, 1)
#根據(jù)直線方程和最大、最小的y值反算對應(yīng)的x
cv2.line(img,
(int((line_y_max - left_line_b)/left_line_k), line_y_max),
(int((line_y_min - left_line_b)/left_line_k), line_y_min),
color, thickness)
cv2.line(img,
(int((line_y_max - right_line_b)/right_line_k), line_y_max),
(int((line_y_min - right_line_b)/right_line_k), line_y_min),
color, thickness)
根據(jù)對線段的后處理,即可得到符合輸出要求的兩條直線方程的斜率、截距和有效長度。將后處理后的結(jié)果繪制在原圖上,如下圖所示:
處理視頻
視頻其實(shí)就是一幀幀連續(xù)不斷的圖像,使用讀取視頻的庫,將視頻截取成一幀幀圖像,然后使用上面的灰度處理、邊緣提取、感興趣區(qū)域選擇、霍夫變換和數(shù)據(jù)后處理,得到車道線檢測結(jié)果,再將圖片結(jié)果拼接成視頻,就完成了視頻中的車道線檢測。
視頻可以看出,當(dāng)汽車在下坡時(shí),車頭會發(fā)生俯仰,造成感興趣區(qū)域的變化,因此檢測到的有效長度有所變化。可見本算法需要針對車輛顛簸的場景進(jìn)行優(yōu)化。
以上就是《初識圖像之初級車道線檢測》的全部內(nèi)容,關(guān)于這個(gè)項(xiàng)目的全部內(nèi)容,可以在優(yōu)達(dá)學(xué)城(Udacity)無人駕駛工程師學(xué)位首頁試聽,建議讀者親身學(xué)習(xí)一遍。
在實(shí)際編寫車道線檢測代碼的過程中,你會發(fā)現(xiàn),每一步都需要調(diào)很多參數(shù),才能滿足后續(xù)算法的處理要求。可見,本算法無法應(yīng)用在不同光照條件的場景中,魯棒性較差;同時(shí),由于霍夫變換檢測直線本身的缺陷,面對彎道場景時(shí),無法很好地將彎道檢測出來。
-
計(jì)算機(jī)視覺
+關(guān)注
關(guān)注
9文章
1708瀏覽量
46735 -
無人駕駛
+關(guān)注
關(guān)注
99文章
4170瀏覽量
123366 -
自動駕駛
+關(guān)注
關(guān)注
788文章
14292瀏覽量
170412
原文標(biāo)題:自動駕駛之——初級車道線檢測
文章出處:【微信號:IV_Technology,微信公眾號:智車科技】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
FPGA在自動駕駛領(lǐng)域有哪些應(yīng)用?
【mBot申請】自動駕駛車
【話題】特斯拉首起自動駕駛致命車禍,自動駕駛的冬天來了?
自動駕駛真的會來嗎?
細(xì)說關(guān)于自動駕駛那些事兒
自動駕駛的到來
速騰聚創(chuàng)首次發(fā)布LiDAR算法 六大模塊助力自動駕駛
自動駕駛汽車的定位技術(shù)
如何讓自動駕駛更加安全?
自動駕駛汽車的處理能力怎么樣?
聯(lián)網(wǎng)安全接受度成自動駕駛的關(guān)鍵
【KV260視覺入門套件試用體驗(yàn)】八、VITis AI自動駕駛多任務(wù)執(zhí)行MultiTask V3
【實(shí)戰(zhàn)】Python+OpenCV車道線檢測識別項(xiàng)目:實(shí)現(xiàn)L2級別自動駕駛必備(配套課程+平臺實(shí)踐)

評論