检测车道线——5.霍夫变换 Hough Transform

1、 霍夫变换原理    
  霍夫变换:从图像空间到霍夫空间的转换。
  目的:是为了用参数空间表示图像空间中的线(图1)。
                                                       图1
  在霍夫空间中,我可以将图像空间的“x vs. y”线表示为霍夫空间的“m vs. b”中的一个点。
  因此,图像空间中一条线的表征将是霍夫空间中位置 (m, b) 处的单个点(图2)。

 

                                                      图2

题1:        
  空间坐标系下,y=mx+b
       变换到霍夫空间后,是图3哪种情况?
                                                      图3

 答案:C

理由:

1、空间坐标系的直线转换到霍夫空间是点,因为直线的参数确定,确定的参数在霍夫空间里面就是点;m相同,b不同

2、b= -xm+y,两个及以上的(x,y)可以确定一个(m,b),所以笛卡尔坐标系里面的2条直线可以对应到霍夫空间里面的2个点。

 

题2:

  下图中,图像空间中的点对应于霍夫空间中的什么? 

                                                      图4

 答案:A

解释:

1、空间坐标系的点转换到霍夫空间是直线,因为经过这个点有无穷个直线,每个直线对应的(m,b)不同,都经过同一个点。

 2、b= -x0m+y0,是一条确定的直线。

 

题3:

  下图中,图像空间中两点在霍夫空间中的表示是什么?  

 

                                                      图5

 答案:C

解释:

1、图像空间中,任意2点都能经过一条直线,所以霍夫空间里面2条直线必有一个交点,即为共线的直线的参数;

2、b= -x1m+y1,b= -x2m+y2, 0> -x> -x2, y< y2

 

题4:  

  下图中,霍夫空间中两条线的交点在图像空间中对应什么?

                                                      图6

答案:A

解释:

1、霍夫空间中,两条直线代表图像空间的两个点,两条直线都经过(m0,b0),说明图像空间的两个点在参数为(m0,b0)的直线上;

2、m> 0,b0 > 0,说明斜率为正、截距为正。

 

  通过题3和题4可以看出,在图像空间中寻找线,可以通过在霍夫空间中寻找相交的线。
  我们通过将霍夫空间划分为一个网格来做到这一点,并将相交线定义为通过给定网格单元的所有线。
  首先运行 Canny 边缘检测算法来查找与图像中的边缘相关的所有点。
  然后,我可以将这个边缘检测图像中的每个点视为霍夫空间中的一条线,如图7所示。在霍夫空间中的许多线相交的地方,可以说明找到了一组点(类比题3和题4),这些点描述了图像空间中的一条线。

                                                                 图7

   由于垂直线的斜率是无限大的,不利于计算,因此我们需要一个新的参数表达——通过极坐标重新定义直线,如图8所示。

  ρ表示直线到原点的垂直距离,θ表示直线的垂线与横轴的夹角。

  可以得到   x00*cosθ0     y00*sinθ0  →   x0*cosθ0 + y0*sinθ0 = ρ0

 

                                                                图8

       因为 x0*cosθ + y0*sinθ = ρ

  现在图像空间中的每个点对应于霍夫空间中的一条正弦曲线。

       假设x0和y0是(1,1)的情况下,x0*cosθ + y0*sinθ = ρ 如下图所示,可以看出是三角函数。

  如果我们取一整条点,它会转化为霍夫空间中的一大堆正弦曲线。类比直线的情况,这些正弦曲线在霍夫空间中的交点,为图像空间直线的参数。

 

                                                              图9

题5:  

  如果我们在正方形图像上运行霍夫变换会发生什么? 霍夫空间中的对应图会是什么样子?

 

                                                              图10

 

答案:C

解释:

1、图像空间有4条直线,说明霍夫空间有4个交点;

2、4个点(θ,ρ)为(0,ρ1)、(0,ρ2)、(π/2,ρ1)、(π/2,ρ2),可以看出,四个点。

        x0*cosθ+ y0*sinθ0 = ρ0   →  y = ρ0 - (cosθ0/ sinθ0) * x

 

 2、 霍夫变换在车道线检测中的应用

  车道线检测涉及更详细的车道线属性,例如长线、短线、弯曲线、虚线等。

  在使用Canny function提取边缘的图像中,使用OpenCV的函数HoughLinesP( )。(相关资料可以参考OpenCV_Hough Line Transform不用OpenCV求解Hough Transform的方法。)

       HoughLinesP如下:

lines = cv2.HoughLinesP(edges, rho, theta, threshold, np.array([]), min_line_length, max_line_gap)

   输入是canny算法输出的边缘检测结果,输出是线段集,通过(x1, y1, x2, y2) 表示。首先,rho 和 theta 是我们网格在霍夫空间中的距离和角分辨率。 在霍夫空间中,我们有一个沿 (Θ, ρ) 轴布置的网格。 您需要以像素为单位指定 rho,以弧度为单位指定 theta。rho 的最小值为 1,theta 的合理起点是 1 度(pi/180 弧度)。 将这些值放大,以便在定义线的构成时更加灵活。阈值参数指定候选线进入输出所需的最小票数(给定网格单元中的交叉点)。 空的 np.array([]) 只是一个占位符,无需更改。 min_line_length 是您将在输出中接受的线的最小长度(以像素为单位),而 max_line_gap 是您允许连接成一条线的线段之间的最大距离(同样,以像素为单位)。 然后你可以遍历你的输出线并将它们绘制到图像上。

# Do relevant imports
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np
import cv2

# Read in and grayscale the image
image = mpimg.imread('C:/test/edge.jpg')

# 因为image就是单通道灰度图像,所以用IMREAD_GRAYSCALE以灰度模式加载图片
gray = cv2.cvtColor(image,cv2.IMREAD_GRAYSCALE)

# Define a kernel size and apply Gaussian smoothing
kernel_size = 5
blur_gray = cv2.GaussianBlur(gray,(kernel_size, kernel_size),0)

# Define our parameters for Canny and apply
low_threshold = 50
high_threshold = 150
edges = cv2.Canny(blur_gray, low_threshold, high_threshold)

# Define the Hough transform parameters
# Make a blank the same size as our image to draw on
rho = 1
theta = np.pi/180
threshold = 1
min_line_length = 10
max_line_gap = 1
line_image = np.copy(image)*0 #creating a blank to draw lines on

# Run Hough on edge detected image
lines = cv2.HoughLinesP(edges, rho, theta, threshold, np.array([]),
min_line_length, max_line_gap)

# Iterate over the output "lines" and draw lines on the blank
for line in lines:
for x1,y1,x2,y2 in line:
cv2.line(line_image,(x1,y1),(x2,y2),(255,0,0),10)


# Draw the lines on the edge image
combo = cv2.addWeighted(edges, 0.8, line_image, 1, 0)

plt.imshow(combo)
plt.show()

  上述的线段很多,有很多无效线段,需要找出在优化车道线检测方面做得好的参数 ,然后,在应用感兴趣区域蒙版来过滤掉图像其他区域中检测到的线段。之前是用的三角形区域掩码,这次使用 cv2.fillPoly() 函数使用四边形区域掩码(或者复杂的多边形区域)。

  使用 50 的 low_threshold 和 150 的 high_threshold 进行 Canny 边缘检测。

  对于区域选择,定义了 vertices = np.array([[(0,imshape[0]),(450, 290), (490, 290), (imshape[1],imshape[0])]], dtype =np.int32)

   将霍夫空间网格的参数选择为 2 像素的 rho 和 1 度的 theta(pi/180 弧度)。 选择了 15 的threshold ,这意味着图像空间中至少有 15 个点需要与每个线段相关联。 40 像素的 min_line_length 和 20 像素的 max_line_gap

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

# Read in and grayscale the image
image = mpimg.imread('C:/test/exit-ramp.jpg')
gray = cv2.cvtColor(image,cv2.COLOR_RGB2GRAY)

# Define a kernel size and apply Gaussian smoothing
kernel_size = 5
blur_gray = cv2.GaussianBlur(gray,(kernel_size, kernel_size),0)

# Define our parameters for Canny and apply
low_threshold = 50
high_threshold = 150
edges = cv2.Canny(blur_gray, low_threshold, high_threshold)

# Next we'll create a masked edges image using cv2.fillPoly()
mask = np.zeros_like(edges)
ignore_mask_color = 255

# This time we are defining a four sided polygon to mask
imshape = image.shape
vertices = np.array([[(0,imshape[0]),(450, 290), (490, 290),(imshape[1],imshape[0])]], dtype=np.int32)
cv2.fillPoly(mask, vertices, ignore_mask_color)
masked_edges = cv2.bitwise_and(edges, mask)

# Define the Hough transform parameters
# Make a blank the same size as our image to draw on
rho = 2 # distance resolution in pixels of the Hough grid
theta = np.pi/180 # angular resolution in radians of the Hough grid
threshold = 15     # minimum number of votes (intersections in Hough grid cell)
min_line_length = 40 #minimum number of pixels making up a line
max_line_gap = 20    # maximum gap in pixels between connectable line segments
line_image = np.copy(image)*0 # creating a blank to draw lines on

# Run Hough on edge detected image
# Output "lines" is an array containing endpoints of detected line segments
lines = cv2.HoughLinesP(masked_edges, rho, theta, threshold, np.array([]),
                            min_line_length, max_line_gap)

# Iterate over the output "lines" and draw lines on a blank image
for line in lines:
    for x1,y1,x2,y2 in line:
        cv2.line(line_image,(x1,y1),(x2,y2),(255,0,0),10)

# Create a "color" binary image to combine with line image
color_edges = np.dstack((edges, edges, edges))

# Draw the lines on the edge image
lines_edges = cv2.addWeighted(color_edges, 0.8, line_image, 1, 0)
plt.imshow(lines_edges)
plt.show()

 

 

 总结:转换灰度图→高斯平滑→canny边缘检测→ROI掩膜→霍夫变换

 

 

-- 附录--

1、进一步理解霍夫变换

  笛卡尔坐标系下的直线如下所示。

 

  在笛卡尔图像空间中共线的点将在m-b(gradient-intercept)参数空间中相交。若图像空间所有点都在同一条直线上,表示相交在参数空间中的同一个点处。 参数空间中的公共点 (m, b) 表示图像空间中的线。如下图所示,图像空间中某一点(x1,y1)处可能存在的所有的直线(蓝色)的(m,b),对应到参数空间中蓝色直线 b= -mx1+y1 中的(m,b)的值。

 

   gradient-intercept参数空间中的斜率会无限大,所以转换到angle-distance参数空间——极坐标系。将笛卡尔坐标系中的(m,b)参数,转为极坐标系的参数如下所示

   ρ = x cos θ + y sin θ

 

 

 

算法步骤:

(1)边缘检测的图像(0/1二值图)作为输入

(2)ρθ的范围设置,ρ最大为图片对角线长度;

(3)θ 与 ρ 的霍夫累加器Hough accumulator, 它是一个二维数组,行数等于 ρ 值的数量,列数等于 θ 值的数量;

(4)在累加器中投票, 对于每个边缘点和每个 θ 值,找到最接近的 ρ 值并在累加器中增加该索引,每个元素都告诉有多少点/像素为具有参数 (ρ,θ) 的潜在候选线贡献了“投票”。

(5)找峰值。 累加器中的局部最大值表示输入图像中最突出的线条的参数。 通过应用阈值或相对阈值(等于或大于全局最大值的某个固定百分比的值),可以最容易地找到峰值。



Python下的霍夫算法
import numpy as np

def hough_line(img):
  # Rho and Theta ranges
  thetas = np.deg2rad(np.arange(-90.0, 90.0))
  width, height = img.shape
  diag_len = np.ceil(np.sqrt(width * width + height * height))   # max_dist
  rhos = np.linspace(-diag_len, diag_len, diag_len * 2.0)

  # Cache some resuable values
  cos_t = np.cos(thetas)
  sin_t = np.sin(thetas)
  num_thetas = len(thetas)

  # Hough accumulator array of theta vs rho
  accumulator = np.zeros((2 * diag_len, num_thetas), dtype=np.uint64)
  y_idxs, x_idxs = np.nonzero(img)  # (row, col) indexes to edges

  # Vote in the hough accumulator
  for i in range(len(x_idxs)):
    x = x_idxs[i]
    y = y_idxs[i]

    for t_idx in range(num_thetas):
      # Calculate rho. diag_len is added for a positive index
      rho = round(x * cos_t[t_idx] + y * sin_t[t_idx]) + diag_len
      accumulator[rho, t_idx] += 1

  return accumulator, thetas, rhos

算法调用

# Create binary image and call hough_line
image = np.zeros((50,50))
image[10:40, 10:40] = np.eye(30)
accumulator, thetas, rhos = hough_line(image)

# Easiest peak finding based on max votes
idx = np.argmax(accumulator)
rho = rhos[idx / accumulator.shape[1]]
theta = thetas[idx % accumulator.shape[1]]
print "rho={0:.2f}, theta={1:.0f}".format(rho, np.rad2deg(theta))

结果

rho=0.50, theta=-45

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2022-06-30 17:36  瘦小园  阅读(1417)  评论(0编辑  收藏  举报