二维点集求外轮廓Java_从二维点集重建平面形状-浅议凹包算法

问题背景

近期遇到一个计算几何问题,需要从点集中重建一个合理的几何形状。这个问题既有二维的也有三维的,二维的情况相对简单一点,即给出平面区域的一系列散点,求出一定程度上反映这些散点轮廓的平面多边形,给出边的连接方式即可。如从下图的左图散点重建为右图的形状:

45e1eb91a42d64093963f43736661dda.png

aea6add219a3a2dd0ae398feaa3afdc6.png

二维平面散点

平面多边形

不过这里有一些细节需要注意,必须明确这一系列点的含义,有时给出的点集是表征图形边界的,如左图的情况;有时则是表征图形所覆盖的范围,即在图形的内部也有一定的点分布。这两种不同的含义是和具体的应用场景相关的。

650443ee419eb5f01154a5cc7902441e.png

d5acc1b6d791c96cc8f7d6f88b65e60c.png

有的场景是求散点描绘的轮廓

有的场景是求散点分布区域的边界轮廓

凹包与Alpha shape

有不少计算几何领域的资料探讨过这一类问题的解决方案,笔者也曾经阅读过部分相关的文献,也在网络上进行过一些搜索。本文不能一一去介绍所有的方法,所以就介绍几种简单直观的方法来解决二维点集重建平面多边形的问题。

首先很多学计算机的人都知道凸包,凸包求取的是覆盖所有点的凸多边形,由于限定死了一定要凸多边形,所以凸包不是本文所讨论问题的解决方案。本文所求的形状肯定不能否定存在凹陷的部分,因而可以联想到这个问题的解是否是求一种“凹包”,或者说一种广义的参数化的凸包。事实上的确有凹包的概念,英文叫做concave hull(凸包叫convex hull)。不过于凸包的情况不同,凹包没有特别明确的定义,给定一个较不规则的点集,可以有各种各样的凹包,但凸包是确定的,如下图所示。

7ff503bf7e8c9a9ab3472b13f68777ff.png

50f73b3751de8bb9e6c9b3a97a4d2766.png

599c1965dd82debed4df9dca1be32caa.png

凸包

一种可能的凹包连接方式

这样连也是凹包

进一步查找相关的资料可以发现一个叫做alpha shape的概念。这个概念最早出现于论文"On the shape of a set of points in the plane"中。alpha shape有较为正式的定义,若要简单点解释它,alpha shape其实就是在凸包基础上多了一个可以设定的参数α,因为这个α,在alpha shape重建形状的过程中可能就不会像凸包一样连接相距过远的顶点。参数α若趋于无穷大,则这个alpha shape会无无限接近凸包;而α取小了,则alpha shape会倾向于在一定位置凹陷进去,以更加贴合点集的外形。选择合理的α就能够控制生成的形状让其更加符合点集所描绘的形状。所以给出一个α值,就有点类似于给出一个长度的限制,例如让多边形的任何一边长度不超过R。

为了更好的展示和测试程序,笔者特地开发了一个基于WPF技术的小软件ConcaveGenerator用来展示算法效果,下文展示的很多图都是对这个软件截图的。如有兴趣可以在这里下载。

21e11480ddea962916ab0f8de8d0fef8.png

自制ConcaveGenerator小软件截图

第一种思路--基于Delaunay三角化

有一定计算几何基础的人一定熟悉Delaunay三角化,通过这一个过程可以形成Delaunay三角网,假如我们为想要求取形状的点集上使用Delaunay三角化算法,可以得到如下的三角网。

d3f6403e969ccc1031998b0ab8ec3781.png

e662c7ab835882edfc090525de86673d.png

一个点集的Delaunay三角网

另一个例子

一看到这样的三角网,就不难想到一个好点子:因为我们想要求取的形状就是Delaunay三角网的子集,所以我们只需要从Delaunay三角网的最外层边开始外不断的删去超过长度限制的边,当这个过程结束时,我们就能够得到一个大致符合我们预期的形状。

92be061008614e0827018120a3aee91b.png

db3dc986d6c355956b5a6f91cacaba40.png

点集的Delaunay三角网

删掉边上太长的边就能形成预期的形状

所以总结这个思路,输入为点集S和长度限制R的求取凹包的边列表算法的过程如下:

为点集S求取Delaunay三角网M,三角网用标准Mesh形式表示。

为M初始化所有Edge对象,并求取Edge的长度以及邻接三角形集合。其中邻接2个三角形的边为内部边,1个三角形的为边界边,0个三角形的为计算过程中会退化的边。

将所有长度大于R的边界边加入队列,并当队列非空时循环下列过程:从队列中取出一条边E,得到E的唯一邻接三角形T。

找到T中另外两个边E1,E2将他们的邻接三角形集合删去T。

将E1,E2中新形成的长度大于R的边界边加入队列。

将E置无效标记,若E1,E2有退化的,也置无效标记。

收集所有有效的边界边,形成边列表,输出。

以下ConcaveGenerator是在这部分的代码,其中Delaunay三角化的实现有比较多的代码能参考,这里是使用了Stackoverflow上一个snippet:

public classDelaunayTriangulation

{public const int MaxVertices = 500;public const int MaxTriangles = 1000;public dVertex[] Vertex = newdVertex[MaxVertices];public dTriangle[] Triangle = newdTriangle[MaxTriangles];private bool InCircle(long xp, long yp, long x1, long y1, long x2, long y2, long x3, long y3, double xc, double yc, doubler)

{doubleeps;doublem1;doublem2;doublemx1;doublemx2;doublemy1;doublemy2;doubledx;doubledy;doublersqr;doubledrsqr;

eps= 0.000000001;if (Math.Abs(y1 - y2) < eps && Math.Abs(y2 - y3)

{return false;

}if (Math.Abs(y2 - y1)

{

m2= (-(Convert.ToDouble(x3) - Convert.ToDouble(x2)) / (Convert.ToDouble(y3) -Convert.ToDouble(y2)));

mx2= Convert.ToDouble((x2 + x3) / 2.0);

my2= Convert.ToDouble((y2 + y3) / 2.0);

xc= Convert.ToDouble((x2 + x1) / 2.0);

yc= Convert.ToDouble(m2 * (xc - mx2) +my2);

}else if (Math.Abs(y3 - y2)

{

m1= (-(Convert.ToDouble(x2) - Convert.ToDouble(x1)) / (Convert.ToDouble(y2) -Convert.ToDouble(y1)));

mx1= Convert.ToDouble((x1 + x2) / 2.0);

my1= Convert.ToDouble((y1 + y2) / 2.0);

xc= Convert.ToDouble((x3 + x2) / 2.0);

yc= Convert.ToDouble(m1 * (xc - mx1) +my1);

}else{

m1= (-(Convert.ToDouble(x2) - Convert.ToDouble(x1)) / (Convert.ToDouble(y2) -Convert.ToDouble(y1)));

m2= (-(Convert.ToDouble(x3) - Convert.ToDouble(x2)) / (Convert.ToDouble(y3) -Convert.ToDouble(y2)));

mx1= Convert.ToDouble((x1 + x2) / 2.0);

mx2= Convert.ToDouble((x2 + x3) / 2.0);

my1= Convert.ToDouble((y1 + y2) / 2.0);

my2= Convert.ToDouble((y2 + y3) / 2.0);

xc= Convert.ToDouble((m1 * mx1 - m2 * mx2 + my2 - my1) / (m1 -m2));

yc= Convert.ToDouble(m1 * (xc - mx1) +my1);

}

dx= (Convert.ToDouble(x2) -Convert.ToDouble(xc));

dy= (Convert.ToDouble(y2) -Convert.ToDouble(yc));

rsqr= Convert.ToDouble(dx * dx + dy *dy);

r=

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值