📄 图像处理.txt
字号:
DstLineBytes-y*DstLineBytes);
for(x=0;x<bi.biWidth;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级灰度图。
4.对比度扩展(contrast stretching)
假设有一幅图,由于成象时光照不足,使得整幅图偏暗,(例如,灰度范围从0到63);或者成象时光照过强,使得整幅图偏亮,(例如,灰度范围从200到255),我们称这些情况为低对比度,即灰度都挤在一起,没有拉开。灰度扩展的意思就是把你所感兴趣的灰度范围拉开,使得该范围内的像素,亮的越亮,暗的越暗,从而达到了增强对比度的目的。我们可以用如下的图来说明对比度扩展的原理。
图5. 对比度扩展的原理
图中的横坐标gold表示原图的灰度值,纵坐标gnew表示gold经过对比度扩展后得到了新的灰度值。a,b,c为三段直线的斜率,因为是对比度扩展,所以斜率b>1。g1old和g2old表示原图中要进行对比度扩展的范围,g1new和g2new表示对应的新值。用公式表示为
a*gold 当0<=gold<g1old时
gnew= b*(gold-g1old)+g1new 当g1old<=gold<g2old时
c*(gold-g2old)+g2new 当g2old<gold<=255时
显然要得到对比度扩展后的灰度,我们需要知道a,b,c,g1old,g2old五个参数。由于有新图的灰度级别也是255这个约束,所以满足a*g1old+b*(g2old-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
这两点是显然的。
下图为图1取g1old=100;g2old=150 ;b=3.0进行对比度扩展的结果。可以看出亮的区域(雕塑)变得更亮,暗的区域(手)变得更暗。
图6. 图1对比度扩展后的结果
下面的这段程序实现了对比度扩展。首先出现对话框,输入b,g1old,g2old的三个参数(在程序中分别是StretchRatio,SecondPoint,FirstPoint),然后对调色板做响应的处理,而实际的位图数据不用动。
BOOL ContrastStretch(HWND hWnd)
{
DLGPROC dlgInputBox = NULL;
DWORD 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;
//新开的缓冲区的大小
BufSize=bf.bfSize-sizeof(BITMAPFILEHEADER);
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<FirstPoint) g=(float)(a*Gray);
else if (Gray<SecondPoint) g=g1+StretchRatio*(Gray-FirstPoint);
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;
}
5.削波(cliping)
削波可以看作是对比度扩展的一个特例,我们用如下的图说明削波的原理。
图7. 削波的原理
不难看出,只要令对比度扩展中的a=c=0就实现了削波。我们只要给出范围的两个端点,斜率b就可以用方程b*(g2old-g1old)=255求出。
下图为图1取g1old=150;g2old=200 进行削波的结果。把亮的区域(雕塑)提取了出来。
图8. 图1削波处理后的结果
削波的程序和对比度扩展的程序很类似,就不再给出了。
6.阈值化(thresholding)
阈值化可以看作是削波的一个特例,我们用如下的图说明阈值化的原理。
图9. 阈值化的原理
不难看出,只要令削波中的g1old=g2old就实现了阈值化。阈值就象个门槛,比它大就是白,比它小就是黑。经过阈值化处理后的图象变成了黑白二值图,所以说阈值化是灰度图转二值图的一种常用方法(我们以前介绍过图案化和抖动的方法)。阈值化只需给出阈值点,即g1old即可。
下图为图1阈值取128,阈值化处理后的结果,得到了一幅黑白图。
图10. 图1阈值化处理后的结果
阈值化的程序和对比度扩展的程序很类似,就不再给出了。
7.灰度窗口变换(slicing)
灰度窗口变换是将某一区间的灰度级和其它部分(背景)分开。我们用如下的图说明灰度窗口变换的原理。其中[g1old,g2old]称为灰度窗口。
图11. 清除背景的灰度窗口变换的原理
图12. 保留背景的灰度窗口变换的原理
灰度窗口变换有两种,一种是清除背景的,一种是保留背景的。前者把不在灰度窗口范围内的像素都赋值为0,在灰度窗口范围内的像素都赋值为255,这也能实现灰度图的二值化;后者是把不在灰度窗口范围内的像素保留原灰度值,在灰度窗口范围内的像素都赋值为255。
灰度窗口变换可以检测出在某一灰度窗口范围内的所有像素,是图象灰度分析中的一个有力工具。下面有三幅图,图13为原图,图14是经过清除背景的灰度窗口变换处理后的图(灰度窗口取[200-255]),将夜景中大厦里的灯光提取了出来。图15是经过保留背景的灰度窗口变换处理后的图(灰度窗口取[200-255]),将夜景中大厦里的灯光提取了出来,同时保留了大厦的背景,可以看出它们的差别还是很明显的。
图13. 原图 图14. 图13经过清除背景 图15. 图13经过保留背景的
的灰度窗口变换处理后的图 灰度窗口变换处理后的图
灰度窗口变换的程序和对比度扩展的程序很类似,就不再给出了。
我突然想起了不久前在一本科学杂志上看到的一篇文章,非常有趣,是介绍电影〈阿甘正传〉特技制作的。其中有一项就用到了类似灰度窗口变换的思想。相信看过这部电影的读者都会对那个断腿的丹尼上校有深刻的印象。他的断腿是怎么拍出来的呢?其实方法很简单,先拍一幅没有演员出现的背景画面,然后拍一幅有演员出现,其它不变的画面。要注意的是,此时演员的腿用蓝布包裹。把前后两幅图输入计算机进行处理。第二幅图中凡是遇到蓝色的像素,就用第一幅图中对应位置的背景像素代替。这样,一位断腿的上校就逼真的出现在屏幕上了。这就是电影特技中经常用到的“蓝幕”技术。
说点题外话,其实现代电影,特别是好莱坞的电影,越来越离不开计算机及图象处理技术。最近引起轰动的大片<Titanic>中的很多特技镜头就是利用了庞大的SGI图形工作站机群没日没夜的计算产生的。图象处理技术和我们所喜爱的电影艺术紧密的结合了起来,这更增加了我们学习它的兴趣。
8.灰度直方图统计(histogram)
有时我们需要知道一幅图中的灰度分布情况,这时就可以采用灰度直方图来表示,图中的横坐标表示灰度值,纵坐标表示该灰度值出现的次数(频率)。图16为图13的灰度直方图,可见,低灰度的像素占了绝大部分。
图16. 图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=bf.bfSize-sizeof(BITMAPFILEHEADER);
lpImgData=(LPBITMAPINFOHEADER)GlobalLock(hImgData);
for(y=0;y<bi.biHeight;y++){
lpPtr=(char *)lpImgData+(BufSize-LineBytes-y*LineBytes);
for(x=0;x<bi.biWidth;x++){
grayindex=(unsigned char)*(lpPtr++);
GrayTable[grayindex]++; //对应的颜色计数值加1
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -