TadaoYamaokaの開発日記

個人開発しているスマホアプリや将棋AIの開発ネタを中心に書いていきます。

Pythonでベジエ曲線を描く

Free-Form Deformation(FFD)を使用して2D画像の変形を行おうと試みているが、FFDはベジエ(Bezier)曲面で表される座標系を使用して変形する。
そこで、ベジエ曲線を復習を兼ねてmatplotlibで描画してみる。

ベジエ曲線を2次元に拡張するとベジエ曲面となる。
まずは、単純なベジエ曲線を描画する。

ベジエ曲線の定義

ベジエ曲線は次式で表される曲線である。
\displaystyle
\begin{eqnarray*}
P(t) &=& \sum_{i=0}^{n} B_i^n(t)\mathbf{q_i},\;\;\; 0 \leq t \leq 1 \\
B_i^n(t) &=& {}_nC_i t^i(1 - t)^{n-i}
\end{eqnarray*} \tag{1}

{}_nC_iは二項係数で、\binom{n}{i}と表記する場合もあり、以下の式で展開される。
\displaystyle
{}_nC_i = \binom{n}{i} = \frac{n!}{i!(n-i)!} \tag{2}

B_i^n(t)は、バーンスタイン(Bernstein)多項式と呼ばれる。

\mathbf{q_i}は制御点で、nはベジエ曲線の次数である。
ベジエ曲線は、始点\mathbf{q_0}と終点\mathbf{q_n}を通る。

Pythonのコード例

ベジエ曲線Pythonで記述すると以下のようになる。

import scipy.misc as scm
import numpy as np
import matplotlib.pyplot as plt

def bernstein(n, i, t):
    return scm.comb(n, i) * t**i * (1 - t)**(n-i)

def bezier(n, t, q):
    p = np.zeros(2)
    for i in range(n + 1):
        p += bernstein(n, i, t) * q[i]
    return p

q = np.array([[0, 0], [1, 1], [4, -1], [5, 0]], dtype=np.float)

list = []
for t in np.linspace(0, 1, 100):
    list.append(bezier(3, t, q))
P = np.array(list)

plt.plot(P.T[0], P.T[1])
plt.plot(q.T[0], q.T[1], '--o')
plt.show()

二項係数は、scipy.misc.combを使用して計算している。

上記のコード例では、制御点の次数は3で、制御点(0, 0)、(1, 1)、(4, -1)、(5, 0)によって表されるベジエ曲線を描画している。

なお、list.appendの部分をリスト内包を使ってもう少しスマートに書くと、

P = np.array([bezier(3, t, q) for t in np.linspace(0, 1, 100)])

のように記述できる。

bezier関数でΣの計算にforを使用しているが、以下のようにリスト内包と行列の積を使用しても記述できる。

np.dot([bernstein(n, i, t) for i in range(n + 1)], q)

numpyの行列は、横ベクトルなので、行列の積を計算する際は間違わないように注意が必要である。

実行結果

f:id:TadaoYamaoka:20170307220018p:plain