實現一個智能駕駛系統,會有幾個層級:html
感知層 → 融合層 → 規劃層 → 控制層python
更具體一點爲:git
傳感器層 → 驅動層 → 信息融合層 → 決策規劃層 → 底層控制層github
各個層級之間都須要編寫代碼,去實現信息的轉化。算法
最基本的層級有如下幾類:採集及預處理、座標轉換、信息融合app
採集機器學習
傳感器跟咱們的PC或者嵌入式模塊通訊時,會有不一樣的傳輸方式。ide
好比咱們採集來自攝像機的圖像信息,有的是經過千兆網卡實現的通訊,也有的是直接經過視頻線進行通訊的。再好比某些毫米波雷達是經過CAN總線給下游發送信息的,所以咱們必須編寫解析CAN信息的代碼。函數
不一樣的傳輸介質,須要使用不一樣的協議去解析這些信息,這就是上文提到的「驅動層」。性能
通俗地講就是把傳感器採集到的信息所有拿到,而且編碼成團隊可使用的數據。
預處理
傳感器的信息拿到後會發現不是全部信息都是有用的。
傳感器層將數據以一幀一幀、固定頻率發送給下游,但下游是沒法拿每一幀的數據去進行決策或者融合的。爲何?
由於傳感器的狀態不是100%有效的,若是僅根據某一幀的信號去斷定前方是否有障礙物(有多是傳感器誤檢了),對下游決策來講是極不負責任的。所以上游須要對信息作預處理,以保證車輛前方的障礙物在時間維度上是一直存在的,而不是一閃而過。
這裏就會使用到智能駕駛領域常用到的一個算法——卡爾曼濾波。
座標轉換
座標轉換在智能駕駛領域十分重要。
傳感器是安裝在不一樣地方的,好比毫米波(上圖中紫色區域)是佈置在車輛前方的;當車輛前方有一個障礙物,距離這個毫米波雷達有50米,那麼咱們就認爲這個障礙物距離汽車有50米嗎?
不是的!由於決策控制層作車輛運動規劃時,是在車體座標系下完成的(車體座標系通常之後軸中心爲O點),所以毫米波雷達檢測到的50米,轉換到自車座標系下,還須要加上傳感器到後軸的距離。最終全部傳感器的信息,都是須要轉移到自車座標系下的,這樣全部傳感器信息才能統一,供規劃決策使用。
同理,攝像機通常安裝在擋風玻璃下面,拿到的數據也是基於攝像機座標系的,給下游的數據,一樣須要轉換到自車座標系下。
自車座標系:拿出你的右手,以大拇指 → 食指 → 中指 的順序開始念 X、Y、Z
而後把手握成以下形狀:
把三個軸的交點(食指根部)放在汽車後軸中心,Z軸指向車頂,X軸指向車輛前進方向。
各個團隊可能定義的座標系方向不一致,只要開發團隊內部統一便可。
信息融合
信息融合是指把相同屬性的信息進行多合一操做。
好比攝像機檢測到了車輛正前方有一個障礙物,毫米波也檢測到車輛前方有一個障礙物,激光雷達也檢測到前方有一個障礙物,而實際上前方只有一個障礙物,因此咱們要作的是把多傳感器下這輛車的信息進行一次融合,以此告訴下游,前面有一輛車,而不是三輛車。
固然,信息融合中還涉及時延的補償,具體以下:
對於一些大容量數據,確實不能以很高的頻率發送(好比10Hz,100ms才發送一次)。這樣的數據對高速行駛中的汽車來講,確定會有誤差。
這些誤差咱們算一下:
傳感器檢測到前方有一個靜止障礙物,我100ms以後收到了這個傳感器的信息,告訴我這個障礙物離我有30m。若是自車這時正以60KM/h的速度行駛,則這100ms,自車行駛了60 / 3.6 * 0.1 = 1.67m。
因此實際上這個障礙與個人距離爲31.67m。
因此面對通訊中產生的時延問題,尤爲是低頻率的信息,必定要考慮時延產生的後果。
時延補償的另一個問題:程序處理時,不能保證任什麼時候候都是按固定的頻率發送的。
這取決於硬件系統當時的環境,可能溫度高了,性能降低,處理速度變慢,10Hz 的發送頻率變成了 8Hz。若是咱們的程序仍是按固定的100ms去計算時延致使的誤差,一定會出現計算錯誤的狀況。
所以咱們須要引入時間戳,即在咱們發送的信息中加入當前的系統時間,經過兩幀數據的時間差來判斷接受到的信號到底延時了多久,這種方式比根據頻率判斷來得更準確。
決策規劃
這一層次主要設計的是拿到融合數據後,如何正確作規劃。
規劃包含縱向控制和橫向控制。
縱向控制即速度控制,表現爲 何時加速,何時制動。
橫向控制即行爲控制,表現爲 何時換道,何時超車等。
我的對這一塊不是很瞭解,不敢妄做評論。
我的興趣愛好,最近在學習一些無人駕駛相關的技術,便萌生了按部就班的寫一系列文章的想法,這是第一篇。文章主要會以Udacity爲主線,綜合本身在學習過程當中蒐集的各類材料,取其精華,補其不足,力求通俗易懂,理論明確,實戰有效,即做爲一個學習總結,potentially又能夠幫助對無人駕駛有興趣可是零基礎的朋友們 —— 注意這裏的零基礎是指未接觸過無人駕駛領域,本系列仍是須要一些簡單的數學和機器學習知識。
由於本文是從零開始的第一篇,這裏的車道檢測是基礎版本,須要知足幾個先決條件:(1)無人車保持在同車道的高速路中行駛(2)車道線清晰可見(3)無人車與同車道內前車保持足夠遠的距離。
一個基礎版的車道檢測步驟主要分爲如下幾點:
代碼:Github連接
最後的效果以下視頻所示,其中紅線表示自動檢測到的車道。
無人車每每配備有數個camera,常見的狀況是有一個camera固定在車的前方,用來perceive前方道路狀況,生成視頻。計算機對該視頻進行分析,綜合其餘sensor的信息,對車輛行爲進行指導。視頻是由圖片組成,若是可以成功檢測圖片上的車道,那咱們就幾乎解決了車道檢測問題。下面是一張車輛行駛過程當中的圖片,讓咱們動手吧!
import matplotlib.image as mplimg img = mplimg.imread('lane.jpg')
Gray Scale Transformation
這個變換比較簡單,是將RGB圖片轉換成灰度圖片,用來做爲Canny Edge Detection的輸入。
import cv2 gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) # Note that if you use cv2.imread() to read image, the image will # be in format BGR.
Gaussian Smoothing
Gaussian Smoothing是對圖片apply一個Gaussian Filter,能夠起到模糊圖片和消除噪聲的效果。其基本原理是從新計算圖片中每一個點的值,計算時取該點及其附近點的加權平均,權重符合高斯分佈。下圖左側展現了一個kernel_size = 5的Gaussian Filter,55是高斯分佈的中心點,341是網格中全部值的和。假設網格矩陣爲,圖片爲,新圖片爲,則:
Gaussian Filter是一種低經過濾器,可以抑制圖片中的高頻部分,而讓低頻部分順利經過。那什麼是圖片的高頻部分呢?下圖給出了一個比較極端的例子。愛好攝影的朋友們都知道相機ISO適當時可以獲得右側圖片,畫質細膩;若是ISO過大,就會致使產生左側圖片,畫質差,噪點多。這些噪點就是圖片中的高頻部分,表示了像素值劇烈升高或下降。
介紹完了Gaussian Filter,如今能夠將其應用到咱們的灰度圖片上:
blur_ksize = 5 # Gaussian blur kernel size blur_gray = cv2.GaussianBlur(gray, (blur_ksize, blur_ksize), 0, 0)
Canny Edge Detection
John F. Canny在1986年發明了Canny Edge Detection技術,其基本原理是對圖片中各個點求gradient,gradient較大的地方每每是edge。Canny Edge Detection精妙的地方在於它有兩個參數:low_threshold和high_threshold。算法先比較gradient與這兩個threshold的關係,若是gradient > high_threshold,就認可這是一個edge point;若是gradient < low_threshold,就判定這不是edge point;對於其餘的點,若是與edge point相鏈接,那麼這個點被認爲也是edge point,不然不是。
canny_lthreshold = 50 # Canny edge detection low threshold canny_hthreshold = 150 # Canny edge detection high threshold edges = cv2.Canny(blur_gray, low_threshold, high_threshold)
ROI Based Edge Filtering
Woohoo! It's awesome! 通過了Canny Edge Detection,咱們發現物體的輪廓都被檢測到了!可是彷佛東西有點兒太多了… 不要緊,還有一個重要的條件沒有用:camera相對於車是固定的,而無人車相對於車道的左右位置也是基本固定的,因此車道在camera視頻中基本保持在一個固定區域內!據此咱們能夠畫出一個大概的Region of Interest (ROI),過濾掉ROI以外的edges。
def roi_mask(img, vertices): mask = np.zeros_like(img) mask_color = 255 cv2.fillPoly(mask, vertices, mask_color) masked_img = cv2.bitwise_and(img, mask) return masked_img roi_vtx = np.array([[(0, img.shape[0]), (460, 325), (520, 325), (img.shape[1], img.shape[0])]]) roi_edges = roi_mask(edges, roi_vtx)
Hough Transformation
目前看起來咱們彷佛已經獲得了車道線了呢,然而…並無! 由於最終目標是獲得exactly兩條直線!而目前如今圖中不只有多條線,還有一些點狀和塊狀區域,Hough Transformation的目的就是找到圖中的線。
下圖中左側是image space,中間和右側是Hough space。先看左側和中間的圖(右側圖見本節備註),image space中的一個點對應Hough space的一條線;image space中的兩個點()對應Hough space的兩條相交線,且交點對應的線必通過image space的這兩個點。
那麼,若是Hough space中有多條線相交於一點,則在image space中對應的那些點應位於同一條線上,例如:
在實際操做中,咱們每每將Hough space劃分爲網格狀,若是通過一個格子的線的數目大於某threshold,咱們認爲這個通過這個格子的線在原image space對應的點應在同一條線上。具有了這些知識,咱們能夠用Hough Transformation來找線啦!
# Hough transform parameters rho = 1 theta = np.pi / 180 threshold = 15 min_line_length = 40 max_line_gap = 20 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) line_img = np.zeros((img.shape[0], img.shape[1], 3), dtype=np.uint8) draw_lines(line_img, lines) return line_img line_img = hough_lines(roi_edges, rho, theta, threshold, min_line_length, max_line_gap)
備註:若是image space中兩個點,則其造成的直線斜率無限大,沒法用中間的圖表示,能夠採用右側的極座標表示方式。
Lane Extrapolation
Almost there! 如今咱們要根據獲得的線計算出左車道和右車道,一種能夠採用的步驟是:
由於這部分代碼有點兒多,就不貼在這裏了,請參見個人Github代碼。結果以下:
最最後,咱們將結果和原圖疊加:
cv2.addWeighted(img, 0.8, line_img, 1, 0)
圖片任務完成!
如今咱們將前面的代碼打個包放到叫process_an_image的函數中,而後
from moviepy.editor import VideoFileClip output = 'video_1_sol.mp4' clip = VideoFileClip("video_1.mp4") out_clip = clip.fl_image(process_an_image) out_clip.write_videofile(output, audio=False)
將代碼應用到三個不一樣的video上,看看結果!
注意:對於不一樣的狀況,有些參數可能須要調節,第三個視頻的處理須要一些額外的操做,會在個人Github代碼中有具體描述。