磐创AI分享
来源 | 深度学习与计算机视觉
编辑 | 磐怼怼
对于所有想知道如何在一篇文章中涵盖这一概念的人,我想说,在你深入探索之前,事情听起来很复杂。我不会说这篇文章非常简单,但是它的确是建立在非常基础的计算机视觉概念之上的。
先决条件是什么?具备一些基本的OpenCV知识会很好。如果没有,请不要担心,我将尝试解释我将使用的OpenCV函数,并为你提供参考,以更详细地检查它们。
本文的每一节将介绍一个最终将在程序的主要部分中使用的函数。此外,在本文中,我将使用图像演示所有内容。你可以重用相同的代码来使用视频(因为视频只是图像的集合)。
步骤1:边缘检测
我们将使用Canny边缘检测。如果你不确定这是什么,看看我之前的文章,它以实用的方式解释了这一点。
def canyEdgeDetector(image):
edged = cv2.Canny(image, 50, 150)
return edged
这是我们应用Canny边缘检测后的输出结果
检测Canny边缘后输出
步骤2:定义ROI(感兴趣区域)
驾驶时,为了让汽车保持在车道上,你只关注当前道路的下一个100米。而且,你也不关心护栏另一边的路。这就是我们感兴趣的区域。我们从图像中隐藏不必要的细节,只显示能帮助我们找到车道的区域。
红色的三角形表示我们感兴趣的区域
def getROI(image):
height = image.shape[0]
width = image.shape[1]
# Defining Triangular ROI: The values will change as per your camera mounts
triangle = np.array([[(100, height), (width, height), (width-500, int(height/1.9))]])
# creating black image same as that of input image
black_image = np.zeros_like(image)
# Put the Triangular shape on top of our Black image to create a mask
mask = cv2.fillPoly(black_image, triangle, 255)
# applying mask on original image
masked_image = cv2.bitwise_and(image, mask)
return masked_image
创建一个与原始图像相同形状的黑色图像
创建一个面具
原始图像+蒙版=具有ROI的最终图像
这一步的输出类似于:
getROI ()之后的输出
在得到感兴趣区域之前进行边缘检测是很重要的,否则边缘检测也会检测出我们感兴趣区域的边界。
步骤3:获取图像中的所有直线
下一步是通过ROI得到图像中的所有直线。houghlinesp()可以帮助你实现这一点。这个函数返回它能在输入图像中找到的所有直线的列表。每一行用[x1, y1, x2, y2]表示。
现在,这看起来很简单,但是houghlinesp检测的基本工作原理需要一点时间来解释。所以我不会在本文中介绍它。相反,我建议你看一看此教程(#28,#29,#30应该足以理解霍夫直线原则)。
def getLines(image):
lines = cv2.HoughLinesP(image, 0.3, np.pi/180, 100, np.array([]), minLineLength=70, maxLineGap=20)
return lines
必须根据你的需求调整cv2.HoughLinesP()的参数(尝试更改和调试最适合你的)。但我认为以上这些应该在大多数情况下都适用。这一步的输出是这样的:
在图像中检测到3条线。图像中可能检测到数百条线。因此,调整参数以获得尽可能少的线
步骤4:一些实用函数
下面的实用函数获取图像和线条列表,并在图像上绘制线条。(这个步骤没有从步骤3获取任何输入。相反,这只是一个将从Step5调用的实用程序步骤,因此你首先查看Step5并在需要时访问该步骤)。
def displayLines(image, lines):
if lines is not None:
for line in lines:
x1, y1, x2, y2 = line.reshape(4) #converting to 1d array
cv2.line(image, (x1, y1), (x2, y2), (255, 0, 0), 10)
return image
我们定义了另一个实用函数来从它的参数(斜率和截距)得到线坐标。记住,直线用y=mx+c表示,其中m是斜率,c是截距。
def getLineCoordinatesFromParameters(image, line_parameters):
slope = line_parameters[0]
intercept = line_parameters[1]
y1 = image.shape[0] # since line will always start from bottom of image
y2 = int(y1 * (3.4 / 5)) # some random point at 3/5
x1 = int((y1 - intercept) / slope)
x2 = int((y2 - intercept) / slope)
return np.array([x1, y1, x2, y2])
注意我们是如何选择y和y的值的
步骤5:平滑线条
一旦我们从步骤3中获得了直线,在这一步中我们将这些直线分成两组(左边和右边)。如果你注意到步骤3的输出图像,那么该步骤将把Line1和line2放到左边的组中,而Line3放到右边的组中。
如何在车道的左侧和右侧获得一条公共线
分组后,我们找到该组的平均斜率(m)和截距(c),并通过调用getLineCoordinatesFromParameters() 并传递平均值m和平均值c来为每个组创建一条线。
下面是完成这一切的函数:
def getSmoothLines(image, lines):
left_fit = [] # will hold m,c parameters for left side lines
right_fit = [] # will hold m,c parameters for right side lines
for line in lines:
x1, y1, x2, y2 = line.reshape(4)
parameters = np.polyfit((x1, x2), (y1, y2), 1)
slope = parameters[0]
intercept = parameters[1]
if slope < 0:
left_fit.append((slope, intercept))
else:
right_fit.append((slope, intercept))
left_fit_average = np.average(left_fit, axis=0)
right_fit_average = np.average(right_fit, axis=0)
# now we have got m,c parameters for left and right line, we need to know x1,y1 x2,y2 parameters
left_line = getLineCoordinatesFromParameters(image, left_fit_average)
right_line = getLineCoordinatesFromParameters(image, right_fit_average)
return np.array([left_line, right_line])
这是线条分组后的图像:
线分组后输出
一旦我们准备好了各个函数,我们只需要在我们的主代码中调用它们,你就会在你的图像中检测到车道。
image = cv2.imread("3.jpg") #Load Image
edged_image = canyEdgeDetector(image) # Step 1
roi_image = getROI(edged_image) # Step 2
lines = getLines(roi_image) # Step 3
smooth_lines = getSmoothLines(image, lines) # Step 5
image_with_smooth_lines = displayLines(image, smooth_lines) # Step 4
cv2.imshow("Output", image_with_smooth_lines)
cv2.waitKey(0)
输出会像这样:
具有确定车道的最
最后的话
终输出
你一直看到文章的结尾。对所有内容进行排序并使其适合图像后,你便知道如何将其用于视频。你可能已经意识到你可以如何巧妙地使用非常基本的计算机视觉操作来实现如此有用的东西。
我想说的是,不要把这项工作与特斯拉这样的大公司做的比较(他们的基础也是类似的)。把这作为动力,也许在某个时候,你也能取得类似的成就。
在此之前,请继续探索:你可以在这里查看Github源代码的链接。
检测到视频上的通道后的样本输出(由于录像机的原因,帧速度很慢):