진정으로 범용적인 효과적인 방법은 도형의 표현을 표준화하여 내부 표현의 회전, 평행 이동, 반사 또는 사소한 변경시 변경되지 않도록합니다.
이를위한 한 가지 방법은 연결된 각 모양을 한쪽 끝에서 시작하여 교대로 가장자리 길이와 (서명 된) 각도로 나열하는 것입니다. 길이가 0 인 모서리 나 직각이 없다는 점에서 모양이 "깨끗해야합니다."반사시 변하지 않도록하려면 첫 번째 0이 아닌 값이 음수이면 모든 각도를 무시하십시오.
임의 접속 폴리 때문에 ( N 정점 것이다 N -1 에지에 의해 분리 된 N -2 각도, I는에 편리 발견 R
두 배열, 에지 길이 하나 이루어지는 데이터 구조를 사용하여 아래의 코드 $lengths
와위한 다른 각도 $angles
세그먼트에는 각도가 전혀 없으므로 이러한 데이터 구조에서 길이가 0 인 배열을 처리하는 것이 중요합니다.)
이러한 표현은 사전 식으로 주문할 수 있습니다. 표준화 프로세스 중에 누적 된 부동 소수점 오류에 대해 약간의 여유가 있어야합니다. 우아한 절차는 이러한 좌표를 원래 좌표의 함수로 추정합니다. 아래 솔루션에서 두 가지 길이가 상대적으로 매우 적은 양 으로 다를 때 동일한 길이로 간주되는 더 간단한 방법이 사용 됩니다. 각도는 절대적으로 아주 소량 만 다를 수 있습니다.
기본 방향을 반전하여 변하지 않게하려면 폴리 라인과 반전 사이의 사 전적으로 가장 빠른 표현을 선택하십시오.
여러 부분으로 구성된 폴리 라인을 처리하려면 구성 요소를 사전 식 순서로 정렬하십시오.
유클리드 변환 아래의 등가 클래스를 찾으려면, 다음,
계산 시간은 O (n * log (n) * N)에 비례합니다. 여기서 n 은 피처 수이고 N 은 피처 에서 가장 많은 정점 수입니다. 이것은 효율적입니다.
폴리 라인 길이, 중심 및 중심 에 대한 모멘트 와 같이 쉽게 계산 된 변하지 않는 기하학적 특성을 기반으로 예비 그룹화를 적용하면 전체 프로세스를 간소화하는 데 종종 적용될 수 있습니다. 그러한 각 예비 그룹 내에서 합동 특징의 하위 그룹 만 찾으면됩니다. 여기에 주어진 완전한 방법은 그렇게 단순한 불변량이 여전히 그들을 구별하지 않을 정도로 매우 유사한 모양에 필요할 것입니다. 예를 들어 래스터 데이터로 구성된 간단한 기능에는 이러한 특성이있을 수 있습니다. 그러나 여기에 제공된 솔루션은 어쨌든 매우 효율적이므로 구현하려는 노력으로 가면 스스로 잘 작동 할 수 있습니다.
예
왼쪽 그림은 5 개의 폴리 라인과 랜덤 변환, 회전, 반사 및 내부 방향 반전 (표시되지 않음)을 통해 얻은 폴리 라인을 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)))