Loading [MathJax]/jax/output/CommonHTML/jax.js

3차의 Hermite spline을 쓰는 interpolation에서 입력 데이터 점에서의 기울기 값을 수정하면 입력 데이터의 monotonic 한 성질을 그대로 유지하는 보간을 할 수 있다. 주어진 데이터가 {(x1,y1),(x2,y2),,(xn,yn)}이고, x/y-좌표가 증가하는 순으로 정렬되었다고 하자. 구간 [xk,xk+1] 시작과 끝에서 값 (p0=yk, p1=yk+1)과 기울기 (m0,m1) 가 주어지면 cubic Hermite spline 보간 함수는 다음과 같이 매개변수 t[0,1]의 함수로 표현된다: 

f(t)=p0h00(t)+m1h10(t)+p1h01(t)+m1h11(t),t=xxkxk+1xk

f(0)=p0,f(0)=m0,f(1)=p1,f(1)=m1

Hermite 기저 함수의 정의는 아래와 같다:

h00(t)=2t33t2+1

h10(t)=t32t2+t

h01(t)=2t3+3t2

h11(t)=t3t2

이를 보이기 위해 3차 보간함수를 

f(t)=a0+a1t+a2t2+a3t3,0t1으로 놓고, constraints를 부여하면

p0=f(0)=a0p1=f(1)=a0+a1+a2+a3m0=f(0)=a1m1=f(1)=a1+2a2+3a3을 얻을 수 있다. 계수 a0,a1,a2,a3에 대해 풀면

f(t)=p0+m0t+(3p13p0m12m0)t2+(2p1+2p0+m0+m1)t3

=p0(2t33t2+1)+p1(2t3+3t2)+m0(t32t2+t)+m1(t3t2) 처럼 표현됨을 알 수 있다.

보간 spline이 단조성(monotonicity)을 가지도록 하려면 각 입력점에서 기울기 값(mk)을 조절해주어야 하는데, 기울기 mk 어떻게 설정해야 인접 구간의 보간 함수와 smooth 하게 연결될 수 있을까? 주어진 입력점에서 기울기는 보통 왼쪽과 오른쪽 인접 입력점에서 각각 평균 변화율(δk1,δk)을 구한 후 이들의 평균을 사용하면 된다. (양 끝점에서는 오른쪽이나 왼쪽 구간의 평균 변화율로 대체)

mk=12(δk1+δk)=12(ykyk1xkxk1+yk+1ykxk+1+xk)

이것만으로는 단조성을 보증을 할 수 없으므로 다음 조건을 추가로 부여해야 됨이 관련 논문에서 증명이 되었다. 

1. For  δk=0mk=mk+1=0

2. For  ϵ=(mkδk)2+(mk+1δk)2>3

mk3mkϵ, mk+13mk+1ϵ

아래의 코드는 위키피디아에 제시된 알고리즘을 구현한 것이다. (입력 데이터가 한 방향으로 monotonic 할 때만 동작함)

참고: http://en.wikipedia.org/wiki/Monotone_cubic_interpolation 

std::vector<double> estimateTangents(const std::vector<CfPt>& P) {
    // slopes;
    std::vector<double> delta(P.size());    
    for (int k = 0; k < delta.size()-1; k++)
        delta[k] = double(P[k+1].y - P[k].y) / (P[k+1].x - P[k].x) ;
        
    // average tangent at data points;
    std::vector<double> m(P.size());
    m.front() = delta.front();
    m.back() = delta[m.size()-2];
    for (int k = 1; k < m.size()-1; k++)
        m[k] = (delta[k-1] + delta[k]) / 2;  //error corrected.
        
    for (int k = 0; k < m.size()-1; k++) {
        if (delta[k] == 0) m[k] = m[k+1] = 0;
        else {
            double alpha = m[k] / delta[k];
            double beta  = m[k+1] / delta[k];
            double epsilon = hypot(alpha, beta);
            if (epsilon > 3) {
                double tau = 3 * delta[k] / epsilon ;
                m[k]   = tau * alpha;
                m[k+1] = tau * beta;
            }
        }
    }
    return m;
};
std::vector<CfPt> monotoneCubicSplineInterp(const std::vector<CfPt>& points) {
    if (points.size() <= 2) return points;
    // tangent table;
    std::vector<double> m = estimateTangents(points);
    // hermite spline;
#define STEPS 12
    std::vector<CfPt> curve;
    curve.push_back(points.front()); //add first point;
    for (int i = 0; i < points.size()-1; i++) {
        double h = points[i+1].x - points[i].x;
        for (int k = 1; k <= STEPS; k++) {
            double t = double(k) / STEPS;         // 0 < t <= 1;
            double tt = t*t, ttt = tt*t;
            double h00 =  2 * ttt - 3 * tt     + 1;
            double h10 =      ttt - 2 * tt + t;
            double h01 = -2 * ttt + 3 * tt;
            double h11 =      ttt     - tt;
            double yf = h00 * points[i].y + h10 * h * m[i] 
                        + h01 * points[i+1].y + h11 * h * m[i+1];
            curve.push_back(CfPt(points[i].x + t * h, yf));
        }
    }
    return curve;
};

Natural cubic spline과의 비교:

https://kipl.tistory.com/569

 

Natural Cubic Spline

구간 [a=t0,...tn1=b]에서 일정한 간격(꼭 일정한 간격일 필요는 없지만 여기서는 1로 선택함)으로 샘플링된 데이터 {f0,...fn1}이 있다. n1 개의 각 구간에서 데이터를 보간하는 삼차다

kipl.tistory.com

 

728x90

'Computational Geometry' 카테고리의 다른 글

Chain Hull  (2) 2012.09.16
Quick Hull  (2) 2012.09.16
Brute Force Convex Hull Algorithm  (0) 2012.09.06
Curvature of a Curve  (0) 2012.08.07
Douglas-Peucker Algorithm  (0) 2012.05.03
,