3

自动驾驶中的车道识别

 2 years ago
source link: https://qiwsir.github.io/2021/12/08/lane-detection/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

车道识别,是自动驾驶中必不可少的,且实现方法也不止一种。

车道的基本概念

“车道”,其相关解释在维基百科或者百度百科上都有,不过,正如我们日常所言,都是用来专指“机动车道”。所以,“车道识别”、“自动驾驶”等术语,也是针对机动车而言。

自行车的“自动驾驶”也值得探索。

其实已经有了,比如:https://www.designboom.com/technology/self-driving-bicycle-huawei-engineers-operate-unmanned-06-14-2021/

如同前面视频演示的实时车道识别,可以用多种方法实现。比如可以用基于学习的方法,也就是训练一个深度学习模型。

本文不用这种方法,本文要介绍一种更简单的方法:用 OpenCV 实现车道识别。

1638930067979-lane.png

如上图所示,车道识别的重要任务就是要识别出车道两边的分道线,这是问题的关键。

那么,怎么识别这些车道的分道线呢?

从机动车的角度开出去,所看到的场景范围中,除了分道线之外,还有许多其他物体,比如车辆、路边障碍物、路灯等。通过前面的视频以及生活尝试,都容易知道,场景中的每一帧都在变化。这就是现实生活中的驾驶情况——有点复杂。

所以,要解决车道识别问题,首先要找到一种方法,能忽略场景中不应该看到的物体——只看到分道线。即如下图所示,除了分道线,别的物体都没有了。随着机动车的行驶,分道线只呈现在这个场景中。

1638953096079-lane_5.png

在接下来的内容中,会演示如何从视频中选择指定的区域,顺带介绍必要的图像预处理技巧。

图像掩模(image mask):用选定的图像、图形或物体,对待处理的图像(局部或全部)进行遮挡来控制图像处理的区域或处理过程。在图像处理中,对图像掩膜会有多种要求。

图像掩膜的本质就是 Numpy 的数组,如下图所示,改变图中选定区域的像素值,比如都改为 0 ,就实现了图中所示的遮罩效果。

1638953757595-mask.png

这是一种非常简单而有效的移除不想看到的物体的方法。

首先是要将图片转化为“黑白”,即设置一个转化的阈值,这样就能得到如下图中右侧的效果。

1639012386675-lane_6.webp

接下来要解决的问题就是如何让机器“看到”分道线。通常使用霍夫变换(Hough Transformation),这是一种特征提取的技术,其数学原理请参阅:http://math.itdiffer.com 中的有关内容。

用 OpenCV 实现车道识别

下面就开始编写实现车道识别的代码。建议使用谷歌的 Colab 或者百度的 AiStudio 执行代码,因为在本地跑的话,对计算能力要求有点高。本文代码就发布到了 AiStudio 上,并且相关视频也可以在该项目中下载,地址是:https://aistudio.baidu.com/aistudio/projectdetail/3215224?contributionType=1。

先引入下列各模块和库,如果本地没有安装,请自行安装。

import os
import re
import cv2
import numpy as np
from tqdm import tqdm_notebook
import matplotlib.pyplot as plt

然后读入视频。此视频文件已经放到本项目中,可以自行下载。

加载视频文件,并从视频中抽取若干帧作为后续应用的图片。

# 将视频按照指定的帧率,将帧转化为图片
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秒取一帧,在真实的业务中,时间较长。
len(images)    # 共计得到了167张图片

特别注意,上面设置的 frame_rate=5 显然比较粗糙,这是为了演示而设置的。

下面显示其中一张图片。

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()

1639027991087-lane_7.png

显然,我们感兴趣的区域是一个多边形范围,其他区域都是应该被遮罩起来的。 因此,首先要指定多边形的坐标,然后通过它创建掩膜。

# 创建 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()

1639028889975-polygon.png

将多边形作为掩膜,用到其中一帧图片上。

mask_img = cv2.bitwise_and(img[:,:,0], 
img[:,:,0],
mask=stencil)

# 显示效果
plt.figure(figsize=(10,10))
plt.imshow(mask_img, cmap= "gray")
plt.show()

1638953096079-lane_5.png

图片预处理

为了识别视频中每帧图片中的车道,必须对所有图片进行预处理,主要是前面提过的两个方面:阈值和霍夫变换

ret, thresh = cv2.threshold(img, 130, 145, cv2.THRESH_BINARY)

# plot image
plt.figure(figsize=(10,10))
plt.imshow(thresh, cmap= "gray")
plt.show()

1639115520636-threshold.png

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()

1639115716135-hough.png

现在,将上面的操作用在每一帧上。

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
# 输入路径
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)))

将所有检测到车道的帧保存到列表中。

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)

最后,将所有的帧合并为视频。

# 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()

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK