Jede wirklich universelle effektive Methode standardisiert die Darstellungen der Formen so, dass sie sich bei Rotation, Translation, Reflexion oder geringfügigen Änderungen der internen Darstellung nicht ändern.
Eine Möglichkeit, dies zu tun, besteht darin, jede verbundene Form als abwechselnde Folge von Kantenlängen und (vorzeichenbehafteten) Winkeln aufzulisten, beginnend an einem Ende. (Die Form sollte "sauber" sein, da keine Kanten mit null Länge oder gerade Winkel vorhanden sind.) Um diese unter Reflexion unveränderlich zu machen, negieren Sie alle Winkel, wenn der erste Winkel ungleich Null negativ ist.
(Da jede verbundene Polylinie von n Eckpunkten n -1 Kanten hat, die durch n -2 Winkel getrennt sind, habe ich es im folgenden R
Code als zweckmäßig empfunden, eine Datenstruktur zu verwenden, die aus zwei Arrays besteht, eines für die Kantenlängen $lengths
und das andere für die Winkel , $angles
. Ein Liniensegment hat überhaupt keine Winkel, daher ist es wichtig, Arrays mit der Länge Null in einer solchen Datenstruktur zu behandeln.)
Solche Darstellungen können lexikographisch geordnet werden. Während des Standardisierungsprozesses akkumulierte Gleitkommafehler sollten berücksichtigt werden. Ein elegantes Verfahren würde diese Fehler als Funktion der ursprünglichen Koordinaten schätzen. In der folgenden Lösung wird ein einfacheres Verfahren verwendet, bei dem zwei Längen als gleich angesehen werden, wenn sie sich relativ um einen sehr kleinen Betrag unterscheiden . Winkel können sich absolut nur um einen sehr kleinen Betrag unterscheiden.
Um sie bei Umkehrung der zugrunde liegenden Ausrichtung invariant zu machen, wählen Sie die lexikographisch früheste Darstellung zwischen der der Polylinie und ihrer Umkehrung.
Um mehrteilige Polylinien zu handhaben, ordnen Sie ihre Komponenten in lexikografischer Reihenfolge an.
Um die Äquivalenzklassen unter euklidischen Transformationen zu finden, dann
Erstellen Sie standardisierte Darstellungen der Formen.
Führen Sie eine lexikografische Sortierung der standardisierten Darstellungen durch.
Durchlaufen Sie die sortierte Reihenfolge, um Sequenzen gleicher Darstellungen zu identifizieren.
Die Berechnungszeit ist proportional zu O (n * log (n) * N), wobei n die Anzahl der Merkmale und N die größte Anzahl von Eckpunkten in einem Merkmal ist. Das ist effizient.
Im Übrigen ist es wahrscheinlich erwähnenswert, dass eine vorläufige Gruppierung, die auf leicht zu berechnenden invarianten geometrischen Eigenschaften wie Polylinienlänge, Mittelpunkt und Momenten um diesen Mittelpunkt basiert , häufig angewendet werden kann, um den gesamten Prozess zu rationalisieren. Man muss nur Untergruppen von kongruenten Merkmalen innerhalb jeder solchen vorläufigen Gruppe finden. Die hier angegebene vollständige Methode wäre für Formen erforderlich, die ansonsten so bemerkenswert ähnlich wären, dass solche einfachen Invarianten sie immer noch nicht unterscheiden würden. Einfache Features, die aus Rasterdaten erstellt wurden, können beispielsweise solche Eigenschaften aufweisen. Da die hier angegebene Lösung jedoch ohnehin so effizient ist, dass sie möglicherweise von selbst funktioniert, wenn man sich die Mühe macht, sie zu implementieren.
Beispiel
Die linke Abbildung zeigt fünf Polylinien plus 15 weitere, die durch zufällige Translation, Rotation, Reflexion und Umkehrung der inneren Orientierung (die nicht sichtbar ist) erhalten wurden. Die rechte Figur färbt sie entsprechend ihrer euklidischen Äquivalenzklasse: Alle Figuren einer gemeinsamen Farbe sind kongruent; verschiedene Farben sind nicht kongruent.
R
Code folgt. Wenn die Eingaben auf 500 Formen, 500 zusätzliche (kongruente) Formen mit einem Mittelwert von 100 Eckpunkten pro Form aktualisiert wurden, betrug die Ausführungszeit auf dieser Maschine 3 Sekunden.
Dieser Code ist unvollständig: Da R
es keine native lexikografische Sortierung gibt und ich keine Lust hatte, eine von Grund auf neu zu codieren, führe ich die Sortierung einfach an der ersten Koordinate jeder standardisierten Form durch. Das ist in Ordnung für die hier erstellten zufälligen Formen, aber für Produktionsarbeiten sollte eine vollständige lexikografische Sortierung implementiert werden. Die Funktion order.shape
wäre die einzige, die von dieser Änderung betroffen ist. Seine Eingabe ist eine Liste standardisierter Formen s
und seine Ausgabe ist die Folge von Indizes, in s
die sie sortiert werden.
#
# 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)))