简体   繁体   English

使用 Scipy 在凸包上找到一个点的投影

[英]Find the projection of a point on the convex hull with Scipy

From a set of points, I'm getting the convex hull with scipy.spatial , either with Delaunay or ConvexHull (from the qhull library).从一组点来看,我得到了带有scipy.spatial的凸包,无论是使用Delaunay还是ConvexHull (来自 qhull 库)。 Now I would like to get the projection of a point outside this convex hull onto the hull (ie the point on the hull that is the smallest distance from the point outside).现在我想得到这个凸包外的一个点到船体上的投影(即船体上与外部点距离最小的点)。

This is the code I have so far:这是我到目前为止的代码:

from scipy.spatial import Delaunay, ConvexHull
import numpy as np

hu = np.random.rand(10, 2) ## the set of points to get the hull from
pt = np.array([1.1, 0.5]) ## a point outside
pt2 = np.array([0.4, 0.4]) ## a point inside
hull = ConvexHull(hu) ## get only the convex hull
#hull2 = Delaunay(hu) ## or get the full Delaunay triangulation

import matplotlib.pyplot as plt
plt.plot(hu[:,0], hu[:,1], "ro") ## plot all points
#plt.triplot(hu[:,0], hu[:,1], hull2.simplices.copy()) ## plot the Delaunay triangulation
## Plot the convexhull
for simplex in hull.simplices:
    plt.plot(hu[simplex,0], hu[simplex,1], "ro-")

## Plot the points inside and outside the convex hull 
plt.plot(pt[0], pt[1], "bs")
plt.plot(pt2[0], pt2[1], "bs")
plt.show()

With a picture it might be easier, I would like to obtain the x and y coordinates in green from the blue point outside the convex hull.使用图片可能更容易,我想从凸包外的蓝点获得绿色的 x 和 y 坐标。 The example is 2d but I would need to apply it in higher dimension as well.这个例子是二维的,但我也需要在更高的维度上应用它。 Thanks for the help.谢谢您的帮助。

凸包和点获得绿色

EDIT: The problem is addressed here but I have trouble implementing it: https://mathoverflow.net/questions/118088/projection-of-a-point-to-a-convex-hull-in-d-dimensions编辑:问题在这里得到解决,但我在实现它时遇到了麻烦: https : //mathoverflow.net/questions/118088/projection-of-a-point-to-a-convex-hull-in-d-dimensions

I am answering to myself.我在回答自己。 As 0Tech pointed out, ConvexHull.equations gives you the plane equations for each plane (in 2d --- a line therefore) with the form : [A, B, C] .正如 0Tech 指出的那样, ConvexHull.equations为您提供了每个平面的平面方程(在 2d 中——因此是一条线),形式为: [A, B, C] The plane is therefore defined by因此平面定义为

A*x + B*y + C = 0

Projecting the point P=(x0, y0) on the plane is explained here .在平面上投影点 P=(x0, y0) 在这里解释。 You want a point on the vector parallel to the plane vector (A, B) and passing through the point to project P, this line is parameterised by t:您希望向量上的一个点平行于平面向量 (A, B) 并通过该点投影 P,这条线由 t 参数化:

P_proj = (x, y) = (x0 + A*t, y0 + B*t)

You then want your point to be on the plane and uses the full plane equation to do that:然后您希望您的点在平面上并使用完整平面方程来做到这一点:

A*(x0 + A*t) + B*(y0 + B*t) + C = 0
=> t=-(C + A*x0 + B*y0)/(A**2+B**2)

In (clumsy) python, it gives for any dimension:在(笨拙的)python 中,它给出了任何维度:

from scipy.spatial import Delaunay, ConvexHull
import numpy as np

hu = np.random.rand(10, 2) ## the set of points to get the hull from
pt = np.array([1.1, 0.5]) ## a point outside
pt2 = np.array([0.4, 0.4]) ## a point inside
hull = ConvexHull(hu) ## get only the convex hull
#hull2 = Delaunay(hu) ## or get the full Delaunay triangulation

import matplotlib.pyplot as plt
plt.plot(hu[:,0], hu[:,1], "ro") ## plot all points
#plt.triplot(hu[:,0], hu[:,1], hull2.simplices.copy()) ## plot the Delaunay triangulation
## Plot the convexhull
for simplex in hull.simplices:
    plt.plot(hu[simplex,0], hu[simplex,1], "ro-")

## Plot the points inside and outside the convex hull 
plt.plot(pt[0], pt[1], "bs")
plt.plot(pt2[0], pt2[1], "bs")

for eq in hull.equations:
    t = -(eq[-1] + np.dot(eq[:-1], pt))/(np.sum(eq[:-1]**2))
    pt_proj = pt + eq[:-1]*t
    plt.plot(pt_proj[0], pt_proj[1], "gD-.")
plt.show()

Browsing stackoverflow , led me to another solution, that has the advantage of using segments instead of the lines, so the projection on one of the segment always lie on the segment:浏览stackoverflow ,让我找到了另一种解决方案,它具有使用段而不是线的优势,因此其中一个段的投影始终位于该段上:

def min_distance(pt1, pt2, p):
    """ return the projection of point p (and the distance) on the closest edge formed by the two points pt1 and pt2"""
    l = np.sum((pt2-pt1)**2) ## compute the squared distance between the 2 vertices
    t = np.max([0., np.min([1., np.dot(p-pt1, pt2-pt1) /l])]) # I let the answer of question 849211 explains this
    proj = pt1 + t*(pt2-pt1) ## project the point
    return proj, np.sum((proj-p)**2) ## return the projection and the point

Then we can browse each vertices and project the point:然后我们可以浏览每个顶点并投影点:

for i in range(len(hull.vertices)):
    pt_proj, d = min_distance(hu[hull.vertices[i]], hu[hull.vertices[(i+1)%len(hull.vertices)]], pt)
    plt.plot([pt[0], pt_proj[0]], [pt[1], pt_proj[1]], "c<:")

And the picture, with the projection of the blue point on the right on each plane (line) in green for the first method and cyan for the second method:和图片,每个平面(线)右侧的蓝点投影为绿色,第一种方法为青色,第二种方法为青色:

投影点数

Since there still doesn't seem to be a good answer here (or anywhere), I followed this post (among others) and solved the problem using quadratic programming:由于这里(或任何地方)似乎仍然没有一个好的答案,我关注了这篇文章(以及其他文章)并使用二次规划解决了这个问题:

$$ \begin{align}\text{minimize} & \quad \frac{1}{2} x^{T} \mathbf{I} x - z^{T} x\\ \text{subject to} & \quad C x \leq b\\ \end{align} $$

Where $C$ are the normal equations and $b$ is the offsets.其中$C$是正规方程, $b$是偏移量。 The key difference here is that I am projecting points to within the convex hull, not necessarily onto it.这里的主要区别是我将点投影到凸包内,而不一定要投影到它上面。 ie a point within the convex hull will remain unchanged and points outside the convex hull will be projected to the closest point in the convex hull (which will always be on the surface of it).即凸包内的点将保持不变,凸包外的点将投影到凸包中最近的点(始终在其表面上)。 By removing this equality constraint, it's a relatively simple quadratic programming problem which I solve using the quadprog package.通过删除这个等式约束,这是一个相对简单的二次规划问题,我使用quadprog包解决了quadprog问题。

There might be a theoretically faster way to do it but this way is fast enough, simple, and robust:理论上可能有一种更快的方法来做到这一点,但这种方法足够快、简单且健壮:

import numpy as np
from scipy.spatial import ConvexHull
from quadprog import solve_qp

def proj2hull(z, equations):
    """
    Project `z` to the convex hull defined by the
    hyperplane equations of the facets

    Arguments
        z: array, shape (ndim,)
        equations: array shape (nfacets, ndim + 1)

    Returns
        x: array, shape (ndim,)
    """
    G = np.eye(len(z), dtype=float)
    a = np.array(z, dtype=float)
    C = np.array(-equations[:, :-1], dtype=float)
    b = np.array(equations[:, -1], dtype=float)
    x, f, xu, itr, lag, act = solve_qp(G, a, C.T, b, meq=0, factorized=True)
    return x

A simple example:一个简单的例子:

X = np.random.normal(size=(1000, 5))
z = np.random.normal(scale=2, size=(5))
hull = ConvexHull(X)
y = proj2hull(z, hull.equations)

(Edit: Sorry that it doesn't look like the Latex is formatting) (编辑:对不起,它看起来不像乳胶正在格式化)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM