趣味で計算流砂水理

趣味で計算流砂水理 Computational Sediment Hydraulics for Fun Learning

数値計算とか河川工学とかプログラミングのことを書いています

MENU

ポリラインのスムージング方法:Smoothing methods for a polyline object

スポンサーリンク

この記事のポイント
  • ポリラインのスムージング方法として、3次Bスプライン、Rbf、区分的3次エルミート内挿(pchip)、ベジェ内挿、Chaikin's algorithmsの5手法の比較を行いました。
  • 滑らかさの点でみるとchaikinが良さそうです。
  • 元の点を通る必要がある場合はpchip、bezierのいずれかを使う必要があります。
  • Bsplineが最も汎用的ですがパラメータの調整が必要です。

はじめに

  • 疎な点により形成されるポリラインオブジェクトから滑らかなポリラインオブジェクトをつくります。
  • スムージング方法は次の2つに分類されます。
    • 補間による方法:点間を滑らかなに接続する。
    • 近似による方法:点情報から近似線を作成する。元の点は必ずしも通らない。
  • また、xyグラフでxが昇順に並ぶデータを対象とする数学的な方法、ジオメトリデータのように複雑なデータを対象とする幾何学的な方法がある。この点が、ジオメトリデータの取り扱いの難しいところだと思います。
  • 言語はすべてpythonで書いてます。また、本ページ内の全グラフはいつもどおりHoloviewsを使用しています。

computational-sediment-hyd.hatenablog.jp

サンプルデータ

import numpy as np
import holoviews as hv
hv.extension('bokeh')

points = np.array([[14747743.41590873,  4097001.07511239],
       [14747765.19891978,  4097203.17304826],
       [14747807.5547746 ,  4097423.42349333],
       [14747841.43945846,  4097607.36891999],
       [14747853.54113126,  4097671.50778586],
       [14747857.17163311,  4097778.00250656],
       [14747849.91062942,  4097844.56170699],
       [14747834.17845477,  4097902.64973646],
       [14747733.73457048,  4097953.47676225],
       [14747655.07369724,  4097963.15810049],
       [14747544.9484747 ,  4097935.32425304],
       [14747465.07743418,  4097888.1277291 ],
       [14747357.37254621,  4097788.89401208],
       [14747261.76933104,  4097689.66029507]])

g0 = hv.Scatter(points, label='Original').options(show_grid=True,size=7,width=400,height=400,legend_position='bottom_left')
# hv.save(g0, 'forg.html')

数学的手法

前処理:媒介変数表示

前述のとおり、ジオメトリデータは昇順ではないため、数学的手法を適用するためには、媒介変数tを用いて、x=f(t)y=f(t)と変換する必要がある。

さらに、描画用にtの最小値から最大値までを1000分割した変数tiを準備している。

x, y = points[:,0], points[:,1]
t = np.arange(len(x))
ti = np.linspace(0, t.max(), 1000, endpoint=True)

3次Bスプライン:近似

Bスプライン はスプラインなので数学的手法に分類しましたが幾何でよく使う手法です。 xが昇順でなくても使えます。

Bスプライン は、 scipyの関数splprep,splev を用いて計算しました。

次数kは1から5まで準備されており、3次がデフォルトです。 パラメータsは調整が必要です。

from scipy.interpolate import splprep,splev
tck,u = splprep([x,y],s=0) 
u = np.linspace(0,1,num=1000,endpoint=True) 
spline = splev(u,tck)

fspline = hv.Curve((spline[0], spline[1]), label='3rd Bspline')
gout = g0*fspline
# hv.save(gout,'fspline.html')

Rbf(radial basis function):近似

Rbf はscipyのRbf を使います。

なお、scipyのRbfは廃止予定で、scipy1.7以降ではRBFInterpolatorが準備されています。

近似する関数を設定する必要があります。 scipyでは、'multiquadric'(多重二乗関数)がデフォルトです。 関数によってはepsilonがパラメータになります。パラメータは調整が必要です。

from scipy.interpolate import Rbf

ff = 'multiquadric' #'gaussian' #"'thin_plate'
rbfx = Rbf(t, x, epsilon=10, function=ff)
rbfy = Rbf(t, y, epsilon=10, function=ff)

frbf = hv.Curve((rbfx(ti), rbfy(ti)),label='Rbf')
gout = g0*frbf
# hv.save(gout,'frbf.html')

区分的 3 次エルミート内挿 : Piecewise Cubic Hermite Interpolating Polynomial(pchip)

アルゴリズムが書いているサイトが見つかりませんでした。書籍を探しておきます。 3 次エルミート内挿 はこちら にあります。

区分的 3 次エルミート内挿は、sciypのPchipInterpolatorを使用します。

パラメータは特に無いです。

from scipy.interpolate import PchipInterpolator

pchipx = PchipInterpolator(t, x)
pchipy = PchipInterpolator(t, y)

fpchip = hv.Curve((pchipx(ti), pchipy(ti)),label='pchip')
gout = g0*fpchip
# hv.save(gout,'fpchip.html')

幾何学的手法

ベジェ内挿

ArcGISに実装されており、スムージングの代表的な手法の一つです。 こちら を参照

ベジェ曲線 によって点間を内挿する方法です。

ライブラリは見つかりませんでした。

コーディングが難しいので以下のwebサイトのものをお借りしました。

Bézier Interpolation. Create smooth shapes using Bézier… | by Omar Aflak | Towards Data Science

# find the a & b points
def get_bezier_coef(points):
    # since the formulas work given that we have n+1 points
    # then n must be this:
    n = len(points) - 1

    # build coefficents matrix
    C = 4 * np.identity(n)
    np.fill_diagonal(C[1:], 1)
    np.fill_diagonal(C[:, 1:], 1)
    C[0, 0] = 2
    C[n - 1, n - 1] = 7
    C[n - 1, n - 2] = 2

    # build points vector
    P = [2 * (2 * points[i] + points[i + 1]) for i in range(n)]
    P[0] = points[0] + 2 * points[1]
    P[n - 1] = 8 * points[n - 1] + points[n]

    # solve system, find a & b
    A = np.linalg.solve(C, P)
    B = [0] * n
    for i in range(n - 1):
        B[i] = 2 * points[i + 1] - A[i + 1]
    B[n - 1] = (A[n - 1] + points[n]) / 2

    return A, B

# returns the general Bezier cubic formula given 4 control points
def get_cubic(a, b, c, d):
    return lambda t: np.power(1 - t, 3) * a + 3 * np.power(1 - t, 2) * t * b + 3 * (1 - t) * np.power(t, 2) * c + np.power(t, 3) * d

# return one cubic curve for each consecutive points
def get_bezier_cubic(points):
    A, B = get_bezier_coef(points)
    return [
        get_cubic(points[i], A[i], B[i], points[i + 1])
        for i in range(len(points) - 1)
    ]

# evalute each cubic curve on the range [0, 1] sliced in n points
def evaluate_bezier(points, n):
    curves = get_bezier_cubic(points)
    return np.array([fun(t) for fun in curves for t in np.linspace(0, 1, n)])
# generate 5 (or any number that you want) random points that we want to fit (or set them youreself)
# fit the points with Bezier interpolation
# use 1000 points between each consecutive points to draw the curve
path = evaluate_bezier(points, 1000)
fbezier = hv.Curve(path, label='bezier:3rd')
gout = g0*fbezier
# hv.save(gout,'fbezier.html')

Chaikin's algorithms : 近似

Chaikin's algorithms は多くのGISソフトに実装されている方法です。

シンプルな手法で計算負荷も小さいので大量のジオメトリデータを処理する際には有効です。 こちらもライブラリは見つかりませんでした。

コーディングはそれほど難しくないですが、以下のwebサイトのコードが美しかったのでお借りしました。当然ですが、スライスを使わずにループでも書けます。

[Solved] Where to find Python implementation of Chaikin's corner cutting algorithm? - Local Coder

def chaikins_corner_cutting(coords, refinements=5):
    coords = np.array(coords)

    for _ in range(refinements):
        L = coords.repeat(2, axis=0)
        R = np.empty_like(L)
        R[0] = L[0]
        R[2::2] = L[1:-1:2]
        R[1:-1:2] = L[2::2]
        R[-1] = L[-1]
        coords = L * 0.75 + R * 0.25

    return coords
npoints = chaikins_corner_cutting(points,5)
fchaikin = hv.Curve(npoints, label='chaikin')
gout = g0*fchaikin
# hv.save(gout,'fchaikin.html')

まとめ

  • ポリラインのスムージングについて複数の手法を検討しましたが、大局的にはRbf以外は使えそうです。
  • 滑らかさの点でみると、chaikinが良いですが、元の点を通る必要がある場合はpchip、bezierのいずれかを使う必要があります。
  • Bsplineが最も汎用的ですがパラメータの調整が必要です。
gout = g0*fspline*frbf*fpchip*fbezier*fchaikin
# hv.save(gout,'fall.html')

拡大図

その他参考サイト

GitHub

github.com

python入門書

スポンサーリンク

pythonを勉強したい方は無料講座が使えます。