Qualsiasi metodo efficace veramente generico standardizzerà le rappresentazioni delle forme in modo che non cambino in seguito a rotazione, traduzione, riflessione o cambiamenti banali nella rappresentazione interna.
Un modo per farlo è elencare ogni forma connessa come una sequenza alternata di lunghezze dei bordi e angoli (con segno), a partire da un'estremità. (La forma dovrebbe essere "pulita", nel senso di non avere bordi di lunghezza zero o angoli retti.) Per rendere questo invariante riflesso, annulla tutti gli angoli se il primo diverso da zero è negativo.
(Poiché qualsiasi polilinea connessa di n vertici avrà n -1 bordi separati da n -2 angoli, ho trovato conveniente nel R
codice seguente utilizzare una struttura di dati composta da due matrici, una per le lunghezze dei bordi $lengths
e l'altra per il angoli $angles
. Un segmento di linea non avrà alcun angolo, quindi è importante gestire matrici di lunghezza zero in tale struttura di dati.)
Tali rappresentazioni possono essere ordinate lessicograficamente. Dovrebbero essere presi in considerazione alcuni errori in virgola mobile accumulati durante il processo di standardizzazione. Una procedura elegante stimerebbe quegli errori in funzione delle coordinate originali. Nella soluzione seguente, viene utilizzato un metodo più semplice in cui due lunghezze sono considerate uguali quando differiscono di una quantità molto piccola su base relativa. Gli angoli possono differire solo di una quantità molto piccola su base assoluta.
Per renderli invarianti sotto l'inversione dell'orientamento sottostante, scegliere la rappresentazione lessicograficamente più antica tra quella della polilinea e la sua inversione.
Per gestire polilinee in più parti, disporre i loro componenti in ordine lessicografico.
Per trovare le classi di equivalenza sotto le trasformazioni euclidee, quindi,
Crea rappresentazioni standardizzate delle forme.
Eseguire una sorta di lessicografia delle rappresentazioni standardizzate.
Fai un passaggio attraverso l'ordinamento ordinato per identificare sequenze di rappresentazioni uguali.
Il tempo di calcolo è proporzionale a O (n * log (n) * N) dove n è il numero di feature e N è il maggior numero di vertici in qualsiasi feature. Questo è efficiente.
Vale probabilmente la pena ricordare che un raggruppamento preliminare basato su proprietà geometriche invarianti facilmente calcolabili, come la lunghezza della polilinea, il centro e i momenti attorno a quel centro, spesso può essere applicato per semplificare l'intero processo. Basta trovare sottogruppi di caratteristiche congruenti all'interno di ciascuno di questi gruppi preliminari. Il metodo completo fornito qui sarebbe necessario per forme che altrimenti sarebbero così notevolmente simili che tali semplici invarianti non li distinguerebbero ancora. Funzionalità semplici basate su dati raster potrebbero avere tali caratteristiche, ad esempio. Tuttavia, poiché la soluzione qui fornita è comunque così efficiente, che se uno si impegnerà a implementarla, potrebbe funzionare perfettamente da solo.
Esempio
La figura a sinistra mostra cinque polilinee più altre 15 che sono state ottenute da quelle tramite traslazione, rotazione, riflessione e inversione casuali dell'orientamento interno (che non è visibile). La figura della mano destra li colora secondo la loro classe di equivalenza euclidea: tutte le figure di un colore comune sono congruenti; colori diversi non sono congruenti.
R
segue il codice. Quando gli input sono stati aggiornati a 500 forme, 500 forme extra (congruenti), con una media di 100 vertici per forma, il tempo di esecuzione su questa macchina era di 3 secondi.
Questo codice è incompleto: poiché R
non ha un ordinamento lessicografico nativo e non avevo voglia di codificarne uno da zero, eseguo semplicemente l'ordinamento sulla prima coordinata di ogni forma standardizzata. Ciò andrà bene per le forme casuali create qui, ma per il lavoro di produzione dovrebbe essere implementato un tipo lessicografico completo. La funzione order.shape
sarebbe l'unica interessata da questo cambiamento. Il suo input è un elenco di forme standardizzate s
e il suo output è la sequenza di indici s
che lo ordinerebbe.
#
# 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)))