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 ",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 ",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 }