B−スプライン関数

自由曲線を描きたいことは、コンピュータで図形やグラフを扱う場合に結構ある。
こんな中で、理解し易く扱い易いのがB−スプラインだ。
利用するだけなら、そのアルゴリズムは容易に理解できると思う。
B−スプラインは、幾つかの制御点を定義してやることで、滑らかな一本の曲線を
得ることができる。ただし、曲線は始点と終点以外の制御点は通らない。
また、制御点が影響する範囲が限定されているため制御点1点を動かした影響が
全体にまで及ぶ事はない。
補間曲線を得たい場合、連立方程式を解いて、通過点を制御点に変換すれば
B−スプラインによる補間も可能である。
ここでは、自分のプログラムにB−スプラインを組み込みたいが、
数式を見ても概念が分からないという人のために原理解説する。
プログラマーには、数式を見るよりプログラムのソースリストを読んだ方が
原理を理解するのが早いという人もあると思う。
そこで、構造が見易いシンプルな動作確認済みのC言語ソース・リストを掲載した。
尚、ここでは実用的な3次に限定して説明する。

命題

M個の制御点P0,P1,・・・,Pn (n=M−1とする)
が与えられた時、これらの制御点で作られる3次B−スプライン曲線を計算せよ。
尚、3次B−スプラインの性質から、媒介変数の範囲は、(−1≦t≦n+1)とする。

パラメトリック表現

 一般的にX−Y平面にグラフを描く場合、y=f(x)という
 関数を使ってグラフを作図することが多い。
 f(x)は任意の関数で例えば、x*x(xの2乗)ならば
 放物線を描く事が出来る。
 しかし、関数は1つのxに対して複数のy(解)は持たないため
 これでは円の様な図形は描けない。
 そこで、パラメトリック表現という手法を使う。
 xとyの直接的な関係式ではなく、別の変数(媒介変数)を
 定義して、その変数とx、その変数とyの関係を独立して定義する。
 例えば、円を描くとしたら、角度θを媒介変数として、θを0度から360度
 (ラジアンでは0〜2π)まで変化させた時、x座標とy座表がそれぞれ
 どう変化するかを個別の関数で定義してやる。
 単位円を描くなら
 x=cos(θ)
 y=sin(θ)
 とすればいい。
 B−スプラインもこの表現で定義される。

3次B−スプライン関数の定義

 数式で書くと以下の様になる。



 ただし、P0とPnを必ず通る様にするため、
 P−2=P0,P−1=P0,Pn+1=Pn,Pn+2=Pnと定義する。
 2次元平面の座標値を得る関数は、これを2組用意して以下の様になる。



重み関数

 B−スプライン曲線は、幾つかの制御点の座標値に重みづけの係数を
 掛けてブレンドするという、補間に似た考え方で曲線を得ている。
 この係数を重み関数で得る訳だが、この関数は次数が決まれば
 一定のものである。3次では次の様になる。



 グラフで描くと以下の様な滑らかな曲線となる。


媒介変数と制御点の関係

 重み関数に与える関数の入力がt−jとなっているが、少し理解しづらい。
 これは媒介変数と制御点(のインデックス)を同じ座標軸で扱う事を意味する。
 制御点をある座標軸に整数間隔で並べて、媒介変数はその間の任意の値を取る。
 下のグラフでは、tを移動させるとtを頂点とした重み関数の山が移動することを示す。



 この山にかかった制御点に関して、その座標値にその制御点における重み係数を
 掛けてその結果を加算したものがtにおけるB−スプライン関数で得られる座標値となる。
 ある制御点における重み係数を得るには、そのインデックスであるjを
 数値としてtから引いて重み関数に渡せばいい訳だ。

その他、3次B−スプライン関数の性質

 重み関数の次数は、制御点の数とは関係ない。
 パラメトリック表現で規定されているので、2次元平面を3次元空間に
 拡張するのは容易で、x,yと同様にz(t)を定義してやればいい。

JAVAアプレット・ソース [BSpline.java]

以下の様な図形を描画する。(プログラムはラインのみ)



import java.applet.Applet; 
import java.awt.*; 
import java.lang.Math; 

public class BSpline extends Applet { 

	static final int M = 5;
	static int	n = M - 1;
	static double px[]={
		10.0,
		77.0,
		21.0,
		171.0,
		153.0
	};
	static double py[]={
		30.0,
		49.0,
		165.0,
		43.0,
		164.0
	};

	public void start() { 
	}
	public void BSpline() {
		repaint();
	}
	public void update(Graphics g) {
		paint(g);
	}
	public boolean handleEvent(Event event) {
		return true;
	}
	public void paint( Graphics g ) {
		double t,step;

		g.setColor(Color.white);
		g.fillRect(0,0,200,200);
		g.setColor(Color.blue);
// Spline Draw main
		step = (n + 1) * 0.01;
		for(t = -1.0 ; t <= n + 1 ; t += step) {
			DrawSpline(g,t);
		}
	}

	public double Coefficent(double t) {
		double	r,d;

		if(t < 0.0) t = -t;
		if(t < 1.0) {
			r = (3.0 * t * t * t -6.0 * t * t + 4.0) / 6.0;
		}
		else if(t < 2.0) {
			d = t - 2.0;
			r = -d * d * d / 6.0;
		}
		else r = 0.0;

		return r;
	}

	public void DrawSpline(Graphics g, double t) {
		double cn,x,y;
		int j,k;
		int ix,iy;

		x = 0.0;
		y = 0.0;
		for(j = -2 ; j <= n + 2 ; j++) {
			k = j;
			if(j < 0) k = 0;
			if(j > n) k = n;
			cn = Coefficent(t - j);
			x += px[k] * cn;
			y += py[k] * cn;
		}
// Plot Pixel
		ix=(int)x;
		iy=(int)y;
		g.drawLine(ix,iy,ix,iy);
	}
}

参考文献

共立出版株式会社 グラフィックスの数理(情報数学講座13)
杉原厚吉著
ISBN4-320-02663-2 C3341 \3200E

フューチャー・ホームページへ戻る

(C)1999,2002 Future on netyou ALL RIGHTS RESERVED.