python+opencv–Hough直线检测
通过Canny算子等边缘检测方法获得图像的边缘信息之后, 我们得到仅是多组连续的边缘像素点, 这些像素点包含了极为有用的信息, 但是这些信息我们无法直接使用, 因为图像噪声和图像像素误差的存在,图像的边缘轮廓并不是规则的几何线条, 不能直接通过函数来表达。比如从下面的建筑物图片和Canny算子提取的边缘图中,可以明显看到边缘图像中包含了大量建筑物的边缘信息。
但是,放大建筑物的边缘图像, 可以发现原来我们主观认为很笔直的建筑, 在图像中并不是笔直的线条,而是曲折的像素点, 如下图所示, 因此边缘检测之后还需要对图像边缘进行特征提取, 从离散的像素点中提取有用的特征信息.
Hough变换
在直线检测中, Hough变换采用参数空间变换的方法提取边缘像素带中直线, Hough变换可以降低噪声的影响,对连续直线的检测具有鲁棒性.
在直角坐标系中, 直线的方程为:
y
=
k
x
+
b
y = kx + b
y=kx+b.
直线方程可以唯一的用
(
k
,
b
)
(k,b)
(k,b)来表示,也可以用极坐标中
(
ρ
,
θ
)
(ρ,\theta)
(ρ,θ)唯一的表示。其中,
ρ
\rho
ρ表示直线垂点到原点的距离,
θ
\theta
θ表示
ρ
\rho
ρ与
x
x
x轴的夹角。
直线方程是唯一的,所以直线上所有的点在极坐标系中对应
(
ρ
,
θ
)
(\rho,\theta)
(ρ,θ)也是唯一的。
而直角坐标系中的一个点
(
x
,
y
)
(x,y)
(x,y),过该点的直线可能有无数条,这无数条直线与
x
x
x轴的夹角取值范围是
[
0
,
2
π
]
[0, 2\pi]
[0,2π],则直角坐标系中的一个点对应于极坐标系中的一条正弦曲线
ρ
=
x
c
o
s
θ
+
y
s
i
n
θ
\rho=xcos\theta + ysin\theta
ρ=xcosθ+ysinθ
同一条直线的
(
ρ
,
θ
)
(\rho, \theta)
(ρ,θ)是唯一的,因此同一条直线上的点在极坐标中的正弦曲线必然相交于一点
轮廓边缘像素点在直角坐标系中的二维像素坐标,转换到极坐标中,就可以得到对应的正弦曲线,如果直接统计所有的正弦曲线的交点,我们会得到很多误匹配的直线。
Hough变换方法是通过统计量化的局部空间的正弦曲线交点,数量满足阈值条件的点才会被选做直线拟合点,具体方法为:
- 将 ( ρ , θ ) (\rho, \theta) (ρ,θ)空间量化成许多小格子
- 将图像平面直角坐标系中的每一个直线点变换到 ( ρ , θ ) (\rho, \theta) (ρ,θ)空间
- 统计每个小格子中的共线点,设置累计阈值 T T T,如果小格子中的共线点数量大于 T T T,则格子中的共线点可以用于拟合直线参数,小于 T T T的格子被认为是非共线点,丢弃
opencv函数
opencv中提供了两个函数用于Hough直线检测
-
cv.HoughLines()
C++: void cv::HoughLines ( InputArray image, # 输入单通道8-bit二值化图像 OutputArray lines, # 输出的直线向量,每条直线用2或3个元素向量(ρ,θ)表示 double rho, # 累加器的像素分辨率,一般取1像素 double theta, # 累加器的角度分辨率(弧度),一般取np.pi/180 int threshold, # 累加器阈值 ''' srn用于多尺度Hough变换,粗累加器距离分辨率为rho, 精确累加器的分辨率为rho/srn(提高提取精度), srn和stn取0的话表示采用经典Hough变换,不使用多尺度Hough变换''' double srn = 0, double stn = 0, # 同上,针对角度分辨率theta double min_theta = 0, # 检测直线的最小角度 double max_theta = CV_PI # 检测直线的最大角度 ) Python: lines = cv.HoughLines( image, rho, theta, threshold[, lines[, srn[, stn[, min_theta[, max_theta]]]]] )
- cv.HoughLinesP()
Hough变换的改进版本——概率Hough变换
C++: void cv::HoughLinesP ( InputArray image, OutputArray lines, double rho, double theta, int threshold, double minLineLength = 0, # 最小直线长度 double maxLineGap = 0 # 最大线段间隙 ) Python: lines = cv.HoughLinesP( image, rho, theta, threshold[, lines[, minLineLength[, maxLineGap]]] )
- cv.HoughLinesP()
示例代码
import cv2 as cv
import numpy as np
import matplotlib as plt
# read image and check
filename = "/home/lihoon/code/opencvImg/hough/building.jpg"
img = cv.imread(filename)
img_p = img.copy() #用于显示概率Hough变换的检测结果
if img is None:
print("Image read error!")
else:
print("Image read successful!")
# cv.imshow("Source", img)
# image space change from BGR to GRAY
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# threshold
# _, binary = cv.threshold(img, 200, 255, cv.THRESH_BINARY)
_, binary = cv.threshold(gray, 0, 255, cv.THRESH_OTSU)
# cv.imshow("Threshold", binary)
# edges detection with Canny method
edges = cv.Canny(binary, threshold1=50, threshold2=200)
# cv.imshow("Canny", edges)
# HoughLines()函数
lines = cv.HoughLines(edges, rho = 1, theta = 1 * np.pi/180, threshold=120, srn=0, stn = 0, min_theta=1, max_theta=2)
for i in range(0, len(lines)):
rho, theta = lines[i][0][0], lines[i][0][1]
a = np.cos(theta)
b = np.sin(theta)
x0 = a*rho
y0 = b*rho
x1 = int(x0 + 1000*(-b))
y1 = int(y0 + 1000*(a))
x2 = int(x0 - 1000*(-b))
y2 = int(y0 - 1000*(a))
cv.line(img, (x1, y1), (x2, y2), (0, 0, 255), 2)
cv.imshow("Hough_line", img)
# HoughLinesP()函数
lines_p = cv.HoughLinesP(edges, rho = 1, theta = np.pi/180, threshold = 50, minLineLength= 30, maxLineGap=10)
for i in range(len(lines_p)):
x_1, y_1, x_2, y_2 = lines_p[i][0]
cv.line(img_p, (x_1, y_1), (x_2, y_2), (0, 255, 0), 2)
print("code successful!")
cv.imshow("Hough_line_p", img_p)
cv.waitKey(0)
cv.destroyAllWindows()
检测结果如下图所示