컬러 영상을 처리할 때 가장 흔히 사용하는 컬러 표현은 RGB 컬러이다. 이것은 R, G, B에 각각 8-비트를 할당하여 256-단계를 표현할 수 있게 하여, 전체적으로 256x256x256=16777216가지의 컬러를 표현할 수 있게 하는 것이다. 그러나 인간의 눈은 이렇게 많은 컬러를 다 구별할 수 없으므로 24-비트 RGB 컬러를 사용하는 경우는 대부분의 경우에 메모리의 낭비와 연산에 오버헤드를 가져오는 경우가 많이 생긴다. RGB 컬러 영상을 R, G, B를 각각 한축으로 하는 3차원의 컬러 공간에서의 벡터(점)로 표현이 가능하다. 컬러 영상의 픽셀들이 RGB삼차원의 공간에 골고루 펴져 있는 경우는 매우 드물고, 많은 경우에는 이 컬러 공간에서 군집(groups)을 이루면서 분포하게 된다. 하나의 군(group)은 유사한 RGB 값을 갖는 픽셀들로 구성이 되므로, 이 군에 포함이 되는 픽셀들에게 (군의 중앙에 해당하는) 대표적인 컬러 값을 대체하면 그 군에 포함이 된 픽셀은 이젠 RGB공간에서 한 점으로 표현이 되고, RGB공간상에는 픽셀 수만큼의 점이 있는 것이 아니라, 대표 RGB 값에 해당하는 점만이 존재하게 된다. 따라서 적당한 Lookup테이블(colormap)을 이용하면, 적은 메모리 공간만을 가지고도 원본의 컬러와 유사한 컬러를 구현할 수 있다.

 

이제 문제는 원래의 컬러 공간을 어떻게 군집화 하는가에 대한 것으로 바뀌었다. 간단한 방법으로는 원래의 컬러 영상이 차지는 하는 RGB공간에서의 영역을 감싸는 최소의 박스를 찾고, 이것을 원하는 최종적으로 원하는 컬러수만큼의 박스로 분할을 하는 것이다. 그러나 박스를 어떨게 분할을 해야만 제대로 컬러를 나누고, 또한 효율적으로 할 수 있는가를 고려해야 한다. 분할된 박스의 크기가 너무 크면 제대로 된 대푯값을 부여하기가 힘들어지고, 너무 작게 만들면 원하는 수에 맞추기가 어렵다.

 

Median-Cut 양자화(quantization)에서 사용하는 방법은 박스의 가장 긴축을 기준으로 그 축으로  projection 된 컬러 히스토그램의 메디안 값을 기준으로 분할을 하여서 근사적으로 픽셀들을 절반 정도 되게 분리를 한다 (한축에 대한 메디안이므로 정확히 반으로 분리되지 않는다). 두 개의 박스가 이렇게 해서 새로 생기는데, 다시 가장 많은 픽셀을 포함하는 박스를 다시 위의 과정을 통해서 분할을 하게 된다. 이렇게 원하는 수의 박스를 얻을 때까지 위의 과정을 반복적으로 시행을 하게 된다.

 

여기서 원래의 컬러 값을 모두 이용하게 되면 계산에 필요한 히스토그램을 만들기 위해서 너무 많은 메모리를 사용하게 되고 이것이 또한 연산의 오버헤드로 작용하게 되므로 RGB 컬러 비트에서 적당히 하위 비트를 버리면, 초기의 RGB공간에서의 histogram의 크기를 줄일 수 있게 된다.(보통  하위 3-비트를 버려서, 각각 5-비트만 이용하여, 전체 컬러의 개수를 32x32x32= 32768로 줄인다)

 

이렇게 RGB공간에서의 컬러 분포가 몇 개의 대표적인 컬러(예:박스의 중앙값)로 줄어들면(양자화 과정:: 공간에 연속적인 분포가 몇 개의 점으로 대체됨), 원본 영상의 각각의 픽셀에서의 대체 컬러 값은 원래의 컬러와 가장 유사한, 즉 RGB 공간에서 Euclidean 거리가 가장 작은 박스의 컬러로 대체하면 된다. 

 

그러나 너무 적은 수의 컬러로 줄이게 되면 인접 픽셀 간의 컬러 값의 차이가 눈에 띄게 나타나는 현상이 생기게 된다. 이러한 것을 줄이기 위해서는 디더링(dithering) 과정과 같은 후처리가 필요하게 된다.

 

**네이버 블로그에서 이전;

source code: dev.w3.org/Amaya/libjpeg/jquant2.c

 
int MedCutQuantizer(CRaster& raster, CRaster& out8) {
    const int MAX_CUBES = 256;
    const int HSIZE =32 * 32 * 32; // # of 15-bit colors;
    int hist[HSIZE] = {0};
    CSize sz = raster.GetSize();
    for (int y = 0; y < sz.cy; y++) {
        BYTE *p = (BYTE *)raster.GetLinePtr(y);
        for (int x = 0; x < sz.cx; x++) {
            int b = *p++; int g = *p++;	int r = *p++;
            hist[bgr15(b, g, r)]++;
        }
    }
    std::vector<int> hist_ptr;
    for (int color15 = 0; color15 < HSIZE; color15++)
        if (hist[color15] != 0) { 
            // 0이 아닌 히스토그램의 bin에 해당하는 15비트 컬러를 줌;
            hist_ptr.push_back(color15);
        }
    
    std::vector<Cube> cubeList;
    Cube cube(0, hist_ptr.size()-1, sz.cx * sz.cy, 0);
    shrinkCube(cube, &hist_ptr[0]);
    cubeList.push_back(cube);
    while (cubeList.size() < MAX_CUBES) {
        int level = 255, splitpos = -1; 
        for (int k = cubeList.size(); k-->0;) {
            if (cubeList[k].lower == cubeList[k].upper) ;	// single color; cannot be split
            else if (cubeList[k].level < level) {
                level = cubeList[k].level;
                splitpos = k;
            }
        }
        if (splitpos == -1)	// no more cubes to split
            break;
        // Find longest dimension of this cube
        Cube cube = cubeList[splitpos];
        int dr = cube.rmax - cube.rmin;
        int dg = cube.gmax - cube.gmin;
        int db = cube.bmax - cube.bmin;
        int longdim = 0;
        if (dr >= dg && dr >= db) longdim = 0;
        if (dg >= dr && dg >= db) longdim = 1;
        if (db >= dr && db >= dg) longdim = 2;
        // 가장 넓게 분포한 color를 colorTab의 15비트 컬러의 상위비트 이동;
        reorderColors(&hist_ptr[0], cube.lower, cube.upper, longdim);
        // hist_ptr값을 증가 순서로 바꿈->가장 폭이 넓은 컬러 방향으로 정렬됨;
        quickSort(&hist_ptr[0], cube.lower, cube.upper);
        // hist_ptr을 다시 원컬러로 복구;
        restoreColorOrder(&hist_ptr[0], cube.lower, cube.upper, longdim);
        // Find median
        int count = 0;
        int med = 0;
        for (med = cube.lower; med < cube.upper; med++) {
            if (count >= cube.count/2) break;
            count += hist[hist_ptr[med]];
        }
        // cube에 들어있는 color의 median을 기준으로 두개 cube로 쪼김;
        // 낮은 쪽은 원래 cube를 대체하고 높은 쪽은 새로 list에 추가;
        Cube cubeLow(cube.lower, med - 1, count, cube.level + 1);
        shrinkCube(cubeLow, &hist_ptr[0]);
        cubeList[splitpos] = cubeLow;				// add in old slot
        // 
        Cube cubeHigh(med, cube.upper, cube.count - count, cube.level + 1);
        shrinkCube(cubeHigh, &hist_ptr[0]);
        cubeList.push_back(cubeHigh);	
    }
    // 각 cube에 포함된 컬러를 하나의 대표값으로 설정; 
    // 대표값은 cube내 컬러의 평균이다;
    BYTE rLUT[256], gLUT[256], bLUT[256];
    for (int k = cubeList.size(); k-->0;) {
        int rsum = 0, gsum = 0, bsum = 0;
        Cube &cube = cubeList[k];
        for (int i = cube.lower; i <= cube.upper; i++) {
            int color15 = hist_ptr[i];
            rsum += getRed(color15) * hist[color15];
            gsum += getGreen(color15) * hist[color15];
            bsum += getBlue(color15) * hist[color15];
        }
        rLUT[k] = int(rsum / (double)cube.count + .5);
        gLUT[k] = int(gsum / (double)cube.count + .5);
        bLUT[k] = int(bsum / (double)cube.count + .5);
    }
        
    // 각 cube에 포함된 color를 대표하는 컬러값을 쉽게 찾도록 lookup table
    // 설정함;
    BYTE inverseMap[HSIZE]; 
    for (int k = cubeList.size(); k-->0;) {
        // 각 cube 내의 컬러는 하나의 대표컬러로...;
        Cube& cube = cubeList[k];
        for (int i = cube.lower; i <= cube.upper; i++)
            inverseMap[hist_ptr[i]] = (BYTE)k;
    }	

    // 8-bit indexed color 이미지 생성;
    out8.SetDimensions(sz, 8);
    for (int y = 0; y < sz.cy; y++) {
        BYTE *p = (BYTE *)raster.GetLinePtr(y);
        BYTE *q = (BYTE *)out8.GetLinePtr(y);
        for (int x = 0; x < sz.cx; x++) {
            int b = *p++; int g = *p++;	int r = *p++;
            *q++ = inverseMap[bgr15(b, g, r)];
        }
    }
    // 8비트 이미지 팔레트 설정;
    for (int i = 0; i < 256; i++) {
        out8.m_palette[i].rgbBlue = bLUT[i];
        out8.m_palette[i].rgbGreen = gLUT[i];
        out8.m_palette[i].rgbRed = rLUT[i];
    }
    // SaveRaster(out8, "res_medcut.bmp");
    return 1;
}
 
 
 
 
 
 
 
 
 
728x90

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

Edge-Preserving Smoothing  (0) 2021.01.12
Octree Quantization  (0) 2021.01.12
Union-Find 알고리즘을 이용한 영역분할  (0) 2021.01.11
Multilevel Otsu Thresholding  (0) 2021.01.09
Binary Image에서 Convex Hull  (0) 2021.01.06
Posted by helloktk
,