在样条曲线上采样到给定的最大弦偏差

时间:2012-12-30 01:36:35

标签: python numpy scipy

我正在寻找一种快速方法来对样条曲线上的点进行采样,这样通过这些点的多边形或线串不会超过原始样条曲线的给定弦线误差。我有一个我前段时间写过的算法,它会在图片中产生结果(如果感兴趣,请参阅下面的代码;我不希望任何人对它进行研究)。它工作正常,但速度不快(在我的计算机上生成该图形约2秒)。有没有更简单的东西,可能是numpy或scipy内置的东西可以实现这个目标吗?

由于

enter image description here

import numpy as np
from scipy.optimize import brentq
from scipy.interpolate import splev

def get_rhos(ts, tck):
    """Get (signed) rhos (1/rad of curvature) for a given
    set of t values.
    """
    tanvs = np.array(splev(ts, tck, der=1)).T
    accvs = np.array(splev(ts, tck, der=2)).T
    if tanvs.ndim == 1:
        tanvs = tanvs.reshape(1, -1)
        accvs = accvs.reshape(1, -1)

    crossp = np.cross(accvs, tanvs, axis=1)
    tanvms = np.array([np.sqrt(np.dot(v, v)) for v in tanvs])
    rhos = crossp / tanvms**3
    return rhos

def calc_rad(pt0, pt1, pt2, calcdrop=False):
    """Calculate a radius from three points on the arc.
    Lifted from http://www.physicsforums.com/showthread.php?t=173847
    """
    pt0 = np.array(pt0)
    pt1 = np.array(pt1)
    pt2 = np.array(pt2)

    v0 = pt1 - pt0
    v1 = pt2 - pt0
    v2 = pt2 - pt1
    a = np.sqrt(np.dot(v0, v0))
    b = np.sqrt(np.dot(v1, v1))
    c = np.sqrt(np.dot(v2, v2))
    R = (a*b*c) / np.sqrt(  2 * a**2 * b**2
                          + 2 * b**2 * c**2
                          + 2 * c**2 * a**2
                          - a**4 - b**4 - c**4)
    if calcdrop:
        # Calculate arc drop
        drop = R - np.sqrt(R**2 - (b/2.)**2)
        return R, drop
    else:
        return R

def chordal_sample(tck, chordaltol, oversample=10):
    """Given a spline definition and a chordal tolerance
    (intol/outol), get the t-values for the spline such
    that, when adjacent points are connected, the chordal
    tolerance is not violated.

    Accomplishes this by bracketing a solution, then using
    the brentq solver to find the point where the chordal
    error equals the chordal tolerance.

    Note that a few extra points may be inserted where there
    are inflections in the cubic; these are sometimes missed
    by the arc-radius-calculating portion of the code.
    """

    # This is the function we'll need when we have to
    # go searching for the answer via brentq
    def makeerrfunc(st, spt, tck, chordaltol):
        def errfunc(et):
            mt = (st + et) / 2.0
            mpt = np.array(splev(mt, tck))
            ept = np.array(splev(et, tck))
            _, arcdrop = calc_rad(spt, mpt, ept, calcdrop=True)
            diff = arcdrop - chordaltol
            return diff
        return errfunc

    # Make sure we're sampling enough points
    # TODO: How can we be sure?
    ts = np.linspace(0, 1, oversample * len(tck[1][0]))
    newts = [0]
    # Loop through the time values
    for nt in ts:
        st = newts[-1]
        rts = ts[ts > st] # Only consider remaining time values
        # Step through adjacent pairs of time values and find
        # ones that bracket the solution.
        for et0, et1 in zip(rts[0:-1], rts[1:]):
            # Get a 'middle time' that we can use to calc
            # a 'middle point' for our arc calculations
            mt0 = (st + et0) / 2.
            mt1 = (st + et1) / 2.

            # Interpolate points at the critical t values
            ipts = np.array(splev([st, mt0, et0, mt1, et1], tck))
            spt, mpt0, ept0, mpt1, ept1 = ipts.T

            _, arcdrop0 = calc_rad(spt, mpt0, ept0, calcdrop=True)
            _, arcdrop1 = calc_rad(spt, mpt1, ept1, calcdrop=True)

            # Have we bracketed the solution yet? If so, use
            # brentq to find a better one within the bracketed
            # range, then move on to a new start t.
            if arcdrop0 > chordaltol: # Check the initial pair
                errfunc = makeerrfunc(st, spt, tck, chordaltol)
                mdt = brentq(errfunc, st, et0)
                newts.append(mdt)
                break
            if arcdrop0 <= chordaltol and arcdrop1 > chordaltol:
                errfunc = makeerrfunc(st, spt, tck, chordaltol)
                mdt = brentq(errfunc, et0, et1)
                newts.append(mdt)
                break
            # Check for the existence of an inflection point
            # in the bracketed range by checking the signs
            # of the two calculated curvatures and looking for
            # a reversal.
            if get_rhos(et0, tck)[0] * get_rhos(et1, tck)[0] < 0:
                newts.append((et0 + et1) / 2.0)
                break
        if et1 == 1.0: # No more points to try
            newts.append(1.0)
            break
    return newts

if __name__ == '__main__':
    import matplotlib.pyplot as plt
    from scipy.interpolate import splprep

    # Create a hi-res sample spline. Start with some
    # low-res points and then resample at a higher
    # res.
    XY = np.array([[0.0,  1.0,  2.0, 3.0, 2.0, 1.0, 0.0],
                   [0.0, -1.0, -0.5, 0.0, 2.5, 1.2, 2.0]])
    tck, u = splprep(XY, s=0)
    XY = splev(np.linspace(0, 1, 400), tck)
    tck, u = splprep(XY, s=0)

    # Get a set of t values that will plot out
    # a linestring with no more than 0.1 chordal
    # error to the original.
    ts = chordal_sample(tck, 0.1)

    fig, ax = plt.subplots()
    # Plot the hi-res spline
    ax.plot(*XY)
    # Plot the approximated spline
    ax.plot(*np.array(splev(ts, tck)), marker='o')

    ax.axis('equal')
    ax.grid()
    plt.show()

1 个答案:

答案 0 :(得分:2)

我在我最喜欢的模块中找到了一个很好的解决方案--Styly。 Shapely几何对象上有一个simplify()方法,它采用公差并为相同的0.1值生成此公式: enter image description here

对我来说看起来更好,只花了1.65毫秒(加速~1200倍)!