车道检测(Advanced Lane Finding Project)

实现步骤:

  • 使用提供的一组棋盘格图片计算相机校正矩阵(camera calibration matrix)和失真系数(distortion coefficients).
  • 校正图片
  • 使用梯度阈值(gradient threshold),颜色阈值(color threshold)等处理图片得到清晰捕捉车道线的二进制图(binary image).
  • 使用透视变换(perspective transform)得到二进制图(binary image)的鸟瞰图(birds-eye view).
  • 检测属于车道线的像素并用它来测出车道边界.
  • 计算车道曲率及车辆相对车道中央的位置.
  • 处理图片展示车道区域,及车道的曲率和车辆位置.

Fork me on GitHub

相机校正(Camera Calibration)

这里会使用opencv提供的方法通过棋盘格图片组计算相机校正矩阵(camera calibration matrix)和失真系数(distortion coefficients)。首先要得到棋盘格内角的世界坐标"object points"和对应图片坐标"image point"。假设棋盘格内角世界坐标的z轴为0,棋盘在(x,y)面上,则对于每张棋盘格图片组的图片而言,对应"object points"都是一样的。而通过使用openCv的cv::findChessboardCorners(),传入棋盘格的灰度(grayscale)图片和横纵内角点个数就可得到图片内角的"image point"。


void get_obj_img_points(const vector<string> & images,const cv::Size & grid,const cv::Size& distance,cv::Mat& cameraMatirx,cv::Mat& distCoeffs){cv::Mat img,gray;//灰度图像vector<cv::Point2f> corners;//用来储存t图片角点vector<cv::Point3f> object_point;//保存标定板上所有角点坐标vector<cv::Mat> rvecs,tvecs;//旋转向量和位移向量vector<vector<cv::Point3f>> object_points;//棋盘格三维坐标容器vector<vector<cv::Point2f>> img_points;//棋盘格角点容器for(auto & imgdir:images){//载入图像img=cv::imread(imgdir);//生成object pointsfor(int i=0;i<grid.height;i++){for(int j=0;j<grid.width;j++){object_point.push_back(cv::Point3f(i*distance.width,j*distance.height,0));//向容器存入每个角点坐标}}//得到灰度图片cv::cvtColor(img,gray,cv::COLOR_BGR2GRAY);//得到图片的image points//NOTE corners的储存方式为从左往右,从上往下每行储存,所以储存object_point的时候需从grid。width开始遍历储存bool ret=cv::findChessboardCorners(gray,grid,corners,cv::CALIB_CB_ADAPTIVE_THRESH+cv::CALIB_CB_NORMALIZE_IMAGE+cv::CALIB_CB_FAST_CHECK);if(ret){//亚像素精细化cv::cornerSubPix(gray,corners,cv::Size(11,11),cv::Size(-1,-1),cv::TermCriteria(cv::TermCriteria::COUNT+cv::TermCriteria::EPS, 30, 0.1));img_points.push_back(corners);object_points.push_back(object_point);}object_point.clear();//清空object_point以便下一幅图使用该容器//绘制角点并显示cv::drawChessboardCorners(img,grid,cv::Mat(corners),ret);// cv::imshow("chessboard corners",img);// cv::waitKey(10);}cv::calibrateCamera(object_points,img_points,img.size(),cameraMatirx,distCoeffs,rvecs,tvecs);
}

然后使用上方法得到的object_points and img_points 传入cv::calibrateCamera() 方法中就可以计算出相机校正矩阵(camera calibration matrix)和失真系数(distortion coefficients),再使用 cv::undistort()方法就可得到校正图片。

def cal_undistort(img, objpoints, imgpoints):ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, img.shape[1::-1], None, None)dst = cv2.undistort(img, mtx, dist, None, mtx)return dst

以下为其中一张棋盘格图片校正前后对比:

校正测试图片

代码如下:

//获取棋盘格图片
get_images_by_dir(cal_dir,filetype,imgs);
//计算矫正系数
get_obj_img_points(imgs,grid,distance,cameraMatirx,distCoeffs);

测试图片校正前后对比:
[alt text][(https://raw.githubusercontent.com/RyanAdex/CarND-Advanced-Lane-Lines/master/output_images/undistortion.png)

阈值过滤(thresholding)

这里会使用梯度阈值(gradient threshold),颜色阈值(color threshold)等来处理校正后的图片,捕获车道线所在位置的像素。(这里的梯度指的是颜色变化的梯度)

以下方法通过"cv::Sobel()"方法计算x轴方向或y轴方向的颜色变化梯度导数,并以此进行阈值过滤(thresholding),得到二进制图(binary image):

void abs_sobel_thresh(const cv::Mat& src,cv::Mat& dst,const char& orient='x',const int& thresh_min=0,const int& thresh_max=255){cv::Mat src_gray,grad;cv::Mat abs_gray;//转换成为灰度图片cv::cvtColor(src,src_gray,cv::COLOR_RGB2GRAY);//使用cv::Sobel()计算x方向或y方向的导if(orient=='x'){cv::Sobel(src_gray,grad,CV_64F,1,0);cv::convertScaleAbs(grad,abs_gray);}if(orient=='y'){cv::Sobel(src_gray,grad,CV_64F,0,1);cv::convertScaleAbs(grad,abs_gray);}//二值化cv::inRange(abs_gray,thresh_min,thresh_max,dst);// cv::threshold(abs_gray,dst,thresh_min,thresh_max,cv::THRESH_BINARY|cv::THRESH_OTSU);
}

通过测试发现使用x轴方向阈值在35到100区间过滤得出的二进制图可以捕捉较为清晰的车道线:

abs_sobel_thresh(imge,absm,'x',55,200);//sobel边缘识别

以下为使用上面方法应用测试图片的过滤前后对比图:

可以看到该方法的缺陷是在路面颜色相对较浅且车道线颜色为黄色时,无法捕捉到车道线(第三,第六,第七张图),但在其他情况车道线捕捉效果还是不错的。

接下来测试一下使用全局的颜色变化梯度来进行阈值过滤:

void mag_thresh(const cv::Mat& src,cv::Mat& dst,const int& sobel_kernel=3,const int& thresh_min=0,const int& thresh_max=255){cv::Mat src_gray,gray_x,gray_y,grad;cv::Mat abs_gray_x,abs_gray_y;//转换成为灰度图片cv::cvtColor(src,src_gray,cv::COLOR_RGB2GRAY);//使用cv::Sobel()计算x方向或y方向的导cv::Sobel(src_gray,gray_x,CV_64F,1,0,sobel_kernel);cv::Sobel(src_gray,gray_y,CV_64F,0,1,sobel_kernel);//转换成CV_8Ucv::convertScaleAbs(gray_x,abs_gray_x);cv::convertScaleAbs(gray_y,abs_gray_y);//合并x和y方向的梯度cv::addWeighted(abs_gray_x,0.5,abs_gray_y,0.5,0,grad);//二值化cv::inRange(grad,thresh_min,thresh_max,dst);// cv::threshold(grad,dst,thresh_min,thresh_max,cv::THRESH_BINARY|cv::THRESH_OTSU);}
mag_thresh(imge,mag,3,45,150);

结果仍然不理想(观察第三,第六,第七张图片),原因是当路面颜色相对较浅且车道线颜色为黄色时,颜色变化梯度较小,想要把捕捉车道线需要把阈值下限调低,然而这样做同时还会捕获大量的噪音像素,效果会更差。

那么使用颜色阈值过滤呢?
下面为使用hls颜色空间的s通道进行阈值过滤:

void hls_select(const cv::Mat& src,cv::Mat& dst,const char& channel='s',const int& thresh_min=0,const int& thresh_max=255){cv::Mat hls,grad;vector<cv::Mat> channels;cv::cvtColor(src,hls,cv::COLOR_RGB2HLS);//分离通道cv::split(hls,channels);//选择通道switch (channel){case 'h':grad=channels.at(0);break;case 'l':grad=channels.at(1);break;case 's':grad=channels.at(2);break;default:break;}//二值化cv::inRange(grad,thresh_min,thresh_max,dst);// cv::threshold(grad,dst,thresh_min,thresh_max,cv::THRESH_BINARY);
}
mag_thresh(imge,mag,3,45,150);

可以看到在路面颜色相对较浅且车道线颜色为黄色的区域,车道线仍然被清晰的捕捉到了,然而在其他地方表现却不太理想(第四,第八张图片)

因此为了应对多变的路面情况,需要结合多种阈值过滤方法。

以下为最终的阈值过滤组合:

abs_sobel_thresh(imge,absm,'x',55,200);//sobel边缘识别
mag_thresh(imge,mag,3,45,150);
hls_select(imge,hls,'s',160,255);
dir_threshold(imge,dir,3,0.7,1.3);
luv_select(imge,luv,'l',180,255);
// lab_select(imge,lab,'b',126,127);imgout=(absm&mag&luv)|(hls&luv);

透视变换(perspective transform)

这里使用"cv::getPerspectiveTransform()"来获取变形矩阵(tranform matrix),把阈值过滤后的二进制图片变形为鸟撒视角。

以下为定义的源点(source points)和目标点(destination points)

Source       Destination  
585, 460 320, 0
203, 720 320, 720
1127, 720 960, 720
695, 460 960, 0

定义方法获取变形矩阵和逆变形矩阵:

void get_M_Minv(const vector<cv::Point2f>& src,const vector<cv::Point2f>& dst,cv::Mat& M,cv::Mat& Minv){M=cv::getPerspectiveTransform(src,dst);Minv=cv::getPerspectiveTransform(dst,src);
}

然后使用"cv::warpPerspective()"传入相关值获得变形图片(wrapped image)

cv::warpPerspective(cimg,imge,M,img.size(),cv::INTER_LINEAR);

以下为原图及变形后的效果:

以下为阈值过滤后二进制图变形后效果:

检测车道边界

上面的二进制图还存在一定的噪音像素,为了准确检测车道边界,首先要确定哪些像素是属于车道线的。

首先要定位车道线的基点(图片最下方车道出现的x轴坐标),由于车道线在的像素都集中在x轴一定范围内,因此把图片一分为二,左右两边的在x轴上的像素分布峰值非常有可能就是车道线基点。

以下为测试片x轴的像素分布图:

定位基点后,再使用使用滑动窗多项式拟合(sliding window polynomial fitting)来获取车道边界。这里使用9个200px宽的滑动窗来定位一条车道线像素:

void find_line(const cv::Mat& src,vector<cv::Point>& lp,vector<cv::Point>& rp,int& rightx_current,int& leftx_current,double& distance_from_center){cv::Mat hist,nonzero,l,r;vector<cv::Point> nonzerol,nonzeror,lpoint,rpoint;int midpoint;cv::Point leftx_base,rightx_base;//选择滑窗个数int nwindows = 9;//设置窗口高度int window_height = int(src.rows/nwindows);//设置窗口宽度int margin=50;//设置非零像素坐标最少个数int minpix=50;//TODO 加入if设置图像连续性,如果leftx_current和rightx_current为零,则认为第一次执行,需要计算该两点,如果已经计算了,则不许再次计算。//rowrange图像区域分割//将图像处理为一行,以行相加为方法    cv::reduce(src.rowRange(src.rows/2,src.rows),hist,0,cv::REDUCE_SUM,CV_32S);midpoint=int(hist.cols/2);//将hist分为左右分别储存,并找出最大值//minMaxIdx针对多通道,minMaxLoc针对单通道cv::minMaxLoc(hist.colRange(0,midpoint),NULL,NULL,NULL,&leftx_base);cv::minMaxLoc(hist.colRange(midpoint,hist.cols),NULL,NULL,NULL,&rightx_base);//左右车道线基础点leftx_current=leftx_base.x;rightx_current=rightx_base.x+midpoint;// 提前存入该基础点坐标lpoint.push_back(cv::Point(leftx_current,src.rows));rpoint.push_back(cv::Point(rightx_current,src.rows));for(int i=0;i<nwindows;i++){int win_y_low=src.rows-(i+1)*window_height;//计算选框x坐标点,并将计算结果限制在图像坐标内int win_xleft_low = leftx_current - margin;win_xleft_low=win_xleft_low>0?win_xleft_low:0;win_xleft_low=win_xleft_low<src.rows?win_xleft_low:src.rows;//int win_xleft_high = leftx_current + margin;int win_xright_low = rightx_current - margin;win_xright_low=win_xright_low>0?win_xright_low:0;win_xright_low=win_xright_low<src.rows?win_xright_low:src.rows;//int win_xright_high = rightx_current + margin;//NOTE要确保参数都大于0,且在src图像范围内,不然会报错//NOTE 设置为ROI矩形区域选择l=src(cv::Rect(win_xleft_low,win_y_low,2*margin,window_height));r=src(cv::Rect(win_xright_low,win_y_low,2*margin,window_height));//NOTE 把像素值不为零的像素坐标存入矩阵cv::findNonZero(l,nonzerol);cv::findNonZero(r,nonzeror);//计算每个选框的leftx_current和rightx_current中心点if(nonzerol.size()>minpix){int leftx=0;for(auto& n:nonzerol){leftx+=n.x;}leftx_current=win_xleft_low+leftx/nonzerol.size();}if(nonzeror.size()>minpix){int rightx=0;for(auto& n:nonzeror){rightx+=n.x;}rightx_current=win_xright_low+rightx/nonzeror.size();}//将中心点坐标存入容器lpoint.push_back(cv::Point(leftx_current,win_y_low));rpoint.push_back(cv::Point(rightx_current,win_y_low));}//拟合左右车道线坐标cv::Mat leftx = polyfit(lpoint,2);cv::Mat rightx = polyfit(rpoint,2);//计算拟合曲线坐标lp=polyval(leftx,lpoint,2);rp=polyval(rightx,rpoint,2);//计算车道偏离距离int lane_width=abs(rpoint.front().x-lpoint.front().x);double lane_xm_per_pix=3.7/lane_width;double veh_pos=(((rpoint.front().x+lpoint.front().x)*lane_xm_per_pix)/2);double cen_pos=((src.cols*lane_xm_per_pix)/2);distance_from_center=veh_pos-cen_pos;// cout<<"dis"<<distance_from_center<<endl;// cout<<lp<<endl;
}

以下为滑动窗多项式拟合(sliding window polynomial fitting)得到的结果:

计算车道曲率及车辆相对车道中心位置

利用检测车道得到的拟合值(find_line 返回的left_fit, right_fit)计算车道曲率,及车辆相对车道中心位置,代码在find_line中:

    int lane_width=abs(rpoint.front().x-lpoint.front().x);double lane_xm_per_pix=3.7/lane_width;double veh_pos=(((rpoint.front().x+lpoint.front().x)*lane_xm_per_pix)/2);double cen_pos=((src.cols*lane_xm_per_pix)/2);distance_from_center=veh_pos-cen_pos;

处理原图,展示信息

使用逆变形矩阵把鸟瞰二进制图检测的车道镶嵌回原图,并高亮车道区域,使用"cv::putText()"方法处理原图展示车道曲率及车辆相对车道中心位置信息:

void draw_area(const cv::Mat& src,vector<cv::Point>& lp,vector<cv::Point>& rp,const cv::Mat& Minv,double& distance_from_center){vector<cv::Point> rflip,ptr;cv::Mat colormask=cv::Mat::zeros(src.rows,src.cols,CV_8UC3);cv::Mat dst,midst;//绘制车道线cv::polylines(colormask,lp,false,cv::Scalar(0,255,0),5);cv::polylines(colormask,rp,false,cv::Scalar(0,0,255),5);//反转坐标,以便绘制填充区域cv::flip(rp,rflip,1);//拼接坐标cv::hconcat(lp,rflip,ptr);//绘制填充区域const cv::Point* em[1]={&ptr[0]};int nop=(int)ptr.size();cv::fillPoly(colormask,em,&nop,1,cv::Scalar(200,200,0));//反变形cv::warpPerspective(colormask,midst,Minv,src.size(),cv::INTER_LINEAR);//将车道线图片和原始图片叠加cv::addWeighted(src,1,midst,0.3,0,dst);//绘制文字cv::putText(dst,"distance bias:"+to_string(distance_from_center)+"m",cv::Point(50,50),cv::FONT_HERSHEY_SIMPLEX,1,cv::Scalar(255,255,255),2);cv::imshow("video",dst);// cv::waitKey(10000);
}

以下为测试图片处理后结果:

以下为处理后测试视频链接:

转载于:https://www.cnblogs.com/ryan-mask/p/10230097.html

车道线识别/Opencv/传统方法相关推荐

  1. 自动驾驶入门(十二):基于Opencv的车道线识别

    车道线识别有两种方法: 基于Opencv的传统视觉车道线识别方案 基于深度学习的车道线识别方案 本文将介绍基于Opencv的传统视觉车道线识别方案. 传统的车道线识别解决方案流程图如下: 代码实现如下 ...

  2. Python OpenCV车道线识别侦测

    Python OpenCV车道线识别侦测 如需安装运行环境或远程调试,可加QQ905733049, 或QQ2945218359由专业技术人员远程协助! 运行结果如下: 代码如下: import cv2 ...

  3. python+opencv图像处理之边缘检测车道线识别

    python+opencv图像处理之边缘检测车道线识别 1.自行安装python和opencv 2.导入我们要使用的相关库 import cv2 from matplotlib import pypl ...

  4. 基于opencv的车道线识别(python)(极易实现)

    简易车道线识别方法 文章目录 简易车道线识别方法 1.先上效果图 1.1原图: 1.2结果图 2.源代码 3.阈值脚本 4.谈谈优缺点 优点: 缺点: 1.先上效果图 1.1原图: 1.2结果图 2. ...

  5. python视觉识别线条_简单车道线识别

    本文将介绍如何利用Opencv,对简单场景下的车道线进行离线识别.梳理整个识别过程的逻辑,并对过程中使用的相关知识点进行介绍.正文中使用C++实现,在文末也会附上利用python实现的代码,读者完全可 ...

  6. 基于视觉的车道线识别技术在智能车导航中的应用研究

    密级:公开 摘  要 摘  要 室外移动机器人的研究是机器人研究领域的重要分支,同时也是备受关注的热点领域.面向高速公路等结构化道路的室外移动机器人研究已成为现阶段民用交通运输领域移动机器人研究的主流 ...

  7. 基于 Amazon DeepRacer Opensource 实现自定义车道线识别任务

    点击上方[凌云驭势 重塑未来] 一起共赴年度科技盛宴! 自动驾驶的视觉感知流程 在自动驾驶系统中,作为识别周边环境的"感官"角色,感知模块是整个系统安全.高效运行的基础,让汽车得以 ...

  8. 总奖金64万!含吸烟打电话检测、车道线识别等,2020中国华录杯·数据湖算法大赛火热进行中!...

    ●赛题背景● 以"数据驱动创新,赋能智慧城市"为主题的2020中国华录杯·数据湖算法大赛,围绕"华录数据湖+智慧城市"的核心理念,着力于智慧城市业务中的真实应用 ...

  9. 吸烟打电话检测、车道线识别等,2020中国华录杯·数据湖算法大赛火热进行中!...

    ●赛题背景● 随着互联网的高速发展,"万物数据化"浪潮奔腾而来.数据湖围绕数据的全生命周期管理打造新一代数字基础设施,在硬件层面构筑了高性能.低成本.智能化.高安全的数字经济底座, ...

最新文章

  1. linux内核计算代码时间,完成一个简单的时间片轮转多道程序内核代码
  2. 如果给你机会,阿里巴巴的中层职位和马云的专属司机,你怎么选?
  3. uva 11105——Semi-prime H-numbers
  4. 源码 解析_最详细集合源码解析之ArrayList集合源码解析
  5. 实现机器学习的循序渐进指南XII——Apriori
  6. FreeRTOS内核实现05:支持多优先级
  7. 软帝java培训实习日志,在软帝学习的第一个星期的小总结
  8. 异步类随机多址接入分析
  9. 数据结构银行叫号系统
  10. 北邮带研究生的计算机导师有哪些,GitHub - sunichi/BUPTNiceMentors: 北邮研究生导师口碑榜...
  11. windows11 git 安装SSH密钥
  12. uniapp中唤醒支付宝,微信进行支付并返回app
  13. 生僻字怎么用计算机打出来,电脑搜狗输入法生僻字怎么打?电脑搜狗输入法怎么打不认识的字?...
  14. 据国外媒体Theverge称微软目前正计划整合Windows Phone应用商店
  15. 初识Quartz之Trigger组件
  16. Mac双屏时程序坞(任务栏)跑到副屏上怎么办
  17. PHP storm安装教程
  18. linux shell 三元运算符,关于语法:Bash中的三元运算符(?:)
  19. 太美医疗搭建临床研究机构信息化体系,助力新药专项临床评价技术平台建设
  20. python科学计算可视化好慢_Python科学计算三维可视化 1

热门文章

  1. centos 使用mysql_Centos下MySQL使用总结
  2. python framework threads_python 多线程,进程的理解
  3. rabbitmq可靠性投递_RabbitMQ 可靠投递
  4. 离线语法设置 科大讯飞_科大讯飞智能键盘K710评测 输入速度超级加倍
  5. PAT乙级(Basic Level)真题--跟奥巴马一起编程(15)
  6. C#序列化和反序列化代码总结
  7. 枚举、宏定义enum /defint/typedef
  8. 推荐一款强大的SQL Internal 查看工具InternalsViewer
  9. Transfer Execute Redirect重定向方法介绍
  10. Mybatis出现文档根元素 mapper 必须匹配 DOCTYPE 根 configuration错误解决办法