vliet9504.pdf
다운로드

Retinex 알고리즘은 영상의 밝기와 시각적으로 인지된 감각 사이에는 로그의 관계를 가진다는 사실과, 영상의 밝기는 실제의 밝기인 반사 성분과 조명에 의한 성분의 곱으로 주어진다는 실험적인 사실에 근거하여 영상에서 조명 성분을 줄이고, 반사 성분만을 나타냄으로써 영상의 콘트라스트를 증대시키고자 하는 시도이다

$$B(x, y)=R(x, y)\times I(x, y)$$

where

$$ B(x, y)=\mbox {observed image}$$

$$ R(x, y)=\mbox {perceived reflectance(조명 성분)}$$

$$ I(x, y)=\mbox {perceived illumination(반사 성분)}$$

따라서, 

$$\log R(x, y)= \log B(x, y) - \log I(x, y)$$

조명 성분을 구하기 위해서는 영상의 디테일에 의존하지 않는 큰 스케일에서의 영상 성분만 보면 된다 (low frequency 성분). 따라서 조명 성분은 원영상을 적당한 스케일($s$)로 blurring 한 영상에서 얻을 수 있다.

$$I\sim \text{Gaussfilter}(s)* B.$$

물론, 여러 스케일을 조합을 하여서 사용을 할 수 있다. 멀티 스케일을 적용하는 경우에 retinex 알고리즘에서 반사 성분은 (각 스케일의 기여에 같은 가중치를 준 경우)

$$\text{Dst}(x, y) = \sum_{s\in  \text{scales}} \left(\log [\text{Src}(x, y)] - \log [ (\text{Gaussfilter}(s) * \text{Src})(x, y)] \right)$$

이다. 

 

아래 코드는 출력 영상의 dynamic range를 $[m-1.2\sigma, m+1.2\sigma]$로 한정한 후, 픽셀 값을 $[0.255]$로 stretching 하였다. 픽셀 값에 $\log$를 취할 때는 $\log(0)$을 막기 위해서 $\mbox+1$을 하였다. 컬러 영상은 각각의 RGB 성분에 대해서 그레이 영상의 retinex process을 적용하면 된다.// recursive 가우시안 필터의 계수 계산;

void compute_coefs3(double c[5], double sigma) {
    /*  "Recursive Implementation of the gaussian filter.", 
    **   Ian T. Young , Lucas J. Van Vliet, Signal Processing 44, Elsevier 1995.
    */
    double q;   
    if (sigma >= 2.5)
        q = 0.98711 * sigma - 0.96330;
    else if ((sigma >= 0.5) && (sigma < 2.5))
        q = 3.97156 - 4.14554 * sqrt (1 - 0.26891 * sigma);
    else
        q = 0.1147705018520355224609375;
    
    const double q2 = q * q;
    const double q3 = q * q2;
    c[0] = (1.57825+(2.44413*q)+(1.4281 *q2)+(0.422205*q3));
    c[1] = (        (2.44413*q)+(2.85619*q2)+(1.26661 *q3));
    c[2] = (                   -((1.4281*q2)+(1.26661 *q3)));
    c[3] = (                                 (0.422205*q3));
    c[4] = 1.0 - (c[1]+c[2]+c[3])/c[0];
};

 // Multiscale 설정;

void retinex_scale_distribution(const int nscales/*=3*/, const int s/*=240*/,
                                double scales []) 
{    
    //  ASSERT(nscales>=3);
    const double size_step = double(s) / nscales;
    for (int i = 0; i < nscales; ++i)
        scales[i] = 2. + i * size_step;
}

// recursive Gaussian convolution;

void gauss_smooth (double *in, int size, int rowstride, double *out, double b[5]) {    
    /* forward pass */
    const int bufsize = size + 3;
    std::vector<double> w1(bufsize, 0), w2(bufsize, 0);
    w1[0] = in[0]; w1[1] = in[0]; w1[2] = in[0];
    for (int i = 0, n = 3; i < size ; i++, n++)
        w1[n] = b[4] * in[i*rowstride] + \
               (b[1] * w1[n-1] + b[2] * w1[n-2] + b[3] * w1[n-3]) / b[0];
    /* backward pass */
    w2[size+0] = w1[size+2];
    w2[size+1] = w1[size+2];
    w2[size+2] = w1[size+2];
    for (int i = size - 1, n = i; i >= 0; i--, n--)
        w2[n] = out[i*rowstride] = b[4] * w1[n] + \
                                  (b[1] * w2[n+1] + b[2] * w2[n+2] + b[3] * w2[n+3]) / b[0];
}
 

// 이미지의 평균과 표준편차 계산;

void image_statistics(double *img, int size /*=width*height*/,
                      double &mean, double &std) {    
    double s = 0, ss = 0;
    for (int i = size; i-- > 0;) {
        double a = img[i];
        s += a;
        ss += SQR(a) ;
    } ;
    mean = s / size ;
    std = sqrt((ss - s * s / size) / size);
}
//
void rescale_range(double *data, int size) {
    double mean, sig;
    image_statistics(data, size, mean, sig);
    const double max_val = mean + 1.2 * sig;
    const double min_val = mean - 1.2 * sig;    
    double range = max_val - min_val;
    if (!range) range = 1;
    range = 255 / range ;
    // change the range;
    for (int i = size; i-->0;)
        data[i] = (data[i] - min_val) * range;
}

// 메인;

void retinex_process(double *src, int width, int height, double *dst) {
    const int nfilter = 3;
    const double weight = 1.0 / double(nfilter); //equal-weight for each scale; 
    const int default_scale = 240;
    double sigma[nfilter];
    retinex_scale_distribution(nfilter, default_scale, sigma);
    std::vector<double> in(width*height);
    std::vector<double> out(width*height, 0);
    // scale-space gauss_smooth;
    double c[5];
    for (int i = 0; i < nfilter; i++) {
        compute_coefs3(c, sigma[i]);
        // copy src to temporay buffer(=in);
        for (int pos = width*height; pos-->0; ) 
            in[pos] = double(src[pos]) + 1;
        // (1) horizontal convolution(stride = 1 for grey);
        for (int y = 0; y < height; y++) 
            gauss_smooth(&in[y * width], width, 1, &out[y * width], c);
        // (2) vertical convolution(stride = width for grey);
        in.swap(out);  //copy out to in;
        for (int x = 0; x < width; x++) 
            gauss_smooth(&in[x], height, width, &out[x], c);
        // 각 스케일에서 반사 성분을 누적(equal weight);
        for (int pos = width*height; pos-->0;) 
            dst[pos] += weight * (log(src[pos] + 1) - log(out[pos]));
    }
    const double alpha = 128;
    const double gain = 1;
    const double offset = 0;
    for (int pos = width*height; pos-->0;) {
        double logI = log(double(src[pos]) + 1);
    	dst[pos] = gain * ((log(alpha* (src[pos] + 1.)) - logI) * dst[pos]) + offset;
    }
    // scale to [0,255];
    rescale_range(dst, width*height); 
};

/** 2020.11.24.일 수정됨;**/

원본 이미지;

사용자 삽입 이미지

 

가우시안 Convolution 결과(sigma[0]=2);

 

가우시안 Convolution 결과(sigma[1]=82);

 

가우시안 Convolution 결과(sigma[2]=162);

 

결과 이미지(scale=240)

 

 
 
728x90

'Image Recognition' 카테고리의 다른 글

Mean Shift Filter  (5) 2008.08.06
Chamfer Match  (0) 2008.08.01
RANSAC: Circle Fit  (0) 2008.07.21
KMeans Algorithm  (0) 2008.07.19
Robust Line Fitting  (0) 2008.07.08
,

RANSAC 알고리즘을 써서 주어진 2차원 점집합에서 원을 추정한다. 원을 만들기 위해서는 최소한 3점이 필요하고, 또 일직선에 있지 않아야 한다. 이렇게 만들어진 원은 세 점을 꼭짓점으로 하는 삼각형의 외접원(circumcircle)이다. 주어진 외접원에서 크게 벗어나지 않는 inliers를 찾는다(추가로 이 inliers에 대해 최소자승법으로 원의 중심과 반지름을 다시 구해서 보다 정밀하게 추정하는 과정을 넣을 수도 있다). 무작위로 선택된 세 점에 대해 위의 과정을 반복 시행해서 구한 원 중에서 가장 많은 inliers를 포함하는 원을 결과로 사용한다.

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

red = inliers


// 2024.5.31 재작성;
double dist_deviate(const CfPt& pts, double cparam[3]) {
    double dx = pts.x - cparam[0];
    double dy = pts.y - cparam[1];
    return fabs(hypot(dx, dy) - cparam[2]);
}
int circumcircle(CfPt pts[3], double cparam[3]) {
    double x1 = pts[0].x, x2 = pts[1].x, x3 = pts[2].x;
    double y1 = pts[0].y, y2 = pts[1].y, y3 = pts[2].y;
    double bax = x2 - x1, bay = y2 - y1;
    double cax = x3 - x1, cay = y3 - y1;
    double E = bax * (x1 + x2) + bay * (y1 + y2);
    double F = cax * (x1 + x3) + cay * (y1 + y3);
    double G = 2. * (bax * (y3 - y2) - bay * (x3 - x2));
    if (G == 0.) return 0;    //error;
    //assert(fabs(G)>small_epsilon); //to prevent collinear or degenerate case;
    cparam[0] = (cay * E - bay * F) / G; //cx;
    cparam[1] = (bax * F - cax * E) / G; //cy;
    cparam[2] = hypot(cparam[0]-x1, cparam[1]-y1); //rad;
    return 1;
};
int num_sampling3(double prob_fail, double inlier_ratio) {
    return int(log(prob_fail)/log(1-pow(inlier_ratio, 3))); 
}
std::vector<int> Ransac_CircleFit(std::vector<CfPt>& points, double circle_param[3]) {
    if (points.size() < 3)
        return std::vector<int> (); //return null_vector;

    CfPt center; double inv_scale;
    // normalize input points for the sake of numerical stability;
    std::vector<CfPt> nor_pts = normalize(points, inv_scale, center);
    // distance threshold;
    double distance_thresh = sqrt(double(points.size())) * inv_scale;
    //ransac
    int sample_num = 1000;	//number of sample
    int ransac_count = 0;
    const double prob_fail = 0.01;
    double best_cparam[3] = {0};
    std::vector<int> best_inliers;
    while (sample_num > ransac_count) {
        // pick random 3 indices:[0,points.size()-1];
        int triple[3];
        random_triple(points.size()-1, triple);
        CfPt selected[3];
        for (int i = 0; i < 3; i++) 
            selected[i] = nor_pts[triple[i]];
        // circumcircle of 3 points;
        if (circumcircle(selected, circle_param)) {
            // find inliers;
            std::vector<int> inliers;
            inliers.reserve(points.size());
            for (int i = nor_pts.size(); i-->0;) {
                // error measure = algebric distance;
                double deviation = dist_deviate(nor_pts[i], circle_param);
                if (fabs(deviation) < distance_thresh)
                    inliers.push_back(i);
            }
            if (inliers.size() > best_inliers.size()) {			
                // update sampling_num;
                sample_num = num_sampling3(prob_fail, double(inliers.size())/points.size());
                // update best_inliers;
                best_inliers.swap(inliers);
                // update best circle param;
                for (int i = 0; i < 3; i++) 
                    best_cparam[i] = circle_param[i];
            }
        }
        if (++ransac_count > 1500) {
            TRACE("error! ransac_count exceed!\n");
            break;
        }
    }
    // recover original coordinate and scale;
    denormalize(best_cparam, best_cparam, inv_scale, center);
    if (best_cparam[0] > 0 && best_cparam[1] > 0) {
        for (int i = 0; i < 3; i++)
            circle_param[i] = best_cparam[i];
        TRACE("circle_found(%d, %d)\n", sample_num, ransac_count);
        // more accurate estimation needed at this stage;
    } else 
        best_inliers.clear();
    return best_inliers;
}

https://kipl.tistory.com/207

 

Least Squares Fitting of Circles

점집합을 일반적인 2차 곡선으로 피팅하는 경우에 방정식은$$ a x^2 + by^2 + cxy +d x + ey +f = 0$$의 계수를 주어진 데이터를 이용하여서 구해야 한다. 실제 문제에서는 타원, 포물선 쌍곡 선등의 타입

kipl.tistory.com

https://kipl.tistory.com/357

 

Circle Fitting: Pratt

주어진 점집합을 원으로 피팅하기 위해 이차식$$A(x^2 + y^2) + Bx + Cy +D =0$$을 이용하자. 원의 경우는 $A=0$인 경우는 직선을 나타내고, $A\ne0$인 경우가 원을 표현한다. 물론 $A=1$로 설정을 할 수 있으

kipl.tistory.com

 

728x90

'Image Recognition' 카테고리의 다른 글

Chamfer Match  (0) 2008.08.01
Retinex Algorithm  (2) 2008.07.26
KMeans Algorithm  (0) 2008.07.19
Robust Line Fitting  (0) 2008.07.08
EM: Binarization  (0) 2008.07.01
,

KMeans Algorithm

Image Recognition 2008. 7. 19. 21:03

KMeans 알고리즘은 주어진 데이터들을 사전에 정해진 수만큼의 클러스터로 자동 분류하는 간단한 클러스터링 알고리즘의 일종이다. 이 알고리즘의 주된 아이디어는 각 클러스터의 중심을 어떻게 하면 잘 잡는가이다. 좋은 선택은 각각의 중심이 가능한 서로 멀리 떨어지게 잡아야 한다. 일단 클러스터 중심이 설정되면 각각의 데이터에 대해서 가장 가까이에 있는 클러스터에 할당시키면 된다. 다음으로 클러스터의 중심을 그 클러스터에 속한 데이터의 무게중심으로 재설정하고, 다시 각각의 데이터를 클러스터에 할당하는 작업을 반복한다. 이 과정은 클러스터의 중심이 더 이상 이동하지 않을 때까지 반복 시행하면 된다. 결과적으로 이 알고리즘은 클러스터의 중심에서 그 클러스터에 할당된 데이터와의 거리의 제곱을 그 클러스터의 모든 점과 전체 클러스터에 대해서 합한 값을 최소화시키는 클러스터 중심과 데이터의 분할을 찾는 것이 된다.

1. $k$개의 클러스터의 중심을 설정한다.
2. 설정된 중심을 가지고, 각 데이터를 분할한다.
3. 분할된 데이터를 이용하여서 클러스터의 중심을 재설정한다.
4. 중심이 변화가 없을 때까지 2,3 과정을 반복한다.

비용 함수의 관점에서 보면 KMeans 클러스터링은 다음 함수를

$$ \text{cost}=\sum_{k \in \rm classes} \sum_{i \in \rm class_k} \big| \text{data}_k(i) - \text{mean}_k \big|^2 $$

을 최소화시키는 분할을 하는 것이다. 그러나,  KMeans는 항상 최적의 비용 함숫값을 보장하지는 않는다. 그리고 알고리즘의 결과는 초기값의 설정에 의존하여서 다른 결과를 낼 수가 있다. 보다 큰 단점은 클러스터의 수를 사전에 정해주어야 한다는 사실인데, 잘못된 클러스터 개수의 설정은 성능에 크게 영향을 미친다. 자동의 클러스터의 수를 설정하면서 클러스터링을 할 수 있는 간단한 것으로는 isodata 알고리즘이 있다.

typedef double * vector_t ;
double kmeans_label(
             const vector_t *obs,             // observations;
             const int n_obs,                   /* in # of vectors */            
             const vector_t *mean,           // mean vectors;
             const int n_mean,                /* # of mean vectors */
             int* label,                           // cluster labeling;
             const int veclen)                 // dimension of feature;
{
    double sqerr=0;
    for (int i = 0; i < n_obs; i++) {
        vector_t c = obs[i];
        /* Get an estimate of best distance (min_dist) and (min_idx) */
        int min_idx = label[i];
        vector_t m = mean[min_idx];
        double min_sqdist = 0 ;
        for (int l = 0; l < veclen; l++) {
            double t = m[l] - c[l]; min_sqdist += t * t;
        }
        for (int j = 0; j < n_mean; j++) {
            vector_t m = mean[j];
            // save time by checking:: dist < min_sqdist=previous min_val;
            double sqdist = 0;
            for (l = 0; (l < veclen) && (sqdist < min_sqdist); l++) {
                double t = m[l] - c[l]; sqdist += t * t;
            }
            if (sqdist < min_sqdist) {
                min_sqdist = sqdist; min_idx = j;
            }
        }
        label[i] = min_idx;
        sqerr += min_sqdist;
    }
    return sqerr ;
};
int kmeans_update(
              const vector_t *obs, //observations;
              const int n_obs,       //# of observations;     
              vector_t *mean,       // mean vectors;
              const int n_mean,    // # of mean vectors;
              int *label,               // cluster labelling;
              const int veclen      // dimension of features;
              ) {
    std::vector<int> cnt(n_mean);
    for (int i = 0; i < n_mean; i++)
        for (int l = 0; l < veclen; l++) mean[i][l] = 0.0;
        
    for (i = 0; i < n_obs; i++) {
        ASSERT((0 <= label[i]) && (label[i] < n_mean));  
        vector_t m = mean[label[i]];
        cnt[label[i]]++;  
        vector_t c = obs[i];
        if (c == NULL)
            TRACE("No observations for %u, but expected up through %u", i, n_obs-1);
        for (int l = 0; l < veclen; l++) m[l] += c[l];
    }
    for (i = 0; i < n_mean; i++) {
        int j = cnt[i];
        if (j != 0) for (int l = 0; l < veclen; l++) mean[i][l] /= (double) j;
        else {
            TRACE("Empty cluster %u\n", i);
            return FALSE;
        }
    }
    return TRUE;
}
double kmeans(
       const vector_t *obs,     // observations;
       const int n_obs,          /* # of observations */
       vector_t *mean,           /* initial set of means */
       const int n_mean,        /* # of means (should be k_mean?) */
       int *label,                   /* cluster labeling*/
       const int veclen,         /* vector length of means and corpus */    
       const double min_conv_ratio,
       const int max_iter
    ) {
    double p_sqerr = DBL_MAX;
    int ret;
    double sqerr = kmeans_label( obs, n_obs,mean, n_mean, &label[0], veclen);
    double conv_ratio = (p_sqerr - sqerr) / p_sqerr;
    for (int i = 0; (i < max_iter) && (conv_ratio > min_conv_ratio); i++) {
        TRACE("kmeans iter [%u] %e ...\n", i, conv_ratio);
        ret = kmeans_update(obs, n_obs, mean, n_mean, &label[0], veclen );
        if (ret != TRUE)
            return (double)ret;
        p_sqerr = sqerr;
        sqerr = kmeans_label(  obs, n_obs,mean, n_mean, &label[0], veclen);
        conv_ratio = (p_sqerr - sqerr) / p_sqerr;
    }
    TRACE("kmeans n_iter %u sqerr %e conv_ratio %e\n", i, sqerr, conv_ratio);   
    return sqerr;
}



사용자 삽입 이미지

728x90

'Image Recognition' 카테고리의 다른 글

Retinex Algorithm  (2) 2008.07.26
RANSAC: Circle Fit  (0) 2008.07.21
Robust Line Fitting  (0) 2008.07.08
EM: Binarization  (0) 2008.07.01
EM Algorithm: Line Fitting  (0) 2008.06.29
,