通过Canny算子等边缘检测方法获得图像的边缘信息之后, 我们得到仅是多组连续的边缘像素点, 这些像素点包含了极为有用的信息, 但是这些信息我们无法直接使用, 因为图像噪声和图像像素误差的存在,图像的边缘轮廓并不是规则的几何线条, 不能直接通过函数来表达。比如从下面的建筑物图片和Canny算子提取的边缘图中,可以明显看到边缘图像中包含了大量建筑物的边缘信息。
但是,放大建筑物的边缘图像, 可以发现原来我们主观认为很笔直的建筑, 在图像中并不是笔直的线条,而是曲折的像素点, 如下图所示, 因此边缘检测之后还需要对图像边缘进行特征提取, 从离散的像素点中提取有用的特征信息.
在直线检测中, 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变换方法是通过统计量化的局部空间的正弦曲线交点,数量满足阈值条件的点才会被选做直线拟合点,具体方法为:
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]]]]] )
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]]] )
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()