用 OpenCV 实现自动驾驶中的车道识别

2021-12-13 13:23:55 浏览数 (1)

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

车道的基本概念

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

★自行车的“自动驾驶”也值得探索。 其实已经有了,比如: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()

图片预处理

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

  1. 阈值
代码语言:javascript复制
ret, thresh = cv2.threshold(img, 130, 145, cv2.THRESH_BINARY)

# plot image
plt.figure(figsize=(10,10))
plt.imshow(thresh, cmap= "gray")
plt.show()
  1. 霍夫变换
代码语言:javascript复制
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()

0 人点赞