车道线检测——直线
参考文章:(https://mp.weixin.qq.com/s/BpCF8n4wNaNZE-UF_SONzg) 。 笔者在实现过程中根据自己理解做了部分改动。
本文主要讲述智能驾驶领域的应用之一——使用传统机器学习方法检测(直线)车道线,编程语言是:python。
智能驾驶汽车的车载摄像头相对于水平路面是固定的,所以可以较容易找到感兴趣区域(Region of Interest)。
处理步骤:
一、载入图像,灰度处理,并用canny算子提取边缘:
1、我们使用opencv库读入图像,此时图像即转化为一二维数组,数值为0-255,每个数组元素包含三个通道,分别对应B、G、R,即blue、green、red三原色。
2、为便于计算,我们根据实际工程需求,将载入的彩色图片进行灰度处理。坐标为(x,y)的像素点进行灰度化操作的具体计算公式如下:
3、我们使用canny算子,对灰度图进行边缘提取,canny算子源程序:(https://blog.csdn.net/weixin_38553390/article/details/103810626)。
canny算子实现基本需要四个步骤:
(1)高斯滤波对图像进行平滑处理;
(2)sobel算子计算图像的梯度幅值;
(3)对图像的梯度幅值进行非极大值抑制;
(4)滞后阈值处理进行边缘链接;
4、载入图片如下图所示
处理后的效果如下图所示
5、代码块如下
import cv2
import numpy as np
img=cv2.imread('D:\pycharm\exercise\spring festival holiday\lane_line.jpg')
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
low_threshold=40
high_threshold=150
canny_image=cv2.Canny(gray,low_threshold,high_threshold)
cv2.imshow('canny_image',canny_image)
cv2.imwrite('D:\pycharm\exercise\spring festival holiday\lane_line2.jpg',canny_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
二、截取感兴趣区域
为了降低环境因素对结果的干扰,我们需要截取出感兴趣的区域(Region of Interest,简称ROI)。
截取ROI的方法,参考链接:(https://www.cnblogs.com/skyfsm/p/6894685.html),详细方法如下:
(1)建立与原图一样大小的mask图像,并将所有像素初始化为0;
(2)将mask图中的ROI区域的所有像素值设置为255;
(3)将该mask图像与经过canny算子处理过的图像进行与运算,得到的结果图只留下原始图感兴趣区域的图像了。也正如下图所示。
源程序参考:https://github.com/udacity/CarND-LaneLines-P1/blob/master/P1.ipynb
为了实现截取功能,可以将这部分程序封装一下OpenCV函数,定义为ROI函数,代码块如下:
def ROI(img,vertices):
# 定义一个和输入图像同样大小的全黑图像mask,这个mask也称掩膜
mask=np.zeros_like(img)
if len(img.shape)>2:
channel_count=img.shape[2] # i.e. 3 or 4 depending on your image
ignore_mask_color=(255,)*channel_count
else:
ignore_mask_color=255
# [vertices]中的点组成了多边形,将在多边形内的mask像素点保留,
cv2.fillPoly(mask,[vertices],ignore_mask_color)
# 与mask做"与"操作,即仅留下多边形部分的图像
masked_image=cv2.bitwise_and(img,mask)
return masked_image
[rows,cols,channel]=img.shape
print(rows) #像素的行数
print(cols) #像素的列数
#图像像素行数 rows = canny_image .shape[0] 296行
#图像像素列数 cols = canny_image .shape[1] 526列
left_bottom=[0,rows]
right_bottom=[cols,rows]
apex=[cols/2, 170]
vertices = np.array([left_bottom, right_bottom, apex], np.int32)
roi_image = ROI(canny_image, vertices)
cv2.imshow('roi_image',roi_image)
cv2.imwrite('D:\pycharm\exercise\spring festival holiday\lane_line3.jpg',roi_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
三、使用霍夫变换检测出图像中的直线,并进行拟合
霍夫变换是一种特征检测方法,其原理和推导过程可以参看经典霍夫变换(Hough Transform)(https://blog.csdn.net/yuyuntan/article/details/80141392)。
霍夫变换的一般公式如下
HoughLinesP(image, rho, theta, threshold, lines=None, minLineLength=None, maxLineGap=None)
image: 必须是二值图像,推荐使用canny边缘检测的结果图像;
rho: 线段以像素为单位的距离精度,double类型的,推荐用1.0
theta: 线段以弧度为单位的角度精度,推荐用numpy.pi/180
threshold: 累加平面的阈值参数,int类型,超过设定阈值才被检测出线段,值越大,基本上意味着检出的线段越长,检出的线段个数越少。根据情况推荐先用100试试
lines:这个参数的意义未知,发现不同的lines对结果没影响,但是不要忽略了它的存在
minLineLength:线段以像素为单位的最小长度,根据应用场景设置
maxLineGap:同一方向上两条线段判定为一条线段的最大允许间隔(断裂),超过了设定值,则把两条线段当成一条线段,值越大,允许线段上的断裂越大,越有可能检出潜在的直线段
由于霍夫变换只能检测有车道线的地方,这与工程需要输出的结果尚有差异,所以需要用右车道线上的点做一次最小二乘直线拟合,才能得到最终的左、右车道线的直线方程。
考虑到实际工程中左右车道线一般是平行的,所以可以认为左右车道线上最上和最下的点对应的y值,就是左右车道线的边界。基于以上思路,我们重新定义draw_lines()函数,将数据后处理过程写入该函数中。
rho=1.0
theta=np.pi/180
threshold=15
#lines=None
minLineLength=40
maxLineGap=20
lines = cv2.HoughLinesP(roi_image, rho, theta, threshold, np.array([]), minLineLength, maxLineGap)
#封装成一个绘画函数,实现把线段绘制在图像上的功能,以实现线段的可视化
'''
def draw_lines(img,lines,color=255,thickness=2):
for line in lines:
for x1,y1,x2,y2 in line:
cv2.line(img,(x1,y1),(x2,y2),color,thickness) #将线段绘制在img上
'''
def draw_lines(img,lines,color=255,thickness=2):
left_lines_x=[]
left_lines_y=[]
right_lines_x=[]
right_lines_y=[]
line_y_min=170
line_y_max=296
for line in lines:
for x1,y1,x2,y2 in line:
if y1>line_y_min:
line_y_min=y1
if y2>line_y_min:
line_y_min=y2
if y1<line_y_max:
line_y_max=y1
if y2<line_y_max:
line_y_max=y2
k=(y2-y1)/(x2-x1) #k为拟合直线的斜率
if k<-0.3:
left_lines_x.append(x1)
left_lines_y.append(y1)
left_lines_x.append(x2)
left_lines_y.append(y2)
elif k>0.3:
right_lines_x.append(x1)
right_lines_y.append(y1)
right_lines_x.append(x2)
right_lines_y.append(y2)
#最小二乘法拟合
'''
np.polyfit函数:采用的是最小二次拟合,numpy.polyfit(x, y, deg, rcond=None, full=False, w=None, cov=False),
前三个参数是必须的,deg=1,代表需要拟合成一条一阶多项式。
'''
left_line_k,left_line_b=np.polyfit(left_lines_x,left_lines_y,1)
right_line_k,right_line_b=np.polyfit(right_lines_x,right_lines_y,1)
#根据直线方程和最大最小的y值,反算对应的x.
'''
img=cv.line(img, pt1, pt2, color[, thickness[, lineType[, shift]]])
第一个参数 img:要划的线所在的图像;
第二个参数 pt1:直线起点
第三个参数 pt2:直线终点
第四个参数 color:直线的颜色
第五个参数 thickness=1:线条粗细
'''
cv2.line(img,(int((line_y_max-left_line_b)/left_line_k),line_y_max),(int((line_y_min-left_line_b)/left_line_k),line_y_min),color,thickness)
cv2.line(img,(int((line_y_max-right_line_b)/right_line_k),line_y_max),(int((line_y_min-right_line_b)/right_line_k),line_y_min),color,thickness)
line_image=np.copy(img) #复制一张原图,将线段绘制在这张图上
draw_lines(line_image,lines)
cv2.imshow('line_image',line_image)
cv2.imwrite('D:\pycharm\exercise\spring festival holiday\lane_line4.jpg',line_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
四、将算法应用于视频
视频其实就是一帧帧连续不断的图像,使用读取视频的库,将视频截取成一帧帧图像,然后使用上面的灰度处理、边缘提取、感兴趣区域选择、霍夫变换和数据后处理,得到车道线检测结果,再将图片结果拼接成视频,就完成了视频中的车道线检测。
output = 'test_video_output.mp4'
from moviepy.editor import VideoFileClip
clip1 = VideoFileClip("test_video.mp4")
white_clip = clip1.fl_image(process_image)
%time white_clip.write_videofile(output , audio=False)
五、总结
1、以上方法不能有效处理带有弧度的曲线,这需要使用radon变换,并进行多项式拟合,这个咱们以后再讲’;
2、实际应用场景肯定会更加复杂,所以需要根据具体应用场景进行优化‘;
3、本算法无法应用在不同光照条件的场景中,鲁棒性较差;