周奇丰,凌莉萍
(苏州建设交通高等职业技术学校,江苏 苏州 215000)
2020年11月11日,由北京市人民政府、工业和信息化部、公安部、交通运输部等共同主办的“2020世界智能网联汽车大会”召开,在会中发布了《智能网联汽车技术路线图2.0》。按照规划,2025年,L2/L3级智能网联汽车销量占当年汽车总销量的比例超过50%。到2035年,中国方案智能网联汽车技术和产业体系全面建成,产业生态健全完善,整车智能化水平显著提升,网联式高度自动驾驶网联汽车大规模应用。而目前在L2/L3级的智能网联汽车中摄像头起到了至关重要的作用。基于OpenCV视觉库的车道线图像处理技术,能够在视频流中准确实现车道线的实时检测。
OpenCV(Open Source Computer Vision Library),是一个基于(开源)发行的跨平台计算机视觉库,可以运行在Linux、Windows和Mac OS操作系统上,能实现图像处理和计算机视觉方面的很多通用算法。而NumPy可用来存储和处理大型矩阵,比使用 Python本身处理要高效得多,支持高维度数组与矩阵的运算,此外也针对数组提供了大量的数学函数库。在车道线的图像处理过程中,如图1所示,重点在霍夫变换,难点在如何处理像素点。
图1 车道线图像识别主要步骤
图像预处理之前,首先要了解什么是RGB图像、灰度图像和二值图像[1]。在进行图像处理时需要将图像中每一个像素点数值化后存放在图像矩阵中。RGB图像是指彩色图像,分别用R(红色)、G(绿色)和B(蓝色)三个通道的颜色值来表示每个像素的颜色,每一个像素点有3个颜色分量。灰度图像是指黑白图像,每一个像素点由0~255的取值构成,其中0代表黑色,255代表白色。二值图像仅由黑色和白色两种颜色构成,取值仅有0或1,0代表黑色,1代表白色。如图2所示:
图2 RGB图像、灰度图像和二值图像
首先需要把图像灰度化后导入程序中,如图3所示:
图3 导入灰度化的图像
import cv2
import numpy as np
img=cv2.imread('C://Users//MIF//Desktop/chedaoxian2.jp g',cv2.IMREAD_GRAYSCALE)
高斯滤波是一种常用的滤波算法,可以有效地抑制噪声,平滑图像,减少图像中噪点对与分析的影响[2]。高斯滤波的原理如图4所示,对于某一个图像,要对红色的这个像素点高斯滤波,首先要选定一个核大小,图示为3×3(任意奇数都可以)。对于红色像素点核内每一个像素点都有一个系数,距离红色像素点越近的像素点系数越大,距离红色像素点越远的点系数越小。即让临近的像素点具有更高的重要度,对周围像素计算加权平均值,较近的像素具有较大的权重值,从而得到新的像素点的值。
图4 高斯滤波原理
高斯滤波在OpenCV中使用cv2.GaussianBlur()这个函数来完成,如下所示:a=cv2.GaussianBlur(img,(5,5),0)。
在将图像高斯滤波后,对图像进行边缘检测,找出图像的边界点[3]。在OpenCV中可以选择sobel算子、scharr算子或者laplacian算子来得出图像梯度,经过试验发现使用canny边缘检测来计算图像的边缘效果更好,如图5所示:b=cv2. Canny(a,100,200,apertureSize =3)。
图5 canny边缘检测后的图像
图像处理中的ROI,即感兴趣区域。在图像中可以选择一个区域作为图像处理关注的重点,使用ROI可以减少图像处理时间,减少资源占用。利用mask掩膜生成ROI时,首先生成一个和原图像大小一致的黑底图像,然后利用坐标画出一个感兴趣区域并且填充白色,利用函数cv2.bitwise_and()进行与操作,保留白色区域的图像,剔除黑色区域的图像,如图6所示:
图6 mask掩膜
mask=np.zeros_like(img)
triangle=np.array([[0,753],[500,376],[640,376],[1073,753]])
cv2.fillPoly(mask,[triangle],(255,255,255))
mask_img=cv2.bitwise_and(mask,b)
经过图像的预处理,可以得到由多条线段构成的图像。霍夫变换的原理就是在笛卡尔坐标系中,一条直线y=kx+b中由唯一的k、q相对应,我们把k、q分别作为x轴,y轴,可以得到霍夫空间。笛卡尔坐标系中的一条线对应霍夫空间中的一个点。反过来也同样成立,霍夫空间中的一条线对应笛卡尔坐标系中的一个点,如图7所示:
图7 霍夫变换原理1
如果在笛卡尔坐标系中有几个点共线,那么对应到霍夫空间中就有几条直线通过同一个点,如图8所示:
图8 霍夫变换原理2
在笛卡尔坐标系中,由多个点共不同直线的情况如图9所示。在笛卡尔坐标系中(1,1),(2,1),(4,1)这3个点共线,(3,2),(2,1),(1,0)这3个点共线。反映到霍夫空间中我们可以看出分别有A,B这两个点由3条直线共点。而由于垂直于y轴的直线没有k值,所以一般在处理的时候会采用极坐标。因此,霍夫变换的目的就是在霍夫空间内尽可能找到由更多条直线构成的点,可以设定阈值,筛选出不符合条件的点。
图9 霍夫变换原理3
在OpenCV中,可以利用cv2.HoughLinesP()函数来完成霍夫变换,如下所示:
minLineLength = 100
maxLineGap = 10
lines = cv2.HoughLinesP(mask_img, 1, np.pi / 180,50, min LineLength, maxLineGap)
其中,mask_ing是我们预处理后的图像;1是极坐标的ρ;np.pi/180是极坐标的θ;50是设定的阈值,超过50个像素点共线的直线才会被检测出来,值越大,被检测出来的线段个数越少,值越小,被检测出来的线段个数越多;minLine Length代表线段的最小长度;maxLineGap代表同一方向上两条线段判定为一条线段的最大允许间隔。经过cv2.Hough LinesP()函数处理后的返回值是(x1,y1,x2,y2)四个元素的列表,其中(x1,y1)和(x2,y2)分别代表线段的两个端点。
完成霍夫变换后,需要对cv2.HoughLinesP()的返回值进行处理。处理的流程如图10所示:
图10 处理像素点集流程
(1)通过斜率正负判断左右车道线,并分别添加入集合。
line_left=[]
line_right=[]
for line in lines:
for x1,y1,x2,y2 in line:
k=(y2-y1)/(x2-x1)
if k<0:
line_left.append(line)
else:
line_right.append(line)
(2)求出所有线段的斜率,并求平均值。分别计算出斜率列表中每一个元素与斜率平均值的差值,并且和平均值比较大小,超过设定阈值大小,删除斜率不符合要求的点。
def clean_line(lines,threshould):
slope=[]
for line in lines:
for x1,y1,x2,y2 in line:
k=(y2-y1)/(x2-x1)
slope.append(k)
while len(lines)>0:
mean=np.mean(slope)
diff=[abs(s-mean) for s in slope]
idx=np.argmax(diff)
if diff[idx]>threshould:
slope.pop(idx)
lines.pop(idx)
else:
break
clean_line(line_left,0.5)
clean_line(line_right,0.5)
(3)分别提取左右两侧所有的点,并放入列表中。
points_left=[]
for points in line_left:
for x1,y1,x2,y2 in points:
left=(x1,y1)
points_left.append(left)
left=(x2,y2)
points_left.append(left)
points_right=[(x1,y1) for line in line_right for x1,y1,x2,y2 in line]
points_right=points_right+[(x2,y2) for line in line_right for x1,y1,x2,y2 in line]
(4)分别在左右点的列表中找出y值的最大值和最小值。
point_left_y=[]
for i in points_left:
point_left_y.append(i[1])
print(point_left_y)
point_left_y_max=np.max(point_left_y)
point_left_y_min=np.min(point_left_y)
point_right_y=[]
for i in points_right:
point_right_y.append(i[1])
point_right_y_max=np.max(point_right_y)
point_right_y_min=np.min(point_right_y)
(5)用一次多项式拟合所有的点,并且得到左右两条线段的端点。
def fit_point_set(point_list,ymin,ymax):
x=[p[0] for p in point_list]
y=[p[1] for p in point_list]
fit=np.polyfit(y,x,1)
fit_set = np.poly1d(fit)
xmin=int(fit_set(ymin))
xmax=int(fit_set(ymax))
return [(xmin,ymin),(xmax,ymax)]
left_fps=fit_point_set(points_left,point_left_y_min,1073)
right_fps=fit_point_set(points_right,point_right_y_min,107 3)
以上程序就是对像素点集的处理,最终可以得到左右两条拟合车道线的两个端点,通过左右分别4个点,可以绘制出车道线。
绘制车道线时需要用OpenCV中的cv2.line()函数,如下所示:
cv2.line(img,left_fps[0],left_fps[1],(0,0,0),5)
cv2.line(img,right_fps[0],right_fps[1],(0,0,0),5)
最终得到的绘制车道线的效果如图11所示:
图11 车道线绘制效果
本文不仅仅可以用于检测车道线的图片,也可以在视频 流中检测车道线,使用cv.VideoCapture()函数就可以实现车道的实时检测。本文以对车道线进行检测这一简单任务为例,说明openCV视觉库的图像处理技术优势,例如检测来自前方车辆的干扰,车道线不清晰,识别障碍物,根据车道线的位置判断车辆的偏移情况等,都可以通过此项技术进一步研究补充优化。