Term-1 p1 lane-detection

简介

  Udacity是硅谷的一个在线教育网站,主要以介绍AI技术为主,除了基本的课程录像还会有专业老师进行代码review,通过课程会颁发nano degree。其中最为火爆的一门课程就是自动驾驶课程CarNd,这门课程由简入繁分了三个term,第一个term主要是以机器视觉识别道路、交通标志为主,需要掌握基本的深度学习和机器视觉知识;第二个term是介绍传感器以及使用卡尔曼滤波进行定位和速度控制;第三个term主要是路径规划以及系统集成,据说最后会把学员的代码放入真车进行实际测试。Ucacity的课程质量很高,但是有些小贵,出于无奈只好从网上搜集资料,开源的资料包含了前两个term,学完可以对无人驾驶有基本的了解,感兴趣的同学可以试试。

处理流程

  无人驾驶车辆的基本功能之一就是检测车道,之前参加过飞思卡尔智能车竞赛的同学对这个项目应该并不陌生,与摄像头组识别赛道的原理类似,可惜当时我在红外组,没有深入了解相关原理。

  先简单说下利用图像处理技术识别车道的基本流程


车道检测流程

  p1相对来说比较简单,使用opencv的一些操作函数即可实现检测功能。下面详细介绍每个步骤的实现

读入图像

  首先读入图像

1
2
3
4
5
6
7
8
9
10
11
//导入opencv
import cv2

//读取图像
image = cv2.imread("./test_images/solidYellowCurve.jpg")
//展示图像
cv2.imshow('lane',image)
//等待键盘按键
cv2.waitKey()
//销毁图片展示窗口
cv2.destroyAllWindows()

效果如下


车道原图

二值化处理

  opencv对于彩色图像的存储使用GBR格式,与一般RGB格式顺序不太一样,据说是为了做硬件兼容。二值化处理是把读入的彩色图像转成灰度图,方便后续计算。

1
2
//彩色图像转化为灰度图
gray_image = cv2.cvtColor(image,cv2.COLOR_GBR2GRAY)

效果如下


二值化图像

高斯模糊

  高斯模糊是一种图像平滑技术,基本原理是在以中心像素为原点取一定半径内像素点值求加权平均值,权重根据高斯函数求得,结果就是高斯模糊处理后的图片。从数值上看,整体上更加平缓,从图片效果上看变得模糊了。

1
blured_image = cv2.GaussianBlur(gray_image,(5,5),0) //第二个参数表示模糊半径,第三个参数代表高斯函数标准差,半径越大标准差越大,图片越模糊

高斯模糊图像

Canny边缘检测

  由于车道具有明显的边缘,所以使用边缘检测可以提取车道信息,opencv提供了canny边缘检测函数可以实现该功能。

1
canny = cv2.cv2.Canny(blur,250,300)

效果如下


canny边缘检测

ROI区域提取

  在理想情况下(直路且行驶平稳),车道的位置在拍摄的图片中的相对位置不变,我们并不关心除车道以外的其他位置,所以可以通过ROI区域提取获取车道所在区域。针对p1所用到的图片,其ROI区域顶点坐标分别为(0,540),(465,320),(475,320),(960,320),其所形成的不规则区域如下所示


ROI区域

与边缘检测结果进行叠加

1
2
3
4
roi_range = np.array([[(0,540),(465,320),(475,320),(960,320)]],dtype = np.int32)
mask = np.zeros_like(canny) //复制一个和canny图像大小一样的叠加矩阵
cv2.fillPoly(mask,roi_range,255) //设置roi区域的像素值为255,其他区域为0
img_masked = cv2.bitwise_and(canny,mask) //将canny图像和叠加图像求并,ROI区域外的部分都变为0

效果如下


ROI区域提取

Hough直线检测

  现在经过处理后的图像基本只包含ROI区域内的车道信息以及其他干扰信息,这两种信息的区别在于车道由多条直线构成,而干扰信息则不具备这样明显的条件,所以直线检测可以提取ROI区域内的车道。
  opencv提供了霍夫变换函数HoughLines和HoughLinesP用于直线检测,目前先不去深究其原理,后续我再补充。其函数定义如下:

HoughLinesP(image, rho, theta, threshold, minLineLength=None, maxLineGap=None)

  • image:必须是二值图像,推荐使用canny边缘检测的结果图像;
  • rho: 线段以像素为单位的距离精度,double类型的,推荐用1.0
  • theta: 线段以弧度为单位的角度精度,推荐用numpy.pi/180
  • threshod: 累加平面的阈值参数,int类型,超过设定阈值才被检测出线段,值越大,基本上意味着检出的线段越长,检出的线段个数越少。根据情况推荐先用100试试
  • minLineLength:线段以像素为单位的最小长度,根据应用场景设置
  • maxLineGap:同一方向上两条线段判定为一条线段的最大允许间隔(断裂),超过了设定值,则把两条线段当成一条线段,值越大,允许线段上的断裂越大,越有可能检出潜在的直线段

    1
    2
    3
    4
    5
    6
    7
    lines = cv2.HoughLinesP(img_mashed,1,np.pi/180,15,25,20)
    for line in lines:
    for x1,y1,x2,y2 in line:
    cv2.line(img_masked,(x1,y1),(x2,y2),255,12)
    cv2.imshow('img_masked',img_masked)
    cv2.waitKey()
    cv2.destroyAllWindows()

效果如下


直线检测

车道标记

  直线检测完成后,需要对检测后的直线进行过滤,把误差较大的点移除,并且还要将这些点连成直线,进行车道标记。

车道左右边线检测

  首先需要区分车道左边线和右边线,我们已经拿到了直线检测后的直线端点,可以利用斜率进行区分。由于opencv默认原点位于图像左上角顶点,所以左边线斜率为负,右边线斜率为正,并计算每段线段的斜率和截距。

1
2
3
4
5
6
7
8
9
positive_slop_intercept = []    //左边线点构成直线的斜率和截距
negative_slop_intercept = [] //右边线点构成直线的斜率和截距
for line in lines:
for x1,y1,x2,y2 in line:
slop = np.float((y2-y1))/np.float((x2-x1)) //计算斜率
if slop > 0:
positive_slop_intercept.append([slop,y1-slop*x1]) //根据点的坐标和斜率计算截距
elif slop < 0 :
negative_slop_intercept.append([slop,y1-slop*x1])

计算左右边线斜率和截距

  上一步完成了斜率和截距的计算,还要对其进行筛选,把误差较大的线段移除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
legal_slop=[]
legal_intercept=[]
slopes=[pair[0] for pair in slop_intercept]
slop_mean = np.mean(slopes) //斜率的均值
slop_std = np.std(slopes) //斜率的标准差
for pair in slop_intercept:
if pair[0] - slop_mean < 3 * slop_std: //挑选出平均值3个标准差误差范围内的斜率和截距
legal_slop.append(pair[0])
legal_intercept.append(pair[1])
if not legal_slop: //如果没有合理范围内的斜率,则使用原始斜率,最终的斜率就是均值
legal_slop = slopes
legal_intercept = [pair[1] for pair in slop_intercept]
slop = np.mean(legal_slop)
intercept = np.mean(legal_intercept)

计算车道两端端点

  计算除了车道的斜率和截距,还需要两个端点才能确定车道的范围。由于我们只关注ROI区域范围内的车道,所以车道左右边线的上顶点的值为ymin=320、下顶点的值为ymax=540,从而求得每条边线的两端顶点。

1
2
xmin = int((ymin - intercept)/slop)
xmax = int((ymax-intercept)/slop)

标记车道

  把标记过车道的图片与原始图片叠加,实现车道的标记。图像叠加通过opencv的addWeight函数实现,该函数定义如下:
cv2.addWeighted(src1, alpha, src2, beta, gamma[, dst[, dtype]])

  • alpha : 第一幅图片中元素的权重
  • beta : 是第二个的权重
  • gamma : 加到最后结果上的值
1
2
//imgae为原始图像,line_image为车道标记图像
res_image = cv2.addWeighted(image,0.8,line_image,1,0)

最终结果如下


车道标记

处理视频

  处理视频需要使用moviepy的VideoFileClip函数

1
2
3
video = VideoFileClip(path) //待处理图像路径
video_after_process = video.fl_image(process_picture) //处理每张图片的函数作为参数
video_after_process.write_videofile("./line_detection.mp4",audio=False) //生成新视频

效果如下

  • 原始视频


  • 处理后视频


代码参考

详见Term-1_lane_detection_demo

-------------本文结束感谢您的阅读-------------

本文标题:Term-1 p1 lane-detection

文章作者:小建儿

发布时间:2018年03月19日 - 10:03

最后更新:2018年11月17日 - 18:11

原始链接:http://yajian.github.io/Term-1-p1-lane-detection/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。