在今天的文章中,将使用基本的计算机视觉技术来解决对于自动驾驶汽车至关重要的街道车道检测问题。到本文结束时,将能够使用Python和OpenCV执行实时通道检测。
实时车道检测
履行
可以在GitHub上找到这个项目的完整代码库
https://github.com/gsurma/street_lanes_finder?source=post_page---------------------------
从定义问题开始。
鉴于道路的图像,想检测它上面的街道。
为了做到这一点,提供一个图像路径并用OpenCV加载它,然后用它来调用find_street_lanes管道。
test_image = cv2.imread(INPUT_FOLDER + TEST_IMAGE)
street_lanes = find_street_lanes(test_image)
这就是find_street_lanes管道的样子
def find_street_lanes(image):
grayscale_image = grayscale(image)
blur_image = blur(grayscale_image)
canny_image = canny(blur_image)
roi_image = roi(canny_image)
hough_lines_image = hough_lines(roi_image, 0.9, np.pi/180, 100, 100, 50)
final_image = combine_images(hough_lines_image, image)
灰度
管道的第一步是从彩色到灰度的图像转换。这样做是因为颜色值不包含任何有价值的信息,因此为了使进一步处理更简单,更快将三个通道转换为一个。
grayscale = (r + g + b) / 3
grayscale_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
模糊
然后为了使图像更平滑,噪点更小,应用了一个温和的高斯模糊。
高斯模糊通过计算每个像素的值作为周围像素的加权平均值来工作。
blur_image = cv2.GaussianBlur(image, (3,3),0)
Canny(边缘检测)
这是有趣的部分开始的地方。现在有了平滑的灰度图像,需要检测其中的边缘。
仅供参考,它被称为'canny',因为它是由John Canny发明的。
在没有涉及太多细节的情况下,canny边缘检测器的核心部分基于扫描图像并计算相邻像素值的导数(梯度)。渐变越高,边缘越可能。
高梯度区的一个例子
canny_image = cv2.Canny(image, 100, 150)
感兴趣的区域
现在检测到边缘,可以清楚地看到街道在哪里,但除此之外,还可以看到其他边缘是多余的。为了摆脱它们,应该将图像掩盖到一个称为感兴趣区域(ROI)的特定区域。确定适当的ROI在很大程度上取决于摄像机校准及其框架,即道路的哪个部分是可见的。
bottom_padding = 100 # Front bumper compensation
height = image.shape[0]
width = image.shape[1]
bottom_left = [0, height-bottom_padding]
bottom_right = [width, height-bottom_padding]
top_right = [width*1/3, height*1/3]
top_left = [width*2/3, height*1/3]
vertices = [np.array([bottom_left, bottom_right, top_left, top_right], dtype=np.int32)]
mask = np.zeros_like(image)
cv2.fillPoly(mask, vertices, 255)
masked_image = cv2.bitwise_and(image, mask)
这是定义的蒙版,其中白色显示ROI
这是带有蒙面感兴趣区域的精确图像。
Hough Lines
现在有明确界定的线条,显示街道所在的位置。但是在屏幕上显示它们看起来并不吸引人,因为它们很吵和闪烁。为了将它们可视化为单行,需要执行霍夫线变换。
首先,检测所有行。将一条线定义为[x1,y1,x2,y2],其中(x1,y1)是它的开始,(x2,y2)是它的结束。
将线条定位后,可以计算它们的斜率,以确定它们是正确的还是左边的。
parameters = np.polyfit((x1, x2), (y1, y2), 1)
slope = parameters[0]
intercept = parameters[1]
if slope >= 0:
right_lines.append([slope, intercept])
else:
left_lines.append([slope, intercept])
然后需要平均线并得到单个左右线。
def merge_lines(image, lines):
if len(lines) > 0:
slope, intercept = np.average(lines, axis=0)
y1 = image.shape[0]
y2 = int(y1*(1/2))
x1 = int((y1 - intercept)/slope)
x2 = int((y2 - intercept)/slope)
return np.array([x1, y1, x2, y2])
left = merge_lines(image, left_lines)
right = merge_lines(image, right_lines)
最后,可以画出最后的界限。
lines_image = np.zeros((image.shape[0], image.shape[1], 3), dtype=np.uint8)
lines = cv2.HoughLinesP(image, rho, theta, threshold, np.array([]), minLineLength=min_line_len, maxLineGap=max_line_gap)
if lines is not None:
lines = averaged_lines(image, lines)
for line in lines:
if line is not None:
x1,y1,x2,y2 = line
cv2.line(lines_image, (x1, y1), (x2, y2), (0, 0, 255), 20)
return lines_image
图像叠加
最后,需要使用派生线覆盖输入图像。
combined_image = cv2.addWeighted(initial_image, α, image, β, λ)
实时检测
拥有可以检测单个帧的线路的管道,可以在每个帧上执行检测的视频流上实时运行它。
下一步是什么?
在这个项目中,学会了如何使用基本的计算机视觉技术来解决现实问题。虽然结果看起来非常有希望,但街道探测器并非完美,在某些情况下可能会失败。这就是为什么在下一部分将使用深度学习方法(整体嵌套边缘检测)来获得更好的准确性和更可靠的探测器。敬请关注!
不要忘记检查项目的GitHub页面。
https://github.com/gsurma/street_lanes_finder?source=post_page---------------------------