구간 $[a=t_0, ...t_{n-1}=b]$에서 일정한 간격(꼭 일정한 간격일 필요는 없지만 여기서는 1로 선택함)으로 샘플링된 데이터 $\{ f_0, ...f_{n-1} \}$이 있다. $n-1$ 개의 각 구간에서 데이터를 보간하는 삼차다항식 함수 모임 $$S(t)= \{S_j (t)| t_j \le t <t_{j+1} ,  ~~j=0,1,2...,n-2 \} $$을 찾아보자. 전체 구간에서 연속적이고 충분히 부드럽게 연결되기 위해서는 우선 각 node에서 연속이어야 하고, 또한 1차 도함수와 2차 도함수도 연속이 되어야 한다.  물론 각 node에서는 샘플링된 데이터값을 가져야 한다. 

\begin{align}     (a) ~~& S(t_j) = f_j \\ (b)~~& S_{j+1}(t_{j+1}) = S_j( t_{j+1}) \\ (c)~~& S'_{j+1}(t_{j+1}) = S'_j(t_{j+1}) \\ (d)~~& S''_{j+1}(t_{j+1})  = S''_{j}(t_{j+1}) \end{align}

$n-1$개 구간에서 각각 정의된  3차 함수를 결정하려면 $4 \times (n-1)$개의 조건이 필요하다. (a)에서 $n$개, (b), (c), (d)에서 $3\times (n-2)$개의 조건이 나오므로 총 $n+3\times(n-2)=4n-6$개의 조건만 생긴다. 삼차식을 완전히 결정하기 위해서는 2개의 추가적인 조건을 부여해야 하는데 보통 양끝에서 2차 도함수값을 0으로 하거나(natural boundary) 또는 양끝에서 도함수 값을 특정한 값으로 고정시킨다(clamped boundary). 여기서는 양끝의 2차 도함수를 0으로 한 natural cubic spline만 고려한다. 그리고 $S_j(t)$가 $n-2$개의 구간에 대해서만 정의되어 있지만, 끝점을 포하는 구간에서도 정의하자. 이 경우 $S_{n-1}(t), t\ge t_{n-1}$에 부여된 조건은 $t_{n-1}$에서 $S_{n-2}(t)$와 연속, 미분연속 그리고 2차 도함수가 0인 조건만 부여된다.

 

$j-$번째 구간의 삼차함수를 

$$S_j(t) = a_j + b_j (t - t_j) + c_j (t-t_j)^2 + d_j (t - t_j)^3$$

로 쓰면 (a)에서 

$$ S_j (t_j) = a_j = f_i,~~ j=0,1,..n-2$$

(b)에서 

$$ a_{j+1} =a_j+  b_j + c_j +d_j,~~j=0,1,...,n-2$$

(c)에서 

$$b_{j+1} = b_j + 2c_j+ 3d_j,~~j=0,1,...,n-2 $$

(d)에서 

$$ c_{j+1} = c_j+ 3d_j,~~j=0,1,...,n-2$$

이므로 (b)와 (c)에서 $d_j$을 소거하면

$$  c_j = 3(a_{j+1}-a_j) -b_{j+1} - 2b_j$$

그리고 (a)에서 $$d_j = -2(a_{j+1}-a_j) + b_j  + b_{j+1}$$ 이므로 $b_j$에 대해 정리하면 다음과 같은 점화식을 얻을 수 있다.

$$ b_{j+1} + 4b_j + b_{j-1}= 3(a_{j+1}- a_{j-1})=3(f_{j+1}-f_{j}),~~j=1,2,...,n-2$$

물론 $j=0$일 때는 (note, $S''_0(t_0) = 0 \to c_0=0$) $a_1 =a_0+b_0+d_0, b_1 = b_0 + 3d_0$이므로

$$ b_1 + 2b_0 = 3(f_1 - f_0) $$

$j=n-1$일 때 계수는 $S_{n-1}(t_{n-1}) = f_{n-1}$이고 $S''_{n-1}(t_{n-1} )=c_{n-1}=0$이므로 $$ 2b_{n-1} + b_{n-2} = 3(f_{n-1}-f_{n-2})$$

임을 알 수 있다. 따라서 계수를 구하는 과정은 $b_j~(j=0,...,n-1)$을 구하는 것으로 결정된다. 이를 행렬로 표현하면

$$ \begin{bmatrix} 2&1& \\1&4&1\\ &1&4&1 \\ & & & \cdots \\ &&&&1&4&1  \\ && & & & 1 &2 \end{bmatrix} \begin{bmatrix} b_0\\b_1\\ \vdots \\   b_{n-2} \\b_{n-1}\end{bmatrix}=\begin{bmatrix} 3(f_1-f_0) \\ 3(f_2-f_0) \\  \vdots \\  3(f_{n-1}- f_{n-3}) \\3(f_{n-1} - f_{n-2}) \end{bmatrix}$$

와 같다. band 행렬은 upper triangle로 변환한 후 역치환과정을 거치면 쉽게 해를 구할 수 있다.

 

평면에서 주어진 점들을 보간하는 곡선은 이들 점을 표현하는 곡선의 매개변수를 일정한 간격으로 나누어서 샘플링된 결과로 취급하면, $x, y$ 성분에 대해서 각각 natural cubic spline를 구하여 얻을 수 있다. 

struct Cubic {
    double a,b,c,d;  /* a + b*t + c*t^2 +d*t^3 */
    Cubic(double a_, double b_, double c_, double d_) 
        : a(a_), b(b_), c(c_), d(d_) { }
    /* evaluate;*/
    double eval(double t) {
        return (((d*t) + c)*t + b)*t + a;
    }
};
void calcNaturalCubic(std::vector<double>& x, std::vector<Cubic>& Spline) {
    std::vector<double> gamma(x.size());
    std::vector<double> delta(x.size());
    std::vector<double> D(x.size());
    /* solve the banded equation:
    [2 1       ] [  D[0]]   [3(x[1]   - x[0])  ]
    |1 4 1     | |  D[1]|   |3(x[2]   - x[0])  |
    |  1 4 1   | | .    | = |      .           |
    |    ..... | | .    |   |      .           |
    |     1 4 1| | .    |   |3(x[N-1] - x[N-3])|
    [       1 2] [D[N-1]]   [3(x[N-1] - x[N-2])]
    ** make the banded matrix to an upper triangle;
    ** and then back sustitution. D[i] are the derivatives at the nodes.
    */
    const int n = x.size() - 1;  // note n != x.size()=N;
    // gamma;
    gamma[0] = 0.5;
    for (int i = 1; i < n; i++)
        gamma[i] = 1/(4-gamma[i-1]);
    gamma[n] = 1/(2-gamma[n-1]);
    // delta;
    delta[0] = 3*(x[1]-x[0])*gamma[0];
    for (int i = 1; i < n; i++) 
        delta[i] = (3*(x[i+1]-x[i-1])-delta[i-1])*gamma[i];
    delta[n] = (3*(x[n]-x[n-1])-delta[n-1])*gamma[n];
    // D;
    D[n] = delta[n];
    for (int i = n; i-->0;)
        D[i] = delta[i] - gamma[i]*D[i+1];

    /* compute the coefficients;*/
    Spline.clear(); Spline.reserve(n);
    for (int i = 0; i < n; i++)
        Spline.push_back(Cubic(x[i], D[i], 3*(x[i+1]-x[i])-2*D[i]-D[i+1],
            2*(x[i]-x[i+1]) + D[i] + D[i+1])) ;
}
void NaturalCubicSpline(std::vector<CPoint>& points, 
                        std::vector<CPoint>& curve) {
    curve.clear();
    if (points.size() < 2) return;
    std::vector<double> xp(points.size()), yp(points.size());
    for (int i = points.size(); i-->0;)
        xp[i] = points[i].x, yp[i] = points[i].y;

    std::vector<Cubic> splineX, splineY;
    calcNaturalCubic(xp, splineX);
    calcNaturalCubic(yp, splineY);
#define STEPS 12
    curve.reserve(splineX.size() * STEPS + 1);
    curve.push_back(CPoint(int(splineX[0].eval(0) + 0.5), int(splineY[0].eval(0) + 0.5)));
    for (int i = 0; i < splineX.size(); i++) {
        for (int j = 1; j <= STEPS; j++) {
            double t = double(j) / STEPS;
            curve.push_back(CPoint(int(splineX[i].eval(t) + 0.5), int(splineY[i].eval(t) + 0.5)));
        }
    }
}

 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
728x90

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

Least Squares Bezier Fit  (0) 2024.04.05
Why Cubic Splines?  (9) 2024.03.16
Approximate Distance Between Ellipse and Point  (0) 2024.03.08
Distance from a Point to an Ellipse  (0) 2024.03.06
Data Fitting with B-Spline Curves  (0) 2021.04.30
Posted by helloktk
,

두 개의 점이 주어지는 경우 이들을 잇는 가장 단순한 곡선은 직선이다. 또 직선은 두 점을 잇는 가장 짧은 거리를 갖는 곡선이기도 하다. 그러면 $N$개의 점이 주어지는 경우는 이들 모두 지나는 곡선을 어떻게 찾을까? 구간별로 직선으로 연결을 시켜서 하나의 곡선을 구성할 수 있으나 부드럽게 이어지는 형태의 곡선은 아니다. 곡선이 smooth 함은 그 곡선의 곡률이 잘 정의된다는 의미이다(곡률은 곡선의 이차 미분에 의존한다). 주어진 점들을 연결하는 충분히 짧고 smooth 한 곡선을 찾는 문제는 곡률에 의한 에너지를 최소화시키는 해를 구하는 문제로 환원할 수 있다. 이 문제의 해는 잘 알려진 cubic spline이다. 그러나 cubic spline은 지나는 점(컨트롤 점)의 변동에 대해서 안정적이 아니다. 컨트롤 점 중에서 한 점만 변해도 곡선이 전역적(global)으로 변하는 특성을 나타낸다. 컨트롤 점의 변동이 있을 때 그 점 주위에서만 변화하는 국소 특성을 갖는 곡선을 찾기 위해서는 smoothness 조건을 완화해야 한다. 그렇지만 충분히 부드러운 곡선이어야 하므로 적어도 일차의 미분 값의 연속성 정도는 보장되어야 한다. 이런 특성을 갖는 삼차 곡선이 Catmull-Rom spline이다. 이름은 Edwin Catmull와 Raphie Rom의 이름에서 유래한다.

 

특징: 

          주어진 점들 위에서 정의됨.

          국소적인 변동 특성(노이즈에 안정적).

          일차 미분의 연속성. 

 

두 점 $P_{0}$와 $P_{1}$을 지나는 일반적인 3차의 곡선을 적으면

$$ P(t) = a_{0}  + a_{1}  t + a_{2}  t^{2} + a_{3}  t^{3}$$

이 곡선이 $t=0$에서 $P_{0}$을 지나고, $t=1$에서 $P_{1}$을 지난다고 하더라도, 완전히 결정되지 않는다. 따라서 추가적인 조건을 주어야 하는데, 여기서는 $P_{0}$와 $P_{1}$에서의 미분 값이 주어진다고 가정한다. 즉,

$$P(0) = P_{0},\quad P(1) = P_{1},\quad  P'(0) = P_{0}',\quad P'(1) = P_{1}'$$

이 조건에서 계수들은

$$a_0 = P_0, \quad   a_1 = P_0', \\a_2 = 3(P_1- P_0) - 2P_0' - P_1', \\ a_3 = 2(P_0- P_1) + P_0' + P_1' $$

로 주어진다;

$$P(t)=\left[\begin{array}{cccc}1 & t &t^2& t^3\end {array}\right]\left [\begin {array}{rrrr}1&0&0&0\\0&0&1&0\\-3&3&-2&-1\\2&-2&1&1\end {array}\right]\left [\begin {array}{r} P_0\\P_1\\P_0'\\P_1'\end {array}\right]$$

그러나  미분 값을 직접 정해서 넣는 것은 문제가 있다.

 

4 이상의 점들이 주어지는 경우에는 컨트롤상에서의 미분 값을 그 점 주위의 두 점을 있는 직선의 기울기 값으로 근사 시킬 수 있다:

$$P_i' \longrightarrow (P_{i+1}-P_{i-1})/2$$

이 미분 값을 사용하면 네 개의 컨트롤 점 $\{P_{i-1}, P_i, P_{i+1}, P_{i+2}\}$이 주어진 경우 $P_i (\leftarrow t=0)$와 $P_{i+1}(\leftarrow t=1)$ 사이를 보간하는 곡선은

$$\begin {align} P(t)&=\left [\begin {array}{cccc}1 & t &t^2& t^3\end {array}\right] \left[\begin{array}{rrrr}1&0&0&0\\0&0&1&0\\-3&3&-2&-1\\2&-2&1&1\end{array}\right] \left[\begin{array}{c}P_i\\P_{i+1}\\(P_{i+1}-P_{i-1})/2\\(P_{i+2}-P_{i})/2\end{array}\right]\\ &=\frac{1}{2}\left[\begin{array}{cccc}1 & t &t^2& t^3\end{array}\right] \left [\begin {array}{rrrr}0&2&0&0\\-1&0&1&0\\2&-5&4&-1\\-1&3&-3&1\end {array}\right] \left [\begin {array}{c} P_{i-1}\\P_{i}\\P_{i+1}\\P_{i+2}\end {array}\right] \end {align}$$

로 표현된다. 다시 정리하면

$$ P(t) = \frac{1}{2}\left[ 2P_i + (-P_{i-1}+P_{i+1})t+ (2P_{i-1}-5P_i +4P_{i+1} - P_{i+2})t^2 \\ + (-P_{i-1} + 3P_i -3P_{i+1} +P_{i+2}) t^3 \right]$$

 

열린 곡선으로 보간을 하는 경우 처음과 끝 구간에는 미분 값을 구할 수 없으므로 정의가 안되지만, 나머지 구간에서는 주어진 점들을 연결하는 충분히 부드러운 곡선이 된다. 양 끝 구간의 보간은 동일점을 반복함으로써 전체적인 구간에서 잘 정의되게 만들 수 있다(끝점에서 기울기를 그 점과 인접점과의 기울기/2로 잡는 것과 같은 효과임). 닫힌 곡선인 경우에는 모든 구간에서 잘 정의가 된다. Catmull-Rom spline은 Bezier나 B-Spline 곡선의 경우와 다르게 컨트롤 점의 convex hull 내부에서만 정의되는 특성을 따르지 않는다.

CPoint CRSpline(double t, CPoint p1, CPoint p2, CPoint p3, CPoint p4) {
    double tt = t * t ;
    double ttt = tt * t ;
    double x = 0.5 * ((-p1.x + 3 * p2.x - 3 * p3.x + p4.x) * ttt
        + (2 * p1.x - 5 * p2.x + 4 * p3.x - p4.x) * tt
        + (-p1.x + p3.x) * t
        + 2 * p2.x);
    double y = 0.5 * ((-p1.y + 3 * p2.y - 3 * p3.y + p4.y) * ttt
        + (2 * p1.y - 5 * p2.y + 4 * p3.y - p4.y) * tt
        + (-p1.y + p3.y) * t
        + 2 * p2.y);
    return CPoint(int(x + .5), int(y + .5)) ;
}

//open spline의 drawing;
void DrawCatmullRom(std::vector<CPoint>& Q, CDC* pDC) {  
#define STEPS (20)
    if (Q.size() < 4) return ;
    CPen red(PS_SOLID, 1, RGB(0xFF, 0, 0));
    CPen *pOld = pDC->SelectObject(&red);
    const int n = Q.size();
    for (int i = 0; i < n - 1; i++) {
        pDC->MoveTo(Q[i]);
        // i = 0 인 경우에는 처음 점을 반복, i = n - 2인 경우에는 마지막 점을 반복..
        int ip = max(i - 1, 0);
        int inn = min(i + 2, n - 1);
        for (int it = 1; it <= STEPS; it++)
            pDC->LineTo(CRSpline(double(it)/STEPS, Q[ip], Q[i], Q[i + 1], Q[inn]));
    };
    pDC->SelectObject(pOld);
}

**네이버 블로그 이전;

 
 
 
 
 
 
728x90

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

삼각형 외접원의 Inclusion Test  (0) 2020.12.30
Point in Polygon  (2) 2020.12.14
Incremental Delaunay Triangulation  (1) 2020.12.01
Chain Hull  (2) 2012.09.16
Quick Hull  (2) 2012.09.16
Posted by helloktk
,

3차의 Hermite spline을 쓰는 interpolation에서 입력 데이터 점에서의 기울기 값을 수정하면 입력 데이터의 monotonic 한 성질을 그대로 유지하는 보간을 할 수 있다. 주어진 데이터가 $\{(x_1, y_1), (x_2, y_2), \dots, (x_n, y_n)\}$이고, $x/y$-좌표가 증가하는 순으로 정렬되었다고 하자. 구간 $[x_k, x_{k+1} ]$의 시작과 끝에서 값 ($p_0 = y_k $, $p_1=y_{k+1}$)과 기울기 ($m_0, m_1$) 가 주어지면 Cubic Hermite spline 보간 함수는 다음과 같이 매개변수 $t \in [0,1]$의 함수로 표현된다: 

$$f(t) = p_0 h_{00}(t) + m_1 h_{10}(t) + p_1 h_{01}(t) + m_1 h_{11}(t),            \quad \quad t= \frac {x-x_k}{x_{k+1}-x_k} $$

$$f(0) = p_0,\quad f'(0) = m_0,\quad f(1) = p_1,\quad f'(1)=m_1$$

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

$$ h_{00}(t) = 2t^3 - 3t^2+1, \quad h_{10}(t) = t^3 - 2t^2+1,\quad h_{01}(t)=-2t^3+3t^2,\quad h_{11}(t)=t^3-t^2$$

 

보간된 spline이 단조성(monotonicity)을 유지하도록 각 입력점에서 기울기 값($m_{k}$)을 조절해주어야 한다. 그럼 기울기 $m_{k}$는 어떻게 설정해야 인접 구간의 보간 함수와 smooth 하게 연결될 수 있을까? 입력점의 각 위치에서 기울기는 각각 왼쪽과 오른쪽 인접 입력점과의 평균 변화율($\delta_{k-1}, \delta_{k}$)을 구한 후 다시 이들의 평균값을 기울기로 사용하면 된다. (양 끝점에서는 오른쪽이나 왼쪽 구간의 평균 변화율로 대체)

$$ m_k = \frac {1}{2}(\delta_{k-1}+\delta_{k} )= \frac {1}{2}\left(\frac { y_k-y_{k-1}}{x_k - x_{k-1}}+\frac {y_{k+1}-y_k}{x_{k+1} + x_k}\right)$$

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

$$1. \text{For}~~\delta_{k} = 0 \quad\Longrightarrow\quad m_k =m_{k+1} = 0$$

$$2. \text{For}~~\epsilon=\sqrt {\left(\frac {m_k }{\delta_k }\right)^2 + \left(\frac {m_{k+1}}{\delta_{k} }\right)^2 } > 3\\ \quad\Longrightarrow\quad m_{k} \leftarrow3\frac {m_{k}}{\epsilon}, ~m_{k+1} \leftarrow 3\frac {m_{k+1}}{\epsilon}$$

 

아래의 코드는 위키피디아에 제시된 알고리즘을 구현한 것이다

(입력 데이터가 한 방향으로 monotonic 할 때만 동작함)

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

void estimateTangents(std::vector <double>& x, std::vector <double>& y, std::vector<double>& m);

void monotoneCubicSplineInterp(std::vector<CPoint>& points, std::vector<CPoint>& curve) {
    curve.clear();
    if (points.size() < 2) return ;
    //if (!isMonotonicPolyline(points)) return ;
    std::vector<double> xx(points.size()), yy(points.size());
    for (int i = points.size(); i-->0;) {
        xx[i] = points[i].x ;
        yy[i] = points[i].y;
    };
    //tangent table;
    std::vector<double> m(points.size(), 0);
    estimateTangents(xx, yy, m);
    //hermite spline;
#define STEPS 12
    curve.push_back(points[0]); //add first point;
    for (int i = 0; i < points.size() - 1; i++) {
        double h = xx[i + 1] - xx[i];
        for (int k = 1; k <= STEPS; k++) {
            double t = double(k) / STEPS;         // 0 < t <= 1;
            double t2 = t * t ;
            double t3 = t2 * t ;
            double h00 =  2. * t3 - 3. * t2     + 1.;
            double h10 =       t3 - 2. * t2 + t;
            double h01 = -2. * t3 + 3. * t2;
            double h11 =       t3      - t2;
            double yf = h00 * yy[i] + h10 * h * m[i] + h01 * yy[i + 1] + h11 * h * m[i + 1];
            curve.push_back(CPoint(int(xx[i] + t * h + 0.5), int(yf + 0.5)));
        }
    }
};

Natural cubic spline과의 비교:

 

 

 

 

 

 

 

 
 
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
Posted by helloktk
,