Hough Transform问题:如何从投票矩阵提取正确直线?
问题定位与修正方案
我仔细检查了你的C++和Python代码,发现几个关键错误导致无法正确提取直线,下面逐一说明并给出修复后的代码:
1. C++代码中的核心错误
投票矩阵初始化不完整
你初始化投票矩阵时只循环到distanceDim(即distanceAxis/2),但投票矩阵的第一维度是distanceAxis,这会导致后半部分的投票单元未被初始化为0,可能引入随机值干扰结果:
// 错误代码 for(int i=0; i<distanceDim; i++) { for(int j=0; j<angleDim; j++) { votingMatrixBuf(i, j) = 0; } } // 修正后 for(int i=0; i<distanceAxis; i++) { for(int j=0; j<angleDim; j++) { votingMatrixBuf(i, j) = 0; } }
角度索引与counter的错位
你的counter初始为0,每次符合thetaIdx % angleStep == 0时先自增再投票,这导致第一个有效角度(thetaIdx=0)对应的counter=1,而投票矩阵的列索引从0开始,这会让所有角度的投票都偏移一列,最终Python中提取的角度值会错误:
// 错误代码 int counter = 0; float theta; float ro; for(int thetaIdx=0; thetaIdx<=angleAxis; thetaIdx++) { if(thetaIdx % angleStep == 0) { counter++; theta = (float) (thetaIdx) * (M_PI / 180); ro = distanceDim + std::round((x * cos(theta)) + (y * sin(theta))); votingMatrixBuf(ro, counter) += 1; } } // 修正后:先投票再自增,同时转换图像坐标系 int counter = 0; float theta; float ro; for(int thetaIdx=0; thetaIdx<angleAmount; thetaIdx++) { // 改为<angleAmount避免重复计算360度 if(thetaIdx % angleStep == 0) { theta = (float) thetaIdx * (M_PI / 180); // 图像坐标系转数学坐标系:原点从左上角移到左下角 int mathY = height - y - 1; ro = distanceDim + std::round((x * cos(theta)) + (mathY * sin(theta))); // 确保ro在投票矩阵的有效范围内,防止越界 if(ro >= 0 && ro < distanceAxis) { votingMatrixBuf(ro, counter) += 1; } counter++; } }
图像坐标系与数学坐标系的差异
图像的原点在左上角,而霍夫变换的数学计算基于原点在左下角的坐标系,所以需要将图像的y坐标反转(mathY = height - y - 1),否则计算出的ro值会符号错误,导致直线位置完全偏离。
2. Python代码中的角度计算与直线绘制优化
由于C++中counter现在从0开始作为列索引,Python中提取角度时不需要再偏移;另外建议将angleAmount改为180度(因为θ和θ+π对应的是同一条直线,重复计算360度会浪费资源):
# 错误代码 theta = p[1] * (np.pi / 180) # 修正后 theta = p[1] * (np.pi / 180) # 如果坚持用360度,添加以下代码处理重复角度 # if theta > np.pi: # theta -= np.pi # ro = -ro
同时,为了避免直线画出图像范围,我们可以添加一个函数计算直线与图像边界的交点:
def get_line_endpoints(ro, theta, img_shape): height, width = img_shape a = np.cos(theta) b = np.sin(theta) points = [] # 计算与左边界x=0的交点 if b != 0: y_left = (ro - 0*a)/b if 0 <= y_left <= height: points.append((0, int(y_left))) # 计算与右边界x=width-1的交点 if b != 0: y_right = (ro - (width-1)*a)/b if 0 <= y_right <= height: points.append((width-1, int(y_right))) # 计算与上边界y=0(图像坐标系)的交点 if a != 0: x_top = (ro - (height-1)*b)/a if 0 <= x_top <= width: points.append((int(x_top), 0)) # 计算与下边界y=height-1(图像坐标系)的交点 if a != 0: x_bottom = (ro - 0*b)/a if 0 <= x_bottom <= width: points.append((int(x_bottom), height-1)) # 返回最远的两个端点 if len(points) >= 2: return points[0], points[-1] return (0,0), (width-1, height-1)
3. 完整修正后的关键代码片段
C++核心修正版
py::array_t<int> houghTransform(py::array_t<int> image, int angleStep, int angleAmount) { auto imageBuf = image.mutable_unchecked<3>(); int height = imageBuf.shape(0); int width = imageBuf.shape(1); py::array_t<int> edgeMatrix = edgeDetect(imageBuf, height, width); auto edgeMatrixBuf = edgeMatrix.mutable_unchecked<2>(); int distanceAxis = 2 * sqrt(pow((float) height, 2.0) + pow((float) width, 2.0)); int angleDim = (int) angleAmount / angleStep; int distanceDim = (int) distanceAxis / 2; py::array_t<int> votingMatrix = py::array_t<int>({distanceAxis, angleDim}); auto votingMatrixBuf = votingMatrix.mutable_unchecked<2>(); // 完整初始化投票矩阵为0 for(int i=0; i<distanceAxis; i++) { for(int j=0; j<angleDim; j++) { votingMatrixBuf(i, j) = 0; } } // 修正后的投票逻辑 for(int x=0; x<edgeMatrixBuf.shape(0); x++) { for(int y=0; y<edgeMatrixBuf.shape(1); y++) { if(edgeMatrixBuf(x, y) == 1) { int counter = 0; float theta; float ro; for(int thetaIdx=0; thetaIdx<angleAmount; thetaIdx++) { if(thetaIdx % angleStep == 0) { theta = (float) thetaIdx * (M_PI / 180); int mathY = height - y - 1; ro = distanceDim + std::round((x * cos(theta)) + (mathY * sin(theta))); if(ro >= 0 && ro < distanceAxis) { votingMatrixBuf(ro, counter) += 1; } counter++; } } } } } return votingMatrix; }
Python核心修正版
from PIL import Image import numpy as np import matplotlib.pyplot as plt from houghTransform import houghTransform def get_line_endpoints(ro, theta, img_shape): height, width = img_shape a = np.cos(theta) b = np.sin(theta) points = [] if b != 0: y_left = (ro - 0*a)/b if 0 <= y_left <= height: points.append((0, int(y_left))) y_right = (ro - (width-1)*a)/b if 0 <= y_right <= height: points.append((width-1, int(y_right))) if a != 0: x_top = (ro - (height-1)*b)/a if 0 <= x_top <= width: points.append((int(x_top), 0)) x_bottom = (ro - 0*b)/a if 0 <= x_bottom <= width: points.append((int(x_bottom), height-1)) if len(points) >= 2: return points[0], points[-1] return (0,0), (width-1, height-1) def apply_hough_transform(image_path: str=""): image = np.array(Image.open(image_path)) lines = houghTransform(image, 1, 180) # 使用180度避免重复计算 p = np.unravel_index(lines.argmax(), lines.shape) max_distance = 2 * np.sqrt(pow(image.shape[0], 2) + pow(image.shape[1], 2)) ro = p[0] - (max_distance / 2) theta = p[1] * (np.pi / 180) pt1, pt2 = get_line_endpoints(ro, theta, image.shape) fig, axs = plt.subplots(2, figsize=(10, 8)) axs[0].matshow(lines) axs[0].scatter(p[1], p[0], facecolors="none", edgecolors="r", s=100) axs[0].set_title("Hough Voting Matrix") axs[1].imshow(image, cmap='gray') axs[1].plot([pt1[0], pt2[0]], [pt1[1], pt2[1]], color='red', linewidth=2) axs[1].set_title("Detected Line") plt.tight_layout() plt.show() apply_hough_transform(image_path="images/black_line.png")
为什么之前的方法无效?
- 投票矩阵未完全初始化,部分区域存在随机值干扰最大值定位
- 角度索引错位,提取的θ值偏移了
angleStep角度,导致直线方向错误 - 未转换图像坐标系,
ro值符号错误,直线位置完全偏离 - 直线端点未限制在图像范围内,可能画出屏幕外的无效直线
按照上面的修正方案,你应该能正确提取并绘制出图像中的直线了。
内容的提问来源于stack exchange,提问作者Theodor Peifer
相关产品推荐
相关产品推荐

