use*_*518 8 javascript algorithm image-processing computer-vision node.js
所以,我正在尝试实现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]
}
}
Run Code Online (Sandbox Code Playgroud)

看来你尝试实现谢永红的算法;强吉(2002)。一种新的高效椭圆检测方法 2. p. 第957章
在您的代码中,您通过将坐标重置为 来删除找到的椭圆(原始论文算法的第 12 步){-1, -1}。
您需要添加:
`if (arr_edges[x1y1].x === -1) break;`
Run Code Online (Sandbox Code Playgroud)
在 x2y2 块的末尾。否则,循环会将-1、-1视为白点。
更重要的是,您的算法包括删除到中心距离小于 的每个点b。b据称是短轴半长(根据原始算法)。但在您的代码中,变量b实际上是最新的(而不是最频繁的)半长,并且您删除了距离小于 b 的点(而不是更大的点,因为它是短轴)。换句话说,您清除了圆内距离小于最新计算轴的所有点。
实际上,可以通过清除圆内距离低于所选主轴的所有点来处理示例图像:
max_minor = arr_accum[max_votes.index].d;
Run Code Online (Sandbox Code Playgroud)
事实上,你没有重叠的椭圆,而且它们足够分散。请考虑一种更好的算法来处理重叠或更接近的椭圆。
该论文的第 6 步内容如下:
对于每个第三个像素 (x, y),如果 (x, y) 和 (x0, y0) 之间的距离大于要考虑的一对像素所需的最小距离,则执行 (7) 中的以下步骤至(9)。
这显然是一个近似值。如果这样做,您最终将考虑比短轴半长更远的点,并最终在长轴上(轴交换)。您应该确保所考虑的点与测试椭圆中心之间的距离小于当前考虑的长轴半长(条件应为d <= a)。这将有助于算法的椭圆擦除部分。
另外,如果您还与一对像素的最小距离进行比较,根据原始论文,40 对于图片中较小的椭圆来说太大了。您代码中的注释是错误的,它应该最多是最小椭圆短轴半长度的一半。
该参数的命名也有误。这是椭圆应被视为有效的最小票数。每个投票对应一个像素。因此,值 6 意味着只有 6+2 个像素才能形成椭圆。由于像素坐标是整数,并且图片中有超过 1 个椭圆,因此该算法可能会检测到不是的椭圆,并最终清除边缘(特别是与有问题的椭圆擦除算法结合使用时)。根据测试,值为 100 将找到图片中五个椭圆中的四个,而值为 80 将找到所有椭圆。较小的值将找不到椭圆的正确中心。
尽管有这样的评论,示例图像并不完全是黑白的。您应该对其进行转换或应用一些阈值(例如大于 10 的 RGB 值,而不是简单的不同形式 0)。
使其工作的最小更改的差异可在此处找到: https://gist.github.com/pguyot/26149fec29ffa47f0cfb/revisions
最后,请注意,parseInt(x.toFixed(0))可以重写Math.floor(x),您可能不希望像这样截断所有浮点数,而是将它们舍入,并在需要的地方继续:从图片中擦除椭圆的算法将受益于中心坐标的非截断值。这段代码肯定可以进一步改进,例如它目前计算点x1y1和x2y2两次之间的距离。