Udacity是硅谷的一个在线教育网站,主要以介绍AI技术为主,除了基本的课程录像还会有专业老师进行代码review,通过课程会颁发nano degree。其中最为火爆的一门课程就是自动驾驶课程CarNd,这门课程由简入繁分了三个term,第一个term主要是以机器视觉识别道路、交通标志为主,需要掌握基本的深度学习和机器视觉知识;第二个term是介绍传感器以及使用卡尔曼滤波进行定位和速度控制;第三个term主要是路径规划以及系统集成,据说最后会把学员的代码放入真车进行实际测试。Ucacity的课程质量很高,但是有些小贵,出于无奈只好从网上搜集资料,开源的资料包含了前两个term,学完可以对无人驾驶有基本的了解,感兴趣的同学可以试试。
无人驾驶车辆的基本功能之一就是检测车道,之前参加过飞思卡尔智能车竞赛的同学对这个项目应该并不陌生,与摄像头组识别赛道的原理类似,可惜当时我在红外组,没有深入了解相关原理。
先简单说下利用图像处理技术识别车道的基本流程
p1相对来说比较简单,使用opencv的一些操作函数即可实现检测功能。下面详细介绍每个步骤的实现
首先读入图像
//导入opencv
import cv2
//读取图像
image = cv2.imread("./test_images/solidYellowCurve.jpg")
//展示图像
cv2.imshow('lane',image)
//等待键盘按键
cv2.waitKey()
//销毁图片展示窗口
cv2.destroyAllWindows()
效果如下
opencv对于彩色图像的存储使用GBR格式,与一般RGB格式顺序不太一样,据说是为了做硬件兼容。二值化处理是把读入的彩色图像转成灰度图,方便后续计算。
//彩色图像转化为灰度图
gray_image = cv2.cvtColor(image,cv2.COLOR_GBR2GRAY)
效果如下
高斯模糊是一种图像平滑技术,基本原理是在以中心像素为原点取一定半径内像素点值求加权平均值,权重根据高斯函数求得,结果就是高斯模糊处理后的图片。从数值上看,整体上更加平缓,从图片效果上看变得模糊了。
blured_image = cv2.GaussianBlur(gray_image,(5,5),0) //第二个参数表示模糊半径,第三个参数代表高斯函数标准差,半径越大标准差越大,图片越模糊
由于车道具有明显的边缘,所以使用边缘检测可以提取车道信息,opencv提供了canny边缘检测函数可以实现该功能。
canny = cv2.cv2.Canny(blur,250,300)
效果如下
在理想情况下(直路且行驶平稳),车道的位置在拍摄的图片中的相对位置不变,我们并不关心除车道以外的其他位置,所以可以通过ROI区域提取获取车道所在区域。针对p1所用到的图片,其ROI区域顶点坐标分别为(0,540),(465,320),(475,320),(960,320),其所形成的不规则区域如下所示
与边缘检测结果进行叠加
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区域内的车道信息以及其他干扰信息,这两种信息的区别在于车道由多条直线构成,而干扰信息则不具备这样明显的条件,所以直线检测可以提取ROI区域内的车道。
opencv提供了霍夫变换函数HoughLines和HoughLinesP用于直线检测,目前先不去深究其原理,后续我再补充。其函数定义如下:
HoughLinesP(image, rho, theta, threshold, minLineLength=None, maxLineGap=None)
maxLineGap:同一方向上两条线段判定为一条线段的最大允许间隔(断裂),超过了设定值,则把两条线段当成一条线段,值越大,允许线段上的断裂越大,越有可能检出潜在的直线段
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默认原点位于图像左上角顶点,所以左边线斜率为负,右边线斜率为正,并计算每段线段的斜率和截距。
“`
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])
“`
上一步完成了斜率和截距的计算,还要对其进行筛选,把误差较大的线段移除
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,从而求得每条边线的两端顶点。
xmin = int((ymin - intercept)/slop)
xmax = int((ymax-intercept)/slop)
把标记过车道的图片与原始图片叠加,实现车道的标记。图像叠加通过opencv的addWeight函数实现,该函数定义如下:
cv2.addWeighted(src1, alpha, src2, beta, gamma[, dst[, dtype]])
//imgae为原始图像,line_image为车道标记图像
res_image = cv2.addWeighted(image,0.8,line_image,1,0)
最终结果如下
处理视频需要使用moviepy的VideoFileClip函数
video = VideoFileClip(path) //待处理图像路径
video_after_process = video.fl_image(process_picture) //处理每张图片的函数作为参数
video_after_process.write_videofile("./line_detection.mp4",audio=False) //生成新视频
效果参见
https://yajian.github.io/Term-1-p1-lane-detection/
详见Term-1_lane_detection_demo