Мне нужно найти точку, которая является визуальным центром многоугольника неправильной формы. Под визуальным центром я подразумеваю точку, которая визуально кажется находящейся в центре большой области многоугольника. Приложение должно разместить метку внутри многоугольника.
Вот решение, использующее внутреннюю буферизацию:
Если это будет использоваться, каков эффективный и быстрый способ найти буфер? Если нужно использовать какой-либо другой способ, то какой?
Хорошим примером действительно сложных многоугольников является гигантская толстая буква U (написанная шрифтом Arial Black, Impact или другим подобным шрифтом).
15 ответов
Если вы можете преобразовать многоугольник в двоичное изображение, вы можете использовать основу, которая существует в области обработки изображений, например: Быстрый скелетный алгоритм о блокировке представленных двоичных изображений.
Но в общем случае это не совсем разумно из-за ошибок дискретизации и лишней работы.
Однако, возможно, они вам пригодятся:
- Прямой каркас простого многоугольника
- Определение каркаса простого многоугольника за (почти) линейное время
РЕДАКТИРОВАТЬ: Возможно, вы хотите найти точку, которая является центром самого большого круга, содержащегося в многоугольнике. Это не обязательно всегда в наблюдаемом центре, но большую часть времени, вероятно, дало бы ожидаемый результат, и только в слегка патологических случаях что-то совсем не так.
Я нашел очень хорошее решение этой проблемы в MapBox под названием Polylabel. Полный исходный код также доступен на их Github.
По сути, он пытается найти визуальный центр многоугольника, как сказал Т. Остин.
Некоторые детали предполагают, что это может быть практическое решение:
К сожалению, вычисление [идеального решения] является сложным и медленным. Опубликованные решения проблемы требуют либо ограниченной триангуляции Делоне, либо вычисления прямого каркаса в качестве шагов предварительной обработки - оба эти действия медленны и подвержены ошибкам.
В нашем случае нам не нужно точное решение - мы готовы пожертвовать точностью, чтобы получить больше скорости. Когда мы размещаем метку на карте, важнее, чтобы она вычислялась за миллисекунды, чем была математически точной.
Небольшое примечание об использовании. Исходный код отлично работает с Javascript из коробки, однако, если вы намереваетесь использовать его с «нормальным» многоугольником, вам следует обернуть его в пустой массив, так как функции здесь принимают GeoJSONPolygons, а не обычные многоугольники, т.е.
var myPolygon = [[x1, y1], [x2, y2], [x3, y3]];
var center = polylabel([myPolygon]);
Как насчет:
Если центроид многоугольника находится внутри многоугольника, используйте его, иначе:
1) Протяните линию от центроида через многоугольник, деля многоугольник на две половины равной площади.
2) «Визуальный центр» - это точка на полпути между ближайшей точкой, в которой линия касается периметра, и следующей точкой, пересекающей периметр в направлении от центроида.
Вот пара картинок, чтобы проиллюстрировать это:
Вы изучали использование формулы центроида?
http://en.wikipedia.org/wiki/Centroid
http://en.wikipedia.org/wiki/K-means_algorithm
Метод центроида уже предлагался несколько раз. Я думаю, что это отличный ресурс, который очень интуитивно описывает процесс (и многие другие полезные трюки с полигонами):
http://paulbourke.net/geometry/polygonmesh/centroid.pdf
Кроме того, для размещения простой метки пользовательского интерфейса может быть достаточно просто вычислить ограничивающую рамку многоугольника (прямоугольник, определяемый наименьшими и наибольшими координатами x и y любой вершины многоугольника) и получить его центр в:
{
x = min_x + (max_x - min_x)/2,
y = min_y + (max_y - min_y)/2
}
Это немного быстрее, чем вычисление центроида, что может иметь значение для приложения реального времени или встроенного приложения.
Также обратите внимание, что если ваши многоугольники статичны (они не меняют форму), вы можете оптимизировать, сохранив результат вычисления центра / центра масс BB (относительно, например, первой вершины многоугольника) в структуре данных многоугольник.
Вот четыре разных подхода, которые я пробовал.
- Центр масс на основе
cv2
(get_center_of_mass
) - Репрезентативная точка на основе
shapely
(get_representative_point
) - Центр масс cv2+
skimage.skeleton
> скелетонизированная форма (get_skeleton_center_of_mass
) scipy
на основе наибольшего расстояния до границы (get_furthest_point_from_edge
)
import numpy as np
import cv2
from shapely.geometry import Polygon
from skimage.morphology import skeletonize, medial_axis
from scipy.ndimage.morphology import distance_transform_edt
import matplotlib.pyplot as plt
H, W = 300, 300
def get_random_contour():
xs = np.random.randint(0, W, 4)
ys = np.random.randint(0, H, 4)
cnt = np.array([[x,y] for x,y in zip(xs,ys)])
mask = draw_contour_on_mask((H,W), cnt)
cnt, _ = cv2.findContours(mask, 1, 2)
cnt = cnt[0]
return cnt
def draw_contour_on_mask(size, cnt):
mask = np.zeros(size, dtype='uint8')
mask = cv2.drawContours(mask, [cnt], -1, 255, -1)
return mask
def get_center_of_mass(cnt):
M = cv2.moments(cnt)
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
return cx, cy
def get_representative_point(cnt):
poly = Polygon(cnt.squeeze())
cx = poly.representative_point().x
cy = poly.representative_point().y
return cx, cy
def get_skeleton_center_of_mass(cnt):
mask = draw_contour_on_mask((H,W), cnt)
skel = medial_axis(mask//255).astype(np.uint8) #<- medial_axis wants binary masks with value 0 and 1
skel_cnt,_ = cv2.findContours(skel,1,2)
skel_cnt = skel_cnt[0]
M = cv2.moments(skel_cnt)
if(M["m00"]==0): # this is a line
cx = int(np.mean(skel_cnt[...,0]))
cy = int(np.mean(skel_cnt[...,1]))
else:
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
return cx, cy
def get_furthest_point_from_edge(cnt):
mask = draw_contour_on_mask((H,W), cnt)
d = distance_transform_edt(mask)
cy, cx = np.unravel_index(d.argmax(), d.shape)
return cx, cy
Вот мой анализ по теме:
get_center_of_mass
является самым быстрым, но, как упоминалось в этом потоке, центр масс может быть расположен за пределами формы для невыпуклых форм.get_representative_point
также выполняется быстро, но идентифицированная точка, хотя всегда гарантированно остается внутри фигуры (или с небольшими правками, даже с несколькими несвязанными фигурами!), Не имеет ничего общего, если вообще имеет какое-либо отношение к центру объекта.get_skeleton_center_of_mass
возвращает приятную для восприятия центральную точку, но работает медленно и требует логики для отдельных фигур.get_furthest_point_from_edge
относительно быстро, легко обобщается на отдельные фигуры, а центральная точка визуально приятна
rows = 4
cols = 4
markers = ['x', '+', "*", "o"]
colors = ['r','b','g','orange']
functions = [get_center_of_mass, get_representative_point, get_skeleton_center_of_mass, get_furthest_point_from_edge]
plt.figure(figsize=(2*cols, 2*rows, ))
for i in range(rows*cols):
cnt = get_random_contour()
mask = draw_contour_on_mask((H,W), cnt)
plt.subplot(cols,rows, i+1)
plt.imshow(mask, cmap='gray')
for c, m, f in zip(colors, markers, functions):
l = f.__name__
cx, cy = f(cnt)
plt.scatter(cx, cy, c=c, s=100, label=l, marker=m, alpha=0.7)
plt.tight_layout()
plt.legend(loc=3)
plt.show()
Вот как алгоритмы, работающие на 100 случайных примерах, сравниваются по скорости:
N_EXAMPLES = 100
cnts = [get_random_contour() for _ in range(N_EXAMPLES)]
%%time
_ = [get_center_of_mass(cnt) for cnt in cnts]
CPU times: user 7.07 ms, sys: 155 µs, total: 7.23 ms
Wall time: 5.75 ms
%%time
_ = [get_representative_point(cnt) for cnt in cnts]
CPU times: user 23.6 ms, sys: 7.84 ms, total: 31.4 ms
Wall time: 28.5 ms
%%time
_ = [get_skeleton_center_of_mass(cnt) for cnt in cnts]
CPU times: user 5.56 s, sys: 3.31 ms, total: 5.56 s
Wall time: 5.55 s
%%time
_ = [get_furthest_point_from_edge(cnt) for cnt in cnts]
CPU times: user 486 ms, sys: 53 µs, total: 486 ms
Wall time: 485 ms
Я не говорю, что это самый быстрый, но он даст вам точку внутри многоугольника. Рассчитайте прямой скелет. Точка, которую вы ищете, находится на этом скелете. Например, вы можете выбрать тот, у которого самое короткое нормальное расстояние до центра ограничивающей рамки.
Как насчет того, чтобы найти «вписанную окружность» многоугольника (самый большой круг, который помещается внутри него), а затем центрировать метку в центре этого многоугольника? Вот пара ссылок, с которых можно начать:
http://www.mathopenref.com/polygonincircle.html
https://nrich.maths.org/discus/messages/145082/ 144373.html? 1219439473
Скорее всего, это не сработает идеально для каждого полигона; многоугольник, который выглядел бы как C, имел бы метку в несколько непредсказуемом месте. Но преимуществом было бы то, что метка всегда перекрывала твердую часть многоугольника.
Если я понимаю суть статьи, на которую вы ссылаетесь (довольно интересная проблема, кстати), эта техника «внутренней буферизации» в некоторой степени аналогична моделированию рассматриваемой формы из куска сахара, который растворяется кислотой с краев в . (например, по мере увеличения буферного расстояния остается меньше исходной формы) Последний оставшийся бит является идеальным местом для размещения метки.
Как добиться этого в алгоритме, к сожалению, мне не очень понятно ...
Я думаю, что если вы разбили многоугольник обратно на вершины, а затем применили функцию, чтобы найти самую большую выпуклую оболочку, а затем найти центр вне этой выпуклой оболочки, он будет точно соответствовать «кажущемуся» центру.
Нахождение самой большой выпуклой оболочки с учетом вершин: Загляните под абзац "Простой многоугольник".
Усредните вершины выпуклой оболочки, чтобы найти центр.
Не могли бы вы разместить метку в наивном центре (возможно, ограничивающего прямоугольника), а затем переместить ее на основе пересечения краев локального многоугольника и BB метки? Перемещаться по нормалям пересекающихся ребер, и если несколько ребер пересекаются, суммировать их нормали для движения?
Просто догадываюсь здесь; в такого рода проблемах я бы, вероятно, попытался решить итеративно, если производительность не слишком важна.
Сейчас не так много времени, чтобы разрабатывать или тестировать это, но я постараюсь сделать больше, когда у меня будет возможность.
В качестве основного метода используйте центроиды. Проверьте, находится ли центроид внутри многоугольника; в противном случае проведите линию через ближайшую точку к другой стороне многоугольника. В середине участка этой линии, которая находится внутри многоугольника, поместите свою подпись.
Поскольку точка, ближайшая к центроиду, вероятно, ограничит довольно большую площадь, я думаю, что это может дать результаты, аналогичные вписанным окружностям Киралессы. Конечно, это могло бы сойти с ума, если бы у вас был многоугольник с дырами. В этом случае, вероятно, было бы намного лучше вписаться в окружности. С другой стороны, для типичных случаев по умолчанию используется (быстрый?) Метод центроида.
Эта проблема, вероятно, была бы аналогична поиску «центра масс» в предположении однородной плотности.
РЕДАКТИРОВАТЬ: этот метод не будет работать, если в многоугольнике есть «дыры».
Вы можете использовать метод центра масс (или центра тяжести), который используется в гражданском строительстве, вот полезная ссылка из википедии:
http://en.wikipedia.org/wiki/Center_of_mass
Вычислите положение центра (x, y) каждого края многоугольника. Вы можете сделать это, найдя разницу между положениями концов каждого края. Возьмите среднее значение каждого центра в каждом измерении. Это будет центр многоугольника.
Похожие вопросы
Связанные вопросы
Новые вопросы
polygon
По вопросам, связанным с созданием, манипулированием и рендерингом полигонов в графических пользовательских интерфейсах.