2012年12月16日 星期日

Histogram Equalization 直方圖等化

Histogram Equalization

當透過 sensor 抓取影像, 我們可能會需要提高影像的品質, 使影像的細節或特徵更為凸顯. 其中一個方法是對影像亮度的分佈進行改變, 此類方法稱為 對比增強. 本文所要介紹的 Cumulative Histogram Equalization(直方圖等化) 法即是影像增強的技術之一.

直方圖等化使用機率分佈, 將原先的亮度分佈重新均勻的等化到新的亮度值. 原來在直方圖分佈中 比較窄化的影像, 例如 : 低對比度的影像, 透過直方圖等化的過程, 其亮度灰階的分佈變成均勻分散, 而成為高對比度的影像. 另外, 對於影像偏暗的部分提高亮度, 偏亮的部分則降低亮度, 使得細節呈現更為清晰.


Cumulative Histogram Equalization 的步驟如下
1.     建立影像的 histogram(PDF).
2.     計算影像的 cumulative distribution function(CDF).
3.     根據 CDF 以及 cumulative equalization 公式 計算灰階亮度的對應關係.
4.     根據對應關係計算出新的灰階亮度.



Histogram (PDF)

影像的 histogram 代表影像中灰階亮度的分佈狀況, 以機率來說稱為 Probability Density Function ; PDF, 如下是計算 PDF pseudo code.

假設灰階範圍是 0~255:
Histogram
images_PDF(0:255) = 0; //clear PDF array
for i = 1 to colume_size
for j = 1 to row_size
level = array(j, i);
images_PDF(level) = images_PDF(level + 1) + 1;
end
end

colume_size以及 row_size 代表影像的列數以及行數, 假設灰階的數值範圍是 0~255, images_PDF 共有 256 個數值, 每個數值代表影像中該灰階數量的統計. 若要將Histogram轉換為機率上的 Probability Density Function ; PDF, 其實需將每一個 Histogram 數值 normalize 0~1 之間.我們將normalize留到後面進行.

Cumulative Distribution Function ; CDF

我們要對影像進行 histogram equalization 需要使用 CDF , CDF 的定義如下:
  
計算 CDF pseudo code 如下:

Cumulative distribution function (cdf)
cdf(0:255) = 0; // clear CDF array
for  i = 0 to 255
cdf(i) = cdf(i) + images_PDF(i);
end


以圖一為例, PDF 統計圖形如圖二. CDF 統計圖型如圖三.
圖一

圖二 PDF
圖三 CDF


接下來根據 CDF 計算出新舊灰階值的對應關係如下:

影像中每一個灰階值對應的 EH(i) 就是新的灰階值.
例如 灰階值 50 其新的灰階值是EH(50). 所以要對一張影像進行 CHE, 只要將影像上每一個 pixel 的灰階值當作 CEH 陣列的索引.

Cumulative histogram equalization 的程式代碼如下:
01 #include "StdAfx.h"
02 #include "CameraDS.h"
03 #include <cv.h>
04 #include <highgui.h>
05 
06 
07 #include <stdio.h>
08 
09 #define  CAP_WIDTH         320 // 640 // 176
10 #define  CAP_HEIGHT        240 // 480 // 144
11 #define  HISTOGRAM_BIN  256
12 
13 int
14 main()
15 {
16     int     HistogramBins = HISTOGRAM_BIN;
17     float   HistogramRange1[ 2 ] = { 0, 255 };
18     float *HistogramRange[ 1 ] = { &HistogramRange1[ 0 ] };
19     int     CDF[ HISTOGRAM_BIN+1 ];
20     int     LUT[ HISTOGRAM_BIN+1 ];
21 
22     IplImage* capimg;
23     IplImage* gray_capimg;
24     CvCapture *capture;
25 
26     CvHistogram *Gray_Histogram;
27     IplImage       *Gray_PDF;
28     IplImage       *Gray_CDF;
29     IplImage       *Gray_CHE;
30 
31     IplImage       *CHE_CDF;
32     IplImage       *CHE_PDF;
33     CvHistogram       *CHE_Histogram;
34 
35     int loop = 1;
36     int file_idx = 0;
37     int key;
38     int DeviceCamCount;
39 
40     char filename[256];
41     char fileidx[10];
42 
43     int        CamCount = 0;
44     int        CamIndex[10];
45     CCameraDS *pCamDS   = NULL;
46 
47     pCamDS  = new CCameraDS();
48     DeviceCamCount = pCamDS->CameraCount();
49     if ( DeviceCamCount == 0 )
50     {
51         printf( "No Camera detected....\n" );
52         return(-1);
53     }
54 
55     for (int i = 0, j = 0; i < DeviceCamCount; i++)
56     {
57         char szCamName[1024];
58 
59         int retval = pCamDS->CameraName(i, szCamName, sizeof(szCamName));
60 
61         if (retval >0)
62         {
63             printf("Camera #%d's Name is '%s'.\n", i, szCamName);
64             if ( !strcmp( szCamName, "USB µø°T¸Ë¸m" ) )
65             {
66                 CamIndex[ j++ ] = i;
67                 CamCount++;
68             }
69         }
70         else
71         {
72             printf("Can not get Camera #%d's name.\n", i);
73         }
74     }
75 
76     if ( pCamDS->OpenCamera(CamIndex[0],false,CAP_WIDTH,CAP_HEIGHT)==false)
77     {
78         printf("Camera do not support w=%d:h=%d\n",CAP_WIDTH,CAP_HEIGHT);
79         return (-1);
80     }
81 
82     cvNamedWindow( "camera0", 1 );
83     capimg=pCamDS->QueryFrame();
84     gray_capimg  = cvCreateImage( cvGetSize(capimg), 8, 1 );
85 
86     Gray_PDF = cvCreateImage( cvSize(256,300), 8, 3 );
87     Gray_CDF = cvCreateImage( cvSize(256,300), 8, 3 );
88     Gray_CHE = cvCreateImage( cvGetSize(capimg), 8, 1 );
89     Gray_Histogram = cvCreateHist( 1, &HistogramBins,
90                                    CV_HIST_ARRAY, HistogramRange );
91 
92     int row, col;
93     while (   loop  )
94     {
95         capimg=pCamDS->QueryFrame();
96         cvCvtColor(capimg ,gray_capimg, CV_RGB2GRAY);
97 
98         cvCalcHist( &gray_capimg, Gray_Histogram );
99 
100         for ( row = 0; row < Gray_PDF->height ; row++ )
101         {
102             for ( col = 0; col < Gray_PDF->width ; col++ )
103             {
104                 Gray_PDF->imageData[row*Gray_PDF->width*3+ col*3]=255;
105                 Gray_PDF->imageData[row*Gray_PDF->width*3+ col*3+1]=128;
106                 Gray_PDF->imageData[row*Gray_PDF->width*3+ col*3+2]=0;
107             }
108         }
109 
110         for ( row = 0; row < Gray_CDF->height ; row++ )
111         {
112             for ( col = 0; col < Gray_CDF->width ; col++ )
113             {
114                 Gray_CDF->imageData[row*Gray_CDF->width*3+ col*3]=0;
115                 Gray_CDF->imageData[row*Gray_CDF->width*3+ col*3+1]=128;
116                 Gray_CDF->imageData[row*Gray_CDF->width*3+ col*3+2]=0;
117             }
118         }
119 
120         memset( CDF, 0, sizeof(CDF) );
121         int sum = 0;
122         int PDFi;
123         float scale = 255.0 / (gray_capimg->width * gray_capimg->height );
124         for ( int i=0 ; i<HistogramBins ; i++ )
125         {
126             //printf("%.f \n",((CvMatND *) Histogram1->bins)->data.fl[i]);
127             PDFi = cvQueryHistValue_1D( Gray_Histogram, i );
128             sum +=   PDFi;
129             CDF[ i ] = sum;
130             LUT[ i ] = cvRound(sum * scale);
131             cvLine( Gray_PDF,cvPoint(i,300),
132                     cvPoint(i,300-(int)( PDFi/15)),CV_RGB(i,i,i) );
133             cvLine( Gray_CDF,cvPoint(i,300),
134                     cvPoint(i,300-(int)( CDF[i]/300)),CV_RGB(i,i,i) );
135         }
136 
137         int test;
138         for ( row = 0; row < gray_capimg->height ; row++ )
139         {
140             for ( col = 0; col < gray_capimg->width ; col++ )
141             {
142                 int gray =
143                     (unsigned char)
144                     gray_capimg->imageData[row*gray_capimg->width+col];
145                 Gray_CHE->imageData[row*Gray_CHE->width+col]=LUT[gray];
146             }
147         }
148 
149 
150         cvShowImage(  "camera0", capimg );
151         cvShowImage(  "gray_camera0", gray_capimg );
152         cvShowImage( "PDF", Gray_PDF );
153         cvShowImage( "CDF", Gray_CDF );
154 
155         cvShowImage( "CHE", Gray_CHE );
156 
157         key = cvWaitKey( 5 );
158         if ( key == 27 )
159         {
160             loop = 0;
161         }
162         else if (key =='s' || key=='S' )
163         {
164             sprintf( fileidx, "%03d", file_idx++ );
165             strcpy( filename, "SAVE_IMG" );
166             strcat( filename, fileidx );
167             strcat( filename, ".bmp" );
168 
169             cvSaveImage( filename, capimg );
170             printf("Image file %s saved.\n", filename);
171         }
172     }
173 
174     if ( pCamDS )
175     {
176         pCamDS->CloseCamera();
177         delete pCamDS;
178     }
179     cvDestroyWindow( "camera0" );
180     cvDestroyWindow( "gray_camera0" );
181 
182     if ( capimg )
183         cvReleaseImage( &capimg );
184 
185     if ( gray_capimg )
186         cvReleaseImage( &gray_capimg );
187 
188     if (Gray_PDF)
189         cvReleaseImage( &Gray_PDF );
190 
191     if (Gray_CDF)
192         cvReleaseImage( &Gray_CDF );
193 
194     if (Gray_CHE)
195         cvReleaseImage( &Gray_CHE );
196 
197     return(0);
198 }



實驗結果

對圖一的影像進行 CHE, 得到的結果如圖四, 圖五則是 CHE 後影像 Histogram , 可以發現灰階值均勻分佈. 圖六是 CHE 後的影像的 CDF, 和圖三的 CDF 比較, 可以發現經過 CHE , CDF 會趨近於一直線, 直線上的鋸齒是因為CHE 後該灰階值出現的機率為 0.



圖四 

圖五 PDF

圖六 CDF




圖七

圖八

比較圖七和圖八, 圖七是圖八 CHE 後的結果, 對比增強後, 圖七左下角部分的衣服皺折比較明顯.

結論

CHE 雖然可以將影像的對比的提高, 但是 CHE 均勻地將灰階值重新分配的結果會將影像某些區域的對比度過於降低或是將某些區域對比度過於提高, 此外, 灰階值損失的現象可能會造成平均亮度的改變, 這些因素會使得 CHE 後的影像不夠自然.



基本的 CHE 使用的 histogram 來自於整個影像的全域資訊. 無法針對某個區域的特性進行處理. 針對全域 CHE 的缺點, 有區域 CHE (local CHE) 或是 自適性 CHE(Adaptive CHE)的提出. 這種 CHE 將影像切分為多個區域, 對個別區域做CHE, 如此避免了只參考全域資訊的缺點. 但也大幅增加了計算量.


5 則留言: