在汽車自動駕駛過程中厢钧,你需要汽車能夠?qū)χ車氖澜邕M(jìn)行很好的感知鳞尔。我們?nèi)祟愅ㄟ^使用眼睛來了解汽車跑的有多快,汽車的車道線在哪早直,明白在哪里可以進(jìn)行轉(zhuǎn)彎寥假,汽車通過攝像頭,毫米波雷達(dá)霞扬,mio等其它的傳感器來幫助我們實(shí)現(xiàn)人眼的功能糕韧。
為了讓車道線檢測的結(jié)果能夠上車,我并沒有上神經(jīng)網(wǎng)絡(luò)祥得,而是采用特征檢測方法進(jìn)行測量。在閱讀相關(guān)論文的方法后蒋得,我按照以下的步驟進(jìn)行實(shí)驗(yàn):
- 視頻圖片預(yù)處理级及。設(shè)置圖片的ROI區(qū)域,對圖片進(jìn)行灰度轉(zhuǎn)換额衙。
- 圖片邊緣處理饮焦。切割ROI為若干個(gè)子圖,二值化后窍侧,計(jì)算子圖的連通域县踢。
- 霍夫變換,計(jì)算圖片滅點(diǎn)伟件。
- 直線特征篩選硼啤,ransac 直線擬合。
- 繪制車道線斧账。
視頻圖片預(yù)處理
我們知道谴返,車道線的特征很固定,它一定在地上咧织,而不會跑到天上去嗓袱。因此,在檢測的第一步习绢,我就把圖片進(jìn)行了裁剪渠抹,減少了天空,樹等干擾闪萄,也減少了處理圖片的時(shí)間梧却。經(jīng)裁剪后的ROI ,就要轉(zhuǎn)換為灰度圖败去。其實(shí)篮幢,看了許多論文后,發(fā)現(xiàn)在這個(gè)步驟前为迈,還有的對圖片進(jìn)行色彩空間的轉(zhuǎn)換三椿,增強(qiáng)黃色和白色的特征(車道線為白色或者黃色)缺菌。我在灰度轉(zhuǎn)換的時(shí)候,也沒有直接使用opencv的轉(zhuǎn)灰度圖的api, 而是提取B,G,R 三個(gè)通道值搜锰,計(jì)算I = R+G-B伴郁。讀者可以實(shí)踐兩種轉(zhuǎn)灰度的方法,會發(fā)現(xiàn)后者得到的灰度圖更加清晰蛋叼。
cv::Mat img2gray(cv::Mat img){
int height= img.rows;
int width = img.cols;
cv::Mat gray (height, width, CV_8U,cv::Scalar(0,0,0));
for (int i=0;i< height;i++) {
for (int j = 0; j < width; j++) {
int B = img.at<cv::Vec3b>(i, j)[0];
int G = img.at<cv::Vec3b>(i, j)[1];
int R = img.at<cv::Vec3b>(i, j)[2];
int I = R + G -B;
if (I > 255)
I = 255;
gray.at<uint8_t >(i, j) = I;
}
}
return gray;
}
圖片的邊緣處理
一提到車道線的邊緣處理焊傅,一定會條件反射想到canny 邊緣檢測。如果只是單純進(jìn)行canny檢測狈涮,那么檢測結(jié)果不會太好狐胎。canny很依賴原始圖片的質(zhì)量,如果原來的圖片車道線很分明歌馍,天氣狀況良好握巢,那么canny的效果會好,但是如果圖片質(zhì)量不太好松却,那么干擾的點(diǎn)就會很多暴浦,這會直接影響霍夫變換的結(jié)果。因此我在canny之前對圖像做了一個(gè)分割處理晓锻,對每個(gè)子圖二值化歌焦,canny檢測后,根據(jù)車道線的特征做連通域計(jì)算砚哆,最后將子圖還原原圖独撇。
可以看出,經(jīng)過這樣的處理后躁锁,車道線就很清晰的呈現(xiàn)出來了券勺。
霍夫變換,滅點(diǎn)計(jì)算
在得到上述的二值化圖后灿里,緊接著就是上opencv概率霍夫變換关炼,檢測直線。調(diào)用opencv很簡單匣吊,但是注意一些參數(shù)的設(shè)定就好儒拂。
直線特征篩選,ransac 直線擬合色鸳。
并不是所有霍夫變換得到的直線都是我們的候選線 社痛,我們有一些篩選條件。最直接的條件是命雀,不經(jīng)過滅點(diǎn)的直線蒜哀,一定不是車道線。所以在吏砂,霍夫變換之后撵儿,我們需要計(jì)算圖片的滅點(diǎn)乘客。車道線有方向角的信息,也可以通過方向角進(jìn)一步篩選淀歇。由于我在圖像邊緣計(jì)算的時(shí)候易核,已經(jīng)在連通域計(jì)算時(shí)候考慮車道方向角,所以這里就不再計(jì)算了浪默。
對于候選的直線牡直,我采用ransac算法進(jìn)行擬合。如果對ransac不是很了解的纳决,可以先看看算法的描述碰逸,我這里直接上代碼了。
void ransac (Mat X ,Mat Y,std::vector<Vec2d > &result ){
int Size = X.rows;
int iters = 400;
double sigma = 0.25;
double best_k = 0.0;
double best_b = 0.0;
double k,b;
int pretotal = 2;
double P = 0.90;
int pos1,pos2;
int total_inlier;
default_random_engine e;
uniform_int_distribution<unsigned> u(0, Size);
int i;
for (i=0;i<iters;i++){
pos1= u(e);
pos2 =u(e);
int * x_1 = X.ptr<int>(pos1);
int * x_2 = X.ptr<int>(pos2);
int * y_1 = Y.ptr<int>(pos1);
int * y_2 = Y.ptr<int>(pos2);
k = (*(y_2) - *(y_1)) / (*(x_2) - *(x_1) + 0.0000001);
b = *(y_1) - k * *(x_1);
total_inlier= 0;
for (int j=0;j<Size;j++){
double y_estimate = k * *(X.ptr<int>(j)) +b;
if (fabs(y_estimate-*(Y.ptr<int>(j)))<sigma){
total_inlier+= 1;
}
}
if (total_inlier>pretotal){
iters = log(1-P)/log( 1-pow(total_inlier/(Size+0.0000001) ,2.0 ));
pretotal = total_inlier;
best_k = k;
best_b = b;
}
if (total_inlier > (Size/2)){
break;
}
}
result.push_back(Vec2d(best_k,best_b));
}
繪制直線
最重要的檢測部分已經(jīng)完成了阔加,那么繪制車道線就很輕松了饵史,通過簡單的幾行代碼,就可以把我們的直線繪制出來了掸哑。
void draw_lane(std::vector<cv::Vec4i>coordinate)
{
std::cout << coordinate.size()<< std::endl;
for (auto it = coordinate.begin();it!= coordinate.end();it ++){
line(img, cv::Point((*it)[0], (*it)[1]), cv::Point((*it)[2], (*it)[3]), cv::Scalar(0,255,0), 3);
}
imshow("lane ",img);
cv::waitKey(1)
}
下圖就是最后的檢測結(jié)果啦~约急。其中零远,紅色圓點(diǎn)表示滅點(diǎn)位置苗分。可看出牵辣,車道線已經(jīng)比較好的擬合出來摔癣,但是左右兩邊還是有雜線,這就需要進(jìn)一步篩選過濾了纬向。