Statistical region merging은 이미지의 픽셀을 일정한 기준에 따라 더 큰 영역으로 합병하는 bottom-up 방식의 과정이다. 두 영역 $R_1$과 $R_2$가 하나의 영역으로 합병이 되기 위해서는 두 영역의 평균 픽셀 값의 차이가 

$$ g \sqrt{ \frac {\ln(2/\delta)}{2Q}  \Big( \frac {1}{|R_1|}+ \frac {1}{|R_2|}\Big) }$$

를 넘지 않아야 한다. $g=256$으로  gray 레벨의 갯수를 의미하고, $|R_i|$는 $R_i$ 영역에 포함된 픽셀 수를 나타낸다. $\delta$는 작은 수로 이미지의 픽셀 수의 제곱에 반비례한다. 보통 $\delta = 1/(6\times \text {width}\times \text {height})^2$로 선택한다. $Q$는 이미지의 통계적인 복잡성을 정량화하는 양으로 이 알고리즘에서는 외부에서 설정이 되는 값이다. 낮은 $Q$값을 선택하면 분할된 영역의 수가 작아지고(undersegmentation), 반대로 높은 $Q$ 값을 입력하면 분할된 영상에 너무 많은 영역이 나타나게 된다(oversegmentation).

Ref:

https://en.wikipedia.org/wiki/Statistical_region_merging

http://www.lix.polytechnique.fr/~nielsen/Srmjava.java

 

Srm::Srm(int width, int height, BYTE *image) {
    this->width  = width;
    this->height = height;
    int n        = width * height;
    this->count = new int [n];
    this->Ravg  = new float [n];
    this->Gavg  = new float [n];
    this->Bavg  = new float [n];
    this->image = image;
    // disjoint sets with n pixels;
    this->UF = new Universe(n);
    // initialize to each pixel a leaf region;
    for (int i = 0, pos = 0; i < n; i++, pos += 3) {
        count[i] = 1;
        Bavg[i] = image[pos    ];
        Gavg[i] = image[pos + 1];
        Ravg[i] = image[pos + 2];
    }
    this->Q = 32;		// adjustable.					    
    this->g = 256.0;
    this->logDelta = 2. * log(6.0 * n);
}
bool Srm::Predicate(int rgn1, int rgn2) {
    double dR = (Ravg[rgn1] - Ravg[rgn2]); dR *= dR;
    double dG = (Gavg[rgn1] - Gavg[rgn2]); dG *= dG;
    double dB = (Bavg[rgn1] - Bavg[rgn2]); dB *= dB;
    double logreg1 = min(g, count[rgn1]) * log(1.0 + count[rgn1]);
    double logreg2 = min(g, count[rgn2]) * log(1.0 + count[rgn2]);
    double factor = g * g / (2.0 * Q);
    double dev1 = factor * (logreg1 + logDelta) / count[rgn1] ;
    double dev2 = factor * (logreg2 + logDelta) / count[rgn2] ;
    double dev = dev1 + dev2;
    return ( (dR < dev) && (dG < dev) && (dB < dev) );
}
void Srm::Merge(int rgn1, int rgn2) {
    if (rgn1 == root2) return;
    int w1 = count[rgn1], w2 = count[rgn2];
    int root = UF->Union(rgn1, rgn2);
    //update the merged region;
    count[root] = w1 + w2;
    double count_sum = w1 + w2;
    Ravg[root] = (w1 * Ravg[rgn1] + w2 * Ravg[rgn2]) / count_sum;
    Gavg[root] = (w1 * Gavg[rgn1] + w2 * Gavg[rgn2]) / count_sum;
    Bavg[root] = (w1 * Bavg[rgn1] + w2 * Bavg[rgn2]) / count_sum;
}
Edge* Srm::Pairs(int nedge) {
    // 4-connectivity;
    int ymax = height - 1, xmax = width - 1;
    Edge* edgeList = new Edge[nedge];
    int cnt = 0;
    for (int y = 0; y < ymax; y++) {
        for (int x = 0; x < xmax; x++) {
            int pos = y * width + x;
            int b1 = image[3 * pos + 0];
            int g1 = image[3 * pos + 1];
            int r1 = image[3 * pos + 2];
            //right: x--x
            edgeList[cnt].r1 = pos;     //current
            edgeList[cnt].r2 = pos + 1; //right
            int bdiff = abs(b1 - image[3 * (pos + 1) + 0]);
            int gdiff = abs(g1 - image[3 * (pos + 1) + 1]);
            int rdiff = abs(r1 - image[3 * (pos + 1) + 2]);
            edgeList[cnt++].diff = max3(bdiff, gdiff, rdiff) ;
            //below: x
            //       |
            //       x
            edgeList[cnt].r1 = pos;
            edgeList[cnt].r2 = pos + width;
            bdiff = abs(b1 - image[3 * (pos + width) + 0]);
            gdiff = abs(g1 - image[3 * (pos + width) + 1]);
            rdiff = abs(r1 - image[3 * (pos + width) + 2]);
            edgeList[cnt++].diff = max3(bdiff, gdiff, rdiff);
        }
    }
    //x=width-1;
    for (int y = 0; y < ymax; y++) {
        int pos = y * width + (width - 1); // (x,y) = (width-1, y)
        // x
        // |
        // x
        edgeList[cnt].r1 = pos;
        edgeList[cnt].r2 = pos + width;
        int bdiff = abs((int)image[3 * pos + 0] - image[3 * (pos + width) + 0]);
        int gdiff = abs((int)image[3 * pos + 1] - image[3 * (pos + width) + 1]);
        int rdiff = abs((int)image[3 * pos + 2] - image[3 * (pos + width) + 2]);
        edgeList[cnt++].diff = max3(bdiff, gdiff, rdiff);
    }
    //y=height-1;
    for (int x = 0; x < xmax; x++) {
        int pos = (height - 1) * width + x;      //(x,y)=(x, height-1);
        //right; x--x
        edgeList[cnt].r1 = pos;
        edgeList[cnt].r2 = pos + 1;
        int bdiff = abs((int)image[3 * pos + 0] - image[3 * (pos + 1) + 0]);
        int gdiff = abs((int)image[3 * pos + 1] - image[3 * (pos + 1) + 1]);
        int rdiff = abs((int)image[3 * pos + 2] - image[3 * (pos + 1) + 2]);
        edgeList[cnt++].diff = max3(bdiff, gdiff, rdiff);
    }
    return edgeList;
}
int Srm::Segment() {
    // 4-connectivity 
    int nedge = 2 * (width - 1) * (height - 1) + (height - 1) + (width - 1);
    Edge* edgeList = Pairs(nedge);
    BucketSort(edgeList, nedge);
    for (int i = 0; i < nedge; i++) {
        Edge &e = edgeList[i];
        int r1 = UF->Find(e.r1);
        int r2 = UF->Find(e.r2);
        if ((r1 != r2) && (Predicate(r1, r2)))
            Merge(r1, r2);
    }
    delete [] edgeList;
    int rgn_count = 0;
    for (int node = width * height; node-- > 0;)
        if (UF->IsRoot(node)) rgn_count++;
    return rgn_count;
}
// sorting with buckets; returns an ordered edgeList;
void BucketSort(Edge* &edgeList, int n) {
    int hist[256] = {0}, chist[256];
    for (int i = 0; i < n; i++) hist[edgeList[i].diff]++;
    // cumulative histogram
    chist[0] = 0;  // Note, chist[0] ne hist[0];
    for (int i = 1; i < 256; i++)
        chist[i] = chist[i - 1] + hist[i - 1];

    Edge *ordered = new Edge [n];
    for (int i = 0; i < n; i++)
        ordered[chist[pair[i].diff]++] = pair[i];        
    delete[] edgeList;
    edgeList = ordered;
}
728x90

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

영상에 Impulse Noise 넣기  (2) 2023.02.09
Canny Edge: Non-maximal suppression  (0) 2023.01.11
Moment-preserving Thresholding  (0) 2022.05.29
Minimum Cross Entropy Thresholding  (0) 2022.05.29
Quadtree Segmentation  (0) 2022.05.21
Posted by helloktk
,

Union-Find 알고리즘을 이용하여 영역 분할 과정이다. 각각의 픽셀에서 4방향으로 연결된 픽셀이 속한 영역 merge 할 수 있는지를 시도한다. merge 조건은 현재 픽셀의 그레이 값과 인접 영역의 평균 그레이 값의 차이가 주어진 임계값보다 작은 경우다.

$$\tt \text{merge 조건: }\quad | 그레이 값- \text{인접 영역 평균값}| < threshold$$

컬러 영상의 경우에는 RGB 채널에 조건을 부여하거나 gray level만 이용해서 판단할 수 있다. root node 수만큼의 분할 영역이 만들어지고, 임계값이 클수록 분할된 각 영역의 사이즈가 커진다.  

gray = 0.2989 * r + 0.5870 * g + 0.1140 * b ;

같은 평균 픽셀값을 가지고 있더라도 4-방향으로 서로 연결된 영역이 아니면 합병되지 못하고 서로 다른 영역으로 남는다. 이를 하나의 영역으로 만들려면 분할된 영역을 다시 검사를 하는 과정이 필요하다. 자세한 과정은 Statistical Region Merge(SRM) 알고리즘(kipl.tistory.com/98)에 잘 구현이 되어 있다.

struct Universe {
    int n;
    std::vector<int> sizes;
    std::vector<int> sums;
    std::vector<int> parent;
    Universe(int n, BYTE *image) {
        this->n = n;
        parent.resize(n, -1);  // all nodes are root;
        sizes.resize(n, 1); 
        sums.resize(n);
        for (int i = 0; i < n; i++)
            sums[i] = image[i];
    }
    int Find(int node) { // find root node;
        if ( parent[node] < 0 ) return node;	 // root-node;
        return parent[node] = Find(parent[node]);
        // 찾음과 동시에 depth을 줄인다;   
    }
    bool Predicate(int root1, int root2, int thresh) {
        double diff = double(sums[root2]) / sizes[root2] 
                      - double(sums[root1]) / sizes[root1];
        return fabs(diff) < thresh;
    }
    void Merge(int root1, int root2) {
        sums[root2]  += sums[root1];
        sizes[root2] += sizes[root1];
        parent[root1] = root2;			 // r2 트리로 합병함;
    }
};
// root 노드는 분할영역의 픽셀 갯수와 픽셀 값의 합을 저장한다.
// 처음 각 픽셀이 root 이고, 픽셀 갯수는 1, 픽셀값은 자신의 픽셀값을 저장;
BOOL UnionFindAverage(BYTE *image, int width, int height, int thresh) {
    Universe *UF = new Universe(width * height, image);
    // 4-connectivity:
    // 첫행; LEFT 픽셀이 속한 영역의 평균값과 차이가 thresh 내이면 left로 합병;
    for (int x = 1; x < width; x++ ) {
        int left = UF->Find(x - 1);
        int curr = UF->Find(x);
        if (UF->Predicate(curr, left, thresh))
            UF->Merge(curr, left);
    }
    //Flatten(y=0);
    for (int x = 0; x < width; x++) UF->Find(x);
    for (int y = 1, pos = y * width; y < height; y++ ) {
        // x = 0; TOP 픽셀이 속학 영역과 합병 체크;
        int up   = UF->Find(pos - width);
        int curr = UF->Find(pos);
        if (UF->Predicate(curr, up, thresh))
            UF->Merge(curr, up);
        pos++;
        // TOP-LEFT 픽셀 영역과 합병 체크;
        for (int x = 1; x < width; x++, pos++ ) {
            int left = UF->Find(pos - 1);
            int curr = UF->Find(pos);
            // left와 합병이 되면 left가 up과 합병이 되는지 확인;
            if (UF->Predicate(curr, left, thresh)) {
                UF->Merge(curr, left);
                curr = left;
            }
            int up = UF->Find(pos - width);
            if ((curr != up) && (UF->Predicate(curr, up, thresh))) 
                UF->Merge(curr, up);
        }
        // Flatten(y>0)
        for (int x = 0; x < width; x++) UF->Find(x + y * width);
    }

    // 평균 이미지 생성;
    for (int k = width*height; k-->0;) {
        int root = UF->Find(k);
        int avg = int(double(UF->sums[root]) / UF->sizes[root] + 0.5);
        image[k] = avg > 255 ? 255 : avg;
    }
    delete UF;
    return TRUE;
};

네이버 블로그 이전
statistical region merge 알고리즘을 적용한 결과

728x90

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

Octree Quantization  (0) 2021.01.12
Median-Cut 컬러 양자화  (0) 2021.01.12
Multilevel Otsu Thresholding  (0) 2021.01.09
Kuwahara Filter  (2) 2020.12.28
Moving Average을 이용한 Thresholding  (0) 2020.11.26
Posted by helloktk
,

Union-Find algorithm을 이용해서 (이진) 이미지의 Connected Component Labeling을 구현한 코드이다. 이미지의 배경은 검정(0)으로 하고 전경은 0이 아닌 값이면 된다. 처음 각 전경에 해당하는 픽셀은 모두 root node로 만든다: parent 정보는 배열 label에 저장되고, root node에 대해서는 label[pos] = pos). 4방향 연결을 기준으로 하면 왼쪽(LEFT)이나 위쪽(TOP)의 픽셀과 연결이 되어 있으면 parent가 더 작은 root 번호를 갖도록 합병한다(UNION).  8-방향 연결인 경우에는 위-왼쪽(TOPLEFT), 위-오른쪽(TOPRIGHT)도 합병한다. 전체 픽셀에 대해서 조사가 끝나면 배열 label에는 각 픽셀의 root 번호가 저장된다. 자신의 위치 값이 label의 값과 같은 경우는 root 노드로 새로운 blob의 시작을 나타낸다.  label 배열에 기록된 root 번호는 합병과정에서 중간 번호가 빠질 수 있을 수 있다. 순차적 레이블링이 필요한 경우에는 배열 label을 다시 스캔해서 번호가 순차적으로 할당되도록 만든다. 

보통 connected component labeling 알고리즘이 이미지를 두 번 스캔을 해야 하는데 반해 이 알고리즘은 한 번의 스캔만 필요하다. 물론 순차적인 레이블링을 원하면 parent table을 다시 조사해야 한다.

 

#define CCL_BG           (-1)
static int Find(int v, int* parent) {
    if (v == parent[v]) return v;
    return parent[v] = Find(parent[v], parent);
}
static void Union(int a, int b, int *parent) {
    a = Find(a, parent);
    b = Find(b, parent);
    if (a > b) parent[a] = b;
    else parent[b] = a;
}
int ConnectedComponentLabel(BYTE *image, int w, int h, int label[]) {
    BYTE *q = &image[0] ;
    for (int y = 0, pos = 0; y < h; y++) {
        for (int x = 0; x < w; x++, pos++) {
            if (*q++) {                     // Foreground;
                label[pos] = pos;           // 초기 root = 현위치             
                if ((y > 0) && q[-w]) 
                    Union(pos, pos-w, label);         
                if ((x > 0) && q[-1]) 
                    Union(pos, pos-1, label);
#if defined (_EIGHTCONN_)
                if ((x + 1 < w) && (y > 0) && q[1-w]) 
                    Union(pos, pos+1-w, label);
                if ((x > 0) && (y > 0) && q[-w-1]) 
                    Union(pos, pos-1-w, label);
#endif // _EIGHTCONN_
            } else 
                label[pos] = CCL_BG;        // BACKGROUND;
        }
    }
    // 합병과정에서 빈 레이블이 생길 수 있으므로 순차적 레이블을 같도록 정리(선택 사항);
    // label = -1은 배경임;
    int curlab = 0;                                // 0-부터 시작;
    for (int pos = 0 ; pos < w * h; pos++) {
        int r = label[pos];
        if (r == CCL_BG)   continue;               // background;
        else if (r == pos) label[pos] = curlab++;  // root: assign a new label;
        else               label[pos] = label[r];  // assign renewed parent's label.
    }  
    return (curlab);                               //배경 제외;
};

 

<CCL을 이용하여 DataMatrix-code를 검출하는 과정>

이진화된 QR 코드 이미지

 

<20000 성분의 체스보드에 대한 4-방향 연결 테스트;>

출력은 정수형으로 바꾸어야 한다.  8-방향 연결로 하면 1개 성분만 나온다.

8 방향 연결 조건에서는 1개

<Percolation Experiment>

percolation simulator에 사용된 예: 위쪽이나 아래쪽에서 연결된 경로를 표시했다.

 

 

728x90
Posted by helloktk
,

http://www.lix.polytechnique.fr/~nielsen/tpami04-nn.pdf

Abstract—This paper explores a statistical basis for a process often described in computer vision: image segmentation by region merging following a particular order in the choice of regions. We exhibit a particular blend of algorithmics and statistics whose segmentation error is, as we show, limited from both the qualitative and quantitative standpoints. This approach can be efficiently approximated in linear time/space, leading to a fast segmentation algorithm tailored to processing images described using most common numerical pixel attribute spaces. The conceptual simplicity of the approach makes it simple to modify and cope with hard noise corruption, handle occlusion, authorize the control of the segmentation scale, and process unconventional data such as spherical images. Experiments on gray-level and color images, obtained with a short readily available C-code, display the quality of the segmentations obtained. 

인간은 영상을 보면 매우 쉽게 비슷한 픽셀 값을 갖는 영역으로 분리해서 인식을 한다. 우리가 보는 영상이 여러 개의 균일한 영역으로 나누어진 기본 영상에 랜덤 노이즈가 추가되어 만들어졌다고 생각해보자. 영상의 픽셀 값이 기본 영상의 픽셀 값에 일정 구간(Q)에서 값을 취하는 랜덤 변수가 더해져서 만들어졌다고 보면, 영상에서 다른 두 영역($R, R'$)의 픽셀 평균값의 차이 $(\overline{R}-\overline{R'})$와 기본 영상에서 랜덤 변수에 의한 통계적 기댓값의 차이 $(E(\overline{R}-\overline{R'}))$는 주어진 $0 < δ < 1$ 에 다음식을 만족함을 보일 수 있다.

따라서, 두 영역 $R$과 $R'$에 대해서 우변의 값이 이 부등식을 만족하지 않으면 두 영역은 하나로 인식될 수 있다.

 

#region count = 576; 많은 영역이 1픽셀로 구성이 되었있다;
#region count > 1 = 302;
#region count > 2 = 222;
#region count > 3 = 179;
#region count > 4 = 140;

 

구현은  https://kipl.tistory.com/460

 

Statistical Region Merging

Statistical region merging은 이미지의 픽셀을 일정한 기준에 따라 더 큰 영역으로 합병하는 bottom-up 방식의 과정이다. 두 영역 $R_1$과 $R_2$가 하나의 영역으로 합병이 되기 위해서는 두 영역의 평균 픽

kipl.tistory.com

 

728x90

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

RANSAC: Ellipse Fitting  (1) 2012.10.07
Autofocus Algorithm  (0) 2012.06.03
Local Histogram Equalization  (0) 2012.03.10
2차원 Savitzky-Golay Filters 응용  (0) 2012.02.28
webcam용 QR code detector  (0) 2012.02.19
Posted by helloktk
,