使用OpenCV完成车道线检测功能

时间:2022-02-06 15:17:10

实验名称:车道线检测

1.实验摘要

使用OpenCV完成车道线检测功能

使用OpenCV完成车道线检测功能









2.实验介绍

本项目是基于反透视变换和Hough直线检测完成的。要理解反透视变换首先要理解摄像机坐标系,成像坐标系和图像坐标系的关系。

1)图像坐标系(Pixel coordinate system)
摄像机采集的数字图像在计算机内可以存储为数组,数组中的每一个元素(象素,pixel)的值即是图像点的亮度(灰度),在图像上定义直角坐标系u-v,每一象素的坐标(u,v)分别是该象素在数组中的列数和行数。故(u,v)是以象素为单位的图像坐标系坐标。

使用OpenCV完成车道线检测功能

2)成像平面坐标系(Retinal coordinate system)

由于图像坐标系只表示象素位于数字图像的列数和行数,并没有用物理单位表示出该象素在图像中的物理位置,因而需要再建立以物理单位(例如:厘米)表示的成像平面坐标系x-y,如图4.1所示。我们用(x,y)表示以物理单位度量的成像平面坐标系的坐标。在x-y坐标系中,原点定义在摄像机光轴和图像平面的交点处,称为图像的主点(principalpoint),该点一般位于图像中心处,但由于摄像机制作的原因,可能会有些偏离,在坐标系下的坐标为(u0,v0),每个象素在x轴和y轴方向上的物理尺寸为dx、dy,两个坐标系的关系如下:

使用OpenCV完成车道线检测功能

使用OpenCV完成车道线检测功能

其中s'表示因摄像机成像平面坐标轴相互不正交引出的倾斜因子

3)摄像机坐标系(Cameracoordinate system)

摄像机成像几何关系可由图4.2表示,其中O点称为摄像机光心,Xc轴和Yc轴分别与成像平面坐标系的x轴和y轴平行,Zc轴为摄像机的光轴,和图像平面垂直。光轴与图像平面的交点为图像主点O',由点O与轴组成的直角坐标系称为摄像机坐标系。OO'为摄像机焦距。

使用OpenCV完成车道线检测功能

在获取视频中某一帧的画面时,获得的是一张二维的平面图,因为在视频中丢失了摄像机坐标系中Zc方向的数据,所以得到的是一张没有深度的图。要进行反透视变换就是要补充深度数据,即对二维图像进行Zc轴方向的拉伸。

完成二维到三维(实际上仍然是二维图像)的转变需要有三个不同的角变量对应三条轴线的透视变化,三个变量需要三个点来确定值。解出之后即可确定变换矩阵T,如果需要进行反过程只需对该矩阵求逆即可得到反过程的变换矩阵。

计算方法参考:http://www.docin.com/p-725340375.html


3.实验构思

1st. 对原视频进行反透视变换,将视角转变为鸟瞰图。

2nd.对视频进行一些预处理,如腐蚀膨胀,平滑处理等,设置ROI,减少画面中车道线以外的干扰物的影响。

3rd. 进行Canny变换,检测出画面的边缘并且对图像自动进行二值化处理。

4th.所得图像进行Hough变换,通过阀值,线段最短长度,连接为线段的最长间隔的设定来检测出画面中存在的直线。

5th.直线筛选,计算第四步所得线段的斜率,从中挑选符合要求的线段,并用cvLine函数标示到画面中。

6th. 将所得画面再次进行透视变换,变回原视角。


4.源码及注解

<pre name="code" class="cpp">/*
* Created on: 2015-1-24
* Author: Lincoln, Deng
* 仅供参考,请勿抄袭
*/


/*********************************使用方法**********************************/
//1.将视频1.avi和矩阵1.xml放入project文件夹,编译执行。
//2.播放视频时按下空格键可以暂停所有视频查看细节,再次按下空格键视频继续播放。
//3.想要查询特定某一帧的画面可以通过result窗口的滑动条滑动到指定的帧数。
//4.想要中断播放退出浏览按下ESC键即可。
/***************************************************************************/


#include<iostream>
#include<opencv\cv.h>
#include <cxcore.h>
#include<opencv\highgui.h>
#include <opencv2/imgproc/imgproc.hpp>
#include <stdio.h>
//#include "stdafx.h"
using namespace std;
using namespace cv;


#define INF 99999999 //用于直线斜率的初始化,代表无穷大


//使用播放控制条需要的全局变量
int g_slider_position = 0;
CvCapture* g_capture = NULL;
int cur_frame = 0; //用于指示g_capture的当前帧


void onTrackbarSlide(int pos); //回调函数


int main(){
g_capture = cvCreateFileCapture("1.avi");//使用全局变量抓取视频画面
//CvCapture *capture = cvCreateFileCapture("1.avi");//注意读取不在工程目录下文件时的路径要加双斜杠
/*cvCreateFileCapture()通过参数设置确定要读入的avi文件,返回一个指向CvCapture结构的指针。
这个结构包括了所有关于要读入avi文件的信息,其中包含状态信息。调用这个函数之后,返回指针
所指向的CvCapture结构被初始化到对应的avi文件的开头。*/


IplImage *img = cvQueryFrame(g_capture);
//cvQueryFrame的参数为CvCapture结构的指针。用来将下一帧视频文件载入内存,返回一个对应当前帧的指针。


/************************************* 预处理 ***************************************/
//读取矩阵
CvMemStorage *memstorageTest=cvCreateMemStorage(0);
/*用来创建一个内存存储器,来统一管理各种动态对象的内存。函数返回一个新创建的内存存储器指针。
参数对应内存器中每个内存块的大小,为0时内存块默认大小为64k。*/


CvFileStorage *warp_read=cvOpenFileStorage("1.xml",memstorageTest,CV_STORAGE_READ);//矩阵所在的xml文件名
/*cvOpenFileStorage打开存在或创建新的文件,第一个参数为矩阵,第二个为存储器,第三
个为flag,有CV_STORAGE_READ(打开文件读数据)和CV_STORAGE_WRITE(打开文件写数据)*/

CvMat *map_matrix = cvCreateMat(3,3,CV_32FC1);
CvMat *inverse = cvCreateMat(3,3,CV_32FC1);
/*函数 cvCreateMat 为新的矩阵分配头和下面的数据,并且返回一个指向新创建的矩阵的指针。
参数分别为矩阵行数,列数,矩阵类型。CV_32FC1表示32位浮点型单通道矩阵*/

map_matrix = (CvMat*)cvReadByName(warp_read,NULL,"WarpMatrix",NULL);//读取矩阵,注意双括号里面是矩阵的名字
cvInvert(map_matrix, inverse, CV_SVD);//求出逆矩阵用以重新投射会原视角

//设置原图像的ROI范围
int x = 0,y = 157;//这个y值就是要修改的,尽量裁掉天空,不然会给处理带来困难
int width = img->width , height = 256;
//视频左上角坐标为(0,0)



//创建显示窗口
cvNamedWindow("OriginalView",CV_WINDOW_AUTOSIZE);//原视频
cvNamedWindow("IPMview",CV_WINDOW_AUTOSIZE);//反透视变换后效果
cvNamedWindow("AfterCanny",CV_WINDOW_AUTOSIZE);//canny边缘检测后效果
cvNamedWindow("Erode&Dilate",CV_WINDOW_AUTOSIZE);//腐蚀膨胀后效果
//cvNamedWindow("AfterSmooth",CV_WINDOW_AUTOSIZE);//高斯模糊后效果
cvNamedWindow("Hough",CV_WINDOW_AUTOSIZE);//Hough直线变换后效果
cvNamedWindow("Result",CV_WINDOW_AUTOSIZE);//最终结果

CvMemStorage *storage = cvCreateMemStorage();//内存块,存储中间变量
CvSeq *lines = 0;//存储Hough变换所得结果

//初始化用于视频播放控制的滑动条
int frames = (int)cvGetCaptureProperty(
g_capture,
CV_CAP_PROP_FRAME_COUNT//以帧数来设置读入位置
);


if(frames != 0){
cvCreateTrackbar(
"Frames", //进度条名称
"Result", //让进度条显示在最终结果的窗口
&g_slider_position,
frames,
onTrackbarSlide//调用一次onTrackbarSlide
);
}
/************************************* 预处理结束 ***************************************/


while(1)
{
if(!img) break;//视频为空则退出
//当拉动进度条时,所有窗口的视频都会同步刷新到指定的帧数播放


cvSetImageROI(img,cvRect(x,y,width,height));//设置ROI
//如果ROI为NULL并且参数rect的值不等于整个图像,则ROI被分配。
//cvRect参数分别为矩形左上角x,y坐标,矩形宽,高。
IplImage *ImageCut = cvCreateImage(cvGetSize(img),8,3);
cvCopy(img,ImageCut);//将原图像的ROI赋给新图像ImageCut
/*ROI(region of interest),感兴趣区域。机器视觉、图像处理中,
从被处理的图像以方框、圆、椭圆、不规则多边形等方式勾勒出需要处
理的区域,称为感兴趣区域,ROI。在Halcon、OpenCV、Matlab等机器
视觉软件上常用到各种算子(Operator)和函数来求得感兴趣区域ROI,
并进行图像的下一步处理。在图像处理领域,感兴趣区域(ROI) 是从图
像中选择的一个图像区域,这个区域是你的图像分析所关注的重点。圈
定该区域以便进行进一步处理。使用ROI圈定你想读的目标,可以减少
处理时间,增加精度。完成后可以释放ROI回到原来的视频尺寸。*/


//创建用于反透视变换的图像
IplImage *ImageIPM = cvCreateImage(cvGetSize(ImageCut),8,3);

cvShowImage("OriginalView",ImageCut);
cvWarpPerspective(ImageCut,ImageIPM,map_matrix);
//对图像做反透视变换,第一个参数为原图,第二个为目标图,第三个为变换矩阵
cvShowImage("IPMview",ImageIPM);



//为了进行更精确的直线检测需要去除道路两旁的障碍,因此再次缩小ROI
cvSetImageROI(ImageIPM,cvRect(330,0,200,256));//设置新的ROI

//创建一个灰度图像
IplImage* ImageIPM2 = cvCreateImage(cvGetSize(ImageIPM), 8, 1);
cvCvtColor(ImageIPM,ImageIPM2,CV_BGR2GRAY);
cvErode( ImageIPM2,ImageIPM2, NULL,2); //腐蚀
cvDilate( ImageIPM2,ImageIPM2, NULL,6); //膨胀
cvShowImage("Erode&Dilate",ImageIPM2);


IplImage *ImageCut2 = cvCreateImage(cvGetSize(ImageIPM2),8,1);
cvCopy(ImageIPM2,ImageCut2);//将透视变换后图像的ROI赋给新图像ImageCut2

//创建用于Canny变换的图像
IplImage *img_thres = cvCreateImage(cvGetSize(ImageCut2),8,1);
cvCanny(ImageCut2,img_thres,50,100);
cvShowImage("AfterCanny",img_thres);

cvSmooth(img_thres,img_thres,CV_GAUSSIAN,3,1,0);//高斯模糊平滑处理
//cvShowImage("AfterSmooth",img_thres);//有的视频使用模糊处理后对直线检测更好


/************************************* Hough ***************************************/
/*函数说明:CvSeq* cvHoughLines2(CvArr* image,void* line_storage,int mehtod,
double rho,double theta,int threshold,double param1 =0,double param2 =0);
image为要做hough变换的图像,line_storage为检测到的线段存储仓, 可以是内存存储仓
(此时,一个线段序列在存储仓中被创建,并且由函数返回),然后是hough变换的类型method
可以是CV_HOUGH_STANDARD(标准变换),CV_HOUGH_PROBABILISTIC(概率 Hough 变换)以及
CV_HOUGH_MULTI_SCALE(多尺度霍夫变换)。rho 与像素相关单位的距离精度 theta 弧度测量
的角度精度 threshold 阈值参数。如果相应的累计值大于 threshold, 则函数返回这条线段.
param1,2对标准变换无用,设为0。 1对概率 Hough 变换是最小线段长度.2对概率 Hough 变换,
表示在同一条直线上进行碎线段连接的最大间隔值(gap), 即当同一条直线上的两条碎线段之间的
间隔小于param2时,将其合二为一。*/
lines = cvHoughLines2(img_thres,storage,CV_HOUGH_PROBABILISTIC,1,CV_PI/180,50,90,50);
printf("Lines number: %d\n",lines->total);

//根据Hough变换后所得线段的斜率筛选出条件合适的
for (int i=0;i<lines->total;i++)
{
double k = INF;//初始化斜率为无限大
CvPoint *line = (CvPoint *)cvGetSeqElem(lines,i);//line包含两个点line[0]和line[1]
if(line[0].x - line[1].x != 0) k = (double)(line[0].y - line[1].y)/(double)(line[0].x - line[1].x);


//printf("x1: %d, y1: %d, x2: %d, y2: %d\n",line[0].x, line[0].y, line[1].x, line[1].y);
//printf("k: %lf\n\n",k);


if(k<-4.5 || k>4.5) cvLine(ImageIPM,line[0],line[1],CV_RGB(0,255,0),2,CV_AA);
//else if(k>-1 && k<1 && lines->total>25) cvLine(ImageIPM,line[0],line[1],CV_RGB(0,255,0),2,CV_AA);
//因为cvLine绘图只有图是3通道图时才能显示线的颜色,所以用ImageIPM作为绘线的地图
//第二三个参数为线的起点终点,第四个为四射,第五个为线的粗细
}
cvShowImage("Hough",ImageIPM);
/**********************************************************************************/


cvResetImageROI(ImageIPM);//释放ROI
cvWarpPerspective(ImageIPM,ImageIPM,inverse);//对效果图进行透视变换回到原视角


//调节系数放大图像方便更清晰地浏览细节
double fScale = 1.1; //可调节的放大倍数,注:若fScale<0则缩小画面
CvSize czSize; //目标图像尺寸
IplImage *result = NULL;

//计算目标图像大小
czSize.width = ImageIPM->width * fScale;
czSize.height = ImageIPM->height * fScale;

//创建图像并放大
result = cvCreateImage(czSize, ImageIPM->depth, ImageIPM->nChannels);
cvResize(ImageIPM, result, CV_INTER_AREA);


cvShowImage("Result", result);




char c = cvWaitKey(33); //每隔33ms播放下一帧
if(c == 27) break; //按下ESC时可退出播放
if(c == 32){ //按下空格键可暂停视频播放
while(1){
char c = cvWaitKey(0);
if(c == 32) break;//再次按下则继续播放
}
}


//释放使用过的图像内存
cvReleaseImage(&ImageCut);
cvReleaseImage(&ImageCut2);
cvReleaseImage(&ImageIPM);
cvReleaseImage(&img_thres);
cvReleaseImage(&result);


//让进度条随着视频播放滚动
cur_frame = (int)cvGetCaptureProperty(g_capture,CV_CAP_PROP_POS_FRAMES);//提取当前帧
cvSetTrackbarPos("Frames","Result",cur_frame);//设置进度条位置

img = cvQueryFrame(g_capture);//抓取下一帧的画面
}


cvReleaseCapture(&g_capture);//释放capture,同时也会释放img
return 0;
}


void onTrackbarSlide(int pos){//回调函数
if (pos!=cur_frame){
//如果回调函数onTrackbarSlide(int pos)中当前的函数参数pos与全局变量相等,
//说明是滚动条自动移动造成的调用,不必重新设置g_capture的当前帧
cvSetCaptureProperty(
g_capture,
CV_CAP_PROP_POS_FRAMES,
pos
);
}
}


 


 


5.实验结果

使用OpenCV完成车道线检测功能使用OpenCV完成车道线检测功能

最终实现图

如图,左上为原视角,右上为俯视图。左下三个窗口分别为Canny边缘检测结果,腐蚀膨胀结果,Hough变换结果。右下为最终结果。

该project是基于反透视变换实现的(其他实现方法包括基于成像模型的做法等),通过反透视变换为鸟瞰图,可以更好地检测直线。在检测前先通过预处理,设置更小的ROI,使检测区域集中于车(摄像头)正前方略多于一个车道宽度的位置(可以根据摄像头安放位置设定,如安排在车中线左侧,则ROI要偏右,使车子中心线所对与ROI中心相吻合)。此后再对灰度图像进行腐蚀与膨胀,去除掉一些影响检测结果的杂质元素。然后进行Canny边缘检测,去除杂质后,检测基本上能够只检出车道线的矩形。然后进行概率Hough变换,借助参数设置可以将车道线的短线段拼合为长线段。最后对所得线段进行筛选,只留下符合斜率要求的线段,并把所得筛选结果映回到视频中。


6.存在问题

该project实现了检测车道线的功能,对于杂质元素有较为良好的排除作用。在直线路段能很好地实现智能车的自动行驶。

使用OpenCV完成车道线检测功能

使用OpenCV完成车道线检测功能

直线路段有良好的检测效果

但由于使用了反透视变换的关系,若使用变换矩阵的逆矩阵重新投影回原视频,视频边缘会呈锯齿状并出现逸出的状况。

使用OpenCV完成车道线检测功能使用OpenCV完成车道线检测功能

能达到无视标识的效果(针对路段上时会出现标示,文字等妨碍检测的杂质的情况)

 

存在问题:

使用OpenCV完成车道线检测功能

使用OpenCV完成车道线检测功能使用OpenCV完成车道线检测功能使用OpenCV完成车道线检测功能

出现杂线

由于使用的Hough办法为概率霍夫变换,因此在变道时,若车道线偏斜较多,在线段拼合时会出现这种线段交杂,连接错误的情况

使用OpenCV完成车道线检测功能使用OpenCV完成车道线检测功能

转弯缺陷

由于要通过斜率来筛选车道线,因此在转弯时会出现直线斜率过大的情况,此时直线无法通过直线筛选,会出现一段时间画面没有指示线的情况。 是否存在一种方案可以使弯道时同样可以做出标识?但注意到转弯时车不在依照车道线方向行驶,因此认为此时不检测出车道线是正确的。

使用OpenCV完成车道线检测功能使用OpenCV完成车道线检测功能

人行道障碍

还有一个存在的问题是人行道障碍。人行道是线段密集区域,由于进行了膨胀腐蚀算法,所以密集线的边缘会合并而变成块状。此时不会影响直线的检测。但是若边缘存在一些瑕疵,菱角,就会很容易像图中这样与画面中其他因素一起连接成线。这是不希望得到的结果,但这样产生的线很难通过斜率筛选来去除掉,由于太过紧密同样很难通过Hough变换参数的设置去除。


7.实验感想

opencv是非常强大的工具。这是我第一次接触到通过C语言来做实际应用的项目,之前做智能小车时,由于函数封装的原因,很多资料都难以在网上查找得到。但是opencv提供了一个相当广阔的平台。通过教材上的内容和网上的资料学会了很多opencv的知识。我暂且分为以下几点。

-1.对于一个视频,世界坐标系和图像坐标的不同,怎样从空间结构变为平面结构。用矩阵和图像结构存储的不同,颜色信息如何保存(double型据),彩色图和二值图如何转换(opencv中一些函数是基于二值图完成的,如果要保留颜色信息,可以通过分通道操作来完成,如RGB图分解为三个单通道图来分别操作),摄像机视角和鸟瞰视角如何切换(反透视变换矩阵)。怎样建立模型存储图像和视频中的信息(对不同坐标系的理解和转换方法)。

-2.如何对视频进行预处理(模糊,腐蚀,膨胀等等),每种操作opencv都提高一个完善的函数来实现,可以通过调节函数参数来得到自己想要的效果。减少画面中的障碍和杂质,使得画面更利于接下来检测的工作(无论是车道线检测还是行人、车辆检测,进行恰当的预处理会大大有利于之后的特征提取算法的运用)。

-3.ROI的概念,当视频画面中我们只需要对某一区域进行扫描和判断时可以对它设置恰当的ROI,当我们需要回到完整画面处理时可以释放ROI。同时注意到如果在设置ROI前进行了矩阵变换,则进行逆变换时必须回到进行矩阵变换当时的那个ROI区域。否则画面的比例是不一致,做不出理想的效果的。

-4.同时,opencv还可以完成丰富的UI设计,包括滑动条,标题,颜色等都是可以自定义的,通过调节这些可以提高视觉效果,尽管这些并非是算法范围内的事情,但是一个好的界面和布局,能为程序员编程和debug带来更多乐趣,也能为完成项目后,使用作品的用户带来更友好的用户体验。用opencv来完成一个小型的Photoshop软件也是可能的(可以调整图像亮度,对比度,剪裁,模糊图像,腐蚀膨胀等效果可以改造为滤镜效果),这是非常棒的事情,因为我个人对图像处理比较热爱,所以对这方面也相当感兴趣,希望接下来能继续完善和做出一些更有趣的东西。


8.参考文献

-1.A Flexible New Technique -Zheng you Zhang,IEEE

-2.基于反透视变换和Hough变换的车道线检测 - 张云港

-3.3D坐标系变换 –ryfdizuo,CSDN论坛

引用网址:http://blog.csdn.net/ryfdizuo/article/details/6287637#t1

-4.OpenCv霍夫变换:霍夫线变换,霍夫圆变换合辑 - 枫落★流年

引用网址:http://www.tuicool.com/articles/Mn2EBn

-5.OpenCv学习笔记-滚动条随着视频播放而滚动- xiongyan10

引用网址:http://blog.sina.com.cn/s/blog_4b826e5b01015xoj.html

-6.世界坐标系

引用网址:http://baike.baidu.com/view/829500.htm