霍夫变换 - javascript - node.js

时间:2014-04-30 19:30:31

标签: javascript node.js algorithm image-processing computer-vision

所以,我试图实现hough变换,这个版本是基于次要属性的1维(它的所有dims减少到1 dim优化)版本。 随附的是我的代码,带有示例图像......输入和输出。

明显的问题是我做错了什么。我三倍检查我的逻辑和代码,它看起来也很好我的参数。但显然我对某些东西缺失了。

请注意,红色像素应为椭圆中心,而蓝色像素是要删除的边(属于符合数学方程的椭圆)。

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

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]
    }
}

Original image Hough transform output

1 个答案:

答案 0 :(得分:4)

您似乎尝试实施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块的末尾。否则,循环将-1,-1视为白点。

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

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

max_minor      = arr_accum[max_votes.index].d;

实际上,你没有重叠的椭圆并且它们已经足够分散。请考虑更好的重叠算法或更接近的省略号。

算法混合长轴和短轴

论文的第6步如下:

  

对于每个第三个像素(x,y),如果(x,y)和(x0)之间的距离,   y0)大于一对像素所需的最小距离   然后考虑从(7)到(9)执行以下步骤。

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

此外,如果您还要比较一对像素的最小距离,则根据原始纸张,40对于图片中较小的椭圆而言太大。你的代码中的注释是错误的,它应该是最小椭圆短轴 half-length 的一半。

LEAST_REQUIRED_ELLIPSES太小

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

样本图像不是黑色&amp;白

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

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

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