车道识别,是自动驾驶中必不可少的,且实现方法也不止一种。
车道的基本概念
“车道”,其相关解释在维基百科或者百度百科上都有,不过,正如我们日常所言,都是用来专指“机动车道”。所以,“车道识别”、“自动驾驶”等术语,也是针对机动车而言。
★自行车的“自动驾驶”也值得探索。 其实已经有了,比如:https://www.designboom.com/technology/self-driving-bicycle-huawei-engineers-operate-unmanned-06-14-2021/”
问题描述
如同前面视频演示的实时车道识别,可以用多种方法实现。比如可以用基于学习的方法,也就是训练一个深度学习模型。
本文不用这种方法,本文要介绍一种更简单的方法:用 OpenCV 实现车道识别。
如上图所示,车道识别的重要任务就是要识别出车道两边的分道线,这是问题的关键。
那么,怎么识别这些车道的分道线呢?
从机动车的角度开出去,所看到的场景范围中,除了分道线之外,还有许多其他物体,比如车辆、路边障碍物、路灯等。通过前面的视频以及生活尝试,都容易知道,场景中的每一帧都在变化。这就是现实生活中的驾驶情况——有点复杂。
所以,要解决车道识别问题,首先要找到一种方法,能忽略场景中不应该看到的物体——只看到分道线。即如下图所示,除了分道线,别的物体都没有了。随着机动车的行驶,分道线只呈现在这个场景中。
在接下来的内容中,会演示如何从视频中选择指定的区域,顺带介绍必要的图像预处理技巧。
图像掩膜
图像掩模(image mask):用选定的图像、图形或物体,对待处理的图像(局部或全部)进行遮挡来控制图像处理的区域或处理过程。在图像处理中,对图像掩膜会有多种要求。
图像掩膜的本质就是 Numpy 的数组,如下图所示,改变图中选定区域的像素值,比如都改为 0 ,就实现了图中所示的遮罩效果。
这是一种非常简单而有效的移除不想看到的物体的方法。
基本思路
首先是要将图片转化为“黑白”,即设置一个转化的阈值,这样就能得到如下图中右侧的效果。
接下来要解决的问题就是如何让机器“看到”分道线。通常使用霍夫变换(Hough Transformation),这是一种特征提取的技术,其数学原理请参阅:http://math.itdiffer.com 中的有关内容。
用 OpenCV 实现车道识别
下面就开始编写实现车道识别的代码。本文代码就发布到了 AiStudio 上,并且相关视频也可以在该项目中下载。
★本项目地址:https://aistudio.baidu.com/aistudio/projectdetail/3215224?contributionType=1。”
先引入下列各模块和库,如果本地没有安装,请自行安装。
代码语言:javascript复制import os
import re
import cv2
import numpy as np
from tqdm import tqdm_notebook
import matplotlib.pyplot as plt
然后读入视频。此视频文件已经放到本项目中,可以自行下载。
准备
加载视频文件,并从视频中抽取若干帧作为后续应用的图片。
代码语言:javascript复制# 将视频按照指定的帧率,将帧转化为图片
image_path = 'frames'
def get_frame(video, image_path, frame_rate=0.5):
vidcap = cv2.VideoCapture(video)
sec = 0
count = 1
image_lst = []
while 1:
vidcap.set(cv2.CAP_PROP_POS_MSEC, sec*1000)
has_frames, image = vidcap.read()
if has_frames:
image_name = f"imge{count}.jpg"
cv2.imwrite(f"{image_path}/{image_name}", image)
image_lst.append(image_name)
else:
break
count = 1
sec = frame_rate
sec = round(sec, 2)
return image_lst
images = get_frame("road.mp4", 'frames', frame_rate=5) # 此处每隔5秒取一帧,在真实的业务中,时间较长。
代码语言:javascript复制len(images) # 共计得到了167张图片
特别注意,上面设置的 frame_rate=5
显然比较粗糙,这是为了演示而设置的。
下面显示其中一张图片。
代码语言:javascript复制image_path = 'frames/'
# 显示其中一张图片
idx = 2 # 指定一个索引
image_example = image_path os.listdir("frames")[idx]
img = cv2.imread(image_example)
# 显示图片
plt.figure(figsize=(10,10))
plt.imshow(img, cmap= "gray")
plt.show()
创建掩膜
显然,我们感兴趣的区域是一个多边形范围,其他区域都是应该被遮罩起来的。因此,首先要指定多边形的坐标,然后通过它创建掩膜。
代码语言:javascript复制# 创建 0 数组
stencil = np.zeros_like(img[:,:,0])
# 确定多边形的坐标
polygon = np.array([[80,370], [300,250], [450,250], [580,370]])
# 用 1 填充多边形
cv2.fillConvexPoly(stencil, polygon, 1)
# 显示多边形效果
plt.figure(figsize=(10,10))
plt.imshow(stencil, cmap= "gray")
plt.show()
将多边形作为掩膜,用到其中一帧图片上。
代码语言:javascript复制mask_img = cv2.bitwise_and(img[:,:,0],
img[:,:,0],
mask=stencil)
# 显示效果
plt.figure(figsize=(10,10))
plt.imshow(mask_img, cmap= "gray")
plt.show()
图片预处理
为了识别视频中每帧图片中的车道,必须对所有图片进行预处理,主要是前面提过的两个方面:阈值和霍夫变换
- 阈值
ret, thresh = cv2.threshold(img, 130, 145, cv2.THRESH_BINARY)
# plot image
plt.figure(figsize=(10,10))
plt.imshow(thresh, cmap= "gray")
plt.show()
- 霍夫变换
lines = cv2.HoughLinesP(thresh, 1, np.pi/180, 30, maxLineGap=200)
# 拷贝帧图片
dmy = img[:,:,0].copy()
# 绘制霍夫线
for line in lines:
x1, y1, x2, y2 = line[0]
cv2.line(dmy, (x1, y1), (x2, y2), (255, 0, 0), 3)
# 画图显示
plt.figure(figsize=(10,10))
plt.imshow(dmy, cmap= "gray")
plt.show()
现在,将上面的操作用在每一帧上。
代码语言:javascript复制cnt = 0
for image in images:
print(image)
img = cv2.imread(f'frames/{image}')
# 对帧掩膜
masked = cv2.bitwise_and(img[:,:,0], img[:,:,0], mask=stencil)
# 阈值
ret, thresh = cv2.threshold(masked, 130, 145, cv2.THRESH_BINARY)
# 霍夫变换
lines = cv2.HoughLinesP(thresh, 1, np.pi/180, 30, maxLineGap=200)
dmy = img[:,:,0].copy()
# 识别到的线
try:
for line in lines:
x1, y1, x2, y2 = line[0]
cv2.line(dmy, (x1, y1), (x2, y2), (255, 0, 0), 3)
cv2.imwrite('detected/' str(cnt) '.png',dmy)
except TypeError:
cv2.imwrite('detected/' str(cnt) '.png',img)
cnt = 1
用于视频
代码语言:javascript复制# 输入路径
pathIn= 'detected/'
# 输出视频
pathOut = 'roads_v2.mp4'
# 设定每秒的帧数
fps = 30.0
from os.path import isfile, join
# 读取文件
files = [f for f in os.listdir(pathIn) if isfile(join(pathIn, f))]
files.sort(key=lambda f: int(re.sub('D', '', f)))
将所有检测到车道的帧保存到列表中。
代码语言:javascript复制frame_list = []
for i in range(len(files)):
filename=pathIn files[i]
#reading each files
img = cv2.imread(filename)
height, width, layers = img.shape
size = (width,height)
#inserting the frames into an image array
frame_list.append(img)
最后,将所有的帧合并为视频。
代码语言:javascript复制# write the video
out = cv2.VideoWriter(pathOut,cv2.VideoWriter_fourcc(*'DIVX'),
fps, size)
for i in range(len(frame_list)):
# writing to a image array
out.write(frame_list[i])
out.release()