先推荐下自己的公众号——Joe学习笔记,公众号上会不定期更新一些文章,主要是自己平时学到的知识,内容包括自动驾驶、计算机视觉、人工智能和机器人技术。我会第一时间把文章更新在公众号上,欢迎大家订阅和分享!
在初级版无人驾驶车道线检测(上)中已经完成了灰度图片转换、高斯滤波、边缘检测、感兴趣区域提取和直线检测,这部分主要介绍直线拟合。本文参考了github上的一个开源代码,觉得写的挺好的,看了之后挺有收获。上次录视频很紧张,再加上这次的代码不是很熟,就不录视频了。
上一篇文章最后的检测的结果如上图所示。我们需要拟合左右两个车道线,所以第一步是根据斜率的正负把图中的线段分为左右两部分。代码如下:
# 1.根据斜率分左右线段
for line in lines:
for x1, y1, x2, y2 in line:
if y2 - y1 != 0 and x2 - x1 != 0:
#计算斜率
slope = (y2 - y1) / (x2 - x1)
if slope > 0:
right_lines.append(line)
else:
left_lines.append(line)
此时左右线段可能包含少量的背景线段,需要删去这些错误的线段。由于错误的线段较少,线段斜率的平均值接近车道线的斜率。因此计算线段斜率的平均值,并求得每条线段斜率和平均值的差,设置一个阈值,将差值大于阈值的线段删去。由于需要对左右线段各进行一次操作,为了方便,写成函数的形式,代码如下:
#去除斜率差异较大的线段
def clean_lines(lines, threshold):
#计算斜率
slope = [(y2 - y1) / (x2 - x1) for line in lines for x1, x2, y1, y2 in line]
while(len(slope) > 0):
#计算斜率平均值
mean = np.mean(slope)
#计算差值
diff = [abs(s - mean) for s in slope]
#找出差值最大的下标
index = np.argmax(diff)
# 如果下标对应的差值大于threshold就删去该线段
if diff[index] > threshold:
slope.pop(index)
lines.pop(index)
else:
break
以上代码在求斜率时,列表中写入循环比单独写出循环求斜率简洁很多,这种方法在之后的代码中也有出现。
去除了错误的线段之后,就可以进行直线拟合。opencv中霍夫变换后保存的是线段的两个端点,首先将所有端点表示成元组(x,y)的列表,代码如下:
left_points = [(x1, y1) for line in left_lines for x1, y1, x2, y2 in line]
left_points = left_points + [(x2, y2) for line in left_lines for x1, y1, x2, y2 in line]
right_points = [(x1, y1) for line in right_lines for x1, y1, x2, y2 in line]
right_points = right_points + [(x2, y2) for line in right_lines for x1, y1, x2, y2 in line]
有了端点,接下来可以进行直线拟合,拟合直线后选取要画出的车道线的起点和终点的y坐标,求出车道线的4个端点。这需要进行2次操作因此也写成函数形式,代码如下:
#拟合直线,计算车道线端点
def cal_lane_vertices(point_list, y_start, y_end):
x = [p[0] for p in point_list]
y = [p[1] for p in point_list]
# 一次函数拟合,fit为(k, b)
fit = np.polyfit(y, x, 1)
#得到x = ky + b
fit_fn = np.poly1d(fit)
x_start = int(fit_fn(y_start))
x_end = int(fit_fn(y_end))
return [(x_start, y_start), (x_end, y_end)]
求得了车道线端点,最后使用多边形来填充即可,最后的效果如下所示。
总结一下直线拟合的步骤,如下图所示。
视频效果——传送门。
在视频中,大部分的车道线拟合的比较好,但由于使用的是直线拟合,所以在车道弯曲的地方拟合的不是很好。最大的问题是视频的最后,出现了拟合失败的情况,出现这种问题的原因是地面的光照条件发生了变化,转换为灰度图像后车道线基本不可见。这是以后可以改进的方面。
应用于视频的整体代码如下所示:
import cv2
import numpy as np
import matplotlib.pyplot as plt
from moviepy.editor import VideoFileClip
def process_img(img):
#转换为灰度图像
img_gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
#cv2.imshow("img_gray", img_gray)
#高斯滤波
img_gaussian = cv2.GaussianBlur(img_gray, (11, 11), 0.0)
#cv2.imshow("img_gaussian", img_gaussian)
#Canny算法,边缘检测
img_edge = cv2.Canny(img_gaussian, 90, 180)
#cv2.imshow("img_edge", img_edge)
mask = np.zeros_like(img_edge)
vertices = np.array([[220, 670], [610, 410], [650, 410], [1100, 670]])
cv2.fillPoly(mask, [vertices], 255)
img_edge_roi = cv2.bitwise_and(img_edge, mask)
#直线检测
lines = cv2.HoughLinesP(img_edge_roi, 1, np.pi/180, 20, minLineLength = 10, maxLineGap = 10)
#print(lines.shape)
img_lane = draw_lanes(img, lines, color=[0, 0, 255])
return img_lane
#去除斜率差异较大的线段
def clean_lines(lines, threshold):
#计算斜率
slope = [(y2 - y1) / (x2 - x1) for line in lines for x1, x2, y1, y2 in line]
while(len(slope) > 0):
#计算斜率平均值
mean = np.mean(slope)
#计算差值
diff = [abs(s - mean) for s in slope]
#找出差值最大的下标
index = np.argmax(diff)
# 如果下标对应的差值大于threshold就删去该线段
if diff[index] > threshold:
slope.pop(index)
lines.pop(index)
else:
break
#拟合直线,计算车道线端点
def cal_lane_vertices(point_list, y_start, y_end):
x = [p[0] for p in point_list]
y = [p[1] for p in point_list]
# 一次函数拟合,fit为(k, b)
fit = np.polyfit(y, x, 1)
#得到x = ky + b
fit_fn = np.poly1d(fit)
x_start = int(fit_fn(y_start))
x_end = int(fit_fn(y_end))
return [(x_start, y_start), (x_end, y_end)]
#画车道线
def draw_lanes(img, lines, color = [0, 0, 255]):
#左线段列表
right_lines = []
#右线段列表
left_lines = []
# 1.根据斜率分左右线段
for line in lines:
for x1, y1, x2, y2 in line:
if y2 - y1 != 0 and x2 - x1 != 0:
#计算斜率
slope = (y2 - y1) / (x2 - x1)
if slope > 0:
right_lines.append(line)
else:
left_lines.append(line)
if (len(left_lines) <= 0 or len(right_lines) <= 0):
return img
#2.去除斜率差异过大的线段
clean_lines(right_lines, 0.1)
clean_lines(left_lines, 0.1)
#3.直线拟合
left_points = [(x1, y1) for line in left_lines for x1, y1, x2, y2 in line]
left_points = left_points + [(x2, y2) for line in left_lines for x1, y1, x2, y2 in line]
right_points = [(x1, y1) for line in right_lines for x1, y1, x2, y2 in line]
right_points = right_points + [(x2, y2) for line in right_lines for x1, y1, x2, y2 in line]
#计算车道线端点
left_vertices = cal_lane_vertices(left_points, img.shape[0] - 50, 450)
right_vertices = cal_lane_vertices(right_points, img.shape[0] - 50, 450)
#以矩形的形式拟合
vertices = np.array([left_vertices[0], left_vertices[1], right_vertices[1], right_vertices[0]])
cv2.fillPoly(img, [vertices], color)
return img
output = 'project_video_lane_detect.mp4'
clip = VideoFileClip("project_video.mp4")
out_clip = clip.fl_image(process_img)
out_clip.write_videofile(output, audio=False)
最后把代码、测试视频和测试图片放在百度网盘。
链接:https://pan.baidu.com/s/1wHoWht5b9KyrRuAjuTGNKA
提取码:pf09