【问题标题】:hough transform - javascript - node.js霍夫变换 - javascript - node.js
【发布时间】:2014-06-17 06:30:50
【问题描述】:

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

明显的问题是我做错了什么。我已经检查了我的逻辑和代码三倍,而且我的参数看起来也不错。但显然我错过了一些东西。

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

另外,我对 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]
    }
}

【问题讨论】:

  • 我不是专家,但是看图已经很奇怪了:LEAST_REQUIRED_ELLIPSES = 6
  • @Dennis Jaheruddin 是的,无论是 1 还是 10,该参数都不会改变结果。这并不奇怪,因为正如您在右侧的图像中看到的那样,它认为还有更多(每个红点都是一个“椭圆中心”)。实际上,对于要计算椭圆的最少票数而言,这可能更有意义。我使用了原来的算法变量名。
  • 我是唯一一个将其误读为 HUGE 变换的人吗? ;-)

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


【解决方案1】:

您似乎尝试实现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 的点(而不是更大,因为它是短轴)。换句话说,您清除了距离低于最新计算轴的圆内的所有点。

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

max_minor      = arr_accum[max_votes.index].d;

确实,您没有重叠的椭圆,而且它们已经足够分散了。请考虑使用更好的算法来处理重叠或更接近的椭圆。

算法混合了长轴和短轴

论文的第 6 步内容如下:

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

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

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

LEAST_REQUIRED_ELLIPSES 太小

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

示例图片不是黑白的

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

可在此处获得使其工作的最小更改差异: https://gist.github.com/pguyot/26149fec29ffa47f0cfb/revisions

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

【讨论】:

  • 亲爱的@Paul Guyot 非常感谢您的详细回答,我尝试按照您的建议理解并更改代码,但没有这样做。要求您写下建议的更改会不会太过分?如果你愿意,我会EDIT 上面写的代码以及我尝试做的更改。但我认为它增加了更多的混乱,你似乎完全知道你在做什么。我不确定我是否 100% 理解逻辑和代码是否有意义(例如,“b”注释对我来说没有意义)。我追求更好的“重叠”或“更接近”的椭圆算法。谢谢!
  • 缺少的部分是提供的图像不是黑白的,因此 80 票不足以正确找到所有这些灰色像素的中心。我添加了一个脚本链接,该脚本实现了计算椭圆参数的最小更改。
  • 亲爱的@Paul Guyot,非常感谢...我会坐下来试着理解你所做的每一个改变。我会尝试添加一个“Guo Hall”细化算法,这样我就可以更好地进行黑白阈值处理。我运行了它,花了一些时间(很慢:/)但得到了正确的结果..如果您有任何其他建议,我非常乐意记笔记(或尝试),再次感谢!
猜你喜欢
  • 1970-01-01
  • 2019-10-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多