本文詳細(xì)描述了當(dāng)前代碼中(git 版本: 16b521b12d2e3bdc00bd996acafe4526f1d1cb9a)道路識(shí)別的算法。 如果沒(méi)有特殊說(shuō)明,下文中所說(shuō)的“算法”均指本代碼中的道路識(shí)別算法。 目標(biāo)本算法的目標(biāo)是識(shí)別出道路上較為清晰的道路標(biāo)線,并給出道路標(biāo)線的位置信息。 算法簡(jiǎn)要流程
詳細(xì)流程為了結(jié)合代碼進(jìn)行說(shuō)明,將使用代碼導(dǎo)讀的方式慢慢分析算法。由于代碼仍在開(kāi)發(fā)中,變動(dòng)頻繁,建議你檢出 tag 為 lane-detection-code-tour 的代碼進(jìn)行對(duì)照閱讀。 選取 ROImain() 函數(shù)位于 driveassist.cpp 文件中,該文件的開(kāi)始部分定義了一些用于測(cè)試的視頻的參數(shù)。由于該版本的代碼僅做研究用,所以將測(cè)試視頻文件的地址寫(xiě)死在了代碼中。 roiX、roiY、roiWidth、roiHeight 就是 ROI 的參數(shù),由于車道線僅僅會(huì)出現(xiàn)在視頻畫(huà)面中的固定區(qū)域,所以我們可以選取這塊固定的區(qū)域作為 ROI,這樣可以加快處理的速度,也可以避開(kāi)環(huán)境的干擾。 ROI 參數(shù)根據(jù)不同的視頻需要自行調(diào)節(jié)。 進(jìn)行俯視變換從視頻畫(huà)面中來(lái)看,車道線是傾斜而且不平行的,但如果我們從空中俯視車道線,應(yīng)該能看到平行的車道線。平行的車道線比不平行的車道線更容易處理。另外,當(dāng)我們把 ROI 變換成 IPM 后,在直行的道路上,車道線會(huì)變成垂直的線,這就能夠讓我們更容易地識(shí)別出車道線。 要嚴(yán)格地進(jìn)行俯視變換,需要考慮到攝像頭高度、傾角、仰角、光圈大小等不容易測(cè)量的量,但我們不需要進(jìn)行精確的變換,我們只需要做一個(gè)簡(jiǎn)單的梯形變形,就能夠得到一個(gè)俯視圖。雖然這樣做得到的俯視圖并不精確,但對(duì)車道線識(shí)別來(lái)說(shuō)已經(jīng)夠用了。 由于攝像頭安裝位置的不同,我們需要根據(jù)實(shí)際畫(huà)面調(diào)整一些參數(shù),以便進(jìn)行俯視變換,我們將這個(gè)過(guò)程叫做標(biāo)定。下面介紹標(biāo)定的過(guò)程。 首先運(yùn)行程序,選擇一個(gè)車輛保持在車道線中央,且當(dāng)前車道是直道的場(chǎng)景,接著開(kāi)始進(jìn)行標(biāo)定。 請(qǐng)看下圖,圖片底部中央的綠色細(xì)線的矩形就是 ROI,舉行中央的兩條綠色細(xì)線是用于調(diào)整俯視變換參數(shù)的輔助線。 標(biāo)定方法如下:
標(biāo)定完成之后,可以將這些參數(shù)記下來(lái),或者直接改寫(xiě)代碼中預(yù)設(shè)的參數(shù),這樣在下次運(yùn)行程序時(shí),就不需要重新進(jìn)行標(biāo)定了。 標(biāo)定原理是這樣的,畫(huà)面中的車道線和 ROI 的上下兩條邊線組成了一個(gè)梯形,我們只要把這個(gè)梯形拉伸成一個(gè)矩形,就可以完成俯視變換。嚴(yán)格來(lái)說(shuō),這并不是一個(gè)標(biāo)準(zhǔn)的俯視變換,但我們只需要將車道線變換成平行的且垂直于地平線的兩條線,這樣的變換對(duì)我們來(lái)說(shuō)已經(jīng)足夠了。 如下圖所示,我們需要將 E、F 點(diǎn)分別拉伸到 A、B 兩點(diǎn),C 和 D 點(diǎn)保持不動(dòng),也就是說(shuō),我們的變換是這樣的:
將這四個(gè)點(diǎn)的坐標(biāo)傳給 OpenCV 提供的 getPerspectiveTransform 方法,我們就得到了我們想要的俯視變換的變換矩陣。同時(shí),我們可以求這個(gè)矩陣的偽逆矩陣,這樣我們就可以將俯視圖轉(zhuǎn)換為 ROI 圖。 下面是一個(gè) ROI 轉(zhuǎn)換成俯視變換的對(duì)比圖,可以看出車道線已經(jīng)變成了兩條平行線,并且在屏幕上是垂直的。 車道線增強(qiáng)現(xiàn)在我們已經(jīng)獲得了姿態(tài)比較好的車道線(車道線平行,且?guī)缀跏谴怪钡模?,為了進(jìn)一步突出車道線,我們要使用一個(gè)特殊的高斯核函對(duì)圖像進(jìn)行卷積。 這個(gè)高斯核的數(shù)學(xué)表達(dá)是這樣的: 這個(gè)高斯核的水平和垂直方向看起來(lái)是這樣的( 和 均取 5): 兩個(gè)高斯核合并起來(lái)之后,看起來(lái)就是這個(gè)樣子的: 可以看到,這個(gè)高斯核在水平方向增強(qiáng)了中央的響應(yīng),弱化了周圍的相應(yīng),同時(shí),在垂直方向拉伸了響應(yīng)。用這個(gè)高斯核對(duì)圖像進(jìn)行卷積后,就可以增強(qiáng)車道線的響應(yīng)。 一般取車道線寬度在圖像上占據(jù)的像素個(gè)數(shù),而 一般取虛線車道線的長(zhǎng)度在圖像上占據(jù)的像素個(gè)數(shù)。 在代碼中,高斯核的計(jì)算在 onGaussianChange 函數(shù)中完成。首先分別計(jì)算水平方向和垂直方向的卷積核,然后調(diào)用 OpenCV 提供的 sepFilter2D 方法,分別傳入水平和垂直方向的卷積核,就可以得到最終的卷積結(jié)果。 完成卷積操作后,車道線會(huì)在圖片上顯現(xiàn)出最強(qiáng)的響應(yīng),這時(shí)候我們要做一個(gè)閾值化操作,將車道線過(guò)濾出來(lái),把無(wú)關(guān)的背景(如路面紋理等)消除。 閾值化的操作很簡(jiǎn)單,計(jì)算圖像中所有像素值的分布,確定一個(gè)閾值,然后保留所有像素值大于閾值的點(diǎn),刪除所有像素值小于閾值的點(diǎn)。在本代碼中,我們使用了 97.5% 作為閾值,也就是取像素值分布中位于 97.5% 這個(gè)位置的像素值作為閾值。實(shí)際的閾值在每一幀中都不相同,而 97.5% 是固定的,需要根據(jù)每一幀的像素值分布來(lái)確定實(shí)際使用的閾值。 車道線經(jīng)過(guò)增強(qiáng)后的效果如下: 使用簡(jiǎn)化的霍夫變換識(shí)別車道線霍夫變換(Hough Transform)是一種識(shí)別圖像中直線的方法。標(biāo)準(zhǔn)的霍夫變換會(huì)識(shí)別出圖片中所有方向的直線,但耗時(shí)相對(duì)較長(zhǎng)。對(duì)我們來(lái)說(shuō),我們只需要識(shí)別出幾乎是垂直的車道線,對(duì)于其他類型的直線我們不關(guān)注。這樣,我們可以極大地簡(jiǎn)化霍夫變換,提高識(shí)別速度。 本代碼使用的簡(jiǎn)化過(guò)的霍夫變換流程如下: 在閾值化后的圖片中,按列掃描圖像,對(duì)于每一列,計(jì)算該列上值不為 0 的像素點(diǎn)的個(gè)數(shù)。最后,我們可以得到一個(gè)函數(shù) ,其中 是圖像的 x 坐標(biāo)(也就是圖像的第幾列),函數(shù)值是 x 列上不為零的像素點(diǎn)的個(gè)數(shù)。 顯然,如果在 列上存在一條接近垂直的直線,那么 的值就應(yīng)該很大。事實(shí)上,由于圖像經(jīng)過(guò)閾值化處理,所以 應(yīng)該是一個(gè)極大值。簡(jiǎn)單來(lái)說(shuō),如果我們把 的圖像畫(huà)出來(lái),那么圖像的波峰處應(yīng)該存在一條直線。 在下圖中, 被繪制到了“二維高斯模糊”圖中,可以看到, 的波峰處確實(shí)是有一條幾乎垂直的直線。這些識(shí)別出來(lái)的直線,就是我們想要尋找的車道線的候選。 上圖中的 是經(jīng)過(guò)處理的。原始的 函數(shù)在直線附近會(huì)出現(xiàn)多個(gè)波峰,這就會(huì)導(dǎo)致一條直線被識(shí)別為多條直線,所以,我們需要對(duì) 進(jìn)行一些預(yù)處理后,再去尋找波峰(極大值)。 首先我們要對(duì) 做一個(gè)高斯模糊,這就可以合并大部分的極大值。高斯模糊的范圍應(yīng)該根據(jù)車道線的寬度來(lái)確認(rèn)。在本代碼中我們沒(méi)有進(jìn)行進(jìn)一步的研究,僅僅依靠實(shí)驗(yàn)來(lái)確定了一個(gè)范圍。 完成高斯模糊后,一些相距相對(duì)較遠(yuǎn)的極值點(diǎn)仍無(wú)法被合并(如雙實(shí)線類型的車道線,我們只希望將雙實(shí)線識(shí)別為一條車道線),所以我們?nèi)孕枰謩?dòng)對(duì)這些極值點(diǎn)進(jìn)行合并,合并方法如下所述: 確定一個(gè)領(lǐng)域范圍 a,對(duì)距離小于 a 的兩個(gè)極值點(diǎn)進(jìn)行合并。如果兩個(gè)極值點(diǎn)的位置分別為 和 ,那么合并后的極值點(diǎn)位置 應(yīng)為: 其實(shí)這是一個(gè)按照 的值作為權(quán)重,對(duì)兩個(gè)極值點(diǎn)的位置進(jìn)行加權(quán)求和的過(guò)程。對(duì)于兩個(gè)相鄰的極值點(diǎn),合并后的極值點(diǎn)位置將會(huì)更偏向 值更大的那個(gè)極值點(diǎn)。 上面給出的公式是在代碼中使用的公式,如果改寫(xiě)成下面的樣子,會(huì)更容易看出這個(gè)公式的加權(quán)思想: 其中 合并極值點(diǎn)的操作到此完成。合并后的極值點(diǎn)就可以認(rèn)為是候選的車道線。接下來(lái),就可以使用這些候選的車道線,結(jié)合圖像信息,擬合出實(shí)際的車道線。 以上合并極值點(diǎn)以及尋找極值點(diǎn)的代碼實(shí)現(xiàn),位于 findPeaks 函數(shù)中。 擬合實(shí)際的車道線一般來(lái)說(shuō),車道線應(yīng)該總是位于車輛的兩側(cè),所以在俯視圖中,車道線應(yīng)該位于圖片中央的兩側(cè)。在上一步中,我們已經(jīng)得到了候選車道線在俯視圖中的 x 坐標(biāo),我們可以選取距離圖像中央最近的左右兩個(gè)候選車道線作為實(shí)際車道線的位置。利用這兩條車道線,結(jié)合圖像信息,可以擬合出實(shí)際的車道線。 擬合方法有很多種,可以大致分為曲線擬合和直線擬合兩類。在 github 的提交記錄中,你可以發(fā)現(xiàn)我們嘗試過(guò)曲線擬合,但最后實(shí)在沒(méi)有找到能夠比較好地?cái)M合車道的曲線,所以現(xiàn)在的代碼中只使用了直線擬合。 直線擬合相對(duì)于曲線擬合的優(yōu)點(diǎn)是算法簡(jiǎn)單,計(jì)算速度稍快。不過(guò)直線擬合的缺點(diǎn)也是比較明顯的:在彎道中,俯視圖中的車道線其實(shí)是一條曲線,而直線擬合是無(wú)法擬合曲線的。 直線擬合無(wú)法擬合彎道上的車道線,這個(gè)問(wèn)題倒不是很嚴(yán)重。目前我們做車道識(shí)別的目的是為了實(shí)現(xiàn)車道保持功能,我們只要保證擬合出來(lái)的直線車道方向及位置大致上符合實(shí)際車道的方向和位置即可。 在現(xiàn)在的代碼中,存在幾種直線擬合扯到的方法,其中大部分被注釋掉了,只留下了一個(gè)名為“高級(jí)直線擬合道路”的方法。下面會(huì)詳細(xì)介紹這個(gè)算法。 擬合道路的方法位于 LineFit 類中,這個(gè)類接受的輸入是俯視圖像和候選車道線位置(在上一步中我們選出來(lái)的兩個(gè)極值點(diǎn))。 對(duì)于每一個(gè)輸入的車道線位置 ,我們做如下計(jì)算:
很顯然,這樣選取出來(lái)的車道線最有可能是實(shí)際的車道線。 在代碼的實(shí)現(xiàn)上,有一些細(xì)節(jié)需要注意。計(jì)算直線的分?jǐn)?shù)時(shí),需要確定直線經(jīng)過(guò)的像素點(diǎn),直線經(jīng)過(guò)哪些像素點(diǎn),是這樣計(jì)算出來(lái)的: 取 ,其中 是圖像高度, 是整數(shù)。對(duì)于所有的 ,利用直線的表達(dá)式計(jì)算 ,于是直線經(jīng)過(guò)的點(diǎn)就是 。最后,我們可以得到 個(gè)點(diǎn)。 按此方法選取直線經(jīng)過(guò)的點(diǎn),可以保證所有直線經(jīng)過(guò)的點(diǎn)的數(shù)量都是固定的,最后計(jì)算出來(lái)的直線分?jǐn)?shù)才具有可比性。 卡爾曼濾波實(shí)際的路面上,不可能總是有清晰無(wú)比的道路標(biāo)線,破爛不堪模糊不清的道路標(biāo)線是很常見(jiàn)的。對(duì)于此類標(biāo)線,即使是人類都不太容易分別出來(lái),以目前的技術(shù)水平就更不可能精確識(shí)別了。 我們希望,在無(wú)法識(shí)別車道線的時(shí)候,也能做出一個(gè)對(duì)于當(dāng)前實(shí)際車道線位置的猜測(cè),為實(shí)現(xiàn)這個(gè)目標(biāo),本代碼在最后使用了一個(gè)卡爾曼濾波器對(duì)識(shí)別出來(lái)的車道線進(jìn)行濾波。 對(duì)識(shí)別出來(lái)的車道線進(jìn)行濾波的好處有兩個(gè),第一個(gè)好處是,在無(wú)法識(shí)別出車道線的時(shí),也能給出車道線可能的位置。目前只能給出一個(gè)可能的位置,而無(wú)法給出車道線位于該位置上的概率,不過(guò),可以在此基礎(chǔ)上繼續(xù)改進(jìn)算法,使算法可以輸出一個(gè)概率,上層的自動(dòng)駕駛程序可以根據(jù)此概率進(jìn)行更好的決策。 第二個(gè)好處是,可以識(shí)別出來(lái)的車道線位置進(jìn)行平滑。路面的實(shí)際狀況千變?nèi)f化,在進(jìn)行實(shí)際的識(shí)別任務(wù)時(shí),可能在某幀畫(huà)面上無(wú)法識(shí)別出車道線,但該幀之前及之后的幀上都能較好地識(shí)別出車道線。另外,由于復(fù)雜的環(huán)境的干擾,識(shí)別出來(lái)的車道線位置可能會(huì)在小范圍內(nèi)擺動(dòng)。對(duì)車道線進(jìn)行濾波后,我們就可以得到一個(gè)足夠穩(wěn)定的車道線預(yù)測(cè)位置。 本代碼使用車道線的坐標(biāo)點(diǎn)位置作為卡爾曼濾波器的測(cè)量變量。對(duì)于在一個(gè)圖像中識(shí)別出來(lái)的車道線,車道線應(yīng)該在直線 y = 0 以及 y = h - 1 (h 是圖像高度)上分別經(jīng)過(guò)點(diǎn) 和點(diǎn) (. 和 就是我們輸入給卡爾曼濾波器的預(yù)測(cè)變量。由于一共有兩條車道線(左車道線和右車道線),所以我們一共有 4 個(gè)變量。 之所以使用兩個(gè)點(diǎn)的 x 坐標(biāo)來(lái)表示車道線,而不使用截距和斜率來(lái)表示車道線的原因是:卡爾曼濾波器是一個(gè)線性濾波器,它不能處理非線性變化的變量。如果使用斜率和截距來(lái)表示車道線,圖像上車道線的斜率 k 與實(shí)際車道線的位置并不呈現(xiàn)線性變化關(guān)系。當(dāng)然 與車道線的位置是呈現(xiàn)線性相關(guān)的,如果用 代替斜率,就可以使用卡爾曼濾波器。另外,在我們的處理過(guò)程中,使用車道線兩端點(diǎn)的水平坐標(biāo)來(lái)表示車道線的變化顯然更直觀。 在這里,我們需要建立一個(gè)假設(shè):在大部分時(shí)間下,車輛應(yīng)該行駛在車道中央,且車道是直道。當(dāng)車輛位置及道路條件滿足這個(gè)假設(shè)的時(shí)候,車輛線在圖像上的位置應(yīng)該是固定的,我們把此時(shí)的車道線稱為理想車道線,理想車道線的位置則作為無(wú)法檢測(cè)到車道時(shí),傳遞給卡爾曼濾波器的測(cè)量變量。 我們的卡爾曼濾波過(guò)程如下: 對(duì)左右兩條車道線,分別進(jìn)行以下步驟: 在代碼的實(shí)現(xiàn)上,我們并沒(méi)有將兩條車道線分別傳給兩個(gè)卡爾曼濾波器,我們直接把兩條車道線的四個(gè)位置傳給了一個(gè)卡爾曼濾波器,這樣做的效果和上述步驟是一樣的。 本代碼中使用的過(guò)程噪聲協(xié)方差矩陣的對(duì)角線元素都是 ,這是一個(gè)經(jīng)驗(yàn)值。 測(cè)量噪聲矩陣是: 這也是一個(gè)經(jīng)驗(yàn)值,這里需要解釋一下為什么右側(cè)車道的測(cè)量誤差比左車道大。在我們用于測(cè)試的視頻中,車輛的左車道線是雙實(shí)線,而右側(cè)是虛線,雙實(shí)線顯然比虛線更容易檢測(cè),這就是左車道線的測(cè)量誤差小于右車道線的原因。 需要指出的是,代碼中使用的這些經(jīng)驗(yàn)值,僅僅是根據(jù)很少的幾個(gè)測(cè)試視頻測(cè)試得到的經(jīng)驗(yàn)值。如果要用于更一般的情形,這些經(jīng)驗(yàn)值應(yīng)該進(jìn)行修改。最好的做法是記錄實(shí)際車道線與預(yù)測(cè)車道線的偏差,然后根據(jù)記錄的數(shù)據(jù)計(jì)算出協(xié)方差矩陣,不過(guò)這樣做的工作量太大,更簡(jiǎn)單迅速但不夠嚴(yán)格的做法就是根據(jù)測(cè)試情況選取經(jīng)驗(yàn)值。 這里給出一個(gè)演示視頻:帶卡爾曼濾波的道路標(biāo)線識(shí)別——一般城市道路。在視頻中可以看到,沒(méi)有經(jīng)過(guò)卡爾曼濾波的車道線存在小范圍跳動(dòng)的情況,而濾波后的車道線位置則更為平滑。如果車道線檢測(cè)難度增大,算法就會(huì)更多地使用理想車道線的位置(算法給出的車道線位置逐漸偏向理想車道線)。在完全無(wú)法檢測(cè)到車道線時(shí),算法會(huì)直接使用理想車道線的位置作為預(yù)測(cè)車道線。 結(jié)束語(yǔ)整個(gè)車道檢測(cè)算法的介紹到這里就結(jié)束了。由于筆者水平有限,文中難免出現(xiàn)謬誤,還請(qǐng)多多拍磚。 |
|