检测车道线——5.霍夫变换 Hough Transform
图2
答案: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> -x1 > -x2, y1 < y2
题4:
下图中,霍夫空间中两条线的交点在图像空间中对应什么?
图6
答案:A
解释:
1、霍夫空间中,两条直线代表图像空间的两个点,两条直线都经过(m0,b0),说明图像空间的两个点在参数为(m0,b0)的直线上;
2、m0 > 0,b0 > 0,说明斜率为正、截距为正。
图7
由于垂直线的斜率是无限大的,不利于计算,因此我们需要一个新的参数表达——通过极坐标重新定义直线,如图8所示。
ρ表示直线到原点的垂直距离,θ表示直线的垂线与横轴的夹角。
可以得到 x0=ρ0*cosθ0 y0=ρ0*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θ0 + 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