Любой действительно эффективный метод общего назначения будет стандартизировать представления форм, чтобы они не изменялись при вращении, перемещении, отражении или тривиальных изменениях во внутреннем представлении.
Один из способов сделать это - перечислить каждую соединенную фигуру в виде чередующейся последовательности длин ребер и (подписанных) углов, начиная с одного конца. (Форма должна быть «чистой» в том смысле, что она не имеет ребер нулевой длины или прямых углов.) Чтобы сделать этот инвариант при отражении, отрицательными являются все углы, если первый ненулевой является отрицательным.
(Поскольку любая соединенная ломаная из n вершин будет иметь n -1 ребер, разделенных n -2 углами, я посчитал удобным в приведенном R
ниже коде использовать структуру данных, состоящую из двух массивов, один для длин ребер $lengths
и другой для углы $angles
. У линейного сегмента вообще не будет углов, поэтому важно обрабатывать массивы нулевой длины в такой структуре данных.)
Такие представления можно заказать лексикографически. Некоторый учет должен быть сделан для ошибок с плавающей запятой, накопленных в процессе стандартизации. Элегантная процедура оценила бы эти ошибки как функцию исходных координат. В приведенном ниже решении используется более простой метод, в котором две длины считаются равными, когда они отличаются на очень небольшую величину на относительной основе. Углы могут отличаться только на очень небольшую величину в абсолютном выражении.
Чтобы сделать их инвариантными при обращении базовой ориентации, выберите лексикографически раннее представление между представлением ломаной и ее обращением.
Для обработки многокомпонентных полилиний расположите их компоненты в лексикографическом порядке.
Чтобы найти классы эквивалентности при евклидовых преобразованиях, тогда
Создайте стандартизированные представления форм.
Выполните лексикографическую сортировку стандартизированных представлений.
Сделайте проход через отсортированный порядок, чтобы идентифицировать последовательности равных представлений.
Время вычислений пропорционально O (n * log (n) * N), где n - количество объектов, а N - наибольшее количество вершин в любом объекте. Это эффективно.
Вероятно, стоит попутно упомянуть, что предварительная группировка, основанная на легко вычисляемых инвариантных геометрических свойствах, таких как длина полилинии, центр и моменты вокруг этого центра, часто может применяться для рационализации всего процесса. Нужно только найти подгруппы конгруэнтных признаков в каждой такой предварительной группе. Полный метод, приведенный здесь, был бы необходим для форм, которые в противном случае были бы настолько удивительно похожи, что такие простые инварианты до сих пор не различали бы их. Простые объекты, построенные из растровых данных, могут иметь такие характеристики, например. Однако, поскольку приведенное здесь решение в любом случае настолько эффективно, что, если кто-то собирается приложить усилия для его реализации, оно само по себе может работать просто отлично.
пример
На рисунке слева показаны пять полилиний плюс еще 15, которые были получены из них путем случайного перемещения, поворота, отражения и изменения внутренней ориентации (которая не видна). Фигура правой руки окрашивает их в соответствии с их евклидовым классом эквивалентности: все фигуры общего цвета являются конгруэнтными; разные цвета не совпадают.
R
код следует. Когда входные данные были обновлены до 500 фигур, 500 дополнительных (конгруэнтных) форм со средним значением 100 вершин на форму, время выполнения на этом компьютере составляло 3 секунды.
Этот код неполон: поскольку R
не имеет нативной лексикографической сортировки, и мне не хотелось кодировать ее с нуля, я просто выполняю сортировку по первой координате каждой стандартизированной формы. Это будет хорошо для случайных фигур, созданных здесь, но для производственных работ должна быть реализована полная лексикографическая сортировка. Функция order.shape
будет единственной, затронутой этим изменением. Его входные данные представляют собой список стандартизированной формы, s
а выходные данные представляют собой последовательность индексов, в s
которую можно было бы их отсортировать.
#
# Create random shapes.
#
n.shapes <- 5 # Unique shapes, up to congruence
n.shapes.new <- 15 # Additional congruent shapes to generate
p.mean <- 5 # Expected number of vertices per shape
set.seed(17) # Create a reproducible starting point
shape.random <- function(n) matrix(rnorm(2*n), nrow=2, ncol=n)
shapes <- lapply(2+rpois(n.shapes, p.mean-2), shape.random)
#
# Randomly move them around.
#
move.random <- function(xy) {
a <- runif(1, 0, 2*pi)
reflection <- sign(runif(1, -1, 1))
translation <- runif(2, -8, 8)
m <- matrix(c(cos(a), sin(a), -sin(a), cos(a)), 2, 2) %*%
matrix(c(reflection, 0, 0, 1), 2, 2)
m <- m %*% xy + translation
if (runif(1, -1, 0) < 0) m <- m[ ,dim(m)[2]:1]
return (m)
}
i <- sample(length(shapes), n.shapes.new, replace=TRUE)
shapes <- c(shapes, lapply(i, function(j) move.random(shapes[[j]])))
#
# Plot the shapes.
#
range.shapes <- c(min(sapply(shapes, min)), max(sapply(shapes, max)))
palette(gray.colors(length(shapes)))
par(mfrow=c(1,2))
plot(range.shapes, range.shapes, type="n",asp=1, bty="n", xlab="", ylab="")
invisible(lapply(1:length(shapes), function(i) lines(t(shapes[[i]]), col=i, lwd=2)))
#
# Standardize the shape description.
#
standardize <- function(xy) {
n <- dim(xy)[2]
vectors <- xy[ ,-1, drop=FALSE] - xy[ ,-n, drop=FALSE]
lengths <- sqrt(colSums(vectors^2))
if (which.min(lengths - rev(lengths))*2 < n) {
lengths <- rev(lengths)
vectors <- vectors[, (n-1):1]
}
if (n > 2) {
vectors <- vectors / rbind(lengths, lengths)
perps <- rbind(-vectors[2, ], vectors[1, ])
angles <- sapply(1:(n-2), function(i) {
cosine <- sum(vectors[, i+1] * vectors[, i])
sine <- sum(perps[, i+1] * vectors[, i])
atan2(sine, cosine)
})
i <- min(which(angles != 0))
angles <- sign(angles[i]) * angles
} else angles <- numeric(0)
list(lengths=lengths, angles=angles)
}
shapes.std <- lapply(shapes, standardize)
#
# Sort lexicographically. (Not implemented: see the text.)
#
order.shape <- function(s) {
order(sapply(s, function(s) s$lengths[1]))
}
i <- order.shape(shapes.std)
#
# Group.
#
equal.shape <- function(s.0, s.1) {
same.length <- function(a,b) abs(a-b) <= (a+b) * 1e-8
same.angle <- function(a,b) min(abs(a-b), abs(a-b)-2*pi) < 1e-11
r <- function(u) {
a <- u$angles
if (length(a) > 0) {
a <- rev(u$angles)
i <- min(which(a != 0))
a <- sign(a[i]) * a
}
list(lengths=rev(u$lengths), angles=a)
}
e <- function(u, v) {
if (length(u$lengths) != length(v$lengths)) return (FALSE)
all(mapply(same.length, u$lengths, v$lengths)) &&
all(mapply(same.angle, u$angles, v$angles))
}
e(s.0, s.1) || e(r(s.0), s.1)
}
g <- rep(1, length(shapes.std))
for (j in 2:length(i)) {
i.0 <- i[j-1]
i.1 <- i[j]
if (equal.shape(shapes.std[[i.0]], shapes.std[[i.1]]))
g[j] <- g[j-1] else g[j] <- g[j-1]+1
}
palette(rainbow(max(g)))
plot(range.shapes, range.shapes, type="n",asp=1, bty="n", xlab="", ylab="")
invisible(lapply(1:length(i), function(j) lines(t(shapes[[i[j]]]), col=g[j], lwd=2)))