алгоритм трассировки границы в 2D массиве - PullRequest
5 голосов
/ 16 января 2012

У меня есть двумерный массив целых чисел, которые представляют группировки (кристаллические зерна) на двумерной поверхности.что-то вроде этого: something like this (каждому пикселю этого изображения назначается целое число в зависимости от группы, к которой оно принадлежит, поэтому каждому красному пикселю присваивается 1, например, каждому синему 2)

с учетом X,Координата Y на границе между двумя такими группировками (пользователь щелкает там) как отследить эту границу между этими двумя группировками, сохранив каждую пиксельную координату вдоль границы и получить две конечные координаты (I 'меня не касается случай вложения, в котором не было бы конечной точки, а только цикл)

любой алгоритм, который я придумал, кажется настолько утомительным для реализации, и я просто не могу себе представить, что никто не делал этогодо.Любая помощь?Предпочтительно было бы решение в c #, но любая подсказка об алгоритме очень ценится.

РЕДАКТИРОВАТЬ:

Я должен был заявить, что я собирался реализовать алгоритм длявекторизовать эту линию следующим образом:

  1. между двумя конечными точками определить линию
  2. получить точку границы, наиболее удаленную от текущей линии, если она дальше, чем d, разделить линию наэта точка, поэтому есть два отрезка линии
  3. , повторяющиеся до тех пор, пока ни одна граничная точка не окажется дальше, чем d от линейной полосы

Я думал, что это достаточно легко реализовать, поэтому я не сделалне утверждайЧтобы попасть туда, мне просто нужно было решить текущую проблему ...

По вопросам:

как форматируются необработанные данные? - это просто short[,] labeling;с размером примерно 250x150 примерно так:

11111112222
11111222222
11112222222
11112222222 <- user clicks on leftmost 2 or rightmost 1 -> I want to trace that border
11111122222                                                down to the first
11111133322                                                encountered 3 and up to the
11333333333                                                frame-border

что такое конечная точка? - поскольку я думал о глобальных решениях, я мог бы описать конечную точку как: область 2x2где 4 пикселя состоят из color1, color2 и, по крайней мере, одной трети разных цветов.

что связано непрерывно? - это не имеет значения для остальной части алгоритма, см. ниже

как насчет y-образных областей? - я их не интересую, можно предположить, что область color1 за границей имеет ширину не менее 2 пикселей, поэтому она также не имеетне имеет значения, если мы говорим о 4 или 8. окрестности.

что у меня есть в настоящее время? - сначала я попробовал алгоритм «ходьбы», что-то вроде mvds опубликовал, нообнаружил, что я делаю степпингИон и проверка во всех 4 направлениях, что было утомительно и выглядело ужасно.Я не нашел хорошего представления для «это направление, в котором был сделан последний шаг, не проверяйте этот пиксель на соседство».

Затем я отказался от алгоритма ходьбы и попробовал глобальный подход (например,фильтр): для каждого пикселя проверьте, является ли он color1 И имеет color2 в его 4-окрестности.С этим я получаю все границы между color1 и color2.Я собирался удалить все границы, которые не связаны с пользовательской координатой, вызванной нажатием кнопки, каким-то образом, но у меня возникла проблема: где конечные точки?

Я все еще благодарен за большевход.Сейчас я посмотрю, как далеко я могу пойти с помощью алгоритма mvds.

Ответы [ 4 ]

3 голосов
/ 16 января 2012

Полагаю, вы уже определили 2 рассматриваемых цвета, например, как описано как mvds, как шаг предварительной обработки.

Я думаю, вам будет полезно использовать систему координат, где каждый(x, y) представляет не пиксель, а точку, где касаются 4 пикселя.Затем вы можете написать функцию, чтобы определить, является ли север граничной пиксельной границей, аналогично для юга, востока, запада (или, может быть, вы предпочитаете терминологию вверх / вниз / влево / вправо).

Начните с точкина границе, например, просмотрите окрестность 4x4 для точки, которая имеет одну из N / S / E / W в качестве границы.Следуйте по этой границе до следующей точки, а затем отсканируйте все 4 направления, кроме направления, в котором вы пришли, для границы следующего пикселя.Повторяйте, пока не исчерпаете границы пикселей.Затем вы знаете, что находитесь в одной конечной точке.

Вернитесь к началу и проведите границу в направлении, отличном от того, что вы изначально взяли, пока не достигнете другой конечной точки.

Это даету вас все границы пикселей.Каждая граница пикселя имеет цвет 1 на одной стороне и цвет 2 на другой стороне.

(я бы подумал, что векторизация будет намного сложнее, чем определение границы, но это не то, что ваш вопрос в основномо, правильно? Чтобы сделать это, я бы начал с конечной точки и следовал за последовательностью границ пикселей по границе, в каждой точке проверяя, соответствует ли прямая линия от конечной точки до текущей точки границам пикселей.как только это не произойдет, это конец одной строки, и вы начинаете новую строку.)

1 голос
/ 22 февраля 2015

Хорошие ресурсы для этого материала здесь обнаружение краевых пикселей с помощью алгоритма марширующих квадратов и в википедии (если эта ссылка исчезнет) марширующие квадраты algo

1 голос
/ 16 января 2012

Ваше описание не на сто процентов ясно для меня, но если я вас правильно понимаю, вы хотите вычислить следующее:

  • множество смежных точек цвета A, которые находятся рядомв цветную точку B
  • , которая содержит заданную начальную точку.

Учитывая эту спецификацию, ваш код практически сам себя пишет.Единственное, с чем вам нужно сейчас определиться, это то, что означает «непрерывное соединение» (например, являются ли пиксели смежными только по их углам или нет?).

Кроме того, ваше описание неоднозначно.Рассмотрим y-образную область, где плечи области имеют ширину в один пиксель: это будет иметь три «конечных точки», если вы определите конечную точку как «член набора с одним соседом также в наборе».Если вы ослабите свое требование разрешить любое количество конечных точек, тогда ваш код может собирать набор конечных точек по ходу.

РЕДАКТИРОВАТЬ

Рад, что вы решили свою проблему.Я набросал решение, которое производит это для вашей типовой задачи:

1111***2222
111**222222
111*2222222
111*2222222
111***22222
11111*33322
11333333333

Вот код, предоставленный только потому, что мне нужна проверка для его кодирования :-) Он написан для ясности, а не для скорости.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

namespace StackOverflowEdgeDetection
{
    class Program
    {
        private static HashSet<Point> FindBorder(char[,] grid, Point start, char inside, char outside)
        {
            var border = new HashSet<Point> {};
            var endpoints = new HashSet<Point> { start };

            while (endpoints.Count != 0)
            {
                var p = endpoints.First();
                endpoints.Remove(p);
                border.Add(p);
                var newEndpoints = Neighbours(p).Where(q =>
                        Grid(grid, q) == inside &&
                        !border.Contains(q) &&
                        Neighbours(q).Any(r => Grid(grid, r) == outside)
                    );
                endpoints.UnionWith(newEndpoints);
            }

            return border;
        }

        private static IEnumerable<Point> Neighbours(Point p)
        {
            yield return new Point(p.X - 0, p.Y - 1);
            yield return new Point(p.X + 1, p.Y - 1);
            yield return new Point(p.X + 1, p.Y + 0);
            yield return new Point(p.X + 1, p.Y + 1);
            yield return new Point(p.X + 0, p.Y + 1);
            yield return new Point(p.X - 1, p.Y + 1);
            yield return new Point(p.X - 1, p.Y - 0);
            yield return new Point(p.X - 1, p.Y - 1);
        }

        public static char Grid(char[,] grid, Point p) {
            var x = p.X;
            var y = p.Y;
            var height = grid.GetLength(0);
            var width = grid.GetLength(1);
            return (0 <= x && x < width && 0 <= y && y < height) ? grid[y, x] : '\0';
        }

        static void Main(string[] args)
        {
            var border = FindBorder(TestGrid, TestStart, TestInside, TestOutside);

            var points = Enumerable.Range(0, TestHeight)
                                   .SelectMany(y => Enumerable.Range(0, TestWidth)
                                                              .Select(x => new Point(x, y)));

            foreach (var p in points) {
                Console.Write(border.Contains(p) ? '*' : Grid(TestGrid, p));
                if (p.X + 1 == TestWidth) Console.WriteLine();
            }

            Console.ReadLine();
        }

        private static readonly char[,] TestGrid = new char[,] {
            { '1', '1', '1', '1', '1', '1', '1', '2', '2', '2', '2' }, 
            { '1', '1', '1', '1', '1', '2', '2', '2', '2', '2', '2' }, 
            { '1', '1', '1', '1', '2', '2', '2', '2', '2', '2', '2' }, 
            { '1', '1', '1', '1', '2', '2', '2', '2', '2', '2', '2' }, 
            { '1', '1', '1', '1', '1', '1', '2', '2', '2', '2', '2' }, 
            { '1', '1', '1', '1', '1', '1', '3', '3', '3', '2', '2' }, 
            { '1', '1', '3', '3', '3', '3', '3', '3', '3', '3', '3' }
        };
        private static readonly Point TestStart = new Point(3, 3);
        private static readonly Point TestAdjacent = new Point(4, 3);
        private static readonly char TestInside = Grid(TestGrid, TestStart);
        private static readonly char TestOutside = Grid(TestGrid, TestAdjacent);
        private static readonly int TestWidth = TestGrid.GetLength(1);
        private static readonly int TestHeight = TestGrid.GetLength(0);
    }
}
1 голос
/ 16 января 2012

Вот несколько мыслей и начало алгоритма:

Найти контур области одинакового цвета легче, чем найти границу, особенно когда граница на самом деле не является "двоичной" (т. Е. Только 2 цвета точно), как это выглядит на вашем изображении.

Поиск смежных частей двух контуров не очень сложен. Для каждой точки на контуре A найдите ближайшую точку контура B. Если расстояние |A-B| < X, точка на полпути между A и B находится на границе. (X зависит от нечеткости вашей границы)

Если вы сможете заставить своих пользователей дважды щелкать по обе стороны границы, это было бы здорово. Если вы настаиваете на одном клике, найдите две самые большие области в радиусе X вокруг точки щелчка.

Найти контур области не сложно:

  1. взять точку (x,y), чтобы начать, принять направление (dx,dy)=(1,0), чтобы начать
  2. взять цвет C точки (x,y), который будет цветом для трассировки
  3. бегите x+=dx,y+=dy, пока на (x+dx,y+dy) не появится другой цвет и вы не окажетесь на границе
  4. посмотрите на (x+dx,y+dy), если это не тот же цвет C, вы попадаете на границу: поверните налево и переходите к 4.
  5. x+=dx, y+=dy, т.е. сделать шаг
  6. запись (x,y) как часть границы
  7. если ( x==xstart && y==ystart ) вы сделали
  8. повернуть направо и перейти к 4.

повернуть налево означает: (dx',dy') = (dy,-dx), revolutions++

повернуть направо означает: (dx',dy') = (-dy,dx), revolutions--

revolutions в конечном итоге будет положительным или отрицательным, в зависимости от направления трассировки (внутри / снаружи)

Есть один угловой случай, в котором это происходит бесконечно, а именно, когда вы начинаете в области размером 1 пиксель. Это легко проверить. Кроме того, вы можете проверить х / у границы. «Один и тот же цвет» и «другой цвет», конечно, также могут быть реализованы как некоторый предел цветового расстояния (то есть |(r,g,b)-(R,G,B)|<D)

отказ от ответственности это рабочий, но простой, медленный алгоритм, который я разработал один раз без бремени каких-либо соответствующих знаний или опыта.

...