草庐IT

javascript - 霍夫变换 - javascript - node.js

coder 2024-07-20 原文

所以,我正在尝试实现霍夫变换,这个版本是基于次要属性的一维版本(它的所有暗淡减少到 1 暗淡优化)版本。 附上我的代码,带有示例图像...输入和输出。

明显的问题是我做错了什么。我已经三次检查我的逻辑和代码,它看起来也不错我的参数。但显然我遗漏了什么。

注意红色像素点应该是椭圆中心,蓝色像素点是要去除的边缘(属于符合数学方程的椭圆)。

此外,我对 openCV/matlab/ocatve/等不感兴趣。使用(没有反对他们)。 非常感谢!

var fs = require("fs"),
    Canvas = require("canvas"),
    Image = Canvas.Image;


var LEAST_REQUIRED_DISTANCE = 40, // LEAST required distance between 2 points , lets say smallest ellipse minor
    LEAST_REQUIRED_ELLIPSES = 6, // number of found ellipse
    arr_accum = [],
    arr_edges = [],
    edges_canvas,
    xy,
    x1y1,
    x2y2,
    x0,
    y0,
    a,
    alpha,
    d,
    b,
    max_votes,
    cos_tau,
    sin_tau_sqr,
    f,
    new_x0,
    new_y0,
    any_minor_dist,
    max_minor,
    i,
    found_minor_in_accum,
    arr_edges_len,
    hough_file = 'sample_me2.jpg',


edges_canvas = drawImgToCanvasSync(hough_file); // make sure everything is black and white!


arr_edges    = getEdgesArr(edges_canvas);

arr_edges_len = arr_edges.length;

var hough_canvas_img_data = edges_canvas.getContext('2d').getImageData(0, 0, edges_canvas.width,edges_canvas.height);

for(x1y1 = 0; x1y1 < arr_edges_len ; x1y1++){

  if (arr_edges[x1y1].x === -1) { continue; }

  for(x2y2 = 0 ; x2y2 < arr_edges_len; x2y2++){

    if ((arr_edges[x2y2].x === -1) ||
        (arr_edges[x2y2].x === arr_edges[x1y1].x && arr_edges[x2y2].y === arr_edges[x1y1].y)) { continue; }

    if (distance(arr_edges[x1y1],arr_edges[x2y2]) > LEAST_REQUIRED_DISTANCE){

      x0    = (arr_edges[x1y1].x + arr_edges[x2y2].x) / 2;
      y0    = (arr_edges[x1y1].y + arr_edges[x2y2].y) / 2;
      a     = Math.sqrt((arr_edges[x1y1].x - arr_edges[x2y2].x) * (arr_edges[x1y1].x - arr_edges[x2y2].x) + (arr_edges[x1y1].y - arr_edges[x2y2].y) * (arr_edges[x1y1].y - arr_edges[x2y2].y)) / 2;
      alpha = Math.atan((arr_edges[x2y2].y - arr_edges[x1y1].y) / (arr_edges[x2y2].x - arr_edges[x1y1].x));

      for(xy = 0 ; xy < arr_edges_len; xy++){

        if ((arr_edges[xy].x === -1) || 
            (arr_edges[xy].x === arr_edges[x2y2].x && arr_edges[xy].y === arr_edges[x2y2].y) ||
            (arr_edges[xy].x === arr_edges[x1y1].x && arr_edges[xy].y === arr_edges[x1y1].y)) { continue; }

        d = distance({x: x0, y: y0},arr_edges[xy]);

        if (d > LEAST_REQUIRED_DISTANCE){
          f           = distance(arr_edges[xy],arr_edges[x2y2]); // focus
          cos_tau     = (a * a + d * d - f * f) / (2 * a * d);
          sin_tau_sqr = (1 - cos_tau * cos_tau);//Math.sqrt(1 - cos_tau * cos_tau); // getting sin out of cos
          b           = (a * a * d * d * sin_tau_sqr ) / (a * a - d * d * cos_tau * cos_tau);
          b           = Math.sqrt(b);
          b           = parseInt(b.toFixed(0));
          d           = parseInt(d.toFixed(0));

          if (b > 0){
            found_minor_in_accum = arr_accum.hasOwnProperty(b);

            if (!found_minor_in_accum){
              arr_accum[b] = {f: f, cos_tau: cos_tau, sin_tau_sqr: sin_tau_sqr, b: b, d: d, xy: xy, xy_point: JSON.stringify(arr_edges[xy]), x0: x0, y0: y0, accum: 0};
            }
            else{
              arr_accum[b].accum++;
            }
          }// b
        }// if2 - LEAST_REQUIRED_DISTANCE
      }// for xy

      max_votes = getMaxMinor(arr_accum);

      // ONE ellipse has been detected
      if (max_votes != null &&
          (max_votes.max_votes > LEAST_REQUIRED_ELLIPSES)){

        // output ellipse details
        new_x0 = parseInt(arr_accum[max_votes.index].x0.toFixed(0)),
        new_y0 = parseInt(arr_accum[max_votes.index].y0.toFixed(0));

        setPixel(hough_canvas_img_data,new_x0,new_y0,255,0,0,255); // Red centers

        // remove the pixels on the detected ellipse from edge pixel array
        for (i=0; i < arr_edges.length; i++){
          any_minor_dist = distance({x:new_x0, y: new_y0}, arr_edges[i]);
          any_minor_dist = parseInt(any_minor_dist.toFixed(0));
          max_minor      = b;//Math.max(b,arr_accum[max_votes.index].d); // between the max and the min

          // coloring in blue the edges we don't need
          if (any_minor_dist <= max_minor){
            setPixel(hough_canvas_img_data,arr_edges[i].x,arr_edges[i].y,0,0,255,255);
            arr_edges[i] = {x: -1, y: -1};

          }// if

        }// for


      }// if - LEAST_REQUIRED_ELLIPSES

      // clear accumulated array
      arr_accum = [];

    }// if1 - LEAST_REQUIRED_DISTANCE

  }// for x2y2
}// for xy

edges_canvas.getContext('2d').putImageData(hough_canvas_img_data, 0, 0);

writeCanvasToFile(edges_canvas, __dirname + '/hough.jpg', function() {
});



function getMaxMinor(accum_in){
  var max_votes = -1,
      max_votes_idx,
      i,
      accum_len = accum_in.length;

  for(i in accum_in){

    if (accum_in[i].accum > max_votes){
      max_votes     = accum_in[i].accum;
      max_votes_idx = i;
    } // if
  }


  if (max_votes > 0){
    return {max_votes: max_votes, index: max_votes_idx};
  }
  return null;
}

function distance(point_a,point_b){
  return Math.sqrt((point_a.x - point_b.x) * (point_a.x - point_b.x) + (point_a.y - point_b.y) * (point_a.y - point_b.y));
}
function getEdgesArr(canvas_in){

  var x,
      y,
      width = canvas_in.width,
      height = canvas_in.height,
      pixel,
      edges = [],
      ctx = canvas_in.getContext('2d'),
      img_data = ctx.getImageData(0, 0, width, height);


  for(x = 0; x < width; x++){
    for(y = 0; y < height; y++){

      pixel = getPixel(img_data, x,y);


      if (pixel.r !== 0 && 
          pixel.g !== 0 &&
          pixel.b !== 0 ){
        edges.push({x: x, y: y});
      }

    } // for
  }// for 

  return edges
} // getEdgesArr

function drawImgToCanvasSync(file) {
  var data = fs.readFileSync(file)
  var canvas = dataToCanvas(data);
  return canvas;
}
function dataToCanvas(imagedata) {
  img = new Canvas.Image();
  img.src = new Buffer(imagedata, 'binary');

  var canvas = new Canvas(img.width, img.height);
  var ctx = canvas.getContext('2d');
  ctx.patternQuality = "best";

  ctx.drawImage(img, 0, 0, img.width, img.height,
    0, 0, img.width, img.height);
  return canvas;
}
function writeCanvasToFile(canvas, file, callback) {
  var out = fs.createWriteStream(file)
  var stream = canvas.createPNGStream();

  stream.on('data', function(chunk) {
    out.write(chunk);
  });

  stream.on('end', function() {
    callback();
  });
}


function setPixel(imageData, x, y, r, g, b, a) {
    index = (x + y * imageData.width) * 4;
    imageData.data[index+0] = r;
    imageData.data[index+1] = g;
    imageData.data[index+2] = b;
    imageData.data[index+3] = a;
}
function getPixel(imageData, x, y) {
    index = (x + y * imageData.width) * 4;

    return {
      r: imageData.data[index+0],
      g: imageData.data[index+1],
      b: imageData.data[index+2],
      a: imageData.data[index+3]
    }
}

最佳答案

您似乎尝试实现 Yonghong Xie; Qiang Ji (2002). A new efficient ellipse detection method 2. p. 957 的算法.

椭圆去除有几个错误

在您的代码中,您通过将坐标重置为 {-1, -1} 来执行找到的椭圆的删除(原始论文算法的第 12 步) .

你需要添加:

`if (arr_edges[x1y1].x === -1) break;`

在 x2y2 block 的末尾。否则,循环会将 -1、-1 视为白点。

更重要的是,您的算法包括删除到中心的距离小于 b 的每个点。 . b据说是短轴的半长(根据原始算法)。但是在您的代码中,变量 b实际上是最新的(而不是最常见的)半长,并且您删除距离小于 b 的点(而不是更大,因为它是短轴)。换句话说,您清除圆内距离小于最新计算轴的所有点。

您的样本图像实际上可以通过清除圆内的所有点来处理,圆内的距离小于选定的长轴:

max_minor      = arr_accum[max_votes.index].d;

确实,您没有重叠的椭圆,而且它们分布得足够多。请考虑更好的重叠或更接近椭圆的算法。

算法混合长轴和短轴

论文的第 6 步如下:

For each third pixel (x, y), if the distance between (x, y) and (x0, y0) is greater than the required least distance for a pair of pixels to be considered then carry out the following steps from (7) to (9).

这显然是一个近似值。如果这样做,您最终会考虑比短轴半长更远的点,并最终在长轴上(交换轴)。您应该确保所考虑的点与测试的椭圆中心之间的距离小于当前考虑的主轴半长(条件应为 d <= a )。这将有助于算法的椭圆删除部分。

此外,如果您还比较一对像素的最小距离,根据原始论文,40 对于图片中较小的椭圆来说太大了。您的代码中的注释是错误的,它最多应该是最小椭圆短轴的一半 half-length

LEAST_REQUIRED_ELLIPSES 太小

这个参数也被错误命名。这是椭圆应被视为有效的最小票数。每个投票对应一个像素。所以值为 6 意味着只有 6+2 个像素构成一个椭圆。由于像素坐标是整数并且您的图片中有超过 1 个椭圆,因此该算法可能会检测到不是的椭圆,并最终清除边缘(尤其是与错误的椭圆删除算法结合使用时)。根据测试,值为 100 会找到图片五个椭圆中的四个,而值为 80 会找到所有椭圆。较小的值将无法找到合适的椭圆中心。

示例图片不是黑白的

尽管有评论,示例图像并不完全是黑白的。您应该转换它或应用一些阈值(例如 RGB 值大于 10 而不是简单的不同形式 0)。

此处提供了使其工作的最小更改差异: https://gist.github.com/pguyot/26149fec29ffa47f0cfb/revisions

最后请注意parseInt(x.toFixed(0))可以重写 Math.floor(x) ,并且您可能不想像这样 chop 所有 float ,而是将它们四舍五入,然后在需要的地方继续:从图片中删除椭圆的算法将受益于中心坐标的非 chop 值。这段代码肯定可以进一步改进,例如它目前计算点之间的距离 x1y1x2y2两次。

关于javascript - 霍夫变换 - javascript - node.js,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23396518/

有关javascript - 霍夫变换 - javascript - node.js的更多相关文章

  1. ruby-on-rails - 使用 javascript 更改数据方法不会更改 ajax 调用用户的什么方法? - 2

    我遇到了一个非常奇怪的问题,我很难解决。在我看来,我有一个与data-remote="true"和data-method="delete"的链接。当我单击该链接时,我可以看到对我的Rails服务器的DELETE请求。返回的JS代码会更改此链接的属性,其中包括href和data-method。再次单击此链接后,我的服务器收到了对新href的请求,但使用的是旧的data-method,即使我已将其从DELETE到POST(它仍然发送一个DELETE请求)。但是,如果我刷新页面,HTML与"new"HTML相同(随返回的JS发生变化),但它实际上发送了正确的请求类型。这就是这个问题令我困惑的

  2. ruby-on-rails - Assets 管道损坏 : Not compiling on the fly css and js files - 2

    我开始了一个新的Rails3.2.5项目,Assets管道不再工作了。CSS和Javascript文件不再编译。这是尝试生成Assets时日志的输出:StartedGET"/assets/application.css?body=1"for127.0.0.1at2012-06-1623:59:11-0700Servedasset/application.css-200OK(0ms)[2012-06-1623:59:11]ERRORNoMethodError:undefinedmethod`each'fornil:NilClass/Users/greg/.rbenv/versions/1

  3. ruby-on-rails - Rails - 理解 application.js 和 application.css - 2

    rails新手。只是想了解\assests目录中的这两个文件。例如,application.js文件有如下行://=requirejquery//=requirejquery_ujs//=require_tree.我理解require_tree。只是将所有JS文件添加到当前目录中。根据上下文,我可以看出requirejquery添加了jQuery库。但是它从哪里得到这些jQuery库呢?我没有在我的Assets文件夹中看到任何jquery.js文件——或者直接在我的整个应用程序中没有看到任何jquery.js文件?同样,我正在按照一些说明安装TwitterBootstrap(http:

  4. ruby - 在 Mechanize 中使用 JavaScript 单击链接 - 2

    我有这个:AccountSummary我想单击该链接,但在使用link_to时出现错误。我试过:bot.click(page.link_with(:href=>/menu_home/))bot.click(page.link_with(:class=>'top_level_active'))bot.click(page.link_with(:href=>/AccountSummary/))我得到的错误是:NoMethodError:nil:NilClass的未定义方法“[]” 最佳答案 那是一个javascript链接。Mechan

  5. node.js - 如何在 Travis CI 上的一个项目中运行 Node.js 和 Ruby 测试 - 2

    我有一个包含多个组件的存储库,其中大部分是用JavaScript(Node.js)编写的,一个是用Ruby(RubyonRails)编写的。我想要一个.travis.yml文件来触发一个运行每个组件的所有测试的构建。根据thisTravisCIGoogleGroupthread,目前还没有官方支持。我的目录结构是这样的:.├──构建服务器├──核心├──扩展├──网络应用├──流浪文件├──package.json├──.travis.yml└──生成文件我希望能够运行特定版本的Ruby(2.2.2)和Node.js(0.12.2)。我已经有了一个make目标,所以maketest在每

  6. javascript - jQuery 的 jquery-1.10.2.min.map 正在触发 404(未找到) - 2

    我看到有关未找到文件min.map的错误消息:GETjQuery'sjquery-1.10.2.min.mapistriggeringa404(NotFound)截图这是从哪里来的? 最佳答案 如果ChromeDevTools报告.map文件的404(可能是jquery-1.10.2.min.map、jquery.min.map或jquery-2.0.3.min.map,但任何事情都可能发生)首先要知道的是,这仅在使用DevTools时才会请求。您的用户不会遇到此404。现在您可以修复此问题或禁用sourcemap功能。修复:获取文

  7. ruby-on-rails - ruby open ssl api for encrypted key (without nodes option) - 2

    在安装了openssllib的linux机器上,当您执行带有“-nodes”选项的“opensslpkcs12”时,您将获得带有未加密私钥的输出,但如果您跳过–nodes选项,则输出将具有加密的私钥。e.g.opensslpkcs12-intest.pfx-outtest.pem你应该看到像下面这样加密的私钥-----BEGINENCRYPTEDPRIVATEKEY-----MIIFDjBABgkqhkiGG7s=-----ENDENCRYPTEDPRIVATEKEY-----如何使用ruby​​的开放ssl库实现上述目标?这就是我用ruby​​生成私钥的方式:@private_key

  8. ruby-on-rails - 我将 Rails3 与 tinymce 一起使用。如何呈现用户关闭浏览器javascript然后输入xss? - 2

    我有一个用Rails3编写的站点。我的帖子模型有一个名为“内容”的文本列。在帖子面板中,html表单使用tinymce将“content”列设置为textarea字段。在首页,因为使用了tinymce,post.html.erb的代码需要用这样的原始方法来实现。.好的,现在如果我关闭浏览器javascript,这个文本区域可以在没有tinymce的情况下输入,也许用户会输入任何xss,比如alert('xss');.我的前台会显示那个警告框。我尝试sanitize(@post.content)在posts_controller中,但sanitize方法将相互过滤tinymce样式。例如

  9. node.js - 从未编写过任何自动化测试,我应该如何开始行为驱动开发? - 2

    按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭9年前。多年来,我一直在使用多种语言进行编程,并且认为自己总体上相当擅长。但是,我从未编写过任何自动化测试:没有单元测试,没有TDD,没有BDD,什么都没有。我已经尝试开始为我的项目编写适当的测试套件。我可以看到在进行任何更改后能够自动测试项目中所有代码的理论值(value)。我可以看到像RSpec和Mocha这样的测试框架应该如何使设置和运行所述测试变得相当容易

  10. ruby - 使用 Selenium WebDriver 启用/禁用 javascript - 2

    出于某种原因,我必须为Firefox禁用javascript(手动,我们按照提到的步骤执行http://support.mozilla.org/en-US/kb/javascript-settings-for-interactive-web-pages#w_enabling-and-disabling-javascript)。使用Ruby的SeleniumWebDriver如何实现这一点? 最佳答案 是的,这是可能的。而是另一种方式。您首先需要查看链接Selenium::WebDriver::Firefox::Profile#[]=

随机推荐