You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 14:57:31