Подтвердить что ты не робот

Определить невыпуклую оболочку набора отрезков

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

Мне нужно определить невыпуклый контур области, определенной несколькими отрезками линии.

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


Как отметил Жан-Франсуа Корбетт, бывают случаи, когда есть несколько решений. Мне явно нужно больше думать о моем определении.

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

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

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


Определение проблемы:

Контур многоугольника никогда не должен пересекать ни один из сегментов и должен быть образован из линий, соединяющих все конечные точки сегментов. Все сегменты должны лежать целиком внутри или вдоль границы многоугольника. Никакая конечная точка не может использоваться более одного раза в контуре (игнорируя "закрытие" многоугольника, добавляя первую точку в конец для программных библиотек, для которых требуется закрыть многоугольники.)

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


Примеры:

В качестве примера у меня есть что-то в этом роде: Segments Defining the Area

И я хотел бы выделить следующую область: Desired Outline

Он также должен работать для непересекающихся сегментов. Например.

enter image description hereenter image description here

Я думаю, что (?) есть уникальное решение в любом случае, с учетом критериев выше. (Edit: В целом нет уникального решения, как отметил Жан-Франсуа Корбетт. Однако меня все еще интересует алгоритм, который либо генерирует одно из приемлемых решений.)

Контрольные случаи

Для тестового примера здесь приведен код для создания вышеуказанных цифр. Я использую python здесь, но вопрос является агностическим.

import matplotlib.pyplot as plt

def main():
    test1()
    test2()
    plt.show()

def test1():
    """Intersecting segments."""
    segments = [[(1, 1), (1, 3)],
                [(3.7, 1), (2, 4)],
                [(2, 0), (3.7, 3)],
                [(4, 0), (4, 4)],
                [(4.3, 1), (4.3, 3)],
                [(0, 2), (6, 3)]]

    desired_outline = [segments[0][0], segments[5][0], segments[0][1], 
                       segments[1][1], segments[2][1], segments[3][1],
                       segments[4][1], segments[5][1], segments[4][0],
                       segments[3][0], segments[1][0], segments[2][0],
                       segments[0][0]]

    plot(segments, desired_outline)

def test2():
    """Non-intersecting segments."""
    segments = [[(0, 1), (0, 3)],
                [(1, 0), (1, 4)],
                [(2, 1), (2, 3)],
                [(3, 0), (3, 4)]]

    desired_outline = [segments[0][0], segments[0][1], segments[1][1],
                       segments[2][1], segments[3][1], segments[3][0], 
                       segments[2][0], segments[1][0], segments[0][0]]

    plot(segments, desired_outline)


def plot(segments, desired_outline):
    fig, ax = plt.subplots()
    plot_segments(ax, segments)
    ax.set_title('Segments')

    fig, ax = plt.subplots()
    ax.fill(*zip(*desired_outline), facecolor='gray')
    plot_segments(ax, segments)
    ax.set_title('Desired Outline')

def plot_segments(ax, segments):
    for segment in segments:
        ax.plot(*zip(*segment), marker='o', linestyle='-')
    xmin, xmax, ymin, ymax = ax.axis()
    ax.axis([xmin - 0.5, xmax + 0.5, ymin - 0.5, ymax + 0.5])

if __name__ == '__main__':
    main()

Любые идеи?

Я начинаю подозревать, что программное обеспечение, результаты которого я пытаюсь воспроизвести, использует алгоритм радиальной развертки в какой-то "внутренней" системе координат (например, система координат с x-prime и y-prime масштабируется и вращается по главным осям, определяемым разбросом точек, что делает проблему более "круговой".) Однако это создает решения, в которых контур во многих случаях пересекает отрезки. Достаточно легко обнаружить эту и грубую силу оттуда, но, конечно же, лучший способ?

4b9b3361

Ответ 1

  1. Выберите безопасную отправную точку. Может быть, например, конечной точкой с максимальным значением x.
  2. Марш вдоль отрезка.
  3. При встрече с любым перекрестком всегда поворачивайте налево и идите по этому новому отрезку.
  4. При обнаружении конечной точки запишите это. Перейти к 2.
  5. Остановитесь, когда вернетесь к исходной точке. Ваш список записанных конечных точек теперь составляет упорядоченный список вершин вашего вогнутого корпуса.

NB: Это не удастся, если есть "свободно плавающий" сегмент внешней линии, который не пересекает любой другой сегмент линии. Однако вы указываете, что "столбцы однозначно определяют решение", что исключает это условие сбоя. (Внешние сегменты делают возможным два разных решения.)

РЕДАКТИРОВАТЬ... или, скорее, внешние сегменты могут сделать два различных решения - в зависимости от точного расположения. Доказательство. Ниже приведен пример, в котором желтый сегмент, который я добавил, делает возможным два решения (синие и серые линии, нарисованные от руки). Если бы желтый сегмент был ориентирован перпендикулярно тому, как он нарисован сейчас, было бы возможно только одно решение. Похоже, ваша проблема плохо определена.

enter image description here

РЕДАКТИРОВАТЬ В действительности это также может произойти сбой, если ваша коллекция сегментов является "очень вогнутой", т.е. если есть конечные точки, спрятанные в углах затвора вашей стопки сегментов. На рисунке ниже я добавил черный сегмент. Мой алгоритм незаконно соединяет свою конечную точку с другой конечной точкой (пунктирная серая линия). Я оставлю свой ответ на тот случай, если другие склонны опираться на него.

РЕДАКТИРУЙТЕ, подумав еще раз: даже в "очень вогнутом" случае это решение определенно даст вам все точки вашего вогнутого корпуса в правильном порядке, но они могут быть перемежены дополнительными неуместными точками, такими как черные один. Так что может быть слишком много очков.

Ответ тогда, конечно, сделать некоторое сокращение. Это будет довольно сложное сокращение, особенно если вы можете иметь несколько последовательных "точек отшельника", таких как черная, поэтому я не имею в виду разумный алгоритм. Но даже слепая, грубая сила может быть осуществимой. Каждая точка может быть либо принята, либо отклонена (булево), поэтому, если у вас есть N правильно упорядоченных точек-кандидатов в вашей вогнутой оболочке, то есть только 2 ^ N возможностей для проверки. Это намного, меньше возможностей, чем грубой силы для вашей исходной задачи перестановок, которая будет иметь SUM of (n!/(nk)!) for k=1:(n-1) возможностей (простите за мои обозначения). Таким образом, этот алгоритм значительно сузит вашу проблему.

Я думаю, что это путь.

enter image description here

Ответ 2

Не полная идея, но в любом случае: предположим, что вы начали с алгоритма круговой развертки для выпуклой оболочки (где вы сортируете, а затем обрабатываете точки по их углу от центральной точки). Если все точки попадают в этот корпус, все готово. Если нет, тогда вам нужно "затянуть" корпус, чтобы включить эти точки. Каждый из этих пунктов был сразу кандидатами на выпуклый корпус и был удален, потому что они сломали выпуклость. Иногда (как с верхней фиолетовой точкой в ​​первом примере) мы можем просто оставить их. Где мы не можем, потому что новый сегмент корпуса пересекает сегмент (как идущий от нижнего зеленого до нижнего фиолетового в первый пример, предполагая, что нижняя точка аква обработана до зеленого), исправление немного больше связано (и часть, которую я не сглаживал, и именно та часть, о которой упоминается в вопросе последнего редактирования).