均匀抽取 3D 表面网格并保持双边对称性
Decimate 3D surface mesh evenly and preserving bilaterally symmetry
这是 3D 面部表面网格的示例。从下面的图 1 可以看出,界标是双边对称的。我希望减少地标的数量。
这里face
是顶点的坐标,triang
是三角剖分文件,landPairs
是包含顶点配对信息的两列文件。 landPairs
以后不用于绘图,但会在需要时提供。所有数据均来自 here:
这是在抽取之前绘制原始顶点的代码:
library(rgl)
library(Rvcg)
# Customized function to convert vb and it information to 3D mesh
lm2mesh <- function(vb, it) {
vb <- t(vb)
vb <- rbind(vb, 1)
rownames(vb) <- c("xpts", "ypts", "zpts", "")
it_mat <- t(as.matrix(it))
rownames(it_mat) <- NULL
vertices <- c(vb)
indices <- c(it_mat)
tmesh3d(vertices = vertices, indices = indices, homogeneous = TRUE,
material = NULL, normals = NULL, texcoords = NULL)
}
# Load `face` and `triang`
face <- as.matrix(read.csv("<PATH>\SampleFace.csv", header=F))
triang <- as.matrix(read.csv("<PATH>\triangulation.csv", header=F))
facemesh <- lm2mesh(face,triang)
# Plot the undecimated mesh
shade3d(facemesh, col="steelblue", specular = "#202020", alpha = 0.7)
plot3d(face, type = "s", col = "red", xlab = "x", ylab = "y", zlab = "z",
size = 0.2, aspect = FALSE, alpha = 0.8, add=T)
下图1(顶点均匀分布,绝对左右对称):
# Plot the decimated mesh
open3d()
facemeshdecim <- vcgQEdecim(facemesh,percent=0.1)
shade3d(facemeshdecim, col="steelblue", specular = "#202020", alpha = 0.7)
plot3d(t(facemeshdecim$vb[-4, ]), type = "s", col = "red", xlab = "x", ylab = "y", zlab = "z",
size = 0.4, aspect = FALSE, alpha = 0.8, add=T)
下面是图2(顶点分布不均,不再对称):
可以看出,在抽取后的面中,顶点的间距不像抽取前那样均匀,原本对称的顶点不再对称。 我的问题是,是否有一种方法可以减少顶点的数量,同时确保减少的顶点尽可能均匀分布并保持顶点的双边对称性?
这是一个方法。从您的代码开始,然后添加:
# Get the positive part of the face
posface <- clipMesh3d(facemesh, fn="y")
# Decimate it, keeping the boundary
posdeci <- vcgQEdecim(posface, percent=0.1, bound = TRUE)
# Duplicate it in a reflection
negdeci <- posdeci
negdeci$vb[2,] <- -negdeci$vb[2,]
# Join them together
fulldeci <- merge(posdeci, negdeci)
# Plot it
open3d()
shade3d(fulldeci, col="steelblue", specular = "#202020", alpha = 0.7)
plot3d(t(fulldeci$vb[-4, ]), type = "s", col = "red", xlab = "x", ylab = "y", zlab = "z",
size = 0.4, aspect = FALSE, alpha = 0.8, add=T)
中间线上的点太多,但除此之外就是你想要的。
编辑添加:
让点更统一有点棘手。如果在调用 vcgQEdecim()
时不使用 bound = TRUE
,它会在面部中间留下一个空隙。要填充它,您需要添加连接边两侧的四边形,但找出哪些顶点构成边需要一个新函数:
getBorder <- function(mesh) {
border <- which(vcgBorder(mesh)$bordervb)
inorder <- NULL
repeat{
i <- 1
inorder <- c(inorder, border[i])
repeat{
found <- FALSE
tris <- which(apply(mesh$it, 2, function(col) border[i] %in% col))
for (j in tris) {
tri <- mesh$it[,j]
i0 <- which(tri == border[i])
i1 <- i0 %% 3 + 1
# keep tri[i1] if the edge from tri[i0] to tri[i1] is external
tris1 <- which(apply(mesh$it[,tris,drop=FALSE], 2, function(col) all(tri[c(i0, i1)] %in% col)))
if (length(tris1) == 1) {
if (tri[i1] %in% inorder)
break
inorder <- c(inorder, tri[i1])
i <- which(border == tri[i1])
found <- TRUE
break
}
}
if (!found) break
}
border <- setdiff(border, inorder)
if (!length(border)) break
inorder <- c(inorder, NA)
}
inorder
}
使用该函数,以下代码可以合理地完成工作:
# Try joining halves using quads
posdeci2 <- vcgQEdecim(posface,percent=0.1, bound = FALSE)
negdeci2 <- posdeci2
negdeci2$vb[2,] <- -negdeci2$vb[2,]
# This one has the gap
fulldeci2 <- merge(posdeci2, negdeci2)
# Fill in the gap with quads
# Keep the ones in the middle, but not the outside edge
border <- getBorder(posdeci2)
border <- border[posdeci2$vb[2, border] < 0.005]
borderverts <- posdeci2$vb[, border]
negverts <- negdeci2$vb[, border]
# The quads have both sets of vertices
quadverts <- cbind(borderverts, negverts)
n <- ncol(borderverts)
# We'll assume n > 1
indices <- rbind(1:(n-1), 2:n, n + 2:n, n + 1:(n-1))
quads <- mesh3d(vertices = quadverts, quads = indices)
fulldeci3 <- merge(fulldeci2, quads)
# plot it
open3d()
shade3d(fulldeci3, col="steelblue", specular = "#202020", alpha = 0.7)
plot3d(t(fulldeci3$vb[-4, ]), type = "s", col = "red", xlab = "x", ylab = "y", zlab = "z",
size = 0.4, aspect = FALSE, alpha = 0.8, add=T)
这是 3D 面部表面网格的示例。从下面的图 1 可以看出,界标是双边对称的。我希望减少地标的数量。
这里face
是顶点的坐标,triang
是三角剖分文件,landPairs
是包含顶点配对信息的两列文件。 landPairs
以后不用于绘图,但会在需要时提供。所有数据均来自 here:
这是在抽取之前绘制原始顶点的代码:
library(rgl)
library(Rvcg)
# Customized function to convert vb and it information to 3D mesh
lm2mesh <- function(vb, it) {
vb <- t(vb)
vb <- rbind(vb, 1)
rownames(vb) <- c("xpts", "ypts", "zpts", "")
it_mat <- t(as.matrix(it))
rownames(it_mat) <- NULL
vertices <- c(vb)
indices <- c(it_mat)
tmesh3d(vertices = vertices, indices = indices, homogeneous = TRUE,
material = NULL, normals = NULL, texcoords = NULL)
}
# Load `face` and `triang`
face <- as.matrix(read.csv("<PATH>\SampleFace.csv", header=F))
triang <- as.matrix(read.csv("<PATH>\triangulation.csv", header=F))
facemesh <- lm2mesh(face,triang)
# Plot the undecimated mesh
shade3d(facemesh, col="steelblue", specular = "#202020", alpha = 0.7)
plot3d(face, type = "s", col = "red", xlab = "x", ylab = "y", zlab = "z",
size = 0.2, aspect = FALSE, alpha = 0.8, add=T)
下图1(顶点均匀分布,绝对左右对称):
# Plot the decimated mesh
open3d()
facemeshdecim <- vcgQEdecim(facemesh,percent=0.1)
shade3d(facemeshdecim, col="steelblue", specular = "#202020", alpha = 0.7)
plot3d(t(facemeshdecim$vb[-4, ]), type = "s", col = "red", xlab = "x", ylab = "y", zlab = "z",
size = 0.4, aspect = FALSE, alpha = 0.8, add=T)
下面是图2(顶点分布不均,不再对称):
可以看出,在抽取后的面中,顶点的间距不像抽取前那样均匀,原本对称的顶点不再对称。 我的问题是,是否有一种方法可以减少顶点的数量,同时确保减少的顶点尽可能均匀分布并保持顶点的双边对称性?
这是一个方法。从您的代码开始,然后添加:
# Get the positive part of the face
posface <- clipMesh3d(facemesh, fn="y")
# Decimate it, keeping the boundary
posdeci <- vcgQEdecim(posface, percent=0.1, bound = TRUE)
# Duplicate it in a reflection
negdeci <- posdeci
negdeci$vb[2,] <- -negdeci$vb[2,]
# Join them together
fulldeci <- merge(posdeci, negdeci)
# Plot it
open3d()
shade3d(fulldeci, col="steelblue", specular = "#202020", alpha = 0.7)
plot3d(t(fulldeci$vb[-4, ]), type = "s", col = "red", xlab = "x", ylab = "y", zlab = "z",
size = 0.4, aspect = FALSE, alpha = 0.8, add=T)
中间线上的点太多,但除此之外就是你想要的。
编辑添加:
让点更统一有点棘手。如果在调用 vcgQEdecim()
时不使用 bound = TRUE
,它会在面部中间留下一个空隙。要填充它,您需要添加连接边两侧的四边形,但找出哪些顶点构成边需要一个新函数:
getBorder <- function(mesh) {
border <- which(vcgBorder(mesh)$bordervb)
inorder <- NULL
repeat{
i <- 1
inorder <- c(inorder, border[i])
repeat{
found <- FALSE
tris <- which(apply(mesh$it, 2, function(col) border[i] %in% col))
for (j in tris) {
tri <- mesh$it[,j]
i0 <- which(tri == border[i])
i1 <- i0 %% 3 + 1
# keep tri[i1] if the edge from tri[i0] to tri[i1] is external
tris1 <- which(apply(mesh$it[,tris,drop=FALSE], 2, function(col) all(tri[c(i0, i1)] %in% col)))
if (length(tris1) == 1) {
if (tri[i1] %in% inorder)
break
inorder <- c(inorder, tri[i1])
i <- which(border == tri[i1])
found <- TRUE
break
}
}
if (!found) break
}
border <- setdiff(border, inorder)
if (!length(border)) break
inorder <- c(inorder, NA)
}
inorder
}
使用该函数,以下代码可以合理地完成工作:
# Try joining halves using quads
posdeci2 <- vcgQEdecim(posface,percent=0.1, bound = FALSE)
negdeci2 <- posdeci2
negdeci2$vb[2,] <- -negdeci2$vb[2,]
# This one has the gap
fulldeci2 <- merge(posdeci2, negdeci2)
# Fill in the gap with quads
# Keep the ones in the middle, but not the outside edge
border <- getBorder(posdeci2)
border <- border[posdeci2$vb[2, border] < 0.005]
borderverts <- posdeci2$vb[, border]
negverts <- negdeci2$vb[, border]
# The quads have both sets of vertices
quadverts <- cbind(borderverts, negverts)
n <- ncol(borderverts)
# We'll assume n > 1
indices <- rbind(1:(n-1), 2:n, n + 2:n, n + 1:(n-1))
quads <- mesh3d(vertices = quadverts, quads = indices)
fulldeci3 <- merge(fulldeci2, quads)
# plot it
open3d()
shade3d(fulldeci3, col="steelblue", specular = "#202020", alpha = 0.7)
plot3d(t(fulldeci3$vb[-4, ]), type = "s", col = "red", xlab = "x", ylab = "y", zlab = "z",
size = 0.4, aspect = FALSE, alpha = 0.8, add=T)