OpenCV—自动驾驶实时道路车道检测(完整代码)
在人工智能领域,自动驾驶汽车被视为一项具有革命性意义的技术。借助于深度学习算法的力量,这些技术不断为社会带来进步,并在移动设备上开创了许多新的可能性。无论传统汽车能到达哪里,自动驾驶汽车都能去,并且它能够执行经验丰富的驾驶员的所有操作。然而,在真正实现这一目标之前必须经过严格的测试和验证工作。无论如何,在自动驾驶汽车的训练过程中، 车道检测被视为一个关键步骤. 在这个阶段, 我们将深入探讨如何利用视频进行车道检测.
道路车道检测涉及的简要步骤
道路车道检测的任务是通过分析自动驾驶汽车的行驶路径来规避进入其他车道的可能性。利用视觉输入的数据来源进行处理后能够准确判定车道线的位置及界限。这些高级驾驶员辅助系统(ADAS)和自动驾驶技术方案都高度依赖于这些技术手段,在今天的讨论中,我们将深入探讨其中一种具体的道路车道检测算法方案。涉及的具体步骤如下:

捕获与解码视频文件: 通过 VideoFileClip 对象来捕获视频,并当开始捕捉视频时生成相应的图像序列。
图像的灰度转换: 视频帧通常采用RGB格式,在进行灰度转换时,原因在于将单色通道的数据量减少至三分之一以减少计算复杂度。
为了减少噪声的影响,在进行进一步处理之前必须采取措施去除这些干扰项。该过程可通过高斯模糊实现。其中一种常见的方法是基于高斯滤波器的技术,在这一过程中将原始图像与经过平滑后的版本进行融合以达到最佳效果。通过基于高斯分布计算权重来确定每个像素的新值,并将这些新值与原始像素值相结合从而生成平滑后的图像输出。这种方法能够有效地降低高频细节,并使整个图像看起来更加柔和和谐。
Canny 边缘检测器: 该方法通过计算模糊图像在各个方向上生成的梯度值,并追踪强度变化明显的边缘来实现目标。
感兴趣区域:
霍尔夫线变换:在图像处理领域中作为一种特征提取技术,在计算机视觉系统中有重要的应用价值。该方法通过对图像的空间进行参数化转换,在参数空间域内实现目标图形(如直线、圆等基本几何图形)的精确检测与识别过程。其中,在我们的算法设计中采用概率型霍尔夫线性检测方案,在该方案的基础上结合统计学原理进一步提升检测效率的同时保证检测精度。该方法已被拓展至应用概率型霍尔夫线检测技术减少计算复杂度,并且在实际应用过程中结合多级采样策略能够有效提高运算效率与结果可靠性程度
在图像或视频中呈现线条: 通过霍夫线变换检测出目标区域内的车道线后,在这些区域外也会进行处理以避免干扰,并将这些线条被叠加到我们的视觉输入流或图像中。
数据集: 为了展示该算法的基本工作原理, 我们打算对道路视频文件进行处理. 您可以从以下GitHub链接下载数据集: – 数据集 (https://github.com/rslim087a/road-video)
注:此代码是在google colab中实现的。如果您正在使用任何其他编辑器,您可能需要对代码进行一些更改,因为 colab 与 OpenCV 存在一些依赖性问题
实施道路车道检测的步骤
第 1 步:
在 Python 中安装 OpenCV 库。
Python3
!pip install ``-``q opencv``-``python |
|---|
第2步:
导入必要的库。
Python3
||
# 用于图像处理的库import numpy as npimport pandas as pdimport cv2from google.colab.patches import cv2_imshow# 用于编辑/保存/查看视频剪辑的库from moviepy import editorimport moviepyAI写代码
步骤 3:
定义我们算法的驱动函数。
Python3
||
def 处理视频(test_video, output_video): """ 读取输入视频流并生成包含检测到的车道线的视频文件。 参数: test_video: 输入视频文件的位置 output_video: 要保存输出视频文件的位置 """ # 使用不带音频的VideoFileClip读取视频文件 input_video = editor.VideoFileClip(test_video, audio=False) # 对视频的每一帧应用"frame_processor"函数 # 在后续步骤中将提供有关"frame_processor"的更多详细信息 # "processed"存储输出视频 processed = input_video.fl_image(frame_processor) # 将输出视频流保存为mp4文件 processed.write_videofile(output_video, audio=False)AI写代码
步骤 4:
定义“frame_processor”函数,其中所有处理都发生在帧上以检测车道线。
Python3
||
def frame_processor(image): """ 处理输入帧以检测车道线。 参数: image: 道路图像,其中要检测车道线 (我们将向该函数传递视频帧) """ # 将RGB图像转换为灰度图像 灰度图 = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 应用高斯模糊以去除图像中的噪声 # 并聚焦在我们感兴趣的区域 # 高斯内核的大小 内核大小 = 5 # 应用高斯模糊以去除帧中的噪声 模糊图像 = cv2.GaussianBlur(灰度图, (内核大小, 内核大小), 0) # Hysteresis过程的第一个阈值 低阈值 = 50 # Hysteresis过程的第二个阈值 高阈值 = 150 # 应用Canny边缘检测并将边缘保存在变量中 边缘图 = cv2.Canny(模糊图像, 低阈值, 高阈值) # 由于我们从图像中获取了太多的边缘,因此我们应用 # 一个掩码多边形,只关注道路 # 将在后续步骤中详细解释区域选择 区域 = 区域选择(边缘图) # 应用霍夫变换以从图像中获取直线 # 并找到车道线 # 将在后续步骤中详细解释霍夫变换 霍夫 = 霍夫变换(区域) # 最后,在生成的帧上绘制线条并将其作为输出返回 结果 = 绘制车道线(image, 车道线(image, 霍夫)) return 结果AI写代码
输出:

Canny 边缘检测输出
第五步:区域选择
至此, 我们已完成将帧从 RGB 转换为灰度的过程, 并通过高斯模糊处理降低了噪声, 并采用先进的边缘检测技术进行了相关处理. 下一步我们将挑选用于检测道路车道区域的部分.
Python3
||
def region_selection(image): """ 确定并裁剪输入图像中的感兴趣区域。 参数: image: 我们在这里传递从Canny算法输出的帧,其中我们已经识别了图像中的边缘 """ # 创建与输入图像相同大小的数组 mask = np.zeros_like(image) # 如果传递的图像具有多个通道 if len(image.shape) > 2: channel_count = image.shape[2] ignore_mask_color = (255,) * channel_count # 如果我们的图像只有一个通道,则进入"else"分支 else: # 掩码多边形的颜色(白色) ignore_mask_color = 255 # 创建一个多边形,只关注图片中的道路部分 # 我们根据相机的放置位置创建了这个多边形 rows, cols = image.shape[:2] bottom_left = [cols * 0.1, rows * 0.95] top_left = [cols * 0.4, rows * 0.6] bottom_right = [cols * 0.9, rows * 0.95] top_right = [cols * 0.6, rows * 0.6] vertices = np.array([[bottom_left, top_left, top_right, bottom_right]], dtype=np.int32) # 用白色填充多边形并生成最终的掩码 cv2.fillPoly(mask, vertices, ignore_mask_color) # 对输入图像和掩码执行位与运算,以仅获取道路上的边缘 masked_image = cv2.bitwise_and(image, mask) return masked_imageAI写代码
输出:

第 6 步:
现在我们将使用概率霍夫变换识别上述函数的输出图像中的直线
Python3
||
def hough_transform(image): """ 确定并从输入图像中获取感兴趣区域。 参数: image: 灰度图像,应该是边缘检测器的输出 """ # 累加器的距离分辨率(以像素为单位) rho = 1 # 累加器的角度分辨率(以弧度为单位) theta = np.pi/180 # 仅返回大于此阈值的直线 阈值 = 20 # 被拒绝的线段长度 minLineLength = 20 # 在同一直线上的点之间允许的最大间隙 maxLineGap = 500 # 函数返回一个包含出现在输入图像中的直线的维度的数组 return cv2.HoughLinesP(image, rho = rho, theta = theta, threshold = 阈值, minLineLength = minLineLength, maxLineGap = maxLineGap)AI写代码
输出:
[[[284 180 382 278]] [[281 180 379 285]] [[137 274 183 192]] [[140285189188]] [[313 210 388 285]] [[139 285 188 188]] [[132282181194]] [[146 285 191 196]] [[286 187 379 284]]]
第7步:在视频帧上绘制线条
通过霍夫变换计算后得到了坐标位置,并将这些坐标标记到原始图像中。然而,在这些结果中存在多于两条车道线需要处理,请先确定左右车道的方向,并将其叠加到原图上完成处理流程
我们在这里定义了 4 个函数来帮助在输入帧上绘制左右车道:
Average_Slope_Intercept: 该函数接收霍夫变换得到的结果,并计算每条直线的斜率与截距。对于每条直线来说,若其斜率为负,则归类于左侧车道;反之,则归类于右侧车道。最后步骤是分别对左、右车道求加权平均值得出最终的整体斜率与截距值。
Pixel_Points: 基于直线方程的相关参数信息(包括斜率、截距及y值),我们计算得到车道在x轴上的位置坐标值,并返回车道中心线在图像坐标的整数表示
使用Average_Slope_Intercept函数并结合Pixel Points数据来确定右侧车道线和左侧车道线的位置
draw_lane_lines():此函数在输入图像上描绘左侧车道线和右侧车道线,并返回处理后的图像。将结果存储到驱动程序函数「process_video」中的变量「processed」中。
Python3
||
def average_slope_intercept(lines): """ 寻找每个图像的左车道和右车道的斜率和截距。 参数: lines: 来自霍夫变换的输出 """ left_lines = [] #(斜率, 截距) left_weights = [] #(长度,) right_lines = [] #(斜率, 截距) right_weights = [] #(长度,) for line in lines: for x1, y1, x2, y2 in line: if x1 == x2: continue # 计算线的斜率 斜率 = (y2 - y1) / (x2 - x1) # 计算线的截距 截距 = y1 - (斜率 * x1) # 计算线的长度 长度 = np.sqrt(((y2 - y1) ** 2) + ((x2 - x1) ** 2)) # 左车道的斜率为负,右车道的斜率为正 if 斜率 < 0: left_lines.append((斜率, 截距)) left_weights.append((长度)) else: right_lines.append((斜率, 截距)) right_weights.append((长度)) # 左车道 = np.dot(left_weights, left_lines) / np.sum(left_weights) if len(left_weights) > 0 else None 右车道 = np.dot(right_weights, right_lines) / np.sum(right_weights) if len(right_weights) > 0 else None return 左车道, 右车道def pixel_points(y1, y2, line): """ 将每条线的斜率和截距转换为像素点。 参数: y1: 线的起点的y值。 y2: 线的终点的y值。 line: 线的斜率和截距。 """ if line is None: return None 斜率, 截距 = line x1 = int((y1 - 截距) / 斜率) x2 = int((y2 - 截距) / 斜率) y1 = int(y1) y2 = int(y2) return ((x1, y1), (x2, y2))def lane_lines(image, lines): """ 从像素点创建完整长度的线。 参数: image: 输入测试图像。 lines: 霍夫变换的输出线。 """ 左车道, 右车道 = average_slope_intercept(lines) y1 = image.shape[0] y2 = y1 * 0.6 左线 = pixel_points(y1, y2, 左车道) 右线 = pixel_points(y1, y2, 右车道) return 左线, 右线 def draw_lane_lines(image, lines, color=[255, 0, 0], thickness=12): """ 将线绘制到输入图像上。 参数: image: 输入测试图像(在我们的案例中是视频帧)。 lines: 霍夫变换的输出线。 color (默认 = 红色): 线的颜色。 thickness (默认 = 12): 线的宽度。 """ line_image = np.zeros_like(image) for line in lines: if line is not None: cv2.line(line_image, *line, color, thickness) return cv2.addWeighted(image, 1.0, line_image, 1.0, 0.0)AI写代码
输出:

图像上的道路车道线检测输出
实时道路车道检测的完整代码
Python3
import numpy as npimport pandas as pdimport cv2from google.colab.patches import cv2_imshow# 导入编辑/保存/观看视频剪辑所需的所有内容from moviepy import editorimport moviepydef region_selection(image): """ 确定并裁剪输入图像中的感兴趣区域。 参数: image: 我们在这里传递从Canny中输出的图像,在这里我们已经识别出了帧的边缘 """ # 创建一个与输入图像相同大小的数组 mask = np.zeros_like(image) # 如果传递的图像具有多个通道 if len(image.shape) > 2: channel_count = image.shape[2] ignore_mask_color = (255,) * channel_count # 如果图像只有一个通道,将会执行“else”部分 else: # 掩码多边形的颜色(白色) ignore_mask_color = 255 # 创建多边形以仅关注图像中的道路 # 我们根据摄像机的放置位置创建了这个多边形 rows, cols = image.shape[:2] bottom_left = [cols * 0.1, rows * 0.95] top_left = [cols * 0.4, rows * 0.6] bottom_right = [cols * 0.9, rows * 0.95] top_right = [cols * 0.6, rows * 0.6] vertices = np.array([[bottom_left, top_left, top_right, bottom_right]], dtype=np.int32) # 用白色颜色填充多边形并生成最终的掩码 cv2.fillPoly(mask, vertices, ignore_mask_color) # 对输入图像和掩码执行位与操作,以获取道路上的边缘 masked_image = cv2.bitwise_and(image, mask) return masked_imagedef hough_transform(image): """ 确定并裁剪输入图像中的感兴趣区域。 参数: image: 应该是边缘检测器的输出的灰度图像 """ # 累加器的像素距离分辨率 rho = 1 # 累加器的弧度角分辨率 theta = np.pi/180 # 只返回大于阈值的直线 阈值 = 20 # 较短的线段将被拒绝 minLineLength = 20 # 同一直线上的点之间允许的最大间隙 maxLineGap = 500 # 函数返回一个包含输入图像中出现的直线维度的数组 return cv2.HoughLinesP(image, rho=rho, theta=theta, threshold=threshold, minLineLength=minLineLength, maxLineGap=maxLineGap)def average_slope_intercept(lines): """ 查找每个图像的左车道和右车道的斜率和截距。 参数: lines: 霍夫变换的输出 """ left_lines = [] # (斜率, 截距) left_weights = [] # (长度,) right_lines = [] # (斜率, 截距) right_weights = [] # (长度,) for line in lines: for x1, y1, x2, y2 in line: if x1 == x2: continue # 计算线的斜率 斜率 = (y2 - y1) / (x2 - x1) # 计算线的截距 截距 = y1 - (斜率 * x1) # 计算线的长度 长度 = np.sqrt(((y2 - y1) ** 2) + ((x2 - x1) ** 2)) # 左车道的斜率为负,右车道的斜率为正 if 斜率 < 0: left_lines.append((斜率, 截距)) left_weights.append((长度)) else: right_lines.append((斜率, 截距)) right_weights.append((长度)) # 如果存在左车道,则计算左车道的平均斜率和截距 left_lane = np.dot(left_weights, left_lines) / np.sum(left_weights) if len(left_weights) > 0 else None # 如果存在右车道,则计算右车道的平均斜率和截距 right_lane = np.dot(right_weights, right_lines) / np.sum(right_weights) if len(right_weights) > 0 else None return left_lane, right_lanedef pixel_points(y1, y2, line): """ 将每条线的斜率和截距转换为像素点。 参数: y1: 线段起点的y值 y2: 线段终点的y值 line: 线段的斜率和截距 """ if line is None: return None 斜率, 截距 = line x1 = int((y1 - 截距) / 斜率) x2 = int((y2 - 截距) / 斜率) y1 = int(y1) y2 = int(y2) return ((x1, y1), (x2, y2))def lane_lines(image, lines): """ 从像素点创建完整长度的线段。 参数: image: 输入测试图像 lines: 霍夫变换的输出线段 """ left_lane, right_lane = average_slope_intercept(lines) y1 = image.shape[0] y2 = y1 * 0.6 left_line = pixel_points(y1, y2, left_lane) right_line = pixel_points(y1, y2, right_lane) return left_line, right_linedef draw_lane_lines(image, lines, color=[255, 0, 0], thickness=12): """ 在输入图像上绘制车道线。 参数: image: 输入测试图像(在我们的情况下为视频帧)。 lines: 霍夫变换的输出线段。 color (默认=红色): 线的颜色。 thickness (默认=12): 线的粗细。 """ line_image = np.zeros_like(image) for line in lines: if line is not None: cv2.line(line_image, *line, color, thickness) return cv2.addWeighted(image, 1.0, line_image, 1.0, 0.0)def frame_processor(image): """ 处理输入帧以检测车道线。 参数: image: 道路图像,其中要检测车道线 (我们将向该函数传递视频帧) """ # 将RGB图像转换为灰度图像 灰度图 = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 应用高斯模糊以去除图像中的噪声 # 并聚焦在我们感兴趣的区域 # 高斯内核的大小 内核大小 = 5 # 应用高斯模糊以去除帧中的噪声 模糊图像 = cv2.GaussianBlur(灰度图, (内核大小, 内核大小), 0) # Hysteresis过程的第一个阈值 低阈值 = 50 # Hysteresis过程的第二个阈值 高阈值 = 150 # 应用Canny边缘检测并将边缘保存在变量中 边缘图 = cv2.Canny(模糊图像, 低阈值, 高阈值) # 由于我们从图像中获取了太多的边缘,我们应用 # 一个掩码多边形来只关注道路 区域 = region_selection(边缘图) # 应用霍夫变换以从图像中获取直线 # 并找到车道线 霍夫 = hough_transform(区域) # 最后,在生成的帧上绘制线条并将其作为输出返回 结果 = draw_lane_lines(image, lane_lines(image, 霍夫)) return 结果# 驱动函数def process_video(test_video, output_video): """ 读取输入视频流并生成检测到车道线的视频文件。 参数: test_video: 输入视频文件的位置 output_video: 要保存输出视频文件的位置 """ # 使用VideoFileClip读取视频文件(不包括音频部分) input_video = editor.VideoFileClip(test_video, audio=False) # 对每个视频帧应用函数“frame_processor” # 将在后续步骤中详细介绍“frame_processor” # “processed”存储输出视频 processed = input_video.fl_image(frame_processor) # 将输出视频流保存到mp4文件 processed.write_videofile(output_video, audio=False)# 调用驱动函数process_video('input.mp4', 'output.mp4')
最终输出:
结论
我们探讨了采用 Canny 边缘检测器和霍夫变换来检测道路车道的各种方法之一。此外,还有其他依赖于复杂的神经网络和传感器数据的道路车道检测方法。这些先进的技术和算法已被特斯拉应用于自动驾驶领域。本文阐述的算法是理解自动驾驶汽车基本工作原理的第一步。我们将发布更多文章以帮助您更轻松地理解自动驾驶等复杂概念。
