Будь-який справді ефективний метод загального призначення стандартизує уявлення фігур, щоб вони не змінювалися при обертанні, перекладі, відображенні або тривіальних змінах внутрішнього представлення.
Один із способів зробити це - перерахувати кожну з'єднану форму у вигляді змінної послідовності довжин ребер та (підписаних) кутів, починаючи з одного кінця. (Форма повинна бути "чистою" в тому сенсі, що не має ребер нульової довжини або прямих кутів.) Щоб зробити цю інваріантну під відображенням, заперечте всі кути, якщо перший ненульовий - негативний.
(Оскільки будь-яка з'єднана полілінія з 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)))