假设有一幅图,由于成象时光照不足,使得整幅图偏暗(例如,灰度范围从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),然后对调色板做响应的处理,而实际的位图数据不用改动。

CODE
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<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;

}

削波(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图形工作站机群没日没夜的计算产生的。图象处理技术和我们所喜爱的电影艺术紧密的结合了起来,更增加了我们学习它的兴趣。