【问题标题】:Implementing Hough transform line detection in 2D coordinate system在二维坐标系中实现霍夫变换线检测
【发布时间】:2016-11-08 05:57:10
【问题描述】:

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

给定一个像这样的 3 x 3 矩阵:

X X X
X X X
- - -

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

我认为使用 Hough 检测线条会相对容易,因为边缘检测基本上只是一个二元决策:元组是否属于线条类型。

我在 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
}

结果是这样的:

Found lines from -1/4 to 1/1
Found lines from 2/4 to 0/0
Found lines from 2/-3 to 0/0
Found lines from -1/4 to 1/1
Found lines from 1/-3 to 0/0
Found lines from 0/4 to 1/1
...

考虑到我只想检测一行,这实际上相当多。我的实现显然是错误的,但我不知道在哪里看,我的数学不够高,无法进一步调试。

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

for each image point p 
{
  if (p is part of an edge)
  {
    for each possible angle
    {
     r = x * cos(angle) + y * sin(angle);
     houghMatrix[angle][r]++;
    }
  }
}

我被困在映射和过滤上,这是根据文章:

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

  2. 一行的最大长度受sqrt(imagewidth2 + imageheight2)的限制。

  3. 点 p、线的角度 a 和最大线长 'maxLength' 可用于计算线的另外两个点。这里的最大长度确保了要计算的两个点都位于实际图像之外,从而导致如果在这两个点之间画一条线,那么这条线在任何情况下都是从图像边界到图像边界并且永远不会被裁剪图像内的某处。

  4. 这两个点 p1 和 p2 由以下公式计算: p1_x = px + maxLength * cos(角度); p1_y = py + maxLength * sin(角度); p2_x = px - maxLength * cos(角度); p2_y = py - maxLength * sin(角度);

  5. ...

编辑

根据@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
}

现在报告的输出是:

angle: 0, r: 0, r_scaled: 1
angle: 30, r: 0, r_scaled: 1
angle: 60, r: 0, r_scaled: 1
angle: 90, r: 0, r_scaled: 1
angle: 120, r: 0, r_scaled: 1
angle: 150, r: 0, r_scaled: 1
angle: 180, r: 0, r_scaled: 1
...
Found lines from 3/4 to -1/-1
Found lines from -3/1 to 2/2 

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

【问题讨论】:

  • Hough 变换很大程度上取决于实际图像和边缘的锐利程度。图像越繁忙,变换的结果就越复杂。我要做的是生成输出图像以检查生成的内容。将输入和输出图像添加到问题中可能会有所帮助。
  • 一个看起来可疑的想法是,您对 r 值进行了非常强烈的四舍五入,只有 4 个级别。这意味着你基本上是在用一条很宽的线检查,很多点会形成同一条线。
  • 如果你真的在输出中绘制线条,你会发现它们非常相似,其中一些是平行的,相差一个像素。通过考虑一个小图像,你会让自己的生活变得更加艰难。尝试更像 100 x 100 的图像,您会看到结果变得更清晰。尝试改变 r 和角度步长的粒度,看看会发生什么。
  • @Salixalba 我也尝试使用 30x30 矩阵,不幸的是,结果与编辑后的版本相差甚远。 :(

标签: algorithm math rust ascii-art


【解决方案1】:

你的角度是度数而不是弧度!

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

在调用cossin 之前,您需要将所有角度转换为弧度。

在我的第一个循环中

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 生锈了(实际上不存在),所以有更好的方法来进行转换,并且应该在某个地方有一个与 pi 的确切值相同的常数。

【讨论】:

  • 谢谢你的回答,我不知道。结果仍然不是我想要的,所以我想我必须重新开始,总而言之,实施似乎有点错误。接受这个答案,因为它给了我一些指导如何使用一般的数学函数和具体的生锈,也感谢@Shepmaster
  • 我已经运行了一个修改后的代码版本,它给出了更可信的答案。您的 r 值还有第二个问题。有时这些会变为负数,但缩放函数会将
  • @Salixalba hm 我想我实现了一个缩放函数,以便将这些值用作累加器矩阵中的偏移量。
【解决方案2】:

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

但是,我们无法确定所有这些行,因为它们的数量是无限的。而且图像是离散化的,所以计算所有的线没有意义。

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

所以这里,对于第一个点 (0,0),对于每个角度,关联的半径是 r = 0

对于第二个 (1,0),关联的半径是 r = cos(angle)

对于第三个 (2,0),相关的半径是 r = 2 cos(angle)

使用四舍五入后,对于同一角度,许多值的关联半径将为 0,这会导致过度检测。离散化导致霍夫累加器的扩散。

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

【讨论】:

  • 有道理,谢谢。我尝试了一个更新版本,它现在只找到 2 行,这对我来说更有意义。然而,问题是,这两行与我的预期相差甚远,程序报告从3,4-1,-1-3,12,2 的行。我用我的新代码更新了问题,可能转换回积分仍然关闭。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-03-09
  • 1970-01-01
  • 2011-11-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多