Algorithm 在二维坐标系下实现Hough变换直线检测

Algorithm 在二维坐标系下实现Hough变换直线检测,algorithm,math,rust,ascii-art,Algorithm,Math,Rust,Ascii Art,我想在一个简单的坐标系中实现线检测。我大致遵循了一篇关于如何实现的文章,但我得到的结果与我想要的相差甚远 给定这样的3 x 3矩阵: X X X X - - - 我想检测从0,0开始到2,0的行。我将坐标系表示为一个简单的元组数组,元组中的第一项是x,第二项是y,第三项是点的类型(画布或线) 我认为使用Hough来检测直线比较容易,因为边缘检测基本上只是一个二进制决策:要么元组是line类型,要么不是 我在Rust中实施了以下程序: use std::f32; extern crate na

我想在一个简单的坐标系中实现线检测。我大致遵循了一篇关于如何实现的文章,但我得到的结果与我想要的相差甚远

给定这样的3 x 3矩阵:

X
X X X
- - -
我想检测从
0,0
开始到
2,0
的行。我将坐标系表示为一个简单的元组数组,元组中的第一项是x,第二项是y,第三项是点的类型(画布或线)

我认为使用Hough来检测直线比较容易,因为边缘检测基本上只是一个二进制决策:要么元组是line类型,要么不是

我在Rust中实施了以下程序:

use std::f32;

extern crate nalgebra as na;
use na::DMatrix;

#[derive(Debug, PartialEq, Clone)]
enum Representation {
   Canvas,
   Line,
}

fn main () {
    let image_width = 3;
    let image_height = 3;

    let grid = vec![
        (0, 0, Representation::Line), (1, 0, Representation::Line), (2, 0, Representation::Line),
        (0, 1, Representation::Canvas), (1, 1, Representation::Canvas), (2, 1, Representation::Canvas),
        (0, 2, Representation::Canvas), (1, 2, Representation::Canvas), (2, 2, Representation::Canvas),
    ];

    //let tmp:f32 = (image_width as f32 * image_width as f32) + (image_height as f32 * image_height as f32);
    let max_line_length = 3;
    let mut accumulator = DMatrix::from_element(180, max_line_length as usize, 0);

    for y in 0..image_height {
        for x in 0..image_width {
            let coords_index = (y * image_width) + x;
            let coords = grid.get(coords_index as usize).unwrap();

            // check if coords is an edge
            if coords.2 == Representation::Line {
                for angle in 0..180 {
                    let r = (x as f32) * (angle as f32).cos() + (y as f32) * (angle as f32).sin();
                    let r_scaled = scale_between(r, 0.0, 2.0, -2.0, 2.0).round() as u32;

                    accumulator[(angle as usize, r_scaled as usize)] += 1;
                }
            }
        }
    }

    let threshold = 3;

    // z = angle
    for z in 0..180 {
        for r in 0..3 {
            let val = accumulator[(z as usize, r as usize)];

            if val < threshold {
                continue;
            }

            let px = (r as f32) * (z as f32).cos();
            let py = (r as f32) * (z as f32).sin();

            let p1_px = px + (max_line_length as f32) * (z as f32).cos();
            let p1_py = py + (max_line_length as f32) * (z as f32).sin();

            let p2_px = px - (max_line_length as f32) * (z as f32).cos();
            let p2_py = px - (max_line_length as f32) * (z as f32).cos();

            println!("Found lines from {}/{} to {}/{}", p1_px.ceil(), p1_py.ceil(), p2_px.ceil(), p2_py.ceil());
        }
    }
}

fn scale_between(unscaled_num: f32, min_allowed: f32, max_allowed: f32, min: f32, max: f32) -> f32 {
    (max_allowed - min_allowed) * (unscaled_num - min) / (max - min) + min_allowed
}
这实际上是相当多的,因为我只想检测一条线。我的实现显然是错误的,但我不知道该往哪里看,我的数学fu不够高,无法进一步调试

我认为第一部分,实际的Hough变换,似乎有点正确,因为链接文章说:

我一直专注于映射和过滤,根据文章:

  • Hough空间中的每个点由角度a和距离r给出。使用这些值,直线的一个单点p(x,y)可以通过以下公式计算: px=r*cos(角度) py=r*sin(角度)

  • 行的最大长度受sqrt(imagewidth2+imageheight2)限制

  • 点p、直线的角度a和最大直线长度“maxLength”可用于计算直线的其他两个点。此处的最大长度确保要计算的两个点都位于实际图像之外,因此,如果在这两个点之间绘制了一条线,则该线在任何情况下都会从图像边界延伸到图像边界,并且永远不会在图像内部的某个位置进行裁剪

  • 这两点p1和p2的计算公式如下: p1_x=px+最大长度*cos(角度); p1_y=py+maxLength*sin(角度); p2_x=px-最大长度*cos(角度); p2_y=py—最大长度*sin(角度)

  • 编辑

    更新版本,考虑了@RaymoAisla建议的图像大小

    use std::f32;
    
    extern crate nalgebra as na;
    use na::DMatrix;
    
    fn main () {
        let image_width = 3;
        let image_height = 3;
    
        let mut grid = DMatrix::from_element(image_width as usize, image_height as usize, 0);
        grid[(0, 0)] = 1;
        grid[(1, 0)] = 1;
        grid[(2, 0)] = 1;
    
        let accu_width = 7;
        let accu_height = 3;
        let max_line_length = 3;
    
        let mut accumulator = DMatrix::from_element(accu_width as usize, accu_height as usize, 0);
    
    
        for y in 0..image_height {
            for x in 0..image_width {
                let coords = (x, y);
                let is_edge = grid[coords] == 1;
    
                if !is_edge {
                    continue;
                }
    
                for i in 0..7 {
                    let angle = i * 30;
    
                    let r = (x as f32) * (angle as f32).cos() + (y as f32) * (angle as f32).sin();
                    let r_scaled = scale_between(r, 0.0, 2.0, -2.0, 2.0).round() as u32;
    
                    accumulator[(i as usize, r_scaled as usize)] += 1;
    
                    println!("angle: {}, r: {}, r_scaled: {}", angle, r, r_scaled);
                }
            }
        }
    
        let threshold = 3;
    
        // z = angle index
        for z in 0..7 {
            for r in 0..3 {
                let val = accumulator[(z as usize, r as usize)];
    
                if val < threshold {
                    continue;
                }
    
                let px = (r as f32) * (z as f32).cos();
                let py = (r as f32) * (z as f32).sin();
    
                let p1_px = px + (max_line_length as f32) * (z as f32).cos();
                let p1_py = py + (max_line_length as f32) * (z as f32).sin();
    
                let p2_px = px - (max_line_length as f32) * (z as f32).cos();
                let p2_py = px - (max_line_length as f32) * (z as f32).cos();
    
                println!("Found lines from {}/{} to {}/{} - val: {}", p1_px.ceil(), p1_py.ceil(), p2_px.ceil(), p2_py.ceil(), val);
            }
        }
    }
    
    fn scale_between(unscaled_num: f32, min_allowed: f32, max_allowed: f32, min: f32, max: f32) -> f32 {
        (max_allowed - min_allowed) * (unscaled_num - min) / (max - min) + min_allowed
    }
    

    我在坐标系上绘制了这些线,这些线与我预期的线相差很远。我想知道转换回积分是否仍然关闭

    Hough变换原理是搜索通过每个考虑点的所有直线,并通过累加器计算这些直线的出现次数

    然而,我们不能确定所有这些线,因为它们的数量是无限的。此外,图像是离散化的,因此计算所有线没有意义

    问题来自于这种离散化。角度离散化需要与图像大小相关。在这里,计算180个角度的半径是过度计算的,因为图像只有9个像素,并且该图像中任何线的可能角度都限制为12个值

    这里,对于第一个点(0,0),对于每个角度,相关半径为r=0

    对于第二个(1,0),相关半径为r=cos(角度)

    对于第三个(2,0),相关半径为r=2 cos(角度)

    使用舍入,对于相同的角度,许多值的关联半径为0,这会导致过度检测。离散化会导致Hough累加器的扩展


    因此,需要根据图像大小计算半径和角度离散化。这里,30°的步长,因此7*3累加器足以检测一条线。

    Hough变换原理是搜索通过每个考虑点的所有线,并通过累加器计算这些线的出现次数

    然而,我们不能确定所有这些线,因为它们的数量是无限的。此外,图像是离散化的,因此计算所有线没有意义

    问题来自于这种离散化。角度离散化需要与图像大小相关。在这里,计算180个角度的半径是过度计算的,因为图像只有9个像素,并且该图像中任何线的可能角度都限制为12个值

    这里,对于第一个点(0,0),对于每个角度,相关半径为r=0

    对于第二个(1,0),相关半径为r=cos(角度)

    对于第三个(2,0),相关半径为r=2 cos(角度)

    使用舍入,对于相同的角度,许多值的关联半径为0,这会导致过度检测。离散化会导致Hough累加器的扩展


    因此,需要根据图像大小计算半径和角度离散化。这里是30°的步长,因此7*3的累加器足以检测直线。

    您的角度以度而不是弧度为单位

    Rust和所有其他编程语言一样,使用弧度作为其三角函数。运行

    let ang_d = 30.0;
    let ang_r = ang_d * 3.1415926 / 180.0;
    println!("sin(30) {} sin(30*pi/180) {}", (ang_d as f32).sin(), (ang_r as f32).sin());
    
    给出了结果

    sin(30)-0.9880316 sin(30*pi/180)0.5
    
    在调用
    cos
    sin
    之前,需要将所有角度转换为弧度

    在我的第一个循环中

    let angle = (i as f32) * 30.0 * 3.1415926 / 180.0;
    let r = (x as f32) * (angle as f32).cos() + (y as f32) * (angle as f32).sin();
    
    在第二个例子中,计算直线上的点

    let ang = (z as f32) * 30.0 * 3.1415926 / 180.0;
    let px = (r as f32) * (ang as f32).cos();
    let py = (r as f32) * (ang as f32).sin();
    let p1_px = px + (max_line_length as f32) * (ang as f32).cos();          
    let p1_py = py + (max_line_length as f32) * (ang as f32).sin();
    let p2_px = px - (max_line_length as f32) * (ang as f32).cos();
    let p2_py = px - (max_line_length as f32) * (ang as f32).cos();
    

    我的铁锈生锈了(实际上不存在),所以有更好的转换方法,而且应该有一个常数,精确值为π。

    你的角度是度而不是弧度

    Rust和所有其他编程语言一样,使用弧度作为其三角函数。运行

    let ang_d = 30.0;
    let ang_r = ang_d * 3.1415926 / 180.0;
    println!("sin(30) {} sin(30*pi/180) {}", (ang_d as f32).sin(), (ang_r as f32).sin());
    
    给出了结果

    sin(30)-0.9880316 sin(30*pi/180)0.5
    
    在调用
    cos
    sin
    之前,需要将所有角度转换为弧度

    在我的第一个循环中

    let angle = (i as f32) * 30.0 * 3.1415926 / 180.0;
    let r = (x as f32) * (angle as f32).cos() + (y as f32) * (angle as f32).sin();
    
    在第二个例子中,计算直线上的点

    let ang = (z as f32) * 30.0 * 3.1415926 / 180.0;
    let px = (r as f32) * (ang as f32).cos();
    let py = (r as f32) * (ang as f32).sin();
    let p1_px = px + (max_line_length as f32) * (ang as f32).cos();          
    let p1_py = py + (max_line_length as f32) * (ang as f32).sin();
    let p2_px = px - (max_line_length as f32) * (ang as f32).cos();
    let p2_py = px - (max_line_length as f32) * (ang as f32).cos();
    
    <