分类: LINUX
2011-05-11 08:43:47
这一章,我们主要和调色板打交道。先从最简单的反色讲起。
反色(invert)就是形成底片效果。例如,图5.2为图5.1反色后的结果。
图5.1 原图 | 图5.2 图5.1反色后的结果 |
反色有时是很有用的,比如,图5.1中黑色区域占绝大多数,这样打印起来很费墨,我们可以先进行反色处理后再打印。
反色的实际含义是将R、G、B值反转。若颜色的量化级别是256,则新图的R、G、B值为255减去原图的R、G、B值。这里针对的是所有图,包括真彩图、带调色板的彩色图(又称为伪彩色图)、和灰度图。针对不同种类有不同的处理。
先看看真彩图。我们知道真彩图不带调色板,每个象素用3个字节,表示R、G、B三个分量。所以处理很简单,把反转后的R、G、B值写入新图即可。
再来看看带调色板的彩色图,我们知道位图中的数据只是对应调色板中的一个索引值,我们只需要将调色板中的颜色反转,形成新调色板,而位图数据不用动,就能够实现反转。
灰度图是一种特殊的伪彩色图,只不过调色板中的R、G、B值 都是一样的而已。所以反转的处理和上面讲的一样。
这里,我想澄清一个概念。过去我们讲二值图时,一直都说成黑白图。二值位图一定是黑白的吗?答案是不一定。我们安装Windows95时看到的那幅setup.bmp是由蓝色和黑色组成的,但它实际上是二值图。原来,它的调色板中的两种颜色是黑与蓝,而不是黑与白。所以说二值图也可以是彩色的,只不过一般情况下是黑白图而已。
下面的程序实现了反色,注意其中真彩图和调色板位图处理时的差别。
BOOL Invert(HWND hWnd)
{
DWORD OffBits,BufSize;
LPBITMAPINFOHEADER lpImgData;
LPSTR lpPtr;
HLOCAL hTempImgData;
LPBITMAPINFOHEADER lpTempImgData;
LPSTR lpTempPtr;
HDC hDc;
HFILE hf;
LONG x,y;
LOGPALETTE *pPal;
HPALETTE hPrevPalette=NULL;
HLOCAL hPal;
DWORD i;
unsigned char Red,Green,Blue;
OffBits=bf.bfOffBits-sizeof(BITMAPFILEHEADER);
BufSize=OffBits+bi.biHeight*LineBytes; //新开缓冲区的大小
if((hTempImgData=LocalAlloc(LHND,BufSize))==NULL)
{
MessageBox(hWnd,"Error alloc memory!","Error Message",MB_OK|
MB_ICONEXCLAMATION);
return FALSE;
}
lpImgData=(LPBITMAPINFOHEADER)GlobalLock(hImgData);
lpTempImgData=(LPBITMAPINFOHEADER)LocalLock(hTempImgData);
//拷贝头信息
memcpy(lpTempImgData,lpImgData,BufSize);
hDc=GetDC(hWnd);
if(NumColors!=0){ //NumColors不为0说明是带调色板的
lpPtr=(char *)lpImgData+sizeof(BITMAPINFOHEADER);
//指向原图数据
lpTempPtr=(char *)lpTempImgData+sizeof(BITMAPINFOHEADER);
//指向新图数据
//为新调色板分配内存
hPal=LocalAlloc(LHND,sizeof(LOGPALETTE)+
NumColors*sizeof(PALETTEENTRY));
pPal =(LOGPALETTE *)LocalLock(hPal);
pPal->palNumEntries =(WORD) NumColors;
pPal->palVersion = 0x300;
for (i = 0; i < NumColors; i++) {
Blue=(unsigned char )(*lpPtr++);
Green=(unsigned char )(*lpPtr++);
Red=(unsigned char )(*lpPtr++);
lpPtr++;
//反转调色板中的颜色,存入新的调色板
pPal->palPalEntry[i].peRed=(BYTE)(255-Red);
pPal->palPalEntry[i].peGreen=(BYTE)(255-Green);
pPal->palPalEntry[i].peBlue=(BYTE)(255-Blue);
pPal->palPalEntry[i].peFlags=0;
*(lpTempPtr++)=(unsigned char)(255-Blue);
*(lpTempPtr++)=(unsigned char)(255-Green);
*(lpTempPtr++)=(unsigned char)(255-Red);
*(lpTempPtr++)=0;
}
if(hPalette!=NULL)
DeleteObject(hPalette);
hPalette=CreatePalette(pPal); //产生新的调色板
LocalUnlock(hPal);
LocalFree(hPal);
if(hPalette){
hPrevPalette=SelectPalette(hDc,hPalette,FALSE);
RealizePalette(hDc);
}
}
else{ //不带调色板,说明是真彩色图
for(y=0;y lpPtr=(char *)lpImgData+(BufSize-LineBytes-y*LineBytes); lpTempPtr=(char *)lpTempImgData+(BufSize-LineBytes-y*LineBytes); for(x=0;x Blue=(unsigned char )(*lpPtr++); Green=(unsigned char )(*lpPtr++); Red=(unsigned char )(*lpPtr++); //反转位图数据中的颜色,存入新的位图数据中 *(lpTempPtr++)=(unsigned char)(255-Blue); *(lpTempPtr++)=(unsigned char)(255-Green); *(lpTempPtr++)=(unsigned char)(255-Red); } } } if(hBitmap!=NULL) DeleteObject(hBitmap); hBitmap=CreateDIBitmap(hDc,(LPBITMAPINFOHEADER)lpTempImgData, (LONG)CBM_INIT, (LPSTR)lpTempImgData+ sizeof(BITMAPINFOHEADER)+ NumColors*sizeof(RGBQUAD), (LPBITMAPINFO)lpTempImgData, DIB_RGB_COLORS); if(hPalette && hPrevPalette){ SelectPalette(hDc,hPrevPalette,FALSE); RealizePalette(hDc); } hf=_lcreat("c:\\invert.bmp",0); _lwrite(hf,(LPSTR)&bf,sizeof(BITMAPFILEHEADER)); _lwrite(hf,(LPSTR)lpTempImgData,BufSize); _lclose(hf); //释放内存和资源 ReleaseDC(hWnd,hDc); LocalUnlock(hTempImgData); LocalFree(hTempImgData); GlobalUnlock(hImgData); return TRUE; } 第2章中提到了YUV的颜色表示方法,在这种表示方法中,Y分量的物理含义就是亮度,它含了灰度图(grayscale)的所有信息,只用Y分量就完全能够表示出一幅灰度图来。YUV和RGB之间有着如下的对应关系: 我们利用上式,根据R、G、B的值求出Y值后,将R、G、B值都赋值成Y,就能表示出灰度图来,这就是彩色图转灰度图的原理。 先看看真彩图。我们知道真彩图不带调色板,每个象素用3个字节,表示R、G、B三个分量。所以处理很简单,根据R、G、B的值求出Y值后,将R、G、B值都赋值成Y,写入新图即可。 再来看看带调色板的彩色图,我们知道位图中的数据只是对应调色板中的一个索引值,我们只需要将调色板中的彩色变成灰度,形成新调色板,而位图数据不用动,就可以了。 下面的程序实现了彩色图到灰度图的转换,注意其中真彩图和调色板位图处理时的差别。 BOOL ColortoGrayScale(HWND hWnd) { DWORD SrcOffBits,SrcBufSize,DstBufSize,DstLineBytes; LPBITMAPINFOHEADER lpImgData; LPSTR lpPtr; HLOCAL hTempImgData; LPBITMAPINFOHEADER lpTempImgData; LPSTR lpTempPtr; HDC hDc; HFILE hf; LONG x,y; BITMAPFILEHEADER DstBf; BITMAPINFOHEADER DstBi; LOGPALETTE *pPal; HPALETTE hPrevPalette; HLOCAL hPal; DWORD NewNumColors; WORD NewBitCount; float Y; DWORD i; unsigned char Red,Green,Blue,Gray; NewNumColors=NumColors; //NewNumColors为新图的颜色数 NewBitCount=bi.biBitCount; //NewBitCount为新图的颜色位数 if(NumColors==0) //真彩图 { NewNumColors=256; NewBitCount=8; } //由于颜色位数有可能发生了改变,所以要重新计算每行占用的字节数以及 //新图的缓冲区大小 DstLineBytes=(DWORD)WIDTHBYTES(bi.biWidth*NewBitCount); DstBufSize=(DWORD)(sizeof(BITMAPINFOHEADER)+NewNumColors* sizeof(RGBQUAD)+(DWORD)DstLineBytes*bi.biHeight); //DstBf和DstBi为新的BITMAPFILEHEADER和BITMAPINFOHEADER //拷贝原来的头信息 memcpy((char *)&DstBf,(char *)&bf,sizeof(BITMAPFILEHEADER)); memcpy((char *)&DstBi,(char *)&bi,sizeof(BITMAPINFOHEADER)); //做必要的改变 DstBf.bfSize=DstBufSize+sizeof(BITMAPFILEHEADER); DstBf.bfOffBits=(DWORD)(NewNumColors*sizeof(RGBQUAD)+ sizeof(BITMAPFILEHEADER)+ sizeof(BITMAPINFOHEADER)); DstBi.biClrUsed=0; DstBi.biBitCount=NewBitCount; //原图的缓冲区的大小 SrcOffBits=bf.bfOffBits- sizeof(BITMAPFILEHEADER); SrcBufSize=SrcOffBits+bi.biHeight*LineBytes; if((hTempImgData=LocalAlloc(LHND,DstBufSize))==NULL) { MessageBox(hWnd,"Error alloc memory!","Error Message",MB_OK| MB_ICONEXCLAMATION); return FALSE; } lpImgData=(LPBITMAPINFOHEADER)GlobalLock(hImgData); lpTempImgData=(LPBITMAPINFOHEADER)LocalLock(hTempImgData); //拷贝头信息和位图数据 memcpy(lpTempImgData,lpImgData,DstBufSize); //用新的BITMAPINFOHEADER替换原来的头信息 memcpy(lpTempImgData,(char *)&DstBi,sizeof(BITMAPINFOHEADER)); //lpPtr指向原图的数据 lpPtr=(char *)lpImgData+sizeof(BITMAPINFOHEADER); //lpTempPtr指向新图的数据 lpTempPtr=(char *)lpTempImgData+sizeof(BITMAPINFOHEADER); //为新的调色板分配内存 hPal=LocalAlloc(LHND,sizeof(LOGPALETTE) + NewNumColors * sizeof(PALETTEENTRY)); pPal =(LOGPALETTE *)LocalLock(hPal); pPal->palNumEntries =(WORD) NewNumColors; pPal->palVersion = 0x300; if(NumColors==0) //真彩色 for (i = 0; i < 256; i++) { //灰度从(0,0,0)到(255,255,255) pPal->palPalEntry[i].peRed=(BYTE)i; pPal->palPalEntry[i].peGreen=(BYTE)i; pPal->palPalEntry[i].peBlue=(BYTE)i; pPal->palPalEntry[i].peFlags=(BYTE)0; *(lpTempPtr++)=(unsigned char)i; *(lpTempPtr++)=(unsigned char)i; *(lpTempPtr++)=(unsigned char)i; *(lpTempPtr++)=0; } else for (i = 0; i < NewNumColors; i++) { //带调色板的彩色图 Blue=(unsigned char )(*lpPtr++); Green=(unsigned char )(*lpPtr++); Red=(unsigned char )(*lpPtr++); Y=(float)(Red*0.299+Green*0.587+Blue*0.114); Gray=(BYTE)Y; lpPtr++; //从原来的调色板中的颜色计算得到Y值,写入新的调色板 pPal->palPalEntry[i].peRed=Gray; pPal->palPalEntry[i].peGreen=Gray; pPal->palPalEntry[i].peBlue=Gray; pPal->palPalEntry[i].peFlags=0; *(lpTempPtr++)=(unsigned char)Gray; *(lpTempPtr++)=(unsigned char)Gray; *(lpTempPtr++)=(unsigned char)Gray; *(lpTempPtr++)=0; } if(hPalette!=NULL) DeleteObject(hPalette); //生成新的逻辑调色板 hPalette=CreatePalette(pPal); LocalUnlock(hPal); LocalFree(hPal); hDc=GetDC(hWnd); if(hPalette){ hPrevPalette=SelectPalette(hDc,hPalette,FALSE); RealizePalette(hDc); } if(NumColors==0) //真彩色图才需要处理位图数据 for(y=0;y lpPtr=(char *)lpImgData+(SrcBufSize-LineBytes-y*LineBytes); lpTempPtr=(char*)lpTempImgData+ (DstBufSize-DstLineBytes-y*DstLineBytes); for(x=0;x Blue=(unsigned char )(*lpPtr++); Green=(unsigned char )(*lpPtr++); Red=(unsigned char )(*lpPtr++); Y=(float)(Red*0.299+Green*0.587+Blue*0.114); //从位图数据计算得到Y值,写入新图中 Gray=(BYTE)Y; *(lpTempPtr++)=(unsigned char)Gray; } } if(hBitmap!=NULL) DeleteObject(hBitmap); //产生新的位图 hBitmap=CreateDIBitmap(hDc,(LPBITMAPINFOHEADER)lpTempImgData, (LONG)CBM_INIT, (LPSTR)lpTempImgData+ sizeof(BITMAPINFOHEADER)+ NewNumColors*sizeof(RGBQUAD), (LPBITMAPINFO)lpTempImgData, DIB_RGB_COLORS); if(hPalette && hPrevPalette){ SelectPalette(hDc,hPrevPalette,FALSE); RealizePalette(hDc); } hf=_lcreat("c:\\gray.bmp",0); _lwrite(hf,(LPSTR)&DstBf,sizeof(BITMAPFILEHEADER)); _lwrite(hf,(LPSTR)lpTempImgData,DstBufSize); _lclose(hf); //释放内存和资源 ReleaseDC(hWnd,hDc); LocalUnlock(hTempImgData); LocalFree(hTempImgData); GlobalUnlock(hImgData); return TRUE; } 我们知道,真彩图中包含最多达224种颜色,怎样从中选出256种颜色,又要使颜色的失真比较小,这是一个比较复杂的问题。一种简单的做法是将R:G:B以3:3:2表示,即取R,G的高3位,B的高两位,组成一个字节,这样就可以表示256种颜色了,但不难想象,这种方法的失真肯定很严重。 我们下面介绍的算法能够比较好地实现真彩图到256色图的转换。它的思想是:准备一个长度为4096的数组,代表4096种颜色。对图中的每一个象素,取R、G、B的最高四位,拼成一个12位的整数,对应的数组元素加1。全部统计完后,就得到了这4096种颜色的使用频率。其中,可能有一些颜色一次也没用到,即对应的数组元素为零(假设不为零的数组元素共有PalCounts个)。将这些为零的数组元素清除出去,使得前PalCounts个元素都不为零。将这PalCounts个数按从大到小的顺序排列(这里我们使用起泡排序)。这样,前256种颜色就是用的最多的颜色,它们将作为调色板上的256种颜色。对于剩下的PalCounts-256种颜色并不是简单地丢弃,而是用前256种颜色中的一种来代替,代替的原则是找有最小平方误差的那个。再次对图中的每一个象素,取R、G、B的最高四位,拼成一个12位的整数,如果对应值在前256种颜色中,则直接将该索引值填入位图数据中,如果是在后PalCounts-256种颜色中,则用代替色的索引值填入位图数据中。 下面的两幅图中,图5.3是原真彩图,图.54是用上面的算法转换成的256色图,可以看出,效果还不错。 图5.3 原真彩图 图5.4 转换后的256色图 下面是上述算法的源程序。 BOOL Trueto256(HWND hWnd) { DWORD SrcBufSize,OffBits,DstBufSize,DstLineBytes; LPBITMAPINFOHEADER lpImgData; LPSTR lpPtr; HLOCAL hTempImgData; LPBITMAPINFOHEADER lpTempImgData; LPSTR lpTempPtr; HDC hDc; HFILE hf; LONG x,y; BITMAPFILEHEADER DstBf; BITMAPINFOHEADER DstBi; LOGPALETTE *pPal; HPALETTE hPrevPalette; HLOCAL hPal; WORD i,j; int Red,Green,Blue,ClrIndex; DWORD ColorHits[4096]; WORD ColorIndex[4096]; DWORD PalCounts,temp; long ColorError1,ColorError2; if(NumColors!=0){ //NumColors不为零,所以不是真彩图 MessageBox(hWnd,"Must be a true color bitmap!","Error Message", MB_OK|MB_ICONEXCLAMATION); return FALSE; } //由于颜色位数有可能发生了改变,所以要重新计算每行占用的字节数以及 //新图的缓冲区大小 DstLineBytes=(DWORD)WIDTHBYTES(bi.biWidth*8); DstBufSize=(DWORD)(sizeof(BITMAPINFOHEADER)+ 256*sizeof(RGBQUAD)+ (DWORD)DstLineBytes*bi.biHeight); //DstBf和DstBi为新的BITMAPFILEHEADER和BITMAPINFOHEADER //拷贝原来的头信息 memcpy((char *)&DstBf,(char *)&bf,sizeof(BITMAPFILEHEADER)); memcpy((char *)&DstBi,(char *)&bi,sizeof(BITMAPINFOHEADER)); //做必要的改变 DstBf.bfSize=DstBufSize+sizeof(BITMAPFILEHEADER); DstBf.bfOffBits=(DWORD)(256*sizeof(RGBQUAD)+ sizeof(BITMAPFILEHEADER) +sizeof(BITMAPINFOHEADER)); DstBi.biClrUsed=0; DstBi.biBitCount=8; //OffBits为到实际位图数据的偏移值 OffBits=bf.bfOffBits-sizeof(BITMAPFILEHEADER); //SrcBufSize为原图缓冲区的大小 SrcBufSize=OffBits+bi.biHeight*LineBytes; if((hTempImgData=LocalAlloc(LHND,DstBufSize))==NULL) { MessageBox(hWnd,"Error alloc memory!","Error Message",MB_OK| MB_ICONEXCLAMATION); return FALSE; } lpImgData=(LPBITMAPINFOHEADER)GlobalLock(hImgData); lpTempImgData=(LPBITMAPINFOHEADER)LocalLock(hTempImgData); //拷贝位图数据 memcpy(lpTempImgData,lpImgData,OffBits); //用新的头信息取代旧的头信息 memcpy(lpTempImgData,(char *)&DstBi,sizeof(BITMAPINFOHEADER)); //ColorHits为记录颜色使用频率的数组,ColorIndex为记录颜色索引值的 //数组 //先全部清零 memset(ColorHits,0,4096*sizeof(DWORD)); memset(ColorIndex,0,4096*sizeof(WORD)); for(y=0;y lpPtr=(unsigned char *)lpImgData+(SrcBufSize-LineBytes-y*LineBytes); for(x=0;x //R,G,B各取4位 Blue=(int)(*(lpPtr++) & 0xf0); Green=(int)(*(lpPtr++) & 0xf0); Red=(int)(*(lpPtr++) & 0xf0); //拼成一个12位整数 ClrIndex=(Blue<<4) + Green +(Red >>4); //相应的数组元素加1 ColorHits[ClrIndex]++; } } PalCounts=0; //将为零的元素清除出去 for (ClrIndex = 0; ClrIndex < 4096; ClrIndex++) { if(ColorHits[ClrIndex]!=0){ ColorHits[PalCounts]=ColorHits[ClrIndex]; //注意调整相应的索引值 ColorIndex[PalCounts]=ClrIndex; PalCounts++; //颜色数加1 } } //用起泡排序将PalCounts种颜色按从大到小的顺序排列 for (i = 0; i < PalCounts-1; i++) for (j = i + 1; j < PalCounts; j++){ if (ColorHits[j] > ColorHits[i]){ temp = ColorHits[i]; ColorHits[i] = ColorHits[j]; ColorHits[j] = temp; //注意调整相应的索引值 temp = ColorIndex[i]; ColorIndex[i] = ColorIndex[j]; ColorIndex[j] = (WORD)temp; } } //为新的调色板分配内存 hPal=LocalAlloc(LHND,sizeof(LOGPALETTE) + 256* sizeof(PALETTEENTRY)); pPal =(LOGPALETTE *)LocalLock(hPal); pPal->palNumEntries =(WORD) 256; pPal->palVersion = 0x300; lpTempPtr=(char *)lpTempImgData+sizeof(BITMAPINFOHEADER); for (i = 0; i < 256; i++) { //由12位索引值得到R,G,B的最高4位值 pPal->palPalEntry[i].peRed=(BYTE)((ColorIndex[i] & 0x00f) << 4); pPal->palPalEntry[i].peGreen=(BYTE)((ColorIndex[i] & 0x0f0)); pPal->palPalEntry[i].peBlue=(BYTE)((ColorIndex[i] & 0xf00) >> 4); pPal->palPalEntry[i].peFlags=(BYTE)0; *(lpTempPtr++)=(unsigned char)((ColorIndex[i] & 0xf00) >> 4); *(lpTempPtr++)=(unsigned char)((ColorIndex[i] & 0x0f0)); *(lpTempPtr++)=(unsigned char)((ColorIndex[i] & 0x00f) << 4); *(lpTempPtr++)=0; //ColorHits作为颜色记数的作用已经完成了,下面的作用是记录12位索 //引值对应的调色板//中的索引值 ColorHits[i]=i; } //其余的颜色依据最小平方误差近似为前256中最接近的一种 if (PalCounts > 256){ for (i = 256; i < PalCounts; i++){ //ColorError1记录最小平方误差,一开始赋一个很大的值 ColorError1=1000000000; //由12位索引值得到R,G,B的最高4位值 Blue = (long)((ColorIndex[i] & 0xf00) >> 4); Green = (long)((ColorIndex[i] & 0x0f0)); Red = (long)((ColorIndex[i] & 0x00f) << 4); ClrIndex = 0; for (j = 0; j < 256; j++){ //ColorError2计算当前的平方误差 ColorError2=(long)(Blue-pPal->palPalEntry[j].peBlue)* (Blue-pPal->palPalEntry[j].peBlue)+ (long)(Green-pPal->palPalEntry[j].peGreen)* (Green-pPal->palPalEntry[j].peGreen)+ (long)(Red-pPal->palPalEntry[j].peRed)* (Red-pPal->palPalEntry[j].peRed); if (ColorError2 < ColorError1){ //找到更小的了 ColorError1 = ColorError2; ClrIndex = j; //记录对应的调色板的索引值 } } //ColorHits记录12位索引值对应的调色板中的索引值 ColorHits[i] = ClrIndex; } } if(hPalette!=NULL) DeleteObject(hPalette); //产生新的逻辑调色板 hPalette=CreatePalette(pPal); LocalUnlock(hPal); LocalFree(hPal); hDc=GetDC(hWnd); if(hPalette){ hPrevPalette=SelectPalette(hDc,hPalette,FALSE); RealizePalette(hDc); } for(y=0;y lpPtr=(char *)lpImgData+(SrcBufSize-LineBytes-y*LineBytes); lpTempPtr=(char*)lpTempImgData+ (DstBufSize-DstLineBytes-y*DstLineBytes); for(x=0;x //R,G,B各取4位 Blue=(int)(*(lpPtr++) & 0xf0); Green=(int)(*(lpPtr++) & 0xf0); Red=(int)(*(lpPtr++) & 0xf0); //拼成一个12位整数 ClrIndex=(Blue<<4) + Green +(Red >>4); for (i = 0; i < PalCounts;i++) if (ClrIndex == ColorIndex[i]){ //根据12索引值取得对应的调色板中的索引值 *(lpTempPtr++)=(unsigned char)ColorHits[i]; break; } } } if(hBitmap!=NULL) DeleteObject(hBitmap); //产生新的位图 hBitmap=CreateDIBitmap(hDc,(LPBITMAPINFOHEADER)lpTempImgData, (LONG)CBM_INIT, (LPSTR)lpTempImgData+ sizeof(BITMAPINFOHEADER)+ 256*sizeof(RGBQUAD), (LPBITMAPINFO)lpTempImgData, DIB_RGB_COLORS); if(hPalette && hPrevPalette){ SelectPalette(hDc,hPrevPalette,FALSE); RealizePalette(hDc); } hf=_lcreat("c:\\256.bmp",0); _lwrite(hf,(LPSTR)&DstBf,sizeof(BITMAPFILEHEADER)); _lwrite(hf,(LPSTR)lpTempImgData,DstBufSize); _lclose(hf); //释放内存和资源 ReleaseDC(hWnd,hDc); LocalUnlock(hTempImgData); LocalFree(hTempImgData); GlobalUnlock(hImgData); return TRUE; } 以下我们将要介绍灰度变换,针对的都是256级灰度图。 假设有一幅图,由于成象时光照不足,使得整幅图偏暗(例如,灰度范围从0到63);或者成象时光照过强,使得整幅图偏亮(例如,灰度范围从200到255),我们称这些情况为低对比度,即灰度都挤在一起,没有拉开。灰度扩展的意思就是把你所感性趣的灰度范围拉开,使得该范围内的象素,亮的越亮,暗的越暗,从而达到了增强对比度的目的。我们可以用图5.5来说明对比度扩展(contrast stretching)的原理。 图5.5 对比度扩展的原理 图5.5中的横坐标gold表示原图的灰度值,纵坐标gnew表示gold经过对比度扩展后得到了新的灰度值。a,b,c为三段直线的斜率,因为是对比度扩展,所以斜率b>1。g1old和g2old表示原图中要进行对比度扩展的范围,g1new和g2new表示对应的新值。用公式表示为 显然要得到对比度扩展后的灰度,我们需要知道a,b,c,g1old,g2old五个参数。由于有新图的灰度级别也是255这个约束,所以满足ag1old+b(gold-g1old)+c(255-g2old)=255这个方程。这样,我们只需给出四个参数,而另一个可以代入方程求得。我们假设a=c,这样,我们只要给出b,g1old和g2old,就可以求出 a=(255-b(g2old-g1old))/(255-(g2old-g1old)) 要注意的是,给出的三个参数必须满:(1) b*(g2old-g1old)<=255;(2) (g2old-g1old)<=255。 下图为图5.1取g1old=100,g2old=150 ,b=3.0进行对比度扩展的结果。可以看出亮的区域(雕塑)变得更亮,暗的区域(手)变得更暗。 图5.6 图5.1对比度扩展后的结果 下面的这段程序实现了对比度扩展。首先出现对话框,输入b,g1old,g2old的三个参数(在程序中分别是StretchRatio,SecondPoint,FirstPoint),然后对调色板做响应的处理,而实际的位图数据不用改动。 BOOL ContrastStretch(HWND hWnd) { DLGPROC dlgInputBox = NULL; DWORD OffBits,BufSize; LPBITMAPINFOHEADER lpImgData; LPSTR lpPtr; HLOCAL hTempImgData; LPBITMAPINFOHEADER lpTempImgData; LPSTR lpTempPtr; HDC hDc; HFILE hf; LOGPALETTE *pPal; HPALETTE hPrevPalette=NULL; HLOCAL hPal; DWORD i; unsigned char Gray; float a,g1,g2,g; if( NumColors!=256){ //必须是256级灰度图 MessageBox(hWnd,"Must be a 256 grayscale bitmap!","Error Message", MB_OK|MB_ICONEXCLAMATION); return FALSE; } //出现对话框,输入三个参数 dlgInputBox = (DLGPROC) MakeProcInstance ( (FARPROC)InputBox, ghInst ); DialogBox (ghInst, "INPUTBOX", hWnd, dlgInputBox); FreeProcInstance ( (FARPROC) dlgInputBox ); if( StretchRatio*(SecondPoint-FirstPoint) > 255.0){ //参数不合法 MessageBox(hWnd,"StretchRatio*(SecondPoint-FirstPoint) can not be larger than 255!",Error Message", MB_OK|MB_ICONEXCLAMATION); return FALSE; } if( (SecondPoint-FirstPoint) >=255){ //参数不合法 MessageBox(hWnd,"The area you selected can not be the whole scale!", "Error Message",MB_OK|MB_ICONEXCLAMATION); return FALSE; } //计算出第一和第三段的斜率a a=(float)((255.0-StretchRatio*(SecondPoint-FirstPoint))/ (255.0-(SecondPoint-FirstPoint))); //对比度扩展范围的边界点所对应的新的灰度 g1=a*FirstPoint; g2=StretchRatio*(SecondPoint-FirstPoint)+g1; //新开的缓冲区的大小 OffBits=bf.bfOffBits- sizeof(BITMAPFILEHEADER); BufSize=OffBits+bi.biHeight*LineBytes; if((hTempImgData=LocalAlloc(LHND,BufSize))==NULL) { MessageBox(hWnd,"Error alloc memory!","Error Message",MB_OK| MB_ICONEXCLAMATION); return FALSE; } lpImgData=(LPBITMAPINFOHEADER)GlobalLock(hImgData); lpTempImgData=(LPBITMAPINFOHEADER)LocalLock(hTempImgData); //拷贝头信息和实际位图数据 memcpy(lpTempImgData,lpImgData,BufSize); hDc=GetDC(hWnd); //lpPtr指向原图数据缓冲区,lpTempPtr指向新图数据缓冲区 lpPtr=(char *)lpImgData+sizeof(BITMAPINFOHEADER); lpTempPtr=(char *)lpTempImgData+sizeof(BITMAPINFOHEADER); //为新的逻辑调色板分配内存 hPal=LocalAlloc(LHND,sizeof(LOGPALETTE)+ NumColors*sizeof(PALETTEENTRY)); pPal =(LOGPALETTE *)LocalLock(hPal); pPal->palNumEntries =(WORD) NumColors; pPal->palVersion = 0x300; for (i = 0; i < 256; i++) { Gray=(unsigned char )*lpPtr; lpPtr+=4; //进行对比度扩展 if(Gray else if (Gray else g=g2+a*(Gray-SecondPoint); pPal->palPalEntry[i].peRed=(BYTE)g; pPal->palPalEntry[i].peGreen=(BYTE)g; pPal->palPalEntry[i].peBlue=(BYTE)g; pPal->palPalEntry[i].peFlags=0; *(lpTempPtr++)=(unsigned char)g; *(lpTempPtr++)=(unsigned char)g; *(lpTempPtr++)=(unsigned char)g; *(lpTempPtr++)=0; } if(hPalette!=NULL) DeleteObject(hPalette); //产生新的逻辑调色板 hPalette=CreatePalette(pPal); LocalUnlock(hPal); LocalFree(hPal); if(hPalette){ hPrevPalette=SelectPalette(hDc,hPalette,FALSE); RealizePalette(hDc); } if(hBitmap!=NULL) DeleteObject(hBitmap); //产生新的位图 hBitmap=CreateDIBitmap(hDc,(LPBITMAPINFOHEADER)lpTempImgData, (LONG)CBM_INIT, (LPSTR)lpTempImgData+ sizeof(BITMAPINFOHEADER) + NumColors*sizeof(RGBQUAD), (LPBITMAPINFO)lpTempImgData, DIB_RGB_COLORS); if(hPalette && hPrevPalette){ SelectPalette(hDc,hPrevPalette,FALSE); RealizePalette(hDc); } hf=_lcreat("c:\\stretch.bmp",0); _lwrite(hf,(LPSTR)&bf,sizeof(BITMAPFILEHEADER)); _lwrite(hf,(LPSTR)lpTempImgData,BufSize); _lclose(hf); //释放内存和资源 ReleaseDC(hWnd,hDc); LocalUnlock(hTempImgData); LocalFree(hTempImgData); GlobalUnlock(hImgData); return TRUE; } 削波(cliping)可以看作是对比度扩展的一个特例,我们用图5.7说明削波的原理。 图5.7 削波的原理 不难看出,只要令对比度扩展中的a=c=0就实现了削波。我们只要给出范围的两个端点,斜率b就可以用方程b(g2old-g1old)=255求出。 图5.8为图5.1取g1old=150,g2old=200 进行削波的结果。把亮的区域(雕塑)提取了出来。 图5.8 图5.1削波处理后的结果 削波的程序和对比度扩展的程序很类似,就不再给出了。 阈值化(thresholding)可以看作是削波的一个特例,我们用图5.9说明阈值化的原理。 图5.9 阈值化的原理 不难看出,只要令削波中的g1old=g2old就实现了阈值化。阈值就象个门槛,比它大就是白,比它小就是黑。经过阈值化处理后的图象变成了黑白二值图,所以说阈值化是灰度图转二值图的一种常用方法(我们以前介绍过图案化和抖动的方法)。进行阈值化只需给出阈值点g1old即可。 图5.10为图5.1阈值取128,阈值化处理后的结果,是一幅黑白图。 图5.10 图5.1阈值化处理后的结果 阈值化的程序和对比度扩展的程序很类似,就不再给出了。 灰度窗口变换(slicing)是将某一区间的灰度级和其它部分(背景)分开。我们用图5.11和图5.12说明灰度窗口变换的原理。其中[g1old,g2old]称为灰度窗口。 图5.11 清除背景的灰度窗口变换的原理 图5.12 保留背景的灰度窗口变换的原理 灰度窗口变换有两种,一种是清除背景的,一种是保留背景的。前者把不在灰度窗口范围内的象素都赋值为0,在灰度窗口范围内的象素都赋值为255,这也能实现灰度图的二值化;后者是把不在灰度窗口范围内的象素保留原灰度值,在灰度窗口范围内的象素都赋值为255。灰度窗口变换可以检测出在某一灰度窗口范围内的所有象素,是图象灰度分析中的一个有力工具。 下面有三幅图,图5.13为原图;图5.14是经过清除背景的灰度窗口变换处理后的图(灰度窗口取[200-255]),将夜景中大厦里的灯光提取了出来;图5.15是经过保留背景的灰度窗口变换处理后的图(灰度窗口取[200-255]),将夜景中大厦里的灯光提取了出来,同时保留了大厦的背景,可以看出它们的差别还是很明显的。 图5.13 原图 图5.14 图5.13经过 清除背景的灰度窗 口变换处理后的图 图5.15 图5.13经过 保留背景的灰度窗 口变换处理后的图 灰度窗口变换的程序和对比度扩展的程序很类似,就不再给出了。 不久前在一本科学杂志上看到一篇文章,非常有趣,是介绍电影“阿甘正传”的特技制作的。其中有一项就用到了类似灰度窗口变换的思想。相信看过这部电影的读者都会对那个断腿的丹尼上校有深刻的印象。他的断腿是怎么拍出来的呢?其实方法很简单,先拍一幅没有演员出现的背景画面,然后拍一幅有演员出现,其它不变的画面。要注意的是,此时演员的腿用蓝布包裹。把前后两幅图输入计算机进行处理。第二幅图中凡是遇到蓝色的象素,就用第一幅图中对应位置的背景象素代替。这样,一位断腿的上校就逼真的出现在屏幕上了。这就是电影特技中经常用到的“蓝幕”技术。 说点题外话。其实现代电影,特别是好莱坞电影,越来越离不开计算机及图象处理技术。最近引起轰动的大片“泰坦尼克号”中的很多特技镜头就是利用了庞大的SGI图形工作站机群没日没夜的计算产生的。图象处理技术和我们所喜爱的电影艺术紧密的结合了起来,更增加了我们学习它的兴趣。 有时我们需要知道一幅图中的灰度分布情况,这时就可以采用灰度直方图(histogram)来表示,图中的横坐标表示灰度值,纵坐标表示该灰度值出现的次数(频率)。图5.16为图5.13的灰度直方图,低灰度的象素占了绝大部分。 图5.16 图5.13的灰度直方图 下面的程序显示一幅图的灰度直方图。有两段程序,第一段统计出每个灰度的象素个数,存放在数组GrayTable[]中,然后产生一个新的窗口,把统计结果显示出来。第二段程序就是该窗口的消息处理函数。要注意的是,由于各灰度出现的频率可能相差很大,所以如何将结果显示在有限的窗口范围内,是一个必须考虑的问题。我们这里的做法是,在所有出现的灰度中,统计出一个最大值max和一个最小值min,假设能显示的窗口最大坐标为270,最小坐标为5,按成比例显示,这样,灰度出现的次数和显示坐标之间呈线形关系。设 a×grayhits+b=coordinate,其中grayhits为灰度出现的次数,coordinate为显示坐标,a和b为两个常数。我们将max和min代入,应该满足a×max+b=270,a×min+b=5;由此可以解得a=265/(max-min),b=270.0-a× max 。 还有一点,不要忘了在WinMain函数中注册那个新产生窗口的窗口类。 int GrayTable[256]; int MaxGrayNum; int MinGrayNum; BOOL Histogram(HWND hWnd) { DWORD OffBits,BufSize; LPBITMAPINFOHEADER lpImgData; LPSTR lpPtr; int x,y; int grayindex; HWND hPopupWnd; int temp; //计数器清零 for(grayindex=0;grayindex<256;grayindex++) GrayTable[grayindex]=0; //OffBits为到实际位图数据的偏移值 OffBits=bf.bfOffBits-sizeof(BITMAPFILEHEADER); //BufSize为缓冲区的大小 BufSize=OffBits+bi.biHeight*LineBytes; lpImgData=(LPBITMAPINFOHEADER)GlobalLock(hImgData); for(y=0;y lpPtr=(char *)lpImgData+(BufSize-LineBytes-y*LineBytes); for(x=0;x grayindex=(unsigned char)*(lpPtr++); GrayTable[grayindex]++; //对应的颜色计数值加1 } } MaxGrayNum=0; MinGrayNum=65535; for(grayindex=0;grayindex<256;grayindex++){ temp=GrayTable[grayindex]; if(temp>MaxGrayNum) MaxGrayNum=temp; //找到更大的了 if( (temp MinGrayNum=temp; //找 到更小的了 } GlobalUnlock(hImgData); //产生新的窗口显示结果 hPopupWnd = CreateWindow ("PopupWindowClass", "Histogram Statistic Window", WS_OVERLAPPEDWINDOW,50,80,550,350, hWnd,NULL,ghInst,NULL); if (hPopupWnd){ ShowWindow (hPopupWnd, SW_SHOW); UpdateWindow (hPopupWnd); } return TRUE; } 下面是新窗口的消息处理函数。 long FAR PASCAL PopupWndProc (HWND hWnd,UINT message, WPARAM wParam,LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; DWORD i; int xstart; static LOGPEN blp={PS_SOLID,1,1,RGB(0,0,255)}; //蓝色画笔 HPEN bhp; //画笔句柄 float a,b,temp; char str[10]; //计算上面所说的a,b的值 a=(float)(265.0 /( MaxGrayNum - MinGrayNum) ); b=(float) (270.0-a* MaxGrayNum); switch (message){ case WM_PAINT: hdc = BeginPaint(hWnd, &ps); bhp = CreatePenIndirect(&blp); SelectObject(hdc,bhp); MoveToEx(hdc,2,270,NULL); LineTo(hdc,518,270); //先画一条水平线 xstart=2; for(i=0;i<256;i++){ MoveToEx(hdc,xstart,270,NULL); if (GrayTable[i]!=0) temp=(float)(a*GrayTable[i]+b); else temp=0.0f; //如果灰度出现的次数是零,则不画线 LineTo(hdc,xstart,270-(int)temp); //画出该灰度的计数值 if (i%16 ==0){ //画出标尺,每16个一格 MoveToEx(hdc,xstart,270,NULL); LineTo(hdc,xstart,280); _itoa(i,str,10); TextOut(hdc,xstart,285,str,strlen(str)); } xstart+=2; } MoveToEx(hdc,xstart,270,NULL); LineTo(hdc,xstart,280); TextOut(hdc,xstart,285,"256",strlen("256")); EndPaint(hWnd,&ps); DeleteObject(bhp); break; default: break; } return DefWindowProc (hWnd, message, wParam, lParam); } 在介绍灰度直方图均衡化(histogram equalization)之前,先讲讲直方图修正。所谓直方图修正,就是通过一个灰度映射函数Gnew=F(Gold),将原灰度直方图改造成你所希望的直方图。所以,直方图修正的关键就是灰度映射函数。我们刚才介绍的阈值化、削波、灰度窗口变换等等,都是灰度映射函数。 直方图均衡化是一种最常用的直方图修正。它是把给定图象的直方图分布改造成均匀直方图分布。由信息学的理论来解释,具有最大熵(信息量)的图象为均衡化图象。直观地讲,直方图均衡化导致图象的对比度增加。 由于直方图均衡化涉及到很多概率和数学的知识,具体的细节这里就不介绍了,只给出算法。通过下面的例子,就很容易明白了。 有一幅图象,共有16级灰度,其直方图分布为Pi, i=0,1,…,15,求经直方图均衡化后,量化级别为10级的灰度图象的直方图分布Qi,其中Pi和Qi为分布的概率,即灰度i出现的次数与总的点数之比。 Pi: 0.03,0,0.06,0.10,0.20,0.11,0,0,0,0.03,0,0.06,0.10,0.20,0.11,0 步骤1:用一个数组s记录Pi,即 s[0]=0.03,s[1]=0,s[2]=0.06,…,s[14]=0.11,s[15]=0 步骤2:i从1开始,令s[i]=s[i]+s[i-1],得到的结果是 s: 0.03,0.03,0.09,0.19,0.39,0.50,0.50,0.50,0.50,0.53,0.53,0.59,0.69,0.89,1.0,1.0 步骤3:用一个数组L记录新的调色板索引值,即令L[i]=s[i]×(10-1),得到的结果是L:0,0,1,2,4,5,5,5,5,5,5,5,6,8,9,9 这样就找到了原来的调色板索引值和新的调色板索引值之间的对应关系,即 0→0,1→0,2→1,3→2,4→4,5→5,6→5,7→5,8→5,9→5,10→5,11→5,12→6, 13→8,14→9,15→9。 步骤4:将老的索引值对应的概率合并,作为对应的新的索引值的概率。例如,原来的索引值0,1都对应了新的索引值0,则灰度索引值为0的概率为P0+P1=0.03;新的索引值3和7找不到老的索引值与之对应,所以令Q3和Q7为0。最后得到的结果是Qi:0.03,0.06,0.10,0,0.20,0.20,0.10,0,0.20,0.11 图5.17为Pi的分布,图5.18为Qi的分布,对照一下,不难发现图5.18的分布比图5.17要均匀一些。 图5.17 Pi的分布 图5.18 Qi的分布 要注意的是,均衡化处理后的图象只能是近似均匀分布。均衡化图象的动态范围扩大了,但其本质是扩大了量化间隔,而量化级别反而减少了,因此,原来灰度不同的象素经处理后可能变的相同,形成了一片的相同灰度的区域,各区域之间有明显的边界,从而出现了伪轮廓。 图5.19为图5.13经直方图均衡化处理后,量化为128级灰度的结果;图5.20为它的直方图分布。为什么天亮了起来呢?分析一下就明白了:因为原图中低灰度的点太多了,所以s数组前面的元素很大。经过L[i]=s[i]×(128-1)的处理后,原图中低灰度的点的灰度值提高了不少,所以那片暗区变亮了。同时可以看出,天空中出现了伪轮廓。 图5.19 图5.13经直方图均衡化处理后的结果 图5.20 图5.19的灰度直方图 图5.21为图5.1直方图均衡化后的结果(128级灰度),暗的区域(手)变亮了,看起来更清楚一些。 图5.21 图5.1直方图均衡化后的结果 下面给出直方图均衡化的源程序: int EquaScale; //为新的灰度级别 BOOL HistogramEqua(HWND hWnd) { DLGPROC dlgInputBox = NULL; DWORD BufSize,OffBits; LPBITMAPINFOHEADER lpImgData; LPSTR lpPtr; HLOCAL hTempImgData; LPBITMAPINFOHEADER lpTempImgData; LPSTR lpTempPtr; HDC hDc; HFILE hf; LONG x,y; LOGPALETTE *pPal; HPALETTE hPrevPalette; HLOCAL hPal; WORD i; int Gray; DWORD GrayHits[256]; int GrayIndex[256]; float s[256]; if( NumColors!=256){ //必须是256级灰度图 MessageBox(hWnd,"Must be a 256 grayscale bitmap!","Error Message", MB_OK|MB_ICONEXCLAMATION); return FALSE; } //出现对话框,输入新的灰度级别 dlgInputBox = (DLGPROC) MakeProcInstance ( (FARPROC)InputBox, ghInst ); DialogBox (ghInst, "INPUTBOX", hWnd, dlgInputBox); FreeProcInstance ( (FARPROC) dlgInputBox ); if( EquaScale >=255){ //量化级别不能大于255 MessageBox(hWnd,"The new scale can not be larger than 255", "Error Message",MB_OK|MB_ICONEXCLAMATION); return FALSE; } //OffBits为到实际位图数据的偏移值 OffBits=bf.bfOffBits-sizeof(BITMAPFILEHEADER); //BufSize为缓冲区的大小 BufSize=OffBits+bi.biHeight*LineBytes; if((hTempImgData=LocalAlloc(LHND,BufSize))==NULL) { MessageBox(hWnd,"Error alloc memory!","Error Message",MB_OK| MB_ICONEXCLAMATION); return FALSE; } //lpImgData指向原图,//lpTempImgData指向新开的缓冲区 lpImgData=(LPBITMAPINFOHEADER)GlobalLock(hImgData); lpTempImgData=(LPBITMAPINFOHEADER)LocalLock(hTempImgData); //拷贝头信息 memcpy(lpTempImgData,lpImgData,OffBits); //ColorHits为记录颜色使用频率的数组,ColorIndex为记录颜色索引值的 //数组 //先清零 memset(GrayHits,0,256*sizeof(DWORD)); memset(GrayIndex,0,256*sizeof(WORD)); for(y=0;y lpPtr=(unsigned char *)lpImgData+(BufSize-LineBytes-y*LineBytes); for(x=0;x Gray=(unsigned char )*(lpPtr++); GrayHits[Gray]++; //统计该颜色用到的次数 } } for(i=0;i<256;i++) //次数除以总点数得到频率 s[i]=(float)GrayHits[i]/((float)bi.biWidth*(float)bi.biHeight); for(i=1;i<256;i++) s[i]+=s[i-1]; //每一项都是前面所有项的累加 for(i=0;i<256;i++) //根据新的量化级别,计算新的灰度索引值 GrayIndex[i]=(int)(s[i]*(EquaScale-1)); //为新的调色板分配内存 hPal=LocalAlloc(LHND,sizeof(LOGPALETTE)+ 256*sizeof(PALETTEENTRY)); pPal =(LOGPALETTE *)LocalLock(hPal); //先将调色板内存全部清零 memset(pPal,0,sizeof(LOGPALETTE) + 256* sizeof(PALETTEENTRY)); pPal->palNumEntries =(WORD) 256; pPal->palVersion = 0x300; lpTempPtr=(char *)lpTempImgData+sizeof(BITMAPINFOHEADER); for (i = 0; i < EquaScale; i++) { Gray=(int)(i*255.0/(EquaScale-1)); //根据新的量化级别,计算灰度值 pPal->palPalEntry[i].peRed=(BYTE)Gray; pPal->palPalEntry[i].peGreen=(BYTE)Gray; pPal->palPalEntry[i].peBlue=(BYTE)Gray; pPal->palPalEntry[i].peFlags=(BYTE)0; *(lpTempPtr++)=(unsigned char)Gray; *(lpTempPtr++)=(unsigned char)Gray; *(lpTempPtr++)=(unsigned char)Gray; *(lpTempPtr++)=0; } if(hPalette!=NULL) DeleteObject(hPalette); //产生新的逻辑调色板 hPalette=CreatePalette(pPal); LocalUnlock(hPal); LocalFree(hPal); hDc=GetDC(hWnd); if(hPalette){ hPrevPalette=SelectPalette(hDc,hPalette,FALSE); RealizePalette(hDc); } for(y=0;y lpPtr=(char *)lpImgData+(BufSize-LineBytes-y*LineBytes); lpTempPtr=(char *)lpTempImgData+(BufSize-LineBytes-y*LineBytes); for(x=0;x Gray=(unsigned char )*(lpPtr++); //原灰度索引值 Gray=GrayIndex[Gray]; //对应的新的灰度索引值 *(lpTempPtr++)=(unsigned char)Gray; } } if(hBitmap!=NULL) DeleteObject(hBitmap); //产生新的位图 hBitmap=CreateDIBitmap(hDc,(LPBITMAPINFOHEADER)lpTempImgData, (LONG)CBM_INIT, (LPSTR)lpTempImgData+ sizeof(BITMAPINFOHEADER)+ 256*sizeof(RGBQUAD), (LPBITMAPINFO)lpTempImgData, DIB_RGB_COLORS); if(hPalette && hPrevPalette){ SelectPalette(hDc,hPrevPalette,FALSE); RealizePalette(hDc); } hf=_lcreat("c:\\equa.bmp",0); _lwrite(hf,(LPSTR)&bf,sizeof(BITMAPFILEHEADER)); _lwrite(hf,(LPSTR)lpTempImgData,BufSize); _lclose(hf); //释放内存和资源 ReleaseDC(hWnd,hDc); LocalUnlock(hTempImgData); LocalFree(hTempImgData); GlobalUnlock(hImgData); return TRUE; }5.2
5.3 色图
5.4
5.5
5.6
5.7
5.8
5.9