0

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

谢谢

在此处输入图像描述

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()
4

1 回答 1

2

我在我最喜欢的模块之一 - Shapely 中找到了一个很好的解决方案。Shapely 几何对象有一种simplify()方法,它采用公差并产生相同的 0.1 值: 在此处输入图像描述

对我来说看起来更好,并且只用了 1.65 毫秒(对于 ~1200 倍的加速)!

于 2012-12-30T02:33:31.387 回答