Find the projection of a point on the convex hull with Scipy

情到浓时终转凉″ 提交于 2020-01-01 16:48:29

问题


From a set of points, I'm getting the convex hull with scipy.spatial, either with Delaunay or ConvexHull (from the qhull library). Now I would like to get the projection of a point outside this convex hull onto the hull (i.e. 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. 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


回答1:


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]. The plane is therefore defined by

A*x + B*y + C = 0

Projecting the point P=(x0, y0) on the plane is explained here: http://www.nabla.hr/CG-LinesPlanesIn3DB5.htm. 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:

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:

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:

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:




回答2:


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. The key difference here is that I am projecting points to within the convex hull, not necessarily onto it. i.e. 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.

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)



来源:https://stackoverflow.com/questions/42248202/find-the-projection-of-a-point-on-the-convex-hull-with-scipy

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!