为什么喜欢无人驾驶,主要原因是无人驾驶任务是机器学习为主的前沿领域,在无人驾驶中各个模块中,机器学习的算法随处可见,其实这么说虽然有些夸张,但其实机器学习特别是深度学习的模型在无人驾驶中还是起着一定重要。而于其他机器学习应用的场景中,机器学习多半是一种辅助或者锦上添花的技术。
以后分享会主要提及以下这些内容,因为之前分享有时候设计的不是很清晰
目标: 说明今天要做任务
难点:关于这个任务难点,我们可以一起讨论
实现方式: 说一下大致自己如何完成这任务思路
TODO: 也就是随后要做的事以及要优化的内容
在无人驾驶中的每一个任务都是相当复杂,复杂到让你感到无从下手,找不到解决问题切入点。那么面对这样极其复杂问题,我们应该如何切入来解决问题。好的方法是从先尝试简化问题,由简入难一步一步尝试先解决简单我们能够把握的问题。
本次任务是检测图像中车道线位置,将车道线识别出来在图像标示出来。
车道线检测在无人驾驶中应该算是先对比较简单的任务,主要会计算机视觉中一些边缘检测和简单几何图像识别的技术。通过读取 camera 感知器传入的图像数据进行分析,来识别出车道线位置。有关车道线检测,这个对于 lidar 可能是无能为力,更多是依赖与 camera 采集的图像,或者是高精度地图提供的信息。因为手上没有高精度地图所以选择使用图像来做车道线检测。
我们先把问题简化,所谓简化问题就是用一些条件限制来缩小车道线检测的问题。先看数据,也就是输入算法是车辆行驶的图像,输出车道线位置。更多时候我们如何处理一件比较困难任务,可能有时候我们拿到任务时还没有任何思路,不要着急也不用想太多,我们先开始一步一步地做,从最简单的开始做起,随着做就会有思路,同样一些问题也会暴露出来。我们先找一段视频,这段视频是我从网上一个关于车道线检测项目中拿到的,也参考他的思路来做这件事。好现在就开始做这件事,那么最简单的事就是先读取视频,然后将其显示在屏幕以便于调试。
图像灰度处理
图像高斯平滑处理
canny 边缘检测
区域 Mask
霍夫变换
绘制车道线
import cv2import numpy as npimport sysimport pygamefrom pygame.locals import *class Display(object): def __init__(self,Width,Height): pygame.init() pygame.display.set_caption('Drive Video') self.screen = pygame.display.set_mode((Width,Height),0,32) def paint(self,draw): self.screen.fill([0,0,0]) draw = cv2.transpose(draw) draw = pygame.surfarray.make_surface(draw) self.screen.blit(draw,(0,0)) pygame.display.update()if __name__ == "__main__": solid_white_right_video_path = "test_videos/solidWhiteRight.mp4" cap = cv2.VideoCapture(solid_white_right_video_path) Width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) Height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) display = Display(Width,Height) while True: ret, draw = cap.read() draw = cv2.cvtColor(draw,cv2.COLOR_BGR2RGB) if ret == False: break display.paint(draw) for event in pygame.event.get(): if event.type == QUIT: sys.exit()
上面代码在这里我就不多说了,默认您对 python 是有所了解,关于如何使用 opencv 读取图片,这样代码在网上代码示例也很多,大家可以自行搜索后一看就懂了。这里因为我用的是 mac 有时候使用 opencv 来显示视频图像可能会有些问题,所以示例用了 pygame 来将 opencv 读取图像处理后图像显示在屏幕上。这个大家根据自己实际情况而定吧。值得说一句的是 opencv 读取图像是 BGR 格式,要想在 pygame 中正确显示图像就需要将 BGR 转换为 RGB 格式。
我们都知道为了让车道线便于驾驶员观察到,通常使用比较明显颜色,例如白色或者黄色作为车道线颜色,这样因为车道线颜色与周围道路颜色明显不同所以便于观察。利用这点我们可以在颜色下一些功夫,我们通过阈值来选择车道线颜色,将其他区域颜色忽略。这里我们通过对 RGB 三个通道都设置阈值来过滤掉除车道线其他区域颜色。
def color_select(img,red_threshold=200,green_threshold=200,blue_threshold=200): ysize,xsize = img.shape[:2] color_select = np.copy(img) rgb_threshold = [red_threshold, green_threshold, blue_threshold] thresholds = (img[:,:,0] < rgb_threshold[0]) \ | (img[:,:,1] < rgb_threshold[1]) \ | (img[:,:,2] < rgb_threshold[2]) color_select[thresholds] = [0,0,0] return color_select
draw = color_select(draw)
因为图中车道线颜色为白色,所以将 RBG 3 个通道阈值都设置为 200,这个大家应该能够理解。
我们要检测车道线位置相对比较固定,通常出现车的前方,所以我们通过绘制,也就是仅检测我们关心区域。通过创建 mask 来过滤掉那些不关心的区域保留关心区域。
def lane_reigion(img,left_bottom=lane_region_dict["left_bottom"], right_bottom=lane_region_dict["right_bottom"], apex=lane_region_dict["apex"]): ysize,xsize = img.shape[:2] line_image = np.copy(img) fit_left = np.polyfit((left_bottom[0], apex[0]), (left_bottom[1], apex[1]), 1) fit_right = np.polyfit((right_bottom[0], apex[0]), (right_bottom[1], apex[1]), 1) fit_bottom = np.polyfit((left_bottom[0], right_bottom[0]), (left_bottom[1], right_bottom[1]), 1) XX, YY = np.meshgrid(np.arange(0, xsize), np.arange(0, ysize)) region_thresholds = (YY > (XX*fit_left[0] + fit_left[1])) & \ (YY > (XX*fit_right[0] + fit_right[1])) & \ (YY < (XX*fit_bottom[0] + fit_bottom[1])) _,color_thresholds = color_select(img) # color_select[color_thresholds | ~region_thresholds] = [0, 0, 0] line_image[~color_thresholds & region_thresholds] = [9, 255, 0] return line_image
有关边缘检测也是计算机视觉。首先利用梯度变化来检测图像中的边,如何识别图像的梯度变化呢,答案是卷积核。卷积核是就是不连续的像素上找到梯度变化较大位置。我们知道 sobal 核可以很好检测边缘,那么 canny 就是 sobal 核检测上进行优化。
def canny_edge_detect(img): gray = cv2.cvtColor(img,cv2.COLOR_RGB2GRAY) kernel_size = 5 blur_gray = cv2.GaussianBlur(gray,(kernel_size, kernel_size),0) low_threshold = 180 high_threshold = 240 edges = cv2.Canny(blur_gray, low_threshold, high_threshold) return edges
霍夫变换是将 x 和 y 坐标系中的线映射表示在霍夫空间的点(m,b)。所以霍夫变换实际上一种由繁到简(类似降维)的操作。当使用 canny 进行边缘检测后图像可以交给霍夫变换进行简单图形(线、圆)等的识别。这里用霍夫变换在 canny 边缘检测结果中寻找直线。
mask = np.zeros_like(edges)ignore_mask_color = 255 # 获取图片尺寸imshape = img.shape# 定义 mask 顶点vertices = np.array([[(0,imshape[0]),(450, 290), (490, 290), (imshape[1],imshape[0])]], dtype=np.int32)# 使用 fillpoly 来绘制 maskcv2.fillPoly(mask, vertices, ignore_mask_color)masked_edges = cv2.bitwise_and(edges, mask)# 定义Hough 变换的参数rho = 1 theta = np.pi/180threshold = 2min_line_length = 4 # 组成一条线的最小像素数max_line_gap = 5 # 可连接线段之间的最大像素间距# 创建一个用于绘制车道线的图片line_image = np.copy(img)*0# 对于 canny 边缘检测结果应用 Hough 变换# 输出“线”是一个数组,其中包含检测到的线段的端点lines = cv2.HoughLinesP(masked_edges, rho, theta, threshold, np.array([]), min_line_length, max_line_gap)# 遍历“线”的数组来在 line_image 上绘制for line in lines: for x1,y1,x2,y2 in line: cv2.line(line_image,(x1,y1),(x2,y2),(255,0,0),10)color_edges = np.dstack((edges, edges, edges))
有关车道线检测今天用到 canny 边缘检测和霍夫变换技术,如果大家感兴趣我们会拿出两次分享来把这两个技术原理讲出来。这是一个简单实现,鲁棒性并不强,还不能适用于复杂路况,如城市路况,或者车道线被前车覆盖,如雨雪天气。但是还好我们还是迈出了第一步。