1 直方圖均衡化(Histogram Equalization)簡介
圖像對比度增強的方法可以分成兩類:一類是直接對比度增強方法;另一類是間接對比度增強方法。直方圖拉伸和直方圖均衡化是兩種最常見的間接對比度增強方法。直方圖拉伸是通過對比度拉伸對直方圖進行調整,從而“擴大”前景和背景灰度的差別,以達到增強對比度的目的,這種方法可以利用線性或非線性的方法來實現;直方圖均衡化則通過使用累積函數對灰度值進行“調整”以實現對比度的增強。
如果一副圖像的像素占有很多的灰度級而且分布均勻,那么這樣的圖像往往有高對比度和多變的灰度色調。直方圖均衡化就是一種能僅靠輸入圖像直方圖信息自動達到這種效果的變換函數。它的基本思想是對圖像中像素個數多的灰度級進行展寬,而對圖像中像素個數少的灰度進行壓縮,從而擴展像原取值的動態范圍,提高了對比度和灰度色調的變化,使圖像更加清晰。
2 直方圖均衡化原理
設變量r代表圖像中像素的灰度級,直方圖變換就是假定一個變換式:
也就是,通過上述變換,每個原始圖像的像素灰度級r都會產生一個s值。變換函數T(r)應滿足以下條件:
(1) T(r)在區間中為單值且單調遞增;
(2) 當 時,
即T(r)的取值范圍與r相同。
直方圖均衡化:對於離散值,我們處理其概率與求和,而不是概率密度函數與積分。一幅圖像中灰度級rk出現的概率近似為
其中,n是圖像中像素的總和, 是灰度級 的像素個數,L為圖像中可能的灰度級總數。
上式中變換函數的離散形式為:
式給出的變換(映射)稱為直方圖均衡化或直方圖線性化。
根據上面公式推導,直方圖均衡化步驟如下:
(1) 統計原圖每灰度級像素個數
(2) 統計原圖像每灰度級像素的累積個數
(3) 建立灰度級的映射規則
(4) 將原圖每個像素點的灰度映射到新圖
3 直方圖均衡化優缺點
這種方法對於背景和前景都太亮或者太暗的圖像非常有用,這種方法尤其是可以帶來X光圖像中更好的骨骼結構顯示以及曝光過度或者曝光不足照片中更好的細節。這種方法的一個主要優勢是它是一個相當直觀的技術並且是可逆操作,如果已知均衡化函數,那么就可以恢復原始的直方圖,並且計算量也不大。
這種方法的一個缺點是它對處理的數據不加選擇,它可能會增加背景雜訊的對比度並且降低有用信號的對比度;變換后圖像的灰度級減少,某些細節消失;某些圖像,如直方圖有高峰,經處理后對比度不自然的過分增強。
4 直方圖均衡化源碼實現
1 #include <windows.h> 2 #include <stdafx.h> 3 #include <stdlib.h> 4 #include <string.h> 5 #include <stdio.h> 6 #include<conio.h> 7 #pragma pack(1) 8 9 typedef unsigned char BYTE; 10 typedef short unsigned int WORD; 11 typedef unsigned long DWORD; 12 13 14 15 16 typedef struct BMP_FILEHEADER 17 {//定義bmp文件頭 18 19 WORD bmpType;/*位圖標識*/ 20 DWORD bmpSize;/*說明文件的大小,用字節為單位*/ 21 DWORD bmpReserved;/*保留,必須設置為0*/ 22 DWORD bmpOffset;/*從文件頭開始到實際的圖象數據之間的字節的偏移量*/ 23 DWORD bmpHeaderSize;/*說明BITMAP_INFOHEADER結構所需要的字數*/ 24 DWORD bmpWidth;/*說明圖象的寬度,以象素為單位*/ 25 DWORD bmpHeight;/*說明圖象的高度,以象素為單位.大多數的BMP文件都是倒向的位圖*/ 26 WORD bmpPlanes;/*為目標設備說明位面數,其值將總是被設為1*/ 27 28 } BMP_FILEHEADER; 29 30 typedef struct BMP_INFOHEADER 31 {//定義bmp信息頭 32 33 WORD bitsPerPixel;/*說明比特數/象素*/ 34 DWORD bmpCompression;/*說明圖象數據壓縮的類型*/ 35 DWORD bmpDataSize; /*說明圖象的大小,以字節為單位。當用BI_RGB格式時,可設置為0*/ 36 DWORD bmpHResolution;/*說明水平分辨率,用象素/米表示*/ 37 DWORD bmpVResolution;/*說明垂直分辨率,用象素/米表示*/ 38 DWORD bmpColors;/*實際使用的顏色數,0說明使用所有調色板項*/ 39 DWORD bmpImportantColors;/*重要影響的顏色索引的數目,如果是0,表示都重要*/ 40 41 } BMP_INFOHEADER; 42 43 typedef struct RGBPALETTE 44 {//定義8位顏色表 45 46 BYTE rgbBlue;/*指定藍色強度*/ 47 BYTE rgbGreen;/*指定綠色強度*/ 48 BYTE rgbRed;/*指定紅色強度*/ 49 BYTE rgbReserved;/*保留,設為0*/ 50 51 } RGBPALETTE; 52 53 typedef struct RGBPALETTE_24 54 {//定義24位顏色表 55 BYTE rgbBlue;/*指定藍色強度*/ 56 BYTE rgbGreen;/*指定綠色強度*/ 57 BYTE rgbRed;/*指定紅色強度*/ 58 }RGBPALETTE_24; 59 60 typedef struct BMPFILEPTR 61 {//定義bmp文件指針類型 62 BMP_FILEHEADER *bmpFileHeader;//bmp文件頭指針 63 BMP_INFOHEADER *bmpInfoHeader;//bmp信息頭指針 64 RGBPALETTE *bmpColorTable;//顏色表指針 65 unsigned char *bmpDataPtr;//bmp數據指針 66 }BMPFILEPTR; 67 68 typedef struct HE 69 {//定義直方圖均衡化結構 70 float ohs[256];//原始灰度級概率 71 float integs[256];//原始灰度概率累計值 72 }HE; 73 74 long FileLength(FILE *fp);//返回文件長度 75 void WriteData(unsigned char *pBmpData,FILE *fp);//寫數據,將文件數據寫入pBmpData所指內存單元 76 unsigned char* MemoryAlloc(int length);//分配length長度的內存單元 77 void MemoryFree(unsigned char *pBmpData);//釋放內存單元 78 int GetPixelColor(BMPFILEPTR bmpFile,unsigned int index,int bitsperpixel);//獲得顏色值 79 void InputPicture(BMPFILEPTR *bmpFile,unsigned char *pBmpData,FILE *fp);//輸入圖片,文件指針初始化 80 void DisplayPicture(HDC hdc,BMPFILEPTR bmpFile,POINT spoint);//顯示圖片,將圖片顯示在畫板上 81 void ComputeOriginalProbabilty(BMPFILEPTR bmpFile,HE *hequal);//計算原始圖片灰度級概率 82 void ComputeEqualizedProbabilty(BMPFILEPTR bmpFile,HE *hequal);//計算均衡化后的灰度級概率 83 void GenerateEqualizedBmpFile(BMPFILEPTR bmpFile,FILE *fp1,HE hequal);//生成均衡化之后的bmp文件 84 85 long WINAPI WndProc(HWND,UINT,UINT,LONG); //處理消息響應的函數 86 BOOL InitWindowsClass(HINSTANCE); //初始化窗口類 87 BOOL InitWindows(HINSTANCE,int); //初始化窗口函數 88 HWND hWndMain;//窗口句柄 89 90 long FileLength(FILE *fp) 91 {//返回文件長度 92 long curpos, length; 93 94 curpos = ftell(fp); 95 fseek(fp, 0L, SEEK_END); 96 length = ftell(fp); 97 fseek(fp, curpos, SEEK_SET); 98 return length; 99 } 100 101 unsigned char* MemoryAlloc(int length) 102 {//按照length長度分配內存單元,返回內存首地址 103 unsigned char *pBmpData; 104 105 if(NULL==(pBmpData=(unsigned char*)malloc(length*sizeof(unsigned char)))) 106 { 107 MessageBox(NULL,(LPCWSTR )"memory allocation failure,error!",NULL,NULL); 108 exit(1); 109 } 110 return pBmpData; 111 } 112 113 void MemoryFree(unsigned char *pBmpData) 114 {//釋放內存單元 115 free(pBmpData); 116 } 117 118 void WriteData(unsigned char *pBmpData,FILE *fp) 119 {//寫數據,將文件數據寫入字節流中 120 int ind=0; 121 fseek(fp, 0, SEEK_SET); 122 while(!feof(fp)) 123 { 124 pBmpData[ind++]=fgetc(fp); 125 } 126 } 127 128 int GetPixelColor(BMPFILEPTR bmpFile,unsigned int index,int bitsperpixel) 129 {//取像素點的顏色值,在VC++中顏色值是0x00bbggrr,返回顏色值 130 unsigned int pixelcolor=0; 131 switch(bitsperpixel) 132 { 133 case 8: pixelcolor+=(bmpFile.bmpColorTable+*(bmpFile.bmpDataPtr+index))->rgbBlue; 134 pixelcolor<<=8; 135 pixelcolor+=(bmpFile.bmpColorTable+*(bmpFile.bmpDataPtr+index))->rgbGreen; 136 pixelcolor<<=8; 137 pixelcolor+=(bmpFile.bmpColorTable+*(bmpFile.bmpDataPtr+index))->rgbRed; 138 break; 139 case 16: 140 break; 141 case 24:pixelcolor+=((RGBPALETTE_24*)bmpFile.bmpDataPtr+index)->rgbBlue; 142 pixelcolor<<=8; 143 pixelcolor+=((RGBPALETTE_24*)bmpFile.bmpDataPtr+index)->rgbGreen; 144 pixelcolor<<=8; 145 pixelcolor+=((RGBPALETTE_24*)bmpFile.bmpDataPtr+index)->rgbRed; 146 break; 147 case 32: 148 break; 149 default: 150 ; 151 } 152 return pixelcolor; 153 154 } 155 void InputPicture(BMPFILEPTR *bmpFile,unsigned char *pBmpData,FILE *fp) 156 {//將bmp文件輸入,存到pBmpData指針所指存儲空間中,初始化bmpFile變量 157 WriteData(pBmpData,fp); 158 bmpFile->bmpFileHeader=(BMP_FILEHEADER*)(pBmpData); 159 bmpFile->bmpInfoHeader=(BMP_INFOHEADER*)((char*)bmpFile->bmpFileHeader+sizeof(BMP_FILEHEADER)); 160 bmpFile->bmpColorTable=(RGBPALETTE*)((char*)bmpFile->bmpInfoHeader+sizeof(BMP_INFOHEADER)); 161 bmpFile->bmpDataPtr=pBmpData+bmpFile->bmpFileHeader->bmpOffset; 162 163 } 164 void DisplayPicture(HDC hdc,BMPFILEPTR bmpFile,POINT spoint) 165 {//顯示圖片,spoint為圖片起始點,左上角 166 static int bmpHeight,bmpWidth; 167 int h,w; 168 unsigned int index=0; 169 static int bitsperpixel; 170 static int pixelcolor; 171 bmpHeight=bmpFile.bmpFileHeader->bmpHeight; 172 bmpWidth=bmpFile.bmpFileHeader->bmpWidth; 173 bitsperpixel=bmpFile.bmpInfoHeader->bitsPerPixel; 174 if(8==bmpFile.bmpInfoHeader->bitsPerPixel) 175 bmpWidth=((bmpWidth*bitsperpixel+31)>>5)<<2;//若寬度不是4的整數倍則對齊 176 177 Rectangle(hdc,spoint.x,spoint.y,bmpWidth,bmpHeight);//畫圖片外框架 178 for(h=spoint.y+bmpHeight;h>spoint.y;h--)//從圖片左下角開始逐點填充 179 for(w=spoint.x;w<spoint.x+bmpWidth;w++) 180 { 181 pixelcolor=GetPixelColor(bmpFile,index++,bitsperpixel); 182 SetPixel(hdc,w,h,pixelcolor); 183 } 184 } 185 void ComputeOriginalProbabilty(BMPFILEPTR bmpFile,HE *hequal) 186 {//計算原始灰度級概率 187 int i; 188 int pixels=(bmpFile.bmpFileHeader->bmpHeight)*(bmpFile.bmpFileHeader->bmpWidth); 189 190 for(i=0;i<pixels;i++) 191 hequal->ohs[bmpFile.bmpDataPtr[i]]++;//累計相同灰度級點個數 192 193 194 FILE *p1; 195 p1=fopen("p1.txt","w"); 196 int p[256]; 197 for(i=0;i<256;i++) 198 { 199 p[i]=hequal->ohs[i]; 200 201 fprintf(p1,"p[%d]=%d\n ",i,p[i]); 202 } 203 204 205 206 for(i=0;i<256;i++) 207 hequal->ohs[i]=hequal->ohs[i]/pixels;//計算每個灰度級概率 208 } 209 void ComputeEqualizedProbabilty(BMPFILEPTR bmpFile,HE *hequal) 210 {// 計算原始灰度級概率的累計概率,為生成均衡化之后概率 211 int i; 212 FILE *p2; 213 p2=fopen("p2.txt","w"); 214 int a[256]; 215 216 hequal->integs[0]=hequal->ohs[0]; 217 for(i=1;i<256;i++) 218 { 219 hequal->integs[i]=hequal->integs[i-1]+hequal->ohs[i]; 220 221 a[i]=(hequal->integs[i])*256; 222 fprintf(p2,"a[%d]=%d\n ",i,a[i]); 223 } 224 225 226 } 227 void GenerateEqualizedBmpFile(BMPFILEPTR bmpFile,FILE *fp1,HE hequal) 228 {//生成均衡化后的bmp文件 229 unsigned long i,j; 230 unsigned char c; 231 fwrite(bmpFile.bmpFileHeader,sizeof(BMP_FILEHEADER),1,fp1);//將bmp文件頭結構數據賦給新文件相應部分 232 fwrite(bmpFile.bmpInfoHeader,sizeof(BMP_INFOHEADER),1,fp1); 233 fwrite(bmpFile.bmpColorTable,256*sizeof(RGBPALETTE),1,fp1); 234 for(i=0;i<bmpFile.bmpFileHeader->bmpSize-bmpFile.bmpFileHeader->bmpOffset;i++) 235 {//將灰度級數據賦給新文件數據部分 236 for(j=0;j<256;j++) 237 { 238 if(*(bmpFile.bmpDataPtr+i)==j) 239 { 240 c=hequal.integs[j]*255; 241 break; 242 } 243 244 } 245 fwrite(&c,sizeof(unsigned char),1,fp1); 246 } 247 } 248 BMPFILEPTR bmpFile,newbmpFile; 249 HE hequal={0,0},newhequal={0,0};//初始化直方圖結構體變量 250 251 252 253 254 255 int WINAPI WinMain( 256 HINSTANCE hInstance, 257 HINSTANCE hPrevInstance, 258 LPSTR lpCmdLine, 259 int nCmdShow 260 ) 261 { //windows API 主函數 262 MSG Message; 263 264 static FILE *fp = NULL,*newfp=NULL;//定義兩個文件指針,分別指向兩個bmp文件 265 static unsigned char *pBmpData,*newpBmpData;//定義兩個數據指針,分別指向兩處內存首地址 266 static int filesize=0; 267 if(NULL==(fp=fopen("Fig4.bmp","rb+"))) 268 { 269 MessageBox(NULL,(LPCWSTR )"file open failure,error!",NULL,NULL); 270 exit(1); 271 } 272 if(NULL==(newfp=fopen("newFig.bmp","wb+"))) 273 { 274 MessageBox(NULL,(LPCWSTR )"file open failure,error!",NULL,NULL); 275 exit(1); 276 } 277 filesize=FileLength(fp); 278 pBmpData=MemoryAlloc(filesize); 279 newpBmpData=MemoryAlloc(filesize); 280 InputPicture(&bmpFile,pBmpData,fp); 281 ComputeOriginalProbabilty(bmpFile,&hequal); 282 ComputeEqualizedProbabilty(bmpFile,&hequal); 283 GenerateEqualizedBmpFile(bmpFile,newfp,hequal); 284 InputPicture(&newbmpFile,newpBmpData,newfp); 285 ComputeOriginalProbabilty(newbmpFile,&newhequal); 286 fclose(fp); 287 fclose(newfp); 288 289 if(!InitWindowsClass(hInstance)) 290 return FALSE; 291 if(!InitWindows(hInstance,nCmdShow)) 292 return FALSE; 293 while(GetMessage(&Message,NULL,0,0)) 294 { 295 TranslateMessage(&Message); 296 DispatchMessage(&Message); 297 } 298 return Message.wParam; 299 } 300 301 302 303 int InitWindows(HINSTANCE hInstance,int nCmdShow) 304 { 305 hWndMain=CreateWindow( 306 (LPCWSTR )"WinFill", // registered class name 307 (LPCWSTR )"直方圖均衡化", // window name 308 WS_OVERLAPPEDWINDOW, // window style 309 0, // horizontal position of window 310 0, // vertical position of window 311 1100, // window width 312 550, // window height 313 NULL, // handle to parent or owner window 314 NULL, // menu handle or child identifier 315 hInstance, // handle to application instance 316 NULL // window-creation data 317 ); 318 if(!hWndMain) 319 return FALSE; 320 ShowWindow(hWndMain,nCmdShow); 321 UpdateWindow(hWndMain); 322 return TRUE; 323 } 324 325 int InitWindowsClass(HINSTANCE hInstance) 326 { //初始化窗口類,對窗口類的對象賦初始值 327 WNDCLASS wndclass; 328 wndclass.style=0; 329 wndclass.cbClsExtra=0; 330 wndclass.cbWndExtra=0; 331 wndclass.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH); 332 wndclass.hCursor=LoadCursor( 333 hInstance, 334 IDC_ARROW 335 ); 336 wndclass.hIcon=LoadIcon( 337 hInstance, 338 IDI_APPLICATION 339 ); 340 wndclass.hInstance=hInstance; 341 wndclass.lpfnWndProc=WndProc; 342 wndclass.lpszClassName=(LPCWSTR )"WinFill"; 343 wndclass.lpszMenuName=NULL; 344 return RegisterClass(&wndclass); 345 } 346 347 long WINAPI WndProc(HWND hWnd,UINT iMessage,UINT wParam,LONG lParam) 348 { 349 HDC hdc; 350 HPEN hpen; 351 PAINTSTRUCT ps; 352 POINT originpoint,pstartpoint; 353 354 switch(iMessage) 355 { 356 case WM_PAINT: hdc=BeginPaint(hWnd,&ps);//繪圖 357 358 hpen=CreatePen(PS_SOLID,1,RGB(0,255,0)); 359 SelectObject(hdc,hpen); 360 ////////////////畫原始的bmp圖////////////////// 361 pstartpoint.x=0; 362 pstartpoint.y=0; 363 DisplayPicture(hdc,bmpFile,pstartpoint); 364 365 366 ///////////畫均衡化后的bmp圖///////////////// 367 pstartpoint.x=550; 368 pstartpoint.y=0; 369 DisplayPicture(hdc,newbmpFile,pstartpoint); 370 371 return 0; 372 case WM_DESTROY:PostQuitMessage(0); 373 return 0; 374 default: return (DefWindowProc(hWnd,iMessage,wParam,lParam)); 375 } 376 }