mieki256's diary



2024/04/20() [n年前の日記]

#1 [prog][python] scipyでスプライン曲線を作成できるか実験

Python の scipy ライブラリを使うとスプライン曲線が作れるらしいと知り、そのあたりを実験中。

環境は Windows10 x64 22H2 + Python 3.10.10 64bit + scipy 1.13.0 + numpy 1.26.4 + matplotlib 3.8.4。

interpolate.splprep()が良さそう :

一般的に、scipy を使ってスプライン補間をする場合は、interpolate.interp1d() を使うようだけど、x の増分方向が同じ方向じゃないと使えないらしくて悩んでしまった。自分が処理したいデータは、平面上であっちに行ったりこっちに行ったりするデータなので…。

以下のページで、interpolate.splprep() ならそういうデータも処理できそうと知った。また、interpolate.interp1d() も、x と y を別々に処理すればどうにかできるらしい。

_pythonでxy座標上の離散点をスプライン補間 #Python - Qiita
_スプライン曲線と補間 - Emotion Explorer

上記のページのサンプルを参考にして、以下のデータを処理してみた。

_housakatouge.csv
_motosuko.csv

Pythonスクリプトは以下。

_draw_spline.py
import sys
import numpy as np
from scipy import interpolate
import matplotlib.pyplot as plt


# S = 10
S = 20


def loadData(infile):
    ax = []
    ay = []
    with open(infile, "r") as file:
        for line in file:
            line = line.rstrip()
            x, y = line.split(",")
            x = float(x)
            y = float(y)
            ax.append(x)
            ay.append(y)
    return ax, ay


# B-Spline 補間
def spline1(x, y):
    tck, u = interpolate.splprep([x, y], k=3, s=0)
    u = np.linspace(0, 1, num=len(x) * S, endpoint=True)
    spline = interpolate.splev(u, tck)
    return spline[0], spline[1]


# interp1dスプライン補間による曲線
def spline3(x, y):
    t = np.linspace(0, 1, len(x), endpoint=True)
    fx = interpolate.interp1d(t, x, kind="cubic")
    fy = interpolate.interp1d(t, y, kind="cubic")
    ta = np.linspace(0, 1, len(x) * S, endpoint=True)
    return fx(ta), fy(ta)


if len(sys.argv) < 2:
    sys.exit()

infile = sys.argv[1]
x, y = loadData(infile)

# B-Spline interpolation
bx, by = spline1(x, y)

# interp1d spline
ax, ay = spline3(x, y)

plt.plot(x, y, "ro", label="Data")
# plt.plot(bx, by, "b.", label="B-Spline interpolation")
plt.plot(bx, by, color="blue", label="B-Spline interpolation")
plt.plot(ax, ay, color="green", label="interp1d")

plt.legend(loc="best")
plt.savefig("output_graph.png")
plt.show()

python draw_spline.py motosuko.csv

draw_spline_py_ss_001.png

draw_spline_py_ss_002.png

なかなか興味深い(?)結果になった気がする。

まず、連続した直線データで構成されている道路データを読み込んで、スプライン曲線として描画することはできている。

ただ、interpolate.interp1d() で処理すると、不自然な曲線になってしまう箇所が、いくつも出てくるようだなと…。これが interpolate.splprep() なら、そこまでおかしな状態にはならない。これは後者を使って処理したほうが良さそうだなと…。

余談。matplotlib を使ってグラフを描画しているけれど、ツールバーに拡大ツールやPANツールがあって、これは便利だなと感心してしまった。
  • 虫眼鏡のアイコンをクリックして、キャンバス(?)上でマウスドラッグをして範囲を指定すると、その範囲が拡大表示される。
  • 上下左右の矢印っぽいアイコンをクリックすると、キャンバス上のマウスドラッグで表示位置を変更できる。
  • 家のアイコンをクリックすると、全体が収まる表示になる。

スプライン曲線化したデータを出力 :

スプライン曲線を描画することはできたけど、座標値として出力するにはどうしたらいいのか…。

以下のような感じだろうか。

_dump_spline.py
import argparse
import sys
import numpy as np
from scipy import interpolate


# load csv file
def loadData(infile):
    ax = []
    ay = []
    with open(infile, "r") as file:
        for line in file:
            line = line.rstrip()
            x, y = line.split(",")
            x = float(x)
            y = float(y)
            ax.append(x)
            ay.append(y)
    return ax, ay


# B-Spline
def spline1(x, y, s):
    tck, u = interpolate.splprep([x, y], k=3, s=0)
    u = np.linspace(0, 1, num=len(x) * s, endpoint=True)
    spline = interpolate.splev(u, tck)
    return spline[0], spline[1]


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("-i", "--input", required=True, help="Input csv file")
    parser.add_argument("-n", "--num", type=int, default=10, help="number")
    args = parser.parse_args()

    if args.input is None:
        sys.exit()

    x, y = loadData(args.input)
    bx, by = spline1(x, y, args.num)

    for i in range(len(bx)):
        print(f"{bx[i]},{by[i]}")


if __name__ == "__main__":
    main()

python -i dump_spline.py -n 10 motosuko.csv > motosuko_spline.csv

これで、スプライン曲線化したデータを取得することができた。

_housakatouge_spline.csv
_motosuko_spline.csv

しかし、このデータは、本当にスプライン曲線になっているのだろうか…? 表示して確認してみないと…。

matplotlibで表示確認 :

先日は Python + pygame でデータを描画して確認したけれど、matplotlib を使って描画したほうが、拡大表示や表示位置変更ができて便利だろうと思えてきた。

以下のような感じだろうか。

_draw_line_plot.py
import sys
import matplotlib.pyplot as plt


def loadData(infile):
    ax = []
    ay = []
    with open(infile, "r") as file:
        for line in file:
            line = line.rstrip()
            x, y = line.split(",")
            x = float(x)
            y = float(y)
            ax.append(x)
            ay.append(y)
    return ax, ay


if len(sys.argv) < 2:
    sys.exit()

infile = sys.argv[1]
x, y = loadData(infile)

plt.plot(x, y, color="cyan", label="Data (line)")
plt.plot(x, y, "r.", label="Data (point)")

plt.legend(loc="best")
# plt.savefig("output_graph.png")
plt.show()

python draw_line_plot.py motosuko_spline.csv

draw_line_plot_py_ss_001.png

draw_line_plot_py_ss_002.png

イイ感じかもしれない。

このデータを元にして、道路幅を加えて、ポリゴン描画すればそれらしくなるのではないかな…。

以上です。

過去ログ表示

Prev - 2024/04 - Next
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project