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

Я пытался решить эту проблему по-разному, и после месяца попыток решить ее самостоятельно я думаю, что пришло время взглянуть на нее свежим взглядом. Я пытаюсь создать приложение для масштабирования изображения для изменения размера 8-битных спрайтов и превращения их в векторные изображения. До сих пор я работал над этим; он берет изображение, разбивает его на формы (области со смежными пикселями одного цвета), а затем каждый пиксель в форме заменяется четырьмя пикселями:

private Point[] expand(int x, int y){
    x *= factor;
    y *= factor;
    return new Point[]{new Point(x+half_factor,y), new Point(x+factor,y+half_factor),
            new Point(x+half_factor, y+factor), new Point(x,y+half_factor)};
}

Каждая из четырех точек помещается в двумерный логический массив:

private void placePoint(int x, int y){
    table[x][y] = !table[x][y];
    extrema(x,y);
}

И результат для одной отдельной формы выглядит так:

введите здесь описание изображения

Теперь я хочу превратить все эти точки (за исключением тех, что внутри) в многоугольник, и я пробовал много разных решений, совсем недавно я пытался найти ближайшего соседа, пока он не дойдет до начала, но каждый алгоритм пробую не получается. В этом конкретном примере он попадает в перевернутую гумбу в правом нижнем углу и запутывается в кластере пикселей слева от него. Программа считает, что путь пройден, и создает линию оттуда в левый верхний угол, полностью игнорируя точки в левом нижнем квадранте.

введите здесь описание изображения

Вот как я хочу, чтобы это выглядело:

введите здесь описание изображения

Вот несколько вещей, которые всегда верны в моей ситуации и могут помочь найти работающий алгоритм:

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

Любая помощь будет высоко ценится!

ОБНОВЛЕНИЕ:

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

ОРИГИНАЛ:

введите здесь описание изображения

ВЫВОД:

введите здесь описание изображения

ПОСЛЕДНЕЕ ОБНОВЛЕНИЕ:

Теперь все работает, осталось исправить мелкие ошибки!

введите здесь описание изображения


person Jedi_Maseter_Sam    schedule 16.03.2016    source источник
comment
Я проголосовал только из-за картинок...   -  person shole    schedule 16.03.2016
comment
1. Я думаю, что это должно быть √2.C единиц для диагонали 2. почему вы изменяете размер / учетверяет точки, если вы все равно собираетесь векторизовать? Если вы остаетесь на том же разрешении, вы можете использовать методы прямого 8-точечного соседства. И можете использовать A* напрямую, просто отключите начальную и конечную точки перед ее применением. Также может помочь Поиск дыр в двухточечных наборах?, поскольку ваш массив представляет собой карту плотности.   -  person Spektre    schedule 16.03.2016
comment
Проблема создания многоугольника выпуклой оболочки набора точек может быть связана с этим.   -  person Codor    schedule 16.03.2016
comment
Первоначально я использовал это без изменения размера, но для фигур шириной в 1 пиксель вы не можете создать многоугольник, а для фигур с выступом шириной всего в 1 пиксель вы вообще не можете создать многоугольник. Кроме того, если я учетверю точки, будет проще использовать алгоритм Катмулла-Рома для перерисовки склонов в виде кривых. Я также изучил использование обычного алгоритма выпуклой оболочки, почти все они есть на этой вики-странице, и обнаружил, что вывод в большинстве случаев слишком отличается от исходного изображения, а мелкие детали, которые я пытался сохранить, терялись. .   -  person Jedi_Maseter_Sam    schedule 16.03.2016


Ответы (2)


Я бы попробовал модифицированную версию классического алгоритма следования контуру. http://www.imageprocessingplace.com/downloads_V3/root_downloads/tutorials/contour_tracing_Abeer_George_Ghuneim/ray.html

Модификация заключается в том, что перемещение по горизонтали/вертикали производится с шагом в два пикселя (вместо одного в стандартной версии).

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

person Yves Daoust    schedule 17.03.2016
comment
Спасибо! Я придумал что-то подобное сам, но у меня поиск соседа начался в неправильном направлении, плюс я упустил некоторые критерии завершения. - person Jedi_Maseter_Sam; 17.03.2016

На высоком уровне,

  1. Заливка, чтобы найти связанный набор пикселей.
  2. Примените граничный оператор, чтобы получить встроенный планарный граф.
  3. Найдите внешнюю грань.

Шаги 2 и 3 являются новыми. Шаг 2 выполняется путем размещения двух полуребер везде, где пиксель в наборе граничит с пикселем, не входящим в набор, по одному в каждом направлении. Например,

***
* *
***
* *

превращается в

._._._.
|* * *|
. ._. .
|*| |*|
. ._. .
|* * *|
. ._. .
|*| |*|
._. ._.

где каждый _ или | обозначает два полуребра, по одному в каждом направлении. Для каждой точки . соберите полуребра, направленные к ней, и сделайте указатель от каждого такого полуребра к следующему в порядке против часовой стрелки. Каждое полуребро также должно указывать на полуребро в противоположном направлении.

Шаг 3 выполняется путем обхода следующим образом: для данного полуребра найти следующее полуребро в порядке против часовой стрелки, направленное к той же точке, а затем повторно найти полуребро, противоположное этому полуреберу. Это проследит одно из лиц. Если вам нужен крайний, начните с верхней границы самого верхнего пикселя в наборе.

person David Eisenstat    schedule 16.03.2016
comment
Я раньше не видел ничего подобного этому подходу, и я думаю, что это может сработать, но я немного запутался в том, что такое полуребро. - person Jedi_Maseter_Sam; 16.03.2016
comment
@Jedi_Maseter_Sam Если бы искусство ASCII не было такой проблемой, каждое ребро было бы двумя стрелками, по одной в каждом направлении. Полуребро — это просто упорядоченная пара точек, где одна точка является головой, а другая — хвостом. - person David Eisenstat; 16.03.2016
comment
ваша идея немного сработала, и она обработала больше изображения, но она все еще дает ошибки в плотно упакованных кривых. - person Jedi_Maseter_Sam; 16.03.2016
comment
@Jedi_Maseter_Sam Является ли новое выходное изображение таким подходом? Это не выглядит правильно - опубликуйте свой код, пожалуйста. - person David Eisenstat; 16.03.2016
comment
вот исходный код, который у меня есть до сих пор. gist.github.com/JediMasterSam/3e3d474034c1da6ad9ad Мне пришлось внести некоторые изменения в то, что вы предложили, потому что Я хочу снять наличники по краям. - person Jedi_Maseter_Sam; 16.03.2016