일정한 간격 h마다 샘플링된 데이터 {(xk,fk)}를 이용해서 이들 데이터를 표현하는 spline를 구해보자. spline은 주어진 샘플링 데이터을 통과할 필요는 없으므로 일반적으로 interpolation 함수는 아니다. 이 spline은 샘플링 데이터와 kernel이라고 불리는 함수의 convolution 형태로 표현할 수 있다.
g(x)=∑kfkK(x−xkh)
이미지의 resampling 과정에서 spline를 이용하는데 이때 사용 가능한 kernel의 형태와 그 효과를 간단히 알아보자.
3차 spline kernel은 중심을 기준으로 반지름이 2인 영역 (−2,1),(−1,0),(0,1),(1,2)에서만 0이 아닌 piecewise 삼차함수다. 그리고 이 함수는 우함수의 특성을 갖는다. 따라서 가능한 형태는
K(s)={A1|s|3+B1|s|2+C1|s|+D1|s|<1A2|s|3+B2|s|2+C2|s|+D21≤|s|<20otherwise처럼 쓸 수 있다. 계수를 완전히 결정하기 위해서는 8개의 조건이 필요한다. 우선 우함수이므로 원점에서 미분값이 제대로 정의되려면 C1=0도 만족해야 한다, 그리고 각 node에서 연속성을 요구하면
s=1±:A1+B1+D1=A2+B2+C2+D2s=2±:8A2+4B2+2C2+D2=0 임을 알 수 있다. 또한 각 node에서 부드럽게 연결되기 위해서 1차 도함수가 연속적임을 요구하면
s=1±:3A1+2B1=3A2+2B2+C2
그리고 샘플링된 데이터가 모두 같은 경우 보간함수도 상수함수가 되는 것이 타당하므로
g(x)=∑kK(x−xkh)=1if∀fj=1
을 만족시켜야 한다. xj<x<xj+1일 때 x=xj+sh,(0<s<1)로 쓸 수 있고, kernel이 반지름이 2인 support를 가지므로
g(x)=K(s+1)+K(s)+K(s−1)+K(s−2)=1
임을 알 수 있다. 위에서 주어진 K(s)을 대입해서 정리하면 다음과 같은 항등식을 얻는다.
따라서 cubic spline kernel은 두 개의 파라미터 (B,C)에 의해서 정해진다. 또한 kernel 함수의 적분은 B,C에 상관없이 항상 1이어서 총 가중치의 합이 1임이 자동으로 보증된다.
∫∞−∞K(s)ds=1
이 중에는 이미지의 resampling에서 많이 사용되는 커널도 있는데, 잘 알려진 경우를 보면
(B,C)=(0,1)Cardinal spline(B,C)=(0,1/2)Catmull-Rom spline (B,C)=(0,3/4)used in photoshop(B,C)=(1/3,1/3) Mitchell-Netravali spline(B,C)=(1,0)B-spline
B=0인 경우는 s=0일 때 1이고, |s|=1,2일 0이므로 interpolation kernel(K(i−j)=δij)에 해당한다. 그리고 B=0,C=1/2인 경우인 Catmul-Rom spline은 node에서 2차 도함수까지도 연속이므로 샘플링 데이터를 생성한 원 아날로그 함수에 O(h3)이내에서 가장 유사하게 근사함을 보일 수도 있다.
// Mitchell Netravali Reconstruction Filter// B = 0 C = 0 - Hermite B-Spline interpolator // B = 0, C = 1/2 - Catmull-Rom spline// B = 1/3, C = 1/3 - Mitchell Netravali spline// B = 1, C = 0 - cubic B-splinedoubleMitchellNetravali(double x, double B, double C){
x = fabs(x);
if (x >= 2) return0;
double xx = x*x;
if (x >= 1) return ((-B - 6*C)*xx*x
+ (6*B + 30*C)*xx + (-12*B - 48*C)*x
+ (8*B + 24*C))/6;
if (x < 1) return ((12 - 9*B - 6*C)*xx*x +
(-18 + 12*B + 6*C) * xx + (6 - 2*B))/6;
}
일반적인 conic section 피팅은 주어진 데이터 {(xi,yi)}를 가장 잘 기술하는 이차식
F(x,y)=ax2+bxy+cy2+dx+ey+f=0
의 계수 uT=(a,b,c,d,e,f)을 찾는 문제이다. 이 conic section이 타원이기 위해서는 2차항의 계수 사이에 다음과 같은 조건을 만족해야 한다.
ellipse constraint:ac−b2/4>0
그리고 얼마나 잘 피팅되었난가에 척도가 필요한데 여기서는 주어진 데이터의 대수적 거리 F(x,y)을 이용하자. 주어진 점이 타원 위의 점이면 이 값은 정확히 0이 된다. 물론 주어진 점에서 타원까지의 거리를 사용할 수도 있으나 이는 훨씬 복잡한 문제가 된다. 따라서 해결해야 하는 문제는
을 최소화시키는 계수 벡터 u를 찾는 것이다. 여기서 제한조건으로 4ac−b2=1=uTCu로 설정했다.
uT에 대해서 미분을 하면
∂L∂uT=Su−λCu=0
즉, 주어진 제한조건 4ac−b2=1하에서 대수적 거리를 최소화시키는 타원방정식의 계수 u를 구하는 문제는 scattering matrix S=DTD에 대한 일반화된 고유값 문제로 환원이 된다.
Su=λCuuTCu=1
이 문제의 풀이는 직전의 포스팅에서 다른 바 있는데 S의 제곱근 행렬 Q=S1/2를 이용하면 된다. 주어진 고유값 λ와 고유벡터 u가 구해지면 대수적 거리는 uTSu=λ
이므로 이를 최소화시키기 위해서는 양의 값을 갖는 고유값 중에 최소에 해당하는 고유벡터를 고르면 된다. 그런데 고유값 λ의 부호별 개수는 C의 고유값 부호별 개수와 동일함을 보일 수 있는데 (Sylverster's law of inertia), C의 고유값이 {−2,−1,2,0,0,0}이므로 λ>0인 고유값은 1개 뿐임을 알 수 있다. 따라서 Su=λCu를 풀어서 얻은 유일한 양의 고유값에 해당하는 고유벡터가 원하는 답이 된다.
// sigmar controls the intensity range that is smoothed out. // Higher values will lead to larger regions being smoothed out. // The sigmar value should be selected with the dynamic range of the image pixel values in mind.// sigmas controls smoothing factor. Higher values will lead to more smoothing.// convolution through using lookup tables.intBilateralFilter(BYTE *image, int width, int height,
double sigmas, double sigmar, int ksize, BYTE* out){
//const double sigmas = 1.7;//const double sigmar = 50.;double sigmas_sq = sigmas * sigmas;
double sigmar_sq = sigmar * sigmar;
//const int ksize = 7;constint hksz = ksize / 2;
ksize = hksz * 2 + 1;
std::vector<double> smooth(width * height, 0);
// LUT for spatial gaussian;std::vector<double> spaceKer(ksize * ksize, 0);
for (int j = -hksz, pos = 0; j <= hksz; j++)
for (int i = -hksz; i <= hksz; i++)
spaceKer[pos++] = exp(- 0.5 * double(i * i + j * j)/ sigmas_sq);
// LUT for image similarity gaussian;double pixelKer[256];
for (int i = 0; i < 256; i++)
pixelKer[i] = exp(- 0.5 * double(i * i) / sigmar_sq);
for (int y = 0, imgpos = 0; y < height; y++) {
int top = y - hksz;
int bot = y + hksz;
for (int x = 0; x < width; x++) {
int left = x - hksz;
int right = x + hksz;
// convolution;double wsum = 0;
double fsum = 0;
int refVal = image[imgpos];
for (int yy = top, kpos = 0; yy <= bot; yy++) {
for (int xx = left; xx <= right; xx++) {
// check whether the kernel rect is inside the image;if ((yy >= 0) && (yy < height) && (xx >= 0) && (xx < width)) {
int curVal = image[yy * width + xx];
int idiff = curVal - refVal;
double weight = spaceKer[kpos] * pixelKer[abs(idiff)];
wsum += weight;
fsum += weight * curVal;
}
kpos++;
}
}
smooth[imgpos++] = fsum / wsum;
}
}
for (int k = smooth.size(); k-- > 0;) {
int a = int(smooth[k]);
out[k] = a < 0 ? 0: a > 255 ? 255: a;
}
return1;
}