이미지에서 관찰된 점집합이 $\{(x_i, y_i)| i = 1, 2,\dots, N\}$이 있다. 이 점집합을 직선 $y = a + bx$ 로 피팅을 하고 싶을 때, 보통 최소자승법을 이용하는데, 원리는 직선의 방정식이 예측한 $y$값과 실제 관찰한 $y$값의 차이의 제곱(=square deviation)을 최소화시키는 직선의 기울기 $a$와 절편 $b$를 찾는 것이다:
$$\chi^2(a, b) = \sum_i |y_i - (b x_i +a) |^2 $$
데이터를 얻는 측정 과정에서 측정값 $y_i$는 랜덤 노이즈를 포함하게 되고, 이 노이즈는 참값 $y(x)$ 근방에서 정규분포를 한다고 가정을 할 수 있다. 만약 모든 측정의 노이즈가 동일한 표준편차 $\sigma$를 갖게 된다면, $N$개의 관측 데이터가 나타날 확률(likelihood)은 (개별 측정은 IID 조건을 만족한다고 전제)
$$P = \prod_{i} e^{ -\frac{ | y_i - (bx_i + a)|^2 }{2\sigma^2 } }$$
의 형태가 된다. 따라서 최소자승법은 이 likelihood를 최대화시키는 파라미터를 찾는 방법이 된다. 최소자승법은 피팅 파라미터를 주어진 데이터를 이용해서 표현할 수 있는 장점은 있지만, outliers에 대해서 매우 취약하다 (아래의 결과 그림을 참조). 이는 적은 수의 outlier도 χ2에 큰 기여를 할 수 있기 때문이다. 따라서 피팅을 좀 더 robust 하게 만들기 위해서는 outliers가 likelihood에 기여하는 정도를 줄여야 한다. 이를 위해서는 likelihood의 지수 인자를 큰 에러에서 덜 민감하게 반응하는 꼴로 바뀌어야 한다. 이를 만족하는 가장 간단한 것 방법 중 하나가 square-deviation 대신에 absolute-deviation을 이용하는 것이다:
$$\text{absolute deviation} = \sum _i | y_i - (bx_i + a)| .$$
그러나 이 식을 사용하는 경우에는 최소자승법과 다르게 기울기 $a$와 절편 $b$를 주어진 데이터 $\{(x_i, y_i)\}$로 표현할 수 없고, 반복적인 방법을 이용해서 찾아야 한다.
수열 $\{c_i\}$에 대해 합 $\sum_{i} |c_i - a|$은 $a$가 수열의 median 값일 때 최솟값을 갖는다는 사실을 이용하면 (증명: 극값을 구하기 위해서 $a$에 대해서 미분하면, $0=(\sum_{c_i > a} 1)-(\sum_{c_i < a} 1)$: 합은 $a$가 $c_i$ 보다 큰 경우와 작은 경우로 분리. 따라서 0이 되기 위해서는 작은 경우와 큰 경우의 수가 같아야 한다. 고로, $a = \text{median}\{c_i\}$ q.e.d.). 고정된 절편 $b$에 대해서 absolute deviation을 최소로 만드는 기울기 $a$는
$$a= \text{median} \{ y_i - b x_i\}$$
임을 알 수 있다. 그리고 absolute deviation 식을 절편 $b$에 대해서 미분해서
$$0 = \sum_i \text{sign} \left( y_i - (bx_i +a) \right)$$
을 얻는데, 위에서 구한 기울기 $a$를 대입한 후 bracketing and bisection 방법을 이용하면 절편 $b$를 얻을 수 있다(불연속 함수이므로 일반적으로 근을 구하는 방법을 사용하는 것은 위험하다). 아래의 코드는 이를 구현한 것이다.
double FitLine_LS(std::vector<double>& x, std::vector<double>& y, double *a, double *b);
// 최소자승법을 이용한 직선 추정:
// return (sigma[dy] / sigma[x]);
double FitLine_LS(std::vector<double>& x, std::vector<double>& y, double& a, double& b) {
double sx = 0, sy = 0, sxx = 0, sxy = 0;
for (int i = x.size(); i-->0;) {
sx += x[i]; sy += y[i];
sxx += x[i] * x[i]; sxy += x[i] * y[i];
};
const int n = x.size();
double det = n * sxx - sx * sx;
if (det == 0.) return -1; // vertical line;
a = (sxx * sy - sx * sxy) / det;
b = (n * sxy - sx * sy) / det;
double chi2 = 0;
for (int i = x.size(); i-->0;) {
double t = y[i] - (*a + *b * x[i]);
chi2 += t * t;
}
det /= n; //det -> var(x) * n;
// chi2 = var(dy) * n;
// (dy vs x의 편차비)
return sqrt(chi2 / det);
}
// 기울기(bb)가 주어진 경우에 y-절편(median = aa)값을 추정하고, 이 때 AD값을 얻는다.
double RhoFunc(std::vector<double>& x, std::vector<double>& y,
double bb, double& aa, double& abdev) {
std::vector<double> h(x.size());
for (int i = x.size(); i-->0;) h[i] = y[i] - bb * x[i];
std::sort(h.begin(), h.end());
// median;
const int med = h.size()/2;
aa = (h.size() & 1) ? h[med] : (h[med] + h[med-1])/2;
double sum = 0;
abdev = 0;
for (int i = x.size(); i-->0;) {
double d = y[i] - (bb * x[i] + aa);
abdev += fabs(d);
// sign-함수의 원점에서 모호함을 없애기 위해서 증폭을 시킴;
if (y[i] != 0.) d /= fabs(y[i]);
if (fabs(d) > DBL_EPSILON) // sign 함수의 모호함에서 벗어나면,
sum += (d >= 0 ? x[i] : -x[i]);
}
return sum; // return sum{xi * sign(yi - (b * xi + a))}
};
// y = a + b * x ;
// Least Absolute Deviation:
double FitLine_MAD (std::vector<double>& x, std::vector<double>& y,
double& a, double& b) {
// least square esimates for (aa, bb);
double aa, bb, abdev;
double sigb = FitLine_LS(x, y, aa, bb); // estimate: y=aa + bb*x;
double b1 = bb;
double f1 = RhoFunc(x, y, b1, aa, abdev);
/* bracket 3-sigma away in the downhill direction;*/
double b2 = fabs(3 * sigb);
b2 = bb + (f1 < 0 ? -b2 : b2);
double f2 = RhoFunc(x, y, b2, aa, abdev);
// if conditional added to take care of the case of a
// line input into this function. It is needed to avoid an
// infinite loop when (b1 == b2) (within floating point error)
if (fabs(b2 - b1) > (sigb + 0.005)) {
// bracketing;
while ((f1 * f2) > 0) {
bb = 2 * b2 - b1;
b1 = b2; b2 = bb;
f1 = f2; f2 = RhoFunc(x, y, b2, aa, abdev) ;
}
}
// refine until the error is a negligible number of std;
sigb *= 0.01;
while (fabs(b2 - b1)> sigb) {
// bisection;
bb = (b1 + b2) / 2.;
if ((bb == b1) || (bb == b2)) break ;
double f = RhoFunc(x, y, bb, aa, abdev) ;
if ((f * f1) >= 0) {
f1 = f; b1 = bb;
} else {
f2 = f; b2 = bb;
}
}
a = aa; b = bb;
return (abdev/x.size());
}
// 붉은 선--> 최소자승법에 의한 피팅.: outlier에 매우 취약한 구조.
// 파란 선--> least absolute deviation을 이용한 피팅: outlier에 매우 robust 하다.
'Image Recognition' 카테고리의 다른 글
RANSAC: Circle Fit (0) | 2008.07.21 |
---|---|
KMeans Algorithm (0) | 2008.07.19 |
EM: Binarization (0) | 2008.07.01 |
EM Algorithm: Line Fitting (0) | 2008.06.29 |
Gaussian Mixture Model (2) | 2008.06.07 |