hough变换 – javascript – node.js

所以,我试图实现霍夫变换,这个版本是基于次要属性的1维(它的所有变暗减less到1个昏暗的优化)版本。 封闭的是我的代码,具有示例图像…input和输出。

明显的问题是我做错了什么。 我翻了三倍检查我的逻辑和代码,它看起来也很好,我的参数。 但显然我错过了什么。

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

另外,我不感兴趣的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] } } 

原始图像Hough变换输出

看来你试着实现了谢永红的algorithm; 强记(2002)。 一种新的有效的椭圆检测方法2. p。 957 。

椭圆删除遭受了几个错误

在您的代码中,通过将坐标重置为{-1, -1}来执行find的椭圆的移除(原始纸张的algorithm的步骤12)。

你需要添加:

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

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

更重要的是,你的algorithm在于擦除到中心的距离小于b每个点。 b被认为是短轴的半长度(按原始algorithm)。 但是在你的代码中,variablesb实际上是最新的 (也不是最频繁的)半长,并且你擦除的距离小于b(而不是更大,因为它是次轴)。 换句话说,清除距离最近计算轴的距离的圆内的所有点。

您的示例图像实际上可以通过清除圆圈内所有点的距离小于所选长轴的距离进行处理:

 max_minor = arr_accum[max_votes.index].d; 

的确,你没有重叠的椭圆,而且它们已经足够分散了。 请考虑更好的重叠algorithm或更接近的省略号。

该algorithm混合主轴和副轴

文章的第6步说:

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

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

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

LEAST_REQUIRED_ELLIPSES太小

这个参数也是错误的。 这是一个椭圆应被视为有效的最低票数。 每个投票对应一个像素。 所以6的值意味着只有6 + 2个像素形成一个椭圆。 由于像素坐标是整数,并且在图片中有多个椭圆,所以该algorithm可能会检测到不是椭圆,并最终清除边缘(特别是与错误的椭圆擦除algorithm结合使用时)。 根据testing,100的值会find你的图片的五个椭圆中的四个,而80会find他们。 较小的值不会find椭圆的正确中心。

示例图像不是黑白的

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

最小变化的差异,使其工作可在这里: https : //gist.github.com/pguyot/26149fec29ffa47f0cfb/revisions

最后,请注意parseInt(x.toFixed(0))可以被重写为Math.floor(x) ,并且您可能不希望像这样截断所有的浮点数,而是将它们parseInt(x.toFixed(0)) ,然后在需要的地方继续:要擦除的algorithm图片中的椭圆会从中心坐标的非截断值中受益。 这个代码绝对可以进一步改进,例如它现在计算两次点x1y1x2y2之间的距离。