深度学习和目标检测系列教程 20-300:OpenCV与图像处理:霍夫变换技术实现直线检测

Posted 刘润森!

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深度学习和目标检测系列教程 20-300:OpenCV与图像处理:霍夫变换技术实现直线检测相关的知识,希望对你有一定的参考价值。

@Author:Runsen

霍夫变换(Hough Transform)是图像处理中的一种特征提取技术,该过程在一个參数空间中通过计算累计结果的局部最大值得到一个符合该特定形状的集合作为霍夫变换结果。

这张图里面有一条看起来挺直的线,就是车道,这张图是百度随便找的。

灰度缩放

将图像转换为灰度的原因是易于处理。与具有三个以上值的彩色图像相比,灰度图像只有一个像素强度值(0 或 1)。这将使灰度图像在单个通道中工作,这将比三个通道的彩色图片更容易和更快地处理。

在代码中创建的图像数组。

import cv2
import numpy as np

image = cv2.imread('lane.jpg')
lane_image = np.copy(image)
# 借助cvtColor openCV 库中的函数将图像转换为灰度。
gray = cv2.cvtColor(lane_image, cv2.COLOR_RGB2GRAY) 

cv2.imshow('result', gray) #to output gray-scale image
cv2.waitKey(0)

运行这个程序,得到的图像应该如下所示。

减少噪音和平滑

在识别图像中,减少噪音和平滑很重要。这种降噪和图像平滑化将通过称为高斯模糊的过滤器来完成。

图像存储为离散像素的集合。灰度图像中的每个像素都由一个描述像素亮度的数字表示。为了平滑图像,我们需要使用周围像素强度的平均值来修改像素的值。

在cv2.GaussianBlur()在灰度图像上应用函数。

在这里,我们在图像上应用 5 * 5 内核窗口。内核的大小取决于情况,但 5*5 窗口对于大多数情况是理想的。我们将blur变量传递给以imshow()获取输出。因此,获得高斯模糊图像的最终代码如下所示:

#coding=gbk
import cv2
import numpy as np

image = cv2.imread('lane.jpg')
lane_image = np.copy(image)
# 借助cvtColor openCV 库中的函数将图像转换为灰度。
gray = cv2.cvtColor(lane_image, cv2.COLOR_RGB2GRAY)
blur = cv2.GaussianBlur(gray, (5, 5), 0)
cv2.imshow('result', blur)
cv2.waitKey(0)

Canny 边缘检测

在图像中,X 对应于图像中的宽度(列数),Y 对应于图像的高度(行数)。宽度和高度的乘积为我们提供了图像中的像素总数。因此可以表示为 X 和 Y 的连续函数,即;f(x, y)。

由于f(x, y)是一个数学函数,我们可以执行运算来确定图像中像素亮度的快速变化。Canny 方法将在 x 和 y 方向上为我们提供函数的导数。我们可以使用这个导数来测量与相邻像素有关的强度变化。

通过计算各个方向的导数,调用cv2.Canny(),我们得到图像的梯度。

cv2.Canny(image, low_threshold, high_threshold)

两个参数low_threshold并high_threshold 允许我们遵循最强gradience相邻像素隔离。如果梯度大于阈值上限,则将其识别为边缘像素。如果低于下限,则会被拒绝。

我们将采用 1:3 的低高阈值比率。这次我们输出的是canny图像

canny = cv2.Canny(blur, 50, 150) 
cv2.imshow('result', canny)
cv2.waitKey(0)

ROI

模型检测之前,我们必须指定我们有兴趣检测车道线的区域(ROI)。

在这种情况下,我们将感兴趣的区域作为道路的中间,如图所示。

为找到我们感兴趣区域的位置,使用matplotlib库来定位坐标并隔离该区域。

import cv2
import numpy as np
import matplotlib.pyplot as plt


def canny(image):
    gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
    blur = cv2.GaussianBlur(gray, (5, 5), 0)
    canny = cv2.Canny(blur, 50, 150)
    return canny


image = cv2.imread('lane.jpg')
lane_image = np.copy(image)
canny = canny(lane_image)
plt.imshow(canny)
plt.show()


现在我们已经获得了 ROI 所需的测量值,我们将生成一个图像来掩盖其他所有内容。我们得到的图像是原始图像中具有指定顶点的多边形的蒙版。

下面的代码的像素坐标是随便找的。

import cv2
import numpy as np
import matplotlib.pyplot as plt


def canny(image):
    gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
    blur = cv2.GaussianBlur(gray,(5, 5), 0)
    canny = cv2.Canny(blur, 50, 150)
    return canny

def region_of_interest(image):
    polygons = np.array([
        [(0, 380), (300, 100),(400, 100), (620, 430)]
    ])
    mask = np.zeros_like(image)
    cv2.fillPoly(mask, polygons, 255)
    masked_image = cv2.bitwise_and(image, mask)
    return masked_image

image = cv2.imread('lane.jpg')
lane_image = np.copy(image)

canny_image = canny(lane_image)
cropped_image = region_of_interest(canny_image)
cv2.imshow('result',cropped_image)
cv2.waitKey(0)


通过利用cv2.bitwise_and() 的函数来实现这一点,该函数计算两个图像的按位 &以仅显示由掩码的多边形轮廓跟踪的 ROI。

虽然说图片上还有一些车,但是影响不大,最后一步是使用霍夫变换来检测我们感兴趣的孤立区域中的直线。

霍夫变换

现在拥有的图片只是一系列像素,我们无法直接找到几何表示来了解斜率和截距。

由于算法时间复杂度的关系,我们不能暴力遍历像素来找到斜率和截距,因此可以使用霍夫变换的地方。它帮助我们找出突出的线条并连接图像中不相交的边缘点。

霍夫变换就是一个斜率和截距转换成为x和y的过程。


XY 平面中的一个点可以有任意数量的线穿过它。为了识别图片中的线条,我们必须将每个边缘像素想象为坐标空间中的一个点,然后将该点转换为霍夫空间中的一条线。

只要所有线都经过一个点,那么这些线的斜率和截距,在霍夫变换必然呈现一条线。


我们需要在 XY 平面上找到两条或更多条线,来表示它们在霍夫空间中相交的对应点,就可以检测线。

这是因为两个点可以确定同一条直线。

在openCV 已经有一个被调用的函数cv2.HoughLinesP(),

lines = cv2.HoughLinesP(cropped_image, 2, np.pi/180, 100, np.array([]), minLineLength=40, maxLineGap=5)
  • 第一个参数是之前生成的裁剪图像,它是孤立车道线的梯度图像。

  • 第二个和第三个参数指定霍夫累加器数组(为识别大多数交叉点而创建的网格)的分辨率。

  • 第四个参数是确定检测线路所需的最小投票数所需的阈值。

我们将定义 3 个函数来优化和显示车道线。

display_lines:这个函数来用与原始图像相似的测量值标记黑色图像上的线条,然后将其混合到我们的彩色图像中。

def display_lines(image, lines):
    line_image = np.zeros_like(image)
    if lines is not None:
        for x1, y1, x2, y2 in lines:
            cv2.line(line_image, (x1, y1), (x2, y2), (255, 0, 0), 10)
    return line_image

make_coordinates:这将为我们指定坐标,能够标记斜率和 y 轴截距。

def make_coordinates(image, line_parameters):
    slope, intercept = line_parameters
    y1 = image.shape[0]
    y2 = int(y1*(3/5))
    x1 = int((y1 - intercept)/slope)
    x2 = int((y2 - intercept)/slope)
    return np.array([x1, y1, x2, y2])

average_slope_intercept:声明了两个空的名单-left_fit和right_fit这将右分别包含左侧的平均线和线的坐标上的坐标。

def average_slope_intercept(image, lines):
    left_fit = []
    right_fit = []
    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)
    left_line = make_coordinates(image, left_fit_average)
    right_line = make_coordinates(image, right_fit_average)
    return np.array([left_line, right_line])

霍夫变换技术实现直线检测的完整代码:

import cv2
import numpy as np

def make_coordinates(image, line_parameters):
    slope, intercept = line_parameters
    y1 = image.shape[0]
    y2 = int(y1*(3/5))
    x1 = int((y1 - intercept)/slope)
    x2 = int((y2 - intercept)/slope)
    return np.array([x1, y1, x2, y2])

def average_slope_intercept(image, lines):
    left_fit = []
    right_fit = []
    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)
    left_line = make_coordinates(image, left_fit_average)
    right_line = make_coordinates(image, right_fit_average)
    return np.array([left_line, right_line])

def canny(image):
    gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
    blur = cv2.GaussianBlur(gray,(5, 5), 0)
    canny = cv2.Canny(blur, 50, 150)
    return canny

def display_lines(image, lines):
    line_image = np.zeros_like(image)
    if lines is not None:
        for x1, y1, x2, y2 in lines:
            cv2.line(line_image, (x1, y1), (x2, y2), (255, 255, 255), 10)
    return line_image

def region_of_interest(image):
    polygons = np.array([
        [(0, 380), (300, 100),(400, 100), (620, 430)]
    ])
    mask = np.zeros_like(image)
    cv2.fillPoly(mask, polygons, 255)
    masked_image = cv2.bitwise_and(image, mask)
    return masked_image

image = cv2.imread('lane.png')
lane_image = np.copy(image)

canny_image = canny(lane_image)
cropped_image = region_of_interest(canny_image)
lines = cv2.HoughLinesP(cropped_image, 2, np.pi/180, 100, np.array([]), minLineLength=40, maxLineGap=5)
averaged_lines = average_slope_intercept(lane_image, lines)
line_image = display_lines(lane_image, averaged_lines)

combo_image = cv2.addWeighted(lane_image, 0.8, line_image, 1, 1)

cv2.imshow('result',combo_image)
cv2.waitKey(0)

检测视频中的车道

#coding=gbk
'''
@Author:Runsen
'''
import cv2
import numpy as np

def make_coordinates(image, line_parameters):
    slope, intercept = line_parameters
    y1 = image.shape[0]
    y2 = int(y1*(3/5))
    x1 = int((y1 - intercept)/slope)
    x2 = int((y2 - intercept)/slope)
    return np.array([x1, y1, x2, y2])

def average_slope_intercept(image, lines):
    left_fit = []
    right_fit = []
    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)
    left_line = make_coordinates(image, left_fit_average)
    right_line = make_coordinates(image, right_fit_average)
    return np.array([left_line, right_line])

def canny(image):
    gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
    blur = cv2.GaussianBlur(gray,(5, 5), 0)
    canny = cv2.Canny(blur, 50, 150)
    return canny

def display_lines(image, lines):
    line_image = np.zeros_like(image)
    if lines is not None:
        for x1, y1, x2, y2 in lines:
            cv2.line(line_image, (x1, y1), (x2, y2), (255, 255, 255), 10)
    return line_image

def region_of_interest(image):
    height = image.shape[0]
    polygons = np.array([
    [(200, height), (1100, height), (550, 250)]
    ])
    mask = np.zeros_like(image)
    cv2.fillPoly(mask, polygons, 255)
    masked_image = cv2.bitwise_and(image, mask)
    return masked_image


cap = cv2.VideoCapture('test2.mp4')
while(cap.isOpened()):
    _, frame = cap.read()
    canny_image = canny(frame)
    cropped_image = region_of_interest(canny_image)
    lines = cv2.HoughLinesP(cropped_image, 2, np.pi/180, 100, np.array([]), minLineLength=40, maxLineGap=5)
    averaged_lines = average_slope_intercept(frame, lines)
    line_image = display_lines(frame, averaged_lines)
    combo_image = cv2.addWeighted(frame, 0.8, line_image, 1, 1)
    cv2.imshow('result',combo_image)
    if cv2.waitKey(1) == ord('q'):
        break
cap.release()
cv2.destroyAllWindows()

以上是关于深度学习和目标检测系列教程 20-300:OpenCV与图像处理:霍夫变换技术实现直线检测的主要内容,如果未能解决你的问题,请参考以下文章

深度学习和目标检测系列教程 1-300:什么是对象检测和常见的8 种基础目标检测算法

深度学习和目标检测系列教程 3-300:了解常见的目标检测的开源数据集

深度学习和目标检测系列教程 2-300:小试牛刀,使用 ImageAI 进行对象检测

深度学习和目标检测系列教程 19-300:关于目标检测APIoU和mAP简介

深度学习和目标检测系列教程 19-300:关于目标检测APIoU和mAP简介

深度学习和目标检测系列教程 5-300:早期的目标检测RCNN架构