如何找到给定点上拟合的最小面积矩形(MAR)?
,一个支持的问题是:
该问题是否有解析解决方案?
(问题的发展将是使一个盒子(3D)适合3D点云中的点簇。)
作为第一步,我建议找到解决问题的点的凸包(通过删除解决方案中不涉及的点)以:
将MAR拟合到多边形。
所需的方法将提供X(矩形的中心),D(二维)和A(角度)。
我的解决方案建议:
查找多边形的质心(请参阅查找对象的几何中心?)
[S]拟合一个简单的拟合矩形,即与X和Y轴平行
您可以对给定点的X和Y使用
minmax
函数(例如,多边形的顶点)存储拟合矩形的区域
将多边形围绕质心旋转1度
从[S]重复直到完全旋转
将结果报告为最小面积的角度
我认为很有希望,但是存在以下问题:
选择良好的分辨率因为角度变化可能具有挑战性,
计算成本很高,
解决方案不是分析性的而是实验性的。
#1 楼
是的,有针对此问题的解析解决方案。您要寻找的算法在多边形概括中被称为“最小的周围矩形”。您描述的算法很好,但是为了解决您列出的问题,您可以使用以下事实: MAR的方向与点云凸包的边缘之一相同。因此,您只需要测试凸包边缘的方向即可。您应该:
计算云的凸包。
对于凸包的每个边缘:
计算边缘方向(使用arctan),
使用此方向旋转凸包,以便轻松计算出旋转后的凸包的x / y的最小值/最大值的边界矩形区域,
存储与找到的最小面积,
返回与找到的最小面积相对应的矩形。
那里有一个Java实现示例。
在3D中同样适用,除了:
凸包将是一个体积,
测试的方向将是凸包的方向(在3D中)面孔。
祝你好运!
#2 楼
为了补充@julien的出色解决方案,这是R
中的一个有效实现,可以用作指导任何GIS特定实现的伪代码(或直接在R
中应用)。输入是点坐标的数组。输出(mbr
的值)是最小边界矩形的顶点的数组(重复第一个矩形以将其关闭)。请注意,完全没有任何三角计算。MBR <- function(p) {
# Analyze the convex hull edges
a <- chull(p) # Indexes of extremal points
a <- c(a, a[1]) # Close the loop
e <- p[a[-1],] - p[a[-length(a)], ] # Edge directions
norms <- sqrt(rowSums(e^2)) # Edge lengths
v <- e / norms # Unit edge directions
w <- cbind(-v[,2], v[,1]) # Normal directions to the edges
# Find the MBR
vertices <- p[a, ] # Convex hull vertices
x <- apply(vertices %*% t(v), 2, range) # Extremes along edges
y <- apply(vertices %*% t(w), 2, range) # Extremes normal to edges
areas <- (y[1,]-y[2,])*(x[1,]-x[2,]) # Areas
k <- which.min(areas) # Index of the best edge (smallest area)
# Form a rectangle from the extremes of the best edge
cbind(x[c(1,2,2,1,1),k], y[c(1,1,2,2,1),k]) %*% rbind(v[k,], w[k,])
}
以下是其用法示例:
# Create sample data
set.seed(23)
p <- matrix(rnorm(20*2), ncol=2) # Random (normally distributed) points
mbr <- MBR(p)
# Plot the hull, the MBR, and the points
limits <- apply(mbr, 2, range) # Plotting limits
plot(p[(function(x) c(x, x[1]))(chull(p)), ],
type="l", asp=1, bty="n", xaxt="n", yaxt="n",
col="Gray", pch=20,
xlab="", ylab="",
xlim=limits[,1], ylim=limits[,2]) # The hull
lines(mbr, col="Blue", lwd=3) # The MBR
points(p, pch=19) # The points
时间的限制是由凸包算法的速度决定的,因为包中的顶点数量几乎总是少于总数。大多数凸包算法的n点都是渐近O(n * log(n)):您几乎可以像读取坐标一样快地进行计算。
评论
+1太棒了!这样的想法只有在长期的经验之后才能产生。从现在开始,我会很好奇地优化我现有的代码,并从这个好答案中得到启发。
–开发人员
2012年4月6日下午0:59
我希望我能两次投票赞成。我正在学习R,您的答案一直是灵感的源泉。
–约翰·鲍威尔(John Powell)
2015年2月6日10:00
@retrovius一组(旋转的)点的边界矩形由四个数字确定:最小的x坐标,最大的x坐标,最小的y坐标和最大的y坐标。这就是“沿边的极端”。
– hu
19年5月29日上午11:13
@retrovius在这些计算中,原点不起作用,因为所有内容都是基于坐标的差异,除了结尾处,即在旋转坐标中计算出的最佳矩形只是简单地向后旋转。尽管使用原点靠近点的坐标系是一个聪明的主意(以最大程度地减少浮点精度的损失),但原点无关紧要。
– hu
19年5月29日在21:56
@Retrovius您可以用旋转的性质来解释这一点:即旋转矩阵是正交的。因此,一种资源将是(通常)研究线性代数或(特别是)解析欧几里得几何。但是,我发现处理平面中旋转(以及平移和重新缩放)的最简单方法是将点视为复数:将值乘以单位长度的数字即可简单地进行旋转。
– hu
19年5月30日在13:06
#3 楼
我自己实现了这个问题,并在StackOverflow上发布了我的答案,但我想将版本放到这里供其他人查看:import numpy as np
from scipy.spatial import ConvexHull
def minimum_bounding_rectangle(points):
"""
Find the smallest bounding rectangle for a set of points.
Returns a set of points representing the corners of the bounding box.
:param points: an nx2 matrix of coordinates
:rval: an nx2 matrix of coordinates
"""
from scipy.ndimage.interpolation import rotate
pi2 = np.pi/2.
# get the convex hull for the points
hull_points = points[ConvexHull(points).vertices]
# calculate edge angles
edges = np.zeros((len(hull_points)-1, 2))
edges = hull_points[1:] - hull_points[:-1]
angles = np.zeros((len(edges)))
angles = np.arctan2(edges[:, 1], edges[:, 0])
angles = np.abs(np.mod(angles, pi2))
angles = np.unique(angles)
# find rotation matrices
# XXX both work
rotations = np.vstack([
np.cos(angles),
np.cos(angles-pi2),
np.cos(angles+pi2),
np.cos(angles)]).T
# rotations = np.vstack([
# np.cos(angles),
# -np.sin(angles),
# np.sin(angles),
# np.cos(angles)]).T
rotations = rotations.reshape((-1, 2, 2))
# apply rotations to the hull
rot_points = np.dot(rotations, hull_points.T)
# find the bounding points
min_x = np.nanmin(rot_points[:, 0], axis=1)
max_x = np.nanmax(rot_points[:, 0], axis=1)
min_y = np.nanmin(rot_points[:, 1], axis=1)
max_y = np.nanmax(rot_points[:, 1], axis=1)
# find the box with the best area
areas = (max_x - min_x) * (max_y - min_y)
best_idx = np.argmin(areas)
# return the best box
x1 = max_x[best_idx]
x2 = min_x[best_idx]
y1 = max_y[best_idx]
y2 = min_y[best_idx]
r = rotations[best_idx]
rval = np.zeros((4, 2))
rval[0] = np.dot([x1, y2], r)
rval[1] = np.dot([x2, y2], r)
rval[2] = np.dot([x2, y1], r)
rval[3] = np.dot([x1, y1], r)
return rval
这是四个不同的示例在行动。对于每个示例,我生成了4个随机点并找到了边界框。 >
>>> %timeit minimum_bounding_rectangle(a)
1000 loops, best of 3: 245 µs per loop
评论
嗨,JesseBuesking,您能够生成90度角的矩形吗?您的代码对于获取平行四边形非常有用,但是在我的特定用例中需要90度角。您能建议如何修改您的代码以达到此目的吗?谢谢!
– Nader Alexan
17年7月7日在5:38
@NaderAlexan如果您正在询问它是否可以处理正方形,那么可以,肯定可以!我只是在一个单位平方点上尝试过np.array([[0,0],[0,1],[1,0],[1,1]]),输出为array([[1.00000000 e + 00、6.12323400e-17],[0.00000000e + 00、0.00000000e + 00],[6.12323400e-17、1.00000000e + 00],[1.00000000e + 00、1.00000000e + 00]])单位平方本身(包括一些浮点数舍入误差)。注意:正方形只是边长相等的矩形,因此我假设如果它可以处理正方形,则可以推广到所有矩形。
–JesseBuesking
17年11月8日在15:32
谢谢您的回答。是的,它的效果很好,但是我试图迫使它始终在任何其他4边多边形上产生一个矩形(4个边,每边成90度角),尽管在某些情况下它确实会产生一个矩形,作为一个恒定约束,您知道如何修改代码以添加此约束吗?谢谢!
– Nader Alexan
17年11月9日在5:05
也许gis.stackexchange.com/a/22934/48041可能会指导您寻求解决方案,因为他们的答案似乎有此限制?找到解决方案后,您应该做出贡献,因为我敢肯定其他人会发现它有用。祝好运!
–JesseBuesking
17年11月10日在13:42
@NaderAlexan我假设只是以不同的轴比例为x和y创建了绘图,从而生成了看起来像平行四边形的矩形。
– moooeeeep
19年11月12日在10:51
#4 楼
Whitebox GAT(http://www.uoguelph.ca/~hydrogeo/Whitebox/)中有一个名为“最小边界框”的工具可以解决此确切问题。那里也有一个最小的凸包工具。补丁形状工具箱中的几种工具,例如修补程序的方向和伸长率是基于找到最小边界框而确定的。#5 楼
我在寻找用于最小面积边界矩形的Python解决方案时碰到了这个线程。这是我的实现,其结果已通过Matlab进行了验证。
测试代码包含用于简单的多边形,我正在使用它来查找3D PointCloud的2D最小边界框和轴方向。
评论
您的答案是否已删除?
–Paul Richter
16年5月5日在16:46
@PaulRichter显然。虽然来源在这里github.com/dbworth/minimum-area-bounding-rectangle
–sehe
18-2-28在13:41
无故删除了它。我在这里重新发布了它:stackoverflow.com/a/60012818/1755401
–大卫
2月1日0:04
#6 楼
感谢@whuber的回答。这是一个很好的解决方案,但是对于大点云来说却很慢。我发现R软件包convhulln
中的geometry
函数要快得多(200000点为138 s vs 0.03 s)。我在这里粘贴我的代码给任何对快速解决方案感兴趣的人。library(alphahull) # Exposes ashape()
MBR <- function(points) {
# Analyze the convex hull edges
a <- ashape(points, alpha=1000) # One way to get a convex hull...
e <- a$edges[, 5:6] - a$edges[, 3:4] # Edge directions
norms <- apply(e, 1, function(x) sqrt(x %*% x)) # Edge lengths
v <- diag(1/norms) %*% e # Unit edge directions
w <- cbind(-v[,2], v[,1]) # Normal directions to the edges
# Find the MBR
vertices <- (points) [a$alpha.extremes, 1:2] # Convex hull vertices
minmax <- function(x) c(min(x), max(x)) # Computes min and max
x <- apply(vertices %*% t(v), 2, minmax) # Extremes along edges
y <- apply(vertices %*% t(w), 2, minmax) # Extremes normal to edges
areas <- (y[1,]-y[2,])*(x[1,]-x[2,]) # Areas
k <- which.min(areas) # Index of the best edge (smallest area)
# Form a rectangle from the extremes of the best edge
cbind(x[c(1,2,2,1,1),k], y[c(1,1,2,2,1),k]) %*% rbind(v[k,], w[k,])
}
MBR2 <- function(points) {
tryCatch({
a2 <- geometry::convhulln(points, options = 'FA')
e <- points[a2$hull[,2],] - points[a2$hull[,1],] # Edge directions
norms <- apply(e, 1, function(x) sqrt(x %*% x)) # Edge lengths
v <- diag(1/norms) %*% as.matrix(e) # Unit edge directions
w <- cbind(-v[,2], v[,1]) # Normal directions to the edges
# Find the MBR
vertices <- as.matrix((points) [a2$hull, 1:2]) # Convex hull vertices
minmax <- function(x) c(min(x), max(x)) # Computes min and max
x <- apply(vertices %*% t(v), 2, minmax) # Extremes along edges
y <- apply(vertices %*% t(w), 2, minmax) # Extremes normal to edges
areas <- (y[1,]-y[2,])*(x[1,]-x[2,]) # Areas
k <- which.min(areas) # Index of the best edge (smallest area)
# Form a rectangle from the extremes of the best edge
as.data.frame(cbind(x[c(1,2,2,1,1),k], y[c(1,1,2,2,1),k]) %*% rbind(v[k,], w[k,]))
}, error = function(e) {
assign('points', points, .GlobalEnv)
stop(e)
})
}
# Create sample data
#set.seed(23)
points <- matrix(rnorm(200000*2), ncol=2) # Random (normally distributed) points
system.time(mbr <- MBR(points))
system.time(mmbr2 <- MBR2(points))
# Plot the hull, the MBR, and the points
limits <- apply(mbr, 2, function(x) c(min(x),max(x))) # Plotting limits
plot(ashape(points, alpha=1000), col="Gray", pch=20,
xlim=limits[,1], ylim=limits[,2]) # The hull
lines(mbr, col="Blue", lwd=10) # The MBR
lines(mbr2, col="red", lwd=3) # The MBR2
points(points, pch=19)
两种方法得到相同的答案(例如2000分):
评论
是否可以将此实现扩展到3d空间(即找到包含3d空间中所有给定点的最小体积框)?
–萨沙
16 Dec 20'在17:37
#7 楼
我只是简单地推荐OpenCV的内置函数minAreaRect
,它会找到包含输入2D点集的最小面积的旋转矩形。要了解如何使用此功能,请参考本教程。
评论
+1非常好的答案!我想指出,云的实际旋转是不必要的。首先-您可能就是这个意思-只需考虑船体的顶点。其次,不是旋转,而是将当前边表示为一对正交单位向量。将它们的点积与船体顶点坐标(可以通过单个矩阵操作完成)相乘即可得到旋转坐标:无需三角函数,快速且完全准确。
– hu
2012年4月5日15:01
感谢您的链接。实际上,仅旋转#个边缘可使所提出的方法非常有效。我发现论文证明了这一点。尽管我将其标记为对第一个好的答案的忠诚度答案(不能选择两个/多个出色的答案:(),但我还是强烈建议您考虑下面的wuber完整答案。那里的给定方法的效率(避免旋转!)是太神奇了,整个过程只有几行代码。对我来说,它很容易翻译成Python :)
–开发人员
2012年4月6日,0:56
能否请您更新Java实现链接?
–迈拉
2012年8月23日在13:49
是的,它完成了!
–朱利安
2012年8月23日15:19
请注意,将其扩展到3D会比这复杂一些。 3D凸包的每个面都定义了边界框的一个面的可能方向,但没有定义垂直于该边界框的面的方向。如何在该平面上旋转盒子的问题变成了在该平面上的二维最小边界矩形问题。对于投影到给定平面上的云的凸包的每个边缘,您可以绘制一个边界框,该边界框将为您提供不同的3D体积。
–将
17年5月5日在11:12