霍夫变换(Hough Transform)是一种用于检测图像中特定形状(如直线、圆等)的经典算法,其检测直线的原理如下:
直线的参数表示
在笛卡尔坐标系中,直线可以用方程\(y = kx + b\)来表示,其中k是斜率,b是截距。但这种表示方法存在一些问题,例如当直线垂直于x轴时,斜率k会趋于无穷大,导致数值计算上的困难。
为了避免这个问题,在霍夫变换中通常使用极坐标来表示直线。对于平面上的任意一点\((x,y)\),从原点到该点的向量可以用极坐标\((\rho,\theta)\)来表示,其中\(\rho\)是向量的长度,\(\theta\)是向量与x轴正方向的夹角。那么,直线在极坐标下的方程可以表示为\(\rho = x\cos\theta + y\sin\theta\)。
霍夫空间与投票机制
- 霍夫空间:对于图像中的每一个点\((x,y)\),将其代入极坐标方程\(\rho = x\cos\theta + y\sin\theta\),可以得到一系列的\((\rho,\theta)\)组合,这些组合构成了该点在霍夫空间中的一条曲线。实际上,图像中同一条直线上的所有点,在霍夫空间中对应的曲线会相交于一点,这个交点的坐标\((\rho,\theta)\)就代表了图像中的这条直线。
- 投票机制:为了找到图像中的直线,需要在霍夫空间中进行 “投票”。首先创建一个二维数组作为霍夫累加器,其维度通常根据\(\rho\)和\(\theta\)的取值范围来确定。对于图像中的每一个边缘点(通过边缘检测算法得到),在霍夫空间中计算其对应的\((\rho,\theta)\)曲线,并在累加器中相应的位置进行投票(通常是将该位置的数值加 1)。投票结束后,累加器中数值较高的点就对应着图像中可能存在的直线。
阈值处理与峰值检测
- 阈值处理:为了确定哪些点是真正的直线,需要设置一个阈值。只有当累加器中的某个点的投票数超过阈值时,才认为该点对应着图像中的一条直线。
- 峰值检测:在超过阈值的点中,通常还需要进行峰值检测,以找到真正的直线参数。这是因为可能存在一些相邻的点都超过了阈值,但它们实际上代表的是同一条直线。通过峰值检测算法(如非极大值抑制等),可以找到霍夫空间中的局部最大值,这些最大值对应的\((\rho,\theta)\)就是最终检测到的直线的参数。
霍夫变换(Hough Transform)是一种用于检测图像中特定形状(如直线、圆等)的经典算法,其检测直线的原理如下:
直线的参数表示
在笛卡尔坐标系中,直线可以用方程\(y = kx + b\)来表示,其中k是斜率,b是截距。但这种表示方法存在一些问题,例如当直线垂直于x轴时,斜率k会趋于无穷大,导致数值计算上的困难。
为了避免这个问题,在霍夫变换中通常使用极坐标来表示直线。对于平面上的任意一点\((x,y)\),从原点到该点的向量可以用极坐标\((\rho,\theta)\)来表示,其中\(\rho\)是向量的长度,\(\theta\)是向量与x轴正方向的夹角。那么,直线在极坐标下的方程可以表示为\(\rho = x\cos\theta + y\sin\theta\)。
霍夫空间与投票机制
- 霍夫空间:对于图像中的每一个点\((x,y)\),将其代入极坐标方程\(\rho = x\cos\theta + y\sin\theta\),可以得到一系列的\((\rho,\theta)\)组合,这些组合构成了该点在霍夫空间中的一条曲线。实际上,图像中同一条直线上的所有点,在霍夫空间中对应的曲线会相交于一点,这个交点的坐标\((\rho,\theta)\)就代表了图像中的这条直线。
- 投票机制:为了找到图像中的直线,需要在霍夫空间中进行 “投票”。首先创建一个二维数组作为霍夫累加器,其维度通常根据\(\rho\)和\(\theta\)的取值范围来确定。对于图像中的每一个边缘点(通过边缘检测算法得到),在霍夫空间中计算其对应的\((\rho,\theta)\)曲线,并在累加器中相应的位置进行投票(通常是将该位置的数值加 1)。投票结束后,累加器中数值较高的点就对应着图像中可能存在的直线。
阈值处理与峰值检测
- 阈值处理:为了确定哪些点是真正的直线,需要设置一个阈值。只有当累加器中的某个点的投票数超过阈值时,才认为该点对应着图像中的一条直线。
- 峰值检测:在超过阈值的点中,通常还需要进行峰值检测,以找到真正的直线参数。这是因为可能存在一些相邻的点都超过了阈值,但它们实际上代表的是同一条直线。通过峰值检测算法(如非极大值抑制等),可以找到霍夫空间中的局部最大值,这些最大值对应的\((\rho,\theta)\)就是最终检测到的直线的参数。
霍夫变换(Hough Transform)是一种用于检测图像中特定形状(如直线、圆等)的经典算法,其检测直线的原理如下:
直线的参数表示
在笛卡尔坐标系中,直线可以用方程\(y = kx + b\)来表示,其中k是斜率,b是截距。但这种表示方法存在一些问题,例如当直线垂直于x轴时,斜率k会趋于无穷大,导致数值计算上的困难。
为了避免这个问题,在霍夫变换中通常使用极坐标来表示直线。对于平面上的任意一点\((x,y)\),从原点到该点的向量可以用极坐标\((\rho,\theta)\)来表示,其中\(\rho\)是向量的长度,\(\theta\)是向量与x轴正方向的夹角。那么,直线在极坐标下的方程可以表示为\(\rho = x\cos\theta + y\sin\theta\)。
霍夫空间与投票机制
- 霍夫空间:对于图像中的每一个点\((x,y)\),将其代入极坐标方程\(\rho = x\cos\theta + y\sin\theta\),可以得到一系列的\((\rho,\theta)\)组合,这些组合构成了该点在霍夫空间中的一条曲线。实际上,图像中同一条直线上的所有点,在霍夫空间中对应的曲线会相交于一点,这个交点的坐标\((\rho,\theta)\)就代表了图像中的这条直线。
- 投票机制:为了找到图像中的直线,需要在霍夫空间中进行 “投票”。首先创建一个二维数组作为霍夫累加器,其维度通常根据\(\rho\)和\(\theta\)的取值范围来确定。对于图像中的每一个边缘点(通过边缘检测算法得到),在霍夫空间中计算其对应的\((\rho,\theta)\)曲线,并在累加器中相应的位置进行投票(通常是将该位置的数值加 1)。投票结束后,累加器中数值较高的点就对应着图像中可能存在的直线。
阈值处理与峰值检测
- 阈值处理:为了确定哪些点是真正的直线,需要设置一个阈值。只有当累加器中的某个点的投票数超过阈值时,才认为该点对应着图像中的一条直线。
- 峰值检测:在超过阈值的点中,通常还需要进行峰值检测,以找到真正的直线参数。这是因为可能存在一些相邻的点都超过了阈值,但它们实际上代表的是同一条直线。通过峰值检测算法(如非极大值抑制等),可以找到霍夫空间中的局部最大值,这些最大值对应的\((\rho,\theta)\)就是最终检测到的直线的参数。
---------------------------------------------------------------------------------------------------------------------------------
检测流程如下:
image_path = r'D:\test1.jpg'
image = cv2.imread(image_path)
if image is None:
print(f"[ERROR] 无法读取图像,请检查路径:{image_path}")
exit()
original = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
- 读取指定路径的图像,若读取失败则输出错误信息并退出程序。
- 将图像从 BGR 颜色空间转换为 RGB 颜色空间,方便后续使用 Matplotlib 显示。
- 将图像转换为灰度图像。
- 对灰度图像进行高斯模糊,以减少噪声。
edges = cv2.Canny(blurred, 50, 150)
plt.figure(figsize=(8, 4))
plt.subplot(121), plt.imshow(blurred, cmap='gray'), plt.title('去噪图像')
plt.subplot(122), plt.imshow(edges, cmap='gray'), plt.title('Canny边缘检测结果')
plt.show()
- 对去噪后的图像进行 Canny 边缘检测,阈值分别设置为 50 和 150。
- 显示去噪后的图像和 Canny 边缘检测结果。
h, w = edges.shape
diag_len = int(np.hypot(h, w))
thetas = np.deg2rad(np.arange(-90, 90))
num_thetas = len(thetas)
H = np.zeros((2 * diag_len + 1, num_thetas), dtype=np.uint64)
y_idxs, x_idxs = np.nonzero(edges)
for i in range(num_thetas):
theta = thetas[i]
cos_theta = np.cos(theta)
sin_theta = np.sin(theta)
rho_values = x_idxs * cos_theta + y_idxs * sin_theta
rho_indices = np.round(rho_values).astype(int) + diag_len
valid = (rho_indices >= 0) & (rho_indices < H.shape[0])
np.add.at(H[:, i], rho_indices[valid], 1)
- 计算图像的对角线长度,作为
rho
的最大值。 - 定义
theta
的取值范围为 - 90 到 89 度,并将其转换为弧度。 - 初始化 Hough 累加器
H
,用于记录每个(rho, theta)
组合的投票数。 - 遍历边缘图像中的每个非零像素,计算其对应的
rho
值,并在累加器中相应位置进行投票。
plt.figure(figsize=(10, 6))
plt.imshow(H, cmap='gray', aspect='auto',
extent=[-90, 89, -diag_len, diag_len])
plt.xlabel('Theta (度)')
plt.ylabel('Rho (像素)')
plt.title('Hough空间投票结果')
plt.colorbar(label='投票数')
plt.show()
使用 Matplotlib 显示 Hough 空间的投票结果,其中颜色表示投票数。
peaks = peak_local_max(H, min_distance=20, threshold_abs=50)
rho_idxs, theta_idxs = peaks[:, 0], peaks[:, 1]
rhos_peak = rho_idxs - diag_len
thetas_peak_deg = np.rad2deg(thetas[theta_idxs])
- 使用
peak_local_max
函数检测 Hough 空间中的局部最大值,最小间隔为 20 像素,阈值为 50。 - 将峰值的索引转换为
rho
和theta
的值。
plt.figure(figsize=(10, 6))
plt.imshow(H, cmap='gray', aspect='auto',
extent=[-90, 89, -diag_len, diag_len])
plt.scatter(np.rad2deg(thetas[theta_idxs]), rhos_peak,
c='red', s=40, edgecolors='white')
plt.xlabel('Theta (度)')
plt.ylabel('Rho (像素)')
plt.title('Hough空间峰值点标记')
plt.colorbar(label='投票数')
plt.show()
在 Hough 空间中标记检测到的峰值点。
result = original.copy()
for rho, theta_deg in zip(rhos_peak, thetas_peak_deg):
theta = np.deg2rad(theta_deg)
a = np.cos(theta)
b = np.sin(theta)
x0 = a * rho
y0 = b * rho
pt1 = (int(x0 + 1000*(-b)), int(y0 + 1000*a))
pt2 = (int(x0 - 1000*(-b)), int(y0 - 1000*a))
cv2.line(result, pt1, pt2, (0, 255, 0), 2)
- 遍历检测到的
(rho, theta)
组合,将其转换为直线的两个端点坐标。 - 在原始图像的副本上绘制检测到的直线。