【自动驾驶】车道保持辅助驾驶”(传统车道检测)

目录

1、 什么是车道保持辅助驾驶系统?

2、 车道偏离预警系统与车道保持辅助驾驶有何区别?

3、 驾驶员自主更换车道会跟车道保持辅助驾驶系统冲突吗?

4、 车道保持系统适用于那些场合?

5、 有了车道保持系统,就能一定程度上解放双手了吗?

6、 车道保持系统在任何路面都能辅助驾驶吗?

7、 车道保持系统在转弯时也能辅助驾驶吗?

8、 车道偏离预警系统或者车道保持系统的应用普及了吗?

9、 车道保持辅助驾驶的弊端有吗?

目前成熟应用案例

 

车道线检测,需要完成以下功能:

  传统方法实现的实例


 

“在欧洲,39.4%的意外交通事故是在无意间偏离车道而产生的,在美国这个比例则是44%,在中国更是高达50%,如此惊人的数据直接催生了“车道保持辅助驾驶”系统的诞生。”

1、 什么是车道保持辅助驾驶系统?

车道保持辅助驾驶系统是在车道偏离预警系统上发展而来的帮助驾驶员纠正车辆行进方向的辅助驾驶系统。

车道保持辅助驾驶系统主要由显示器、摄像头、控制器以及传感器组成,当车道偏离系统开启时,摄像头(一般安置在车身侧面或后视镜位置)会时刻采集行驶车道的标识线,通过图像处理获得汽车在当前车道中的位置参数,当检测到汽车即将偏离车道时,传感器会及时收集车辆数据和驾驶员的操作状态,之后由控制器自动控制方向盘系统,恢复行驶方向。

 

2、 车道偏离预警系统与车道保持辅助驾驶有何区别?

虽然命名有些差距,但两者原理是类似的,只是在发生偏离时的具体处理不同而已。

车道偏离预警系统在车辆偏离车道时会以警报的形式提醒驾驶员,如语音警报,方向盘振动,座椅振动等。但是科学数据表明在车辆非正常变道时仅仅给出警报并不能在最大程度上保证安全,所以他们又在车道偏离预警系统的基础上开发出了车道保持辅助系统。与车道偏离预警系统仅仅发出警报不同的是,车道保持辅助系统会在车辆偏离正常行驶方向时自动修正车辆行驶方向。

3、 驾驶员自主更换车道会跟车道保持辅助驾驶系统冲突吗?

有两种情况。

一,当驾驶员先打了转向灯,车道保持系统会暂时不生效,因此车主可以自行决定驾驶方向。

二,当驾驶员没有打转向灯的情况下更换车道,车道保持系统则会产生一定的抗力(可能会有警报),试图自行纠正行进方向,保持在当前的车道上。但是当驾驶员执意要在不打转向灯的情况下更换车道,只要驾驶员转动方向盘的力量大于系统预设的力量时,依然可以“任性地”转向的。

保持良好的驾驶习惯,你可能永远也用不到车道保持系统。

4、 车道保持系统适用于那些场合?

一、高速路上的微小的方向盘扰动都会导致车道偏离,时刻修正方向让人心累,让人崩溃?

二、回家的路太远,归心似箭,恨不得马不停蹄,结果长时间驾驶疲劳驾驶睡着了?

三、半夜行车,对面来车光芒太耀眼,突然看不到路了?

四、车道太窄,技术太菜,会车时不知道自己在不在自己的车道上?

五、高速驾驶中突然爆胎了?

···

不管目前的车道保持系统配置的价格高低,以上场合,对任何驾驶员,只要生效一次,就够本了,甚至只是为了预防,也足够打动你掏腰包。

5、 有了车道保持系统,就能一定程度上解放双手了吗?

距离真正的自动驾驶还有很长的路要走,如果您希望这项技术能帮助您解放双手,可能要大失所望了。

有人曾想当然地觉得在高速路上,如果自己需要长时间地在一条车道上行驶,方向盘交给车道保持系统不就行了?

大错特错。

你会发现,当你的手离开方向盘超过10s,车道保持系统就不会生效了。

不要忘了,该技术研发出来的初衷,是为了防止意外,而不是自动驾驶。

 

6、 车道保持系统在任何路面都能辅助驾驶吗?

不要忘了,车道保持系统是采集路标信息来定位的,当路标难以识别时,如大雾天气,如路面积雪,如路标不清晰或者根本没有路标时,就不要寄希望于它了,虽然也有红外线识别技术,但极端情况唯有专心驾驶才是王道。

7、 车道保持系统在转弯时也能辅助驾驶吗?

其实只要理解了车道保持技术的原理是采集路标信号,就不难想到,不管路标是直的还是弯的,计算系统都能让车时刻保持在车道中央。因此,弯道上也是能用车道保持辅助驾驶的。

8、 车道偏离预警系统或者车道保持系统的应用普及了吗?

目前车道偏离预警系统在大众CC、宝马5系、奔驰E级、英菲尼迪M系等车型均已配备。从目前的汽车消费市场来看,消费者不仅仅只关心车型的价格,拥有更舒适更丰富的配置才是他们更想要的,车道偏离预警系统作为主动安全中的先进技术之一,未来有望在各级别车型中得到普及,或许在未来一些低端车型上,都能看到这项技术的身影。

9、 车道保持辅助驾驶的弊端有吗?

任何技术都是有利有弊的,车道保持技术也不例外。

在美国,IIHS调查了涉及9个品牌900多辆车的车主,最终发现在行驶中使用车道偏离预警的驾驶员不超过一半。“车道偏离警报经常在没有危险的时候发出不合时宜的警报,”该研究的主要发起者Ian Reagan说,“发出嘟嘟声,或者让方向盘、座椅产生振动,它们让驾驶员心烦的作用甚至比预警作用还大。”如果驾驶员无法原谅这一点,就只能关掉它。当然,也有厂商把这个关闭功能设计极为繁琐,以达到驾驶员懒得去关它的目的。

 

一、车道保持系统的基本功能

车道保持系统用于帮助司机使车辆一直保持在规定的某个车道上行驶,车辆不偏离车道(见图1)。

 

图1  车道保持系统的基本功能示意图

如果车辆行驶偏离自己的车道,车道保持系统会让转向系统自动纠偏

 

二、车道线识别的基本原理

在前车窗内侧的上方,安装一个摄像头(见图2)。摄像头能看清车道线,形成清晰的图像。在计算机的帮助下,通过一定算法,判断出车辆是否在规定车道内 。如果偏离车道(左右偏离),计算机会给出报警信号和纠偏指令。摄像头成像(这是传统技术)是基本要求,对图像扫描后,形成数字信号,这是比较关键的技术,接下来关键的是计算机芯片(处理器)的计算速度和存储器容量大小。

 

图2 车道线识别原理示意图

通俗的讲,摄像头要满足车用要求,车辆是高速运动的,摄像头拍的照片要清晰,计算机芯片(处理器)也必须是专业级

 注意:道路上要划分出车道是基础,其车道线清晰度的要求,符合国际标准ISO 17361和GB/T26773-2011。

 

图3  国家符合标准的车道图

三、车道保持系统工作原理

车道保持系统基本结构分为:识别、分析、决策系统和控制执行系统。

 1)识别、分析、决策系统

 通过摄像头的画面的处理,得出当前车辆相对车道线的位置,偏离的方向和速度,当车辆靠近识别出的边界线且要驶离该车道时,系统会通过声音和图像对司机进行提醒。其控制策略:

 Ⅰ)如果车辆压过车道边界线之前,操纵了转向拨杆(转向灯亮了),那么就不会出现警告信号。这时该系统认为,是司机的正常车道变换驾驶。

 Ⅱ)在车辆靠近车道边界线,系统会出现一次警告信号;

 Ⅲ)在第一次警告出现后,车辆前轮远离对应的车道边界线,随后后轮也靠近边界线时,系统会就发出第二次警报。这样,就可防止车辆在与车道边界线平行行驶时持续出现这种警告。

 Ⅳ)系统在发出告警信号的同时,发出辅助控制(纠偏)命令是有前提条件的,即驾驶员没有打转向灯并且没有操作方向盘的情况下,车辆发生了车道偏离且车辆速度达到一定规定(乘用车一般约定为65公里/小时)时。

 注意:如果驾驶员在操作方向盘,则系统认为驾驶员是正常、有意图的驾驶行为,系统不会对转向系统予以进行控制。

 2)控制执行系统

 接到决策系统的辅助控制命令时,由ESP系统或电机输出转向助力,使得车辆保持在道路内行驶。控制执行系统逻辑示意图见图4。

 

图4  控制执行系统逻辑示意图

 注意:转向助力的大小和时长,由车道半径、车速、偏离方向、偏离速度等参数(由不同传感器给出,由计算机计算出来)控制执行系统的电控转向机构原理见图5。这是一个负反馈平衡系统。

 

图5 电控转向机构原理

 ①三个原件:步进电机、转矩传感器、电机控制器。外加电源线和信号和控制线缆。

②控制策略:

a)车道保持系统是用于高速公路和路况良好的普通公路的,因此该系统只有在车速超过约65 km/h时才开始工作。(如果用户有要求,按用户设定,比如公交公司最高限速是50公里)。

b)如果车道被雪覆盖着、道路过窄或者道路边界线不清晰的情况下,车道保持系统会自动关闭。

 说明:

①车道保持辅助系统属于智能驾驶辅助系统的一种,它是在车道偏离预警系统(LDWS)的基础上对转向控制协调装置进行控制。在换道前,必须打转向灯。

②车辆速度低时,车道保持辅助系统不工作。

③如果是园区(码头)、企业物流车辆,再另外设置参数即可。

 

目前成熟应用案例

视觉感知解决方案

近期芯片巨头英特尔联合全球自动 驾驶算法领军者 Mobileye 为宝马开发自动驾驶汽车技术,再度显示了芯片 在无人驾驶领域至高的战略地位。实际上 Mobileye 在 ADAS 芯片领域已经 进行充分的长远布局,其主要产品均是围绕核心芯片提供的自动驾驶整体 解决方案。Mobileye 和意法半导体已经合作研发 Mobileye 迭代至第五代系统 芯片——EyeQ5,包含计算机视觉、信号处理以及深度神经网络等加速计算 能力,该款芯片将作为 2020 年实现全自动驾驶(FAD)汽车的中央处理器, 执行传感器融合程序等功能。

 

车道检测的目标:

1. 车道形状,包括宽度、曲率等几何参数

2. 车辆在车道中的位置,包括横向偏移量,车辆与道路的夹角(偏航角)

车道检测与跟踪一般分为以下几个部分:

1. 车辆、道路、相机模型

2. 道路特征提取

3. 道路参数计算,如曲率,

4. 车道跟踪

车辆、道路、相机模型

在现代道路设计中,道路有比较固定的设计模型,因此,对于高速公路等道路类型,车道的几何模型可以以固定的形式表示。

车道弧长、曲率、偏航角、横向偏移量构成车辆与车道几何模型的要素。

车道一般由直线、圆弧和缓和曲线构成,缓和曲线通常是不同曲率的圆弧或直线的连接过渡,其曲率均匀变化,螺旋曲线是缓和曲线常用形式。

道路曲率与弧长(路长)的关系:

C = C0 + C1*L.

C0为起始点曲率,C1为曲率变化率。C0,C1都为0时,直线; C1为0时,C0不为0,圆弧;C1不为0时,缓和曲线。


在世界坐标系下,或俯视图下,在相机可视范围内,若车道的变化方向较小,则道路可用圆弧近似表示:

道路的坐标可以由弧长和曲率一般表示为:

y = L

x = 0.5*C*L^2

若相机与车道的横向偏移量为d,与车道的夹角为a,则车道模型为

y = L

x = d + a*L + 0.5*C*L^2

从公式来看,这是一个抛物线模型。这里没有考虑曲率变换率,即忽略了高次项(C1*L^3)/6。

不同的系统要求与道路环境,道路模型的精度要求也不同。在较早的系统里,在大路(highway)环境下,

基于视觉的车道模型经历了,平行直线模型 --> 固定曲率圆弧模型 --> 螺旋曲线模型。道路模型的精度不断提高。

使用哪种模型,要根据系统的实际需求。如早期的系统里,检测大路(highway)中10米内的车道状况,应用简单的线性模型即可。而车道偏离告警(LDW)系统中,在高速公路上,需要30米-40米的精确的道路模型,这时,螺旋曲线(高阶)或抛物线(二阶)模型就更为精确。


相机模型:

由图像的二维信息恢复出场景的三维信息,就需要相机模型来确立两者之间的对应关系。相机参数包括内部参数和外部参数。相机模型分为针孔相机模型和透镜畸变扩展模型。这些在另外一篇文章里有描述。相机的内外参数通过标定都可以获取。相机的外部参数体现了相机坐标下图像与世界坐标下场景的齐次变换关系。


道路特征检测

道路上车道标志的检测是道路特征检测的关键部分,并且已有很多算法,但道路场景太多,单一的算法还是无法适用所有的场景。算法分类:


      适用场景     不适用场景
基于边缘检测     虚线、实线明显     阴影、光照变化、反射不均匀
 基于频域技术     能处理反射不均的场景     阴影
基于路面纹理/模板     阴影、光照不均等     反射不均
            

基于边缘检测的常用算法有:

Sobel, DOG, LOG, Steerable Filter等。每种算法都有各自的优缺点。这里不再描述。


由基本算法处理后得到道路的特征图像,这里以边缘为例,需要进一步分析其特性,去除干扰,保留符合车道特征的边缘。如平行性、宽度等结构特征。如果这些结构特征分析做的好,也可以弥补基本算法的不足。


其他检测方法:

双阈值特征检测:原图像、梯度图像(边缘图像)分别有各自的阈值,分割出车道标志特征,当某点的灰度和梯度值分别大于各自的阈值时,才被选取为车道的特征点,这个方法会去掉一些阴影等干扰。

可调滤波器steerable filter:

对原图分别获取Gxx, Gyy, Gxy,高斯二阶分量。角度可变的滤波器的强度响应如下:


求上式的极值,则需求角度的导数,则能获取两个角度

对于滤波器窗口内圆形对称的物体,则两个角度的响应差不多;而对车道,两个角度响应的差值就比较大,响应大的方向就是车道方向,车道方向也可以探测出。

道路参数计算

道路方向、曲率的计算。霍夫变换是常用的检测直线的方法,还有其他方法筛选特征计算参数的方法,如最小二乘估计,RANSAC,这些方法基本上都设定了道路模型,由特征点来计算参数。但也可以由计算出的模型,去除不符合条件的特征。

跟踪

一般跟踪的作用就是预测下一帧图像内道路特征的位置,在一个较小的范围内检测道路特征,提高效率。若预测范围内没有检测到道路特征,则采用估计或上一帧特征的位置,若连续几帧都没有检测到道路特征,则启动全图像道路特征检测。KalmanFilter是常用的跟踪算法。

车道的状态要考虑车道的位置、速度、偏航角及车辆行驶转角之间的关系。

夹角增量 = 曲率 * 长度

状态变量为道路弧长、车道夹角、道路曲率、车道宽度

车辆的行驶转角作为控制输入变量。


测量变量为道路弧长和车辆与道路的夹角。

则状态转移方程如下:

 ---------------------  
作者:viewcode  原文:https://blog.csdn.net/viewcode/article/details/7969259

 

车道线检测,需要完成以下功能:

    图像裁剪:通过设定图像ROI区域,拷贝图像获得裁剪图像
    反透视变换:用的是室外采集到的视频,没有对应的变换矩阵。所以建立二维坐标,通过四点映射的方法计算矩阵,进行反透视变化。后因ROI区域的设置易造成变换矩阵获取困难和插值得到的透视图效果不理想,故没应用
    二值化:先变化为灰度图,然后设定阈值直接变成二值化图像。
    形态学滤波:对二值化图像进行腐蚀,去除噪点,然后对图像进行膨胀,弥补对车道线的腐蚀。
    边缘检测:canny变化、sobel变化和laplacian变化中选择了效果比较好的canny变化,三者在代码中均可以使用,canny变化效果稍微好一点。
    直线检测:实现了两种方法 1>使用opencv库封装好的霍夫直线检测函数,在原图对应区域用红线描出车道线 2>自己写一种直线检测,在头文件中,遍历ROI区域进行特定角度范围的直线检测。两种方法均可在视频中体现,第一种方法运行效率较快。
    按键控制:空格暂停,其余键退出,方便调试和截图。

实现的效果
这里写图片描述
在亮度良好道路条件良好的情况下,检测车前区域的车道线实现比较成功,排除掉高速护栏的影响,而且原图像还能完整体现。

这里写图片描述
车子行驶在高速公路大型弯道上,可以在一定角度范围内认定车道线仍是直线,检测出为直线。

这里写图片描述
车子切换过程中只有一根车道线被识别,但是稳定回变换车道后,实现效果良好。减速线为黄色,二值化是也被过滤,没造成影响。

这里写图片描述
这里写图片描述
刚进入隧道时,摄像机光源基本处于高光状态,拍摄亮度基本不变,二值化图像时情况良好,噪声比较多但是没产生多大线状影响;当摄像头自动调节亮度,图像亮度变低,二值化时同一阈值把车道线给过滤掉,造成无法识别车道线的现象。

这里写图片描述
在道路损坏的情况下,由于阈值一定,基本上检测不出车道线。

结论

   实现的功能:实现了车道线检测的基本功能,反透视变换矩阵实现了但效果不太理想,使用自己写的直线检测部分,车道线识别抗干扰能力较强。

   缺点:整个识别系统都是固定的参数,只能在特定的环境产生良好的效果。

   改进空间:提取全部关键参数,每次对ROI图像进行快速扫描更新参数,否则使用默认参数。例如,可以选择每次5间隔取点,以像素最高点的85%作为该次二值化的阈值。从而做到动态车道线识别。

  传统方法实现的实例

完整代码
方法一
main.cpp

#include<cv.h>
#include<cxcore.h>
#include<highgui.h>
#include"mylinedetect.h"

#include<cstdio>
#include<iostream>
using namespace std;

int main(){
    //声明IplImage指针
    IplImage* pFrame = NULL;
    IplImage* pCutFrame = NULL;
    IplImage* pCutFrImg = NULL;
    //声明CvCapture指针
    CvCapture* pCapture = NULL;
    //声明CvMemStorage和CvSeg指针
    CvMemStorage* storage = cvCreateMemStorage();
    CvSeq* lines = NULL;
    //生成视频的结构
    VideoWriter writer("result.avi", CV_FOURCC('M', 'J', 'P', 'G'), 25.0, Size(856, 480));
    //当前帧数
    int nFrmNum = 0;
    //裁剪的天空高度
    int CutHeight = 310;
    //窗口命名
    cvNamedWindow("video", 1);
    cvNamedWindow("BWmode", 1);
    //调整窗口初始位置
    cvMoveWindow("video", 300, 0);
    cvMoveWindow("BWmode", 300, 520);
    //不能打开则退出
    if (!(pCapture = cvCaptureFromFile("lane.avi"))){
        fprintf(stderr, "Can not open video file\n");
        return -2;
    }
    //每次读取一桢的视频
    while (pFrame = cvQueryFrame(pCapture)){
        //设置ROI裁剪图像
        cvSetImageROI(pFrame, cvRect(0, CutHeight, pFrame->width, pFrame->height - CutHeight));
        nFrmNum++;
        //第一次要申请内存p
        if (nFrmNum == 1){
            pCutFrame = cvCreateImage(cvSize(pFrame->width, pFrame->height - CutHeight), pFrame->depth, pFrame->nChannels);
            cvCopy(pFrame, pCutFrame, 0);
            pCutFrImg = cvCreateImage(cvSize(pCutFrame->width, pCutFrame->height), IPL_DEPTH_8U, 1);
            //转化成单通道图像再处理
            cvCvtColor(pCutFrame, pCutFrImg, CV_BGR2GRAY);
        }
        else{
            //获得剪切图
            cvCopy(pFrame, pCutFrame, 0);
#if 0       //反透视变换
            //二维坐标下的点,类型为浮点
            CvPoint2D32f srcTri[4], dstTri[4];
            CvMat* warp_mat = cvCreateMat(3, 3, CV_32FC1);
            //计算矩阵反射变换
            srcTri[0].x = 10;
            srcTri[0].y = 20;
            srcTri[1].x = pCutFrame->width - 5;
            srcTri[1].y = 0;
            srcTri[2].x = 0;
            srcTri[2].y = pCutFrame->height - 1;
            srcTri[3].x = pCutFrame->width - 1;
            srcTri[3].y = pCutFrame->height - 1;
            //改变目标图像大小
            dstTri[0].x = 0;
            dstTri[0].y = 0;
            dstTri[1].x = pCutFrImg->width - 1;
            dstTri[1].y = 0;
            dstTri[2].x = 0;
            dstTri[2].y = pCutFrImg->height - 1;
            dstTri[3].x = pCutFrImg->width - 1;
            dstTri[3].y = pCutFrImg->height - 1;
            //获得矩阵
            cvGetPerspectiveTransform(srcTri, dstTri, warp_mat);
            //反透视变换
            cvWarpPerspective(pCutFrame, pCutFrImg, warp_mat);
#endif
            //前景图转换为灰度图
            cvCvtColor(pCutFrame, pCutFrImg, CV_BGR2GRAY);
            //二值化前景图
            cvThreshold(pCutFrImg, pCutFrImg, 80, 255.0, CV_THRESH_BINARY);
            //进行形态学滤波,去掉噪音
            cvErode(pCutFrImg, pCutFrImg, 0, 2);
            cvDilate(pCutFrImg, pCutFrImg, 0, 2);
            //canny变化
            cvCanny(pCutFrImg, pCutFrImg, 50, 120);
            //sobel变化
            //Mat pCutFrMat(pCutFrImg);
            //Sobel(pCutFrMat, pCutFrMat, pCutFrMat.depth(), 1, 1);
            //laplacian变化
            //Laplacian(pCutFrMat, pCutFrMat, pCutFrMat.depth());
#if 1       //0为下面的代码,1为上面的代码
    #pragma region Hough直线检测
            lines = cvHoughLines2(pCutFrImg, storage, CV_HOUGH_PROBABILISTIC, 1, CV_PI / 180, 100, 15, 15);
            printf("Lines number: %d\n", lines->total);
            //画出直线
            for (int i = 0; i<lines->total; i++){
                CvPoint* line = (CvPoint*)cvGetSeqElem(lines, i);
                double k = ((line[0].y - line[1].y)*1.0 / (line[0].x - line[1].x));
                cout<<"nFrmNum "<<nFrmNum<<" 's k = "<<k<<endl;
                if(!(abs(k)<0.1))//去掉水平直线
                    cvLine(pFrame, line[0], line[1], CV_RGB(255, 0, 0), 6, CV_AA);
            }
    #pragma endregion
#else
    #pragma region mylinedetect
            Mat edge(pCutFrImg);
            vector<struct line> lines = detectLine(edge, 60);
            Mat pFrameMat(pFrame);
            drawLines(pFrameMat, lines);
            namedWindow("mylinedetect", 1);
            imshow("mylinedetect", pFrameMat);
    #pragma endregion
#endif
            //恢复ROI区域
            cvResetImageROI(pFrame);
            //写入视频流
            writer << pFrame;
            //显示图像
            cvShowImage("video", pFrame);
            cvShowImage("BWmode", pCutFrImg);
            //按键事件,空格暂停,其他跳出循环
            int temp = cvWaitKey(2);
            if (temp == 32){
                while (cvWaitKey() == -1);
            }
            else if (temp >= 0){
                break;
            }
        }
    }
    //销毁窗口
    cvDestroyWindow("video");
    cvDestroyWindow("BWmode");
    //释放图像
    cvReleaseImage(&pCutFrImg);
    cvReleaseImage(&pCutFrame);
    cvReleaseCapture(&pCapture);

    return 0;
}

  

mylinedetect.h

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
#include <vector>
#include <cmath>
using namespace cv;
using namespace std;

const double pi = 3.1415926f;
const double RADIAN = 180.0 / pi;

struct line{
    int theta;
    int r;
};

vector<struct line> detectLine(Mat &img, int threshold){
    vector<struct line> lines;
    int diagonal = floor(sqrt(img.rows*img.rows + img.cols*img.cols));
    vector< vector<int> >p(360, vector<int>(diagonal));
    //统计数量
    for (int j = 0; j < img.rows; j++) {
        for (int i = 0; i < img.cols; i++) {
            if (img.at<unsigned char>(j, i) > 0){
                for (int theta = 0; theta < 360; theta++){
                    int r = floor(i*cos(theta / RADIAN) + j*sin(theta / RADIAN));
                    if (r < 0)
                        continue;
                    p[theta][r]++;
                }
            }
        }
    }
    //获得最大值
    for (int theta = 0; theta < 360; theta++){
        for (int r = 0; r < diagonal; r++){
            int thetaLeft = max(0, theta - 1);
            int thetaRight = min(359, theta + 1);
            int rLeft = max(0, r - 1);
            int rRight = min(diagonal - 1, r + 1);
            int tmp = p[theta][r];
            if (tmp > threshold
                && tmp > p[thetaLeft][rLeft] && tmp > p[thetaLeft][r] && tmp > p[thetaLeft][rRight]
                && tmp > p[theta][rLeft] && tmp > p[theta][rRight]
                && tmp > p[thetaRight][rLeft] && tmp > p[thetaRight][r] && tmp > p[thetaRight][rRight]){
                struct line newline;
                newline.theta = theta;
                newline.r = r;
                lines.push_back(newline);
            }
        }
    }
    return lines;
}

void drawLines(Mat &img, const vector<struct line> &lines){
    for (int i = 0; i < lines.size(); i++){
        vector<Point> points;
        int theta = lines[i].theta;
        int r = lines[i].r;

        double ct = cos(theta / RADIAN);
        double st = sin(theta / RADIAN);

        //公式 r = x*ct + y*st
        //计算左边
        int y = int(r / st);
        if (y >= 0 && y < img.rows){
            Point p(0, y);
            points.push_back(p);
        }
        //计算右边
        y = int((r - ct*(img.cols - 1)) / st);
        if (y >= 0 && y < img.rows){
            Point p(img.cols - 1, y);
            points.push_back(p);
        }
        //计算上边
        int x = int(r / ct);
        if (x >= 0 && x < img.cols){
            Point p(x, 0);
            points.push_back(p);
        }
        //计算下边
        x = int((r - st*(img.rows - 1)) / ct);
        if (x >= 0 && x < img.cols){
            Point p(x, img.rows - 1);
            points.push_back(p);
        }
        //画线
        cv::line(img, points[0], points[1], Scalar(255, 0, 0), 5, CV_AA);
    }
}

方法二:

#include<cv.h>
#include<cxcore.h>
#include<highgui.h>

#include<cstdio>
#include<iostream>
using namespace std;

int main(){
    //声明IplImage指针
    IplImage* pFrame = NULL;
    IplImage* pCutFrame = NULL;
    IplImage* pCutFrImg = NULL;
    IplImage* pCutBkImg = NULL;
    //声明CvMat指针
    CvMat* pCutFrameMat = NULL;
    CvMat* pCutFrMat = NULL;
    CvMat* pCutBkMat = NULL;
    //声明CvCapture指针
    CvCapture* pCapture = NULL;
    //声明CvMemStorage和CvSeg指针
    CvMemStorage* storage = cvCreateMemStorage();
    CvSeq* lines = NULL;
    //当前帧数
    int nFrmNum = 0;
    //裁剪的天空高度
    int CutHeight = 250;
    //窗口命名
    cvNamedWindow("video", 1);
    //cvNamedWindow("background", 1);
    cvNamedWindow("foreground", 1);
    //调整窗口初始位置
    cvMoveWindow("video", 300, 30);
    cvMoveWindow("background", 100, 100);
    cvMoveWindow("foreground", 300, 370);
    //不能打开则退出
    if (!(pCapture = cvCaptureFromFile("lane.avi"))){
        fprintf(stderr, "Can not open video file\n");
        return -2;
    }
    //每次读取一桢的视频
    while (pFrame = cvQueryFrame(pCapture)){
        //设置ROI裁剪图像
        cvSetImageROI(pFrame, cvRect(0, CutHeight, pFrame->width, pFrame->height - CutHeight));
        nFrmNum++;
        //第一次要申请内存p
        if (nFrmNum == 1){
            pCutFrame = cvCreateImage(cvSize(pFrame->width, pFrame->height - CutHeight), pFrame->depth, pFrame->nChannels);
            cvCopy(pFrame, pCutFrame, 0);
            pCutBkImg = cvCreateImage(cvSize(pCutFrame->width, pCutFrame->height), IPL_DEPTH_8U, 1);
            pCutFrImg = cvCreateImage(cvSize(pCutFrame->width, pCutFrame->height), IPL_DEPTH_8U, 1);

            pCutBkMat = cvCreateMat(pCutFrame->height, pCutFrame->width, CV_32FC1);
            pCutFrMat = cvCreateMat(pCutFrame->height, pCutFrame->width, CV_32FC1);
            pCutFrameMat = cvCreateMat(pCutFrame->height, pCutFrame->width, CV_32FC1);
            //转化成单通道图像再处理
            cvCvtColor(pCutFrame, pCutBkImg, CV_BGR2GRAY);
            cvCvtColor(pCutFrame, pCutFrImg, CV_BGR2GRAY);
            //转换成矩阵
            cvConvert(pCutFrImg, pCutFrameMat);
            cvConvert(pCutFrImg, pCutFrMat);
            cvConvert(pCutFrImg, pCutBkMat);
        }
        else{
            //获得剪切图
            cvCopy(pFrame, pCutFrame, 0);
            //前景图转换为灰度图
            cvCvtColor(pCutFrame, pCutFrImg, CV_BGR2GRAY);
            cvConvert(pCutFrImg, pCutFrameMat);
            //高斯滤波先,以平滑图像
            cvSmooth(pCutFrameMat, pCutFrameMat, CV_GAUSSIAN, 3, 0, 0.0);
            //当前帧跟背景图相减
            cvAbsDiff(pCutFrameMat, pCutBkMat, pCutFrMat);
            //二值化前景图
            cvThreshold(pCutFrMat, pCutFrImg, 35, 255.0, CV_THRESH_BINARY);
            //进行形态学滤波,去掉噪音
            cvErode(pCutFrImg, pCutFrImg, 0, 1);
            cvDilate(pCutFrImg, pCutFrImg, 0, 1);
            //更新背景
            cvRunningAvg(pCutFrameMat, pCutBkMat, 0.003, 0);
            //pCutBkMat = cvCloneMat(pCutFrameMat);
            //将背景转化为图像格式,用以显示
            //cvConvert(pCutBkMat, pCutBkImg);
            cvCvtColor(pCutFrame, pCutBkImg, CV_BGR2GRAY);
            //canny变化
            cvCanny(pCutFrImg, pCutFrImg, 50, 100);
            #pragma region Hough检测
            lines = cvHoughLines2(pCutFrImg, storage, CV_HOUGH_PROBABILISTIC, 1, CV_PI / 180, 100, 30, 15);
            printf("Lines number: %d\n", lines->total);
            //画出直线
            for (int i = 0; i<lines->total; i++){
                CvPoint* line = (CvPoint* )cvGetSeqElem(lines, i);
                cvLine(pCutFrame, line[0], line[1], CV_RGB(255, 0, 0), 6, CV_AA);
            }
            #pragma endregion
            //显示图像
            cvShowImage("video", pCutFrame);
            cvShowImage("background", pCutBkImg);
            cvShowImage("foreground", pCutFrImg);
            //按键事件,空格暂停,其他跳出循环
            int temp = cvWaitKey(2);
            if (temp == 32){
                while (cvWaitKey() == -1);
            }
            else if (temp >= 0){
                break;
            }
        }
        //恢复ROI区域(多余可去掉)
        cvResetImageROI(pFrame);
    }
    //销毁窗口
    cvDestroyWindow("video");
    cvDestroyWindow("background");
    cvDestroyWindow("foreground");
    //释放图像和矩阵
    cvReleaseImage(&pCutFrImg);
    cvReleaseImage(&pCutBkImg);
    cvReleaseImage(&pCutFrame);
    cvReleaseMat(&pCutFrameMat);
    cvReleaseMat(&pCutFrMat);
    cvReleaseMat(&pCutBkMat);
    cvReleaseCapture(&pCapture);

    return 0;
}
---------------------  
作者:chongshangyunxiao321   原文:https://blog.csdn.net/chongshangyunxiao321/article/details/50999212

 

 

  • 6
    点赞
  • 88
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值