将多边形的一组点压缩为较短的一组点

时间:2019-04-09 22:39:03

标签: matlab polygon

我有以下多边形,它只是一组2D点,如下所示:-

poly0=[80    60
    90    60
   100    60
   110    60
   110    50
   120    50
   130    50
   140    50
   150    50
   160    50
   170    50
   180    50
   190    50
   200    50
   210    50
   210    60
   210    70
   210    80
   210    90
   220    90
   220   100
   210   100
   210   110
   200   110
   200   120
   190   120
   180   120
   180   130
   170   130
   160   130
   150   130
   140   130
   130   130
   130   120
   120   120
   110   120
   110   110
   100   110
   100   100
    90   100
    90    90
    90    80
    90    70
    80    70
    80    60];

现在我可以使用它了。

>> line(poly0(:,1), poly0(:,2),'Color','k','LineWidth',3,'LineStyle',':');

这清楚地表明了我的原始多边形点集是高度冗余的。基本上,位于同一直线上的多个点被枚举,不需要。我可以开始检查每对点,如果它们在同一条直线上,则可以将其删除。但这意味着要使用许多for循环。我无法提出一种智能的矢量化方法。

如何获得一组新的点,这些点的大小比以前的点短很多,但仍然代表完全相同的多边形?我应该只有多边形中的顶点数量一样多的点。那么换句话说,如何从上述数据集中快速找到顶点?

PS :此处的顶点角为90度,但是如果您提供解决方案,请不要尝试利用这一事实。我想要一个更一般的答案。

3 个答案:

答案 0 :(得分:5)

两个现有的答案都有很大的缺点:

  • Durkee's method仅在后续点之间的距离完全相同时才有效。点必须具有完全可表示为浮点值的坐标,以便可以找到直线上后续点之间的距离相同。如果点不等距,则该方法不执行任何操作。另外,多边形的起点和终点不会一起检查,因此,如果在多边形的起点/终点形成一条直线,则将留下太多点。

  • ShadowMan's method更好,因为距离不必相同,并且正确处理了多边形起点/终点的线。但是,它也使用浮点相等比较,通常无法正常工作。只有使用整数坐标,此方法才能正常工作。此外,它使用vecnorm(具有平方根)和除法(相对于此处显示的方法而言)都是比较昂贵的操作。

要查看三个点是否形成一条直线,可以使用简单的算术规则。假设我们有点p0p1p2。从p0p1的向量和从p0p2的向量形成平行四边形的基础,平行四边形的面积可以由the cross product of the two vectors计算(在2D中,则叉积被理解为使用z=0,结果向量具有x=0y=0,只有z值才有用;因此,我们假设2D叉积产生标量值)。可以计算如下:

v1 = p1 - p0;
v2 = p2 - p0;
x = v1(1)*v2(2) - v1(2)*v2(1);
如果两个向量平行,则叉积

x将为零,这意味着三个点是共线的。但是,由于浮点算术不精确,因此对于等于0的相等性测试必须具有一定的容差。我在这里使用1e-6作为公差。使用比点之间的距离小几个数量级的值。

给出一组输入点p,我们可以通过以下步骤找到角点:

p1 = p;                                  % point 1
p0 = circshift(p1,1);                    % point 0
v1 = p1 - p0;                            % vector from point 0 to 1
v2 = circshift(p1,-1) - p0;              % vector from point 0 to 2
x = v1(:,1).*v2(:,2) - v1(:,2).*v2(:,1); % cross product
idx = abs(x) > 1e-6;                     % comparison with tolerance
p = p(idx,:);                            % corner points

请注意,如果两个连续的点具有相同的坐标(即向量之一的长度为零),则叉积测试将失败。如果数据可能有重复的点,则需要进行额外的测试。

这是这三种方法的结果。我创建了一个具有非平凡坐标且顶点间距不相等的多边形。我还将开始/结束间隙放在一条直线边缘的中间。这些特性旨在说明其他两种方法的缺点。

comparison of the three methods

这是我用来生成图形的代码:

% Make a polygon that will be difficult for the other two methods
p = [0,0 ; 0.5,0 ; 1,0 ; 1,1 ; 0.5,1 ; 0,1];
p = p + rand(size(p))/3;
p(end+1,:) = p(1,:);
q = [];
for ii = 1:size(p,1)-1
   t = p(ii,:) + (p(ii+1,:) - p(ii,:)) .* [0;0.1;1/3;0.45;0.5897545;pi/4;exp(1)/3];
   q = [q;t];
end
q = circshift(q,3,1);

figure
subplot(2,2,1)
plot(q(:,1),q(:,2),'bo-')
axis equal
title('input')

subplot(2,2,2)
res1 = method1(q);
plot(res1(:,1),res1(:,2),'ro-')
axis equal
title('Durkee''s method')

subplot(2,2,3)
res2 = method2(q);
plot(res2(:,1),res2(:,2),'ro-')
axis equal
title('ShadowMan''s method')

subplot(2,2,4)
res3 = method3(q);
plot(res3(:,1),res3(:,2),'go-')
axis equal
title('correct method')

% Durkee's method: https://stackoverflow.com/a/55603145/7328782
function P = method1(P)
a = logical([1 diff(P(:,1),2)' 1]);
b = logical([1 diff(P(:,2),2)' 1]);
idx = or(a,b);
P = P(idx,:);
end

% ShadowMan's method: https://stackoverflow.com/a/55603040/7328782
function corners = method2(poly0)
poly0Z = circshift(poly0,1);
poly0I = circshift(poly0,-1);
unitVectIn =(poly0 - poly0I)./vecnorm((poly0 - poly0I),2,2);
unitVectOut =(poly0Z - poly0)./vecnorm((poly0Z - poly0),2,2);
cornerIndices = sum(unitVectIn == unitVectOut,2)==0;
corners = poly0(cornerIndices,:);
end
% vecnorm is new to R2017b, I'm still running R2017a.
function p = vecnorm(p,n,d)
% n is always 2
p = sqrt(sum(p.^2,d));
end

function p = method3(p1)
p0 = circshift(p1,1);
v1 = p1 - p0;
v2 = circshift(p1,-1) - p0;
x = v1(:,1).*v2(:,2) - v1(:,2).*v2(:,1);
idx = abs(x) > 1e-6;
p = p1(idx,:);
end

答案 1 :(得分:1)

可以非常优雅地完成“向量”方式。我也尝试过for循环的方式,您可以用这种方式做同样的事情,但是您要求向量,所以这是我的方式。

我对数据所做的唯一更改是在启动此脚本之前删除了所有重复项。另外,提供的点应按顺时针或逆时针顺序排列。

    poly0Z = circshift(poly0,1);
    poly0I = circshift(poly0,-1); 
    unitVectIn =(poly0 - poly0I)./vecnorm((poly0 - poly0I),2,2);
    unitVectOut =(poly0Z - poly0)./vecnorm((poly0Z - poly0),2,2)  ;
    cornerIndices = sum(unitVectIn == unitVectOut,2)==0
    corners = poly0(cornerIndices,:)

    line(poly0(:,1), poly0(:,2),'Color','k','LineWidth',2,'LineStyle',':');
    hold on
    scatter(corners(:,1), corners(:,2),'filled')

此方法的基础是转到每个点,计算进入的单位矢量,然后计算出的单位矢量。单位矢量输入与单位矢量输出不匹配的点是角。

答案 2 :(得分:1)

好的,我修改了它以处理非方形角。

考虑一个由点标识的三角形

P = [0 0; 1 0; 2 0; 1.5 1; 1 2; .5 1; 0 0];

如果我们按照注释中提到的问题定义2个导数向量,则这是一个7x2数组。

a = logical([1 diff(P(:,1),2)' 1]);
b = logical([1 diff(P(:,2),2)' 1]);

从那里,我们可以将两者结合起来以获得新的索引变量

idx = or(a,b);

最后,我们可以用它来绘制剧情

line(P(idx,1), P(idx,2),'Color','k','LineWidth',3,'LineStyle',':');

如果要绘制线图,我认为您需要将最后一个变量设置为false。

idx(end) = false;