Хотя преобразование - javascript - node.js
Итак, я пытаюсь реализовать грубое преобразование, эта версия является одномерной (для всех димов уменьшена до 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]
}
}
1 ответ
Кажется, вы пытаетесь реализовать алгоритм Yonghong Xie; Цян Цзи (2002). Новый эффективный метод обнаружения эллипса 2. с. 957
Удаление эллипса страдает от нескольких ошибок
В своем коде вы выполняете удаление найденного эллипса (шаг 12 алгоритма оригинальной статьи), сбрасывая координаты в {-1, -1}
,
Вам нужно добавить:
`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 <= a
). Это поможет стереть эллипс в алгоритме.
Кроме того, если вы сравниваете с наименьшим расстоянием для пары пикселей, как в оригинальной статье, значение 40 слишком велико для меньшего эллипса на изображении. Комментарий в вашем коде неправильный, он должен быть максимум на половину длины наименьшей оси эллипса.
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)
и, возможно, вы захотите не обрезать все поплавки, как это, а скорее округлить их и продолжить, где необходимо: алгоритм стирания эллипса с картинки выиграл бы от не усеченных значений для координат центра. Этот код определенно может быть улучшен и далее, например, он в настоящее время вычисляет расстояние между точками x1y1
а также x2y2
дважды.