将图片分成4×4格局,按从左到右、从上到下的顺序,分别计算各子块的颜色直方图,因此需要设定一个三维数组,前两维为子块的坐标,最后一维为颜色级,但现在采样得到的象素点的颜色值是RGB形式的,因此,需要将RGB形式转换为可以用比较合理的有限数表示的颜色级,而人眼对亮度是最为敏感的,因此可以将RGB转换为亮度值Y,公式为:
Y=R×0.299+G×0.587+B×0.114
这样就确定的一个256级的颜色级别,而统计颜色直方图的三维数组就可以定义为:int Color[4][4][256],当采样到某一颜色级时候,将相应的位置加一即可。
根据以上的子块间的相似公式:
对于用户选定的块其实是代表查询对象的,因此应该加大权重,相对来说就是减小其他块的权重,然后可以将乘过对应权重的块的相似度相加,得到最终的相似度,然后将所有目标图像与用户输入的图像的相似度从大到小排序,选出值最大的几张作为最后的查询结果显示出来返回。
以上是具体实现设想,程序实现如下:
//基于颜色直方图的方法
pDC->TextOut(10,168,"检索结果:");
CBmpProc *pDestBmp;
CString comp_pic_path;
double fsim[15]; file://15张待比较的目标图片与用户输入图片的相似度存放的数组
int psim[15]; file://与fsim想对应的图片编号数组,以便显示
for(int comp_pic=1;comp_pic<=15;comp_pic++){
comp_pic_path.Format("image%d.bmp",comp_pic);
bmp.LoadFromFile(comp_pic_path); // 从库中读入位图
pDestBmp = (CBmpProc*)new(CBmpProc); // 用new分配类目标
pDestBmp->LoadFromObject(bmp, &CRect(0,0,128,128));
// 从bmp中的指定区域读入图像,以便图片匹配的进行
pDestBmp->CalculateColor(*pDC); file://计算目标图片的颜色直方图
int x1,x2,y1,y2,x3,x4,y3,y4;
x1=obj_set.m_x1;x2=obj_set.m_x2;x3=obj_set.m_x3;x4=obj_set.m_x4;
y1=obj_set.m_y1;y2=obj_set.m_y2;y3=obj_set.m_y3;y4=obj_set.m_y4;
file://用户输入的对象所在子块(既用户选定的4个子块)的坐标
double sim[4][4]; file://子块之间的相似度数组
int ccount[4][4]; file://有过统计的颜色数目记录数组
for(int i=0;i<4;i++)
for(int j=0;j<4;j++){
sim
ccount
}
file://以下两个for按公式计算两幅图像的各对应子块之间的相似度
for(i=0;i<4;i++)
for(int j=0;j<4;j++)
for(int k=0;k<256;k++){
if((pDestBmp->Color
sim
ccount
}
if((pDestBmp->Color
Color
sim
ccount
}
}
for(i=0;i<4;i++)
for(int j=0;j<4;j++){
sim
}
file://计算两图像最终的相似度结果
double final_sim=0;
for(i=0;i<4;i++)
for(int j=0;j<4;j++){
file://对用户指定的块设置权重为1
if((i==x1&&j==y1)||(i==x2&&j==y2)||(i==x3&&j==y3)||(i==x4&&j==y4))
final_sim+=sim
else
file://其他块降低权重为0.7,提高对对象匹配的精确度
final_sim+=(sim
}
file://将15幅被比较图像与用户输入源图像的最后计算出来的相似度结果记录在数组中
fsim[comp_pic-1]=final_sim;
delete (CBmpProc*)pDestBmp;
}
int count=15;double tempf;int tempp;
for(int l=0;l<15;l++){
psim[l]=l+1; file://设定编号数组
}
file://将15个相似度从大到小排列,并且改变次序的时候编号数组和跟着改变
for(int i=count;i>0;i--){
for(int j=0;j
该方法也需要分成4×4子块,计算颜色直方图,具体计算颜色直方图的方法上面已经有过详细的解释。
该方法主要在于对颜色对表示结构的实现,颜色对是某一图片的代表特征,因此在程序中必须有定量表示,现在采取用两个子块颜色直方图的欧氏距离表示,因此计算某一子块的颜色对表就是按八方向计算其与周围的子块之间的欧氏距离,将结果存放于一个double o_dis[8]的数组中,然后将这个数组从大到小排序,排序完成后再将数组中相互之间值的差小于某一域值(取8个颜色对的平均值的2%)的颜色对祛除(按序两两比较再移动数组里的变量实现),最后将结果先填入图像的特征颜色对表(有4×8=32个变量,是一个结构数组,结构记录用户选定子块的坐标和与其相对应的被选中的颜色对值)。
最后,对4个用户选定的子块依次计算完毕,就可以调用SortColorPair()函数,对特征颜色对表做出处理(先从大到小排序,然后祛除差值小于总平均值的2%的特征颜色对)。
在比较的时候,按顺序计算出目标图像的子块颜色对表,和以上的特征颜色对表匹配,如果匹配到,则标记该颜色对(设定另一标记0数组),并且将匹配数变量加一,如果最后匹配到的数目是60%以上,就算目标图像被搜索到。
具体程序实现如下:
//计算子块(x,y)的颜色对表,采取"八方向邻接技术"
int CBmpProc::CalculateColorPair(int x, int y)
{
file://颜色对采取欧氏距离来描述
double o_dis[8];
for(int k=0;k<8;k++){
o_dis[k]=0;
}
file://计算(x,y)与周围所有子块的颜色直方图的欧氏距离
file://---------------------------------------------
for(int i=0;i<256;i++){
if((x-1)>=0&&(y-1)>=0)
o_dis[0]=o_dis[0]+(Color[x-1][y-1]
else
o_dis[0]=-1;
if((y-1)>=0)
o_dis[1]=o_dis[1]+(Color[x][y-1]
else
o_dis[1]=-1;
if((x+1)<=3&&(y-1)>=0)
o_dis[2]=o_dis[2]+(Color[x+1][y-1]
else
o_dis[2]=-1;
if((x-1)>=0)
o_dis[3]=o_dis[3]+(Color[x-1][y]
else
o_dis[3]=-1;
if((x+1)<=3)
o_dis[4]=o_dis[4]+(Color[x+1][y]
else
o_dis[4]=-1;
if((x-1)>=0&&(y+1)<=3)
o_dis[5]=o_dis[5]+(Color[x-1][y+1]
else
o_dis[5]=-1;
if((y+1)<=3)
o_dis[6]=o_dis[6]+(Color[x][y+1]
else
o_dis[6]=-1;
if((x+1)<=3&&(y+1)<=3)
o_dis[7]=o_dis[7]+(Color[x+1][y+1]
else
o_dis[7]=-1;
}
for(int j=0;j<8;j++){
if(o_dis[j]>=0)
o_dis[j]=sqrt(o_dis[j]);
}
file://------------------------------------------------
file://欧氏距离计算结束
int flag=0;
int num=0;
for(int pairnum=0;pairnum<32;pairnum++){
if(pair[pairnum].x!=-1){
num++;
}
}//因为在计算子块的颜色对表的时候已经写了特征颜色对数组,因此要先统计一下特征颜色对数组里已经//有多少有数值了,以便下次的写入可以接在后面,而不至于覆盖了前面的数值
file://计算颜色对差值小于某个"域值"的这个域值
double ave=0;
for(int e=0;e<8;e++){
ave+=o_dis[e];
}
ave=ave/8;ave=ave*0.02; file://采取与子块周围颜色对的平均值的2%计为域值
file://对该子块的颜色对表进行从大到小的排序,采取冒泡排序
int count=8; double temp;
for(i=count;i>0;i--){
for(int j=0;j
//计算用户确定的4块位置与其周围位置的颜色对(颜色对现采取用相邻两块的直方图的欧氏距离表示)
pBmp->CalculateColorPair(obj_set.m_x1,obj_set.m_y1);
pBmp->CalculateColorPair(obj_set.m_x2,obj_set.m_y2);
pBmp->CalculateColorPair(obj_set.m_x3,obj_set.m_y3);
pBmp->CalculateColorPair(obj_set.m_x4,obj_set.m_y4);
file://其实在以上的4部计算中,已经形成了初步的颜色对表,在此只不过是将表中的数据从大到小排列出来//并且祛除差值小于某一域值的颜色对
file://计算颜色对结束,形成颜色对表
pBmp->SortColorPair();
file://颜色对表计算出来,表中的数据既是用户输入的该图像的代表特征
pDC->TextOut(10,168,"检索结果:");
CBmpProc *pDestBmp;
CString comp_pic_path;
int disp=0;
int space=-128;
file://读取带比较的图像(在此初定15幅--现定义这15幅图像即图片数据库)
for(int comp_pic=1;comp_pic<=15;comp_pic++){
comp_pic_path.Format("image%d.bmp",comp_pic);
bmp.LoadFromFile(comp_pic_path); // 从库中读入位图
pDestBmp = (CBmpProc*)new(CBmpProc); // 用new分配类目标
pDestBmp->LoadFromObject(bmp, &CRect(0,0,128,128)); // 从bmp中的指定区域读入图像
file://计算当前被比较的图像的颜色直方图
pDestBmp->CalculateColor(*pDC);
int match=0; file://颜色对匹配数目
double ave=0; file://确定匹配时候不能使用精确匹配,所以需要一个差值小于某一域值时的域值
for(int s=0;s
pair_count;s++){
ave+=pBmp->pair[s].o_dis;
}
ave=ave/pBmp->pair_count; file://这个域值的基数即是用户输入的图片的颜色对表中颜色对的平均值
ave=ave*0.02; file://确定误差小于2%的颜色对均属于这个域值 int pairflag[32]; file://颜色对匹配标志数组,即某一颜色对如果在目标图像中找到,下一次就不能再匹配
for(int t=0;t<32;t++){
pairflag[t]=-1;
}
for(int i=0;i<4;i++){
for(int j=0;j<4;j++){
file://按顺序计算目标图像中一子块与其周围子块的颜色对,然后在用户输入的图像的颜色对表中查询计算出//来的颜色对
pDestBmp->CalculateColorPair(i,j);
for(int scan=0;scan<8;scan++){
if(pDestBmp->pair[scan].x==-1)
break;
}
for(int comp=0;comp
pair_count;count++){
if((fabs(pBmp->pair[count].o_dis-pDestBmp->pair[comp].o_dis)) 通过以上的程序,我们就实现了真正的图像内容检索,简单的程序就实现了现代计算机科学在多媒体研究前沿的任务。
对于一个图像处理系统来说,可以将流程分为三个阶段,在获取原始图像后,首先是图像预处理阶段、第二是特征抽取阶段、第三是识别分析阶段。图像预处理阶段尤为重要,如果这阶段处理不好,后面的工作根本无法展开。
在实际应用中,我们的系统获取的原始图像不是完美的,例如对于系统获取的原始图像,由于噪声、光照等原因,图像的质量不高,所以需要进行预处理,以有利于提取我们感兴趣的信息。图像的预处理包括图像增强、平滑滤波、锐化等内容。图像的预处理既可以在空间域实现,也可以在频域内实现,我们主要介绍在空间域内对图像进行点运算,它是一种既简单又重要的图像处理技术,它能让用户改变图像上像素点的灰度值,这样通过点运算处理将产生一幅新图像。下面我们开始介绍与图像点运算的相关知识。
图像直方图是图像处理中一种十分重要的图像分析工具,它描述了一幅图像的灰度级内容,任何一幅图像的直方图都包含了丰富的信息,它主要用在图象分割,图像灰度变换等处理过程中。从数学上来说图像直方图是图像各灰度值统计特性与图像灰度值的函数,它统计一幅图像中各个灰度级出现的次数或概率;从图形上来说,它是一个二维图,横坐标表示图像中各个像素点的灰度级,纵坐标为各个灰度级上图像各个像素点出现的次数或概率。如果不特别说明,本讲座中的直方图的纵坐标都对应着该灰度级在图像中出现的概率。我们的例子是在一个对话框中显示一个图像的直方图,为实现该目的,我们定义了一个名为"ZFT"的对话框类用来显示图像的直方图,具体实现代码和效果图如下(关于代码实现部分可以参考笔者2001年在天极网上发表的一篇VC实现数字图像处理的文章):
//////////////////////////////////直方图对话框构造函数;
ZFT::ZFT(CWnd* pParent /*=NULL*/)
: CDialog(ZFT::IDD, pParent)//ZFT为定义的用来显示直方图的对话框类;
{
Width=Height=0;//对话框初始化阶段设置图像的宽和高为"0";
}
////////////////////////对话框重画函数;
void ZFT::OnPaint()
{
CRect rect;//矩形区域对象;
CWnd *pWnd;//得到图片框的窗口指针;
pWnd=GetDlgItem(IDC_Graphic);//得到ZFT对话框内的"Frame"控件的指针;
file://(IDC_Graphic为放置在对话框上的一个"Picture"控件,并讲类型设置为"Frame")。
pWnd->GetClientRect(&rect);//得到"Frame"控件窗口的"视"区域;
int i;
CPaintDC dc(pWnd);//得到"Frame"控件的设备上下文;
file://画直方图的x、y轴;
dc.MoveTo(0,rect.Height());
dc.LineTo(rect.Width(),rect.Height());
dc.MoveTo(0,rect.Height());
dc.LineTo(0,0);
file://画直方图,num[]是"ZFT"的内部数组变量,存放的是图像各个灰度级出现的概率;该数组的各个分量在 显示具体图像的直方图时设置;
for(i=0;i<256;i++)//根据图像上的各个灰度级出现的概率,在坐标上对应的画出一根直线,从而各个表示各灰度级出现概率的直线构成了图像的直方图;
{
dc.MoveTo(i+1,rect.Height());
dc.LineTo (i+1,(rect.Height()-rect.Height()*num
file://此处num分量乘以"30"是为了放大个灰度级上对应的出现概率直线,增强显示效果;
}
}
////////////////////////////////////////////////////////
void ZFT::OnMouseMove(UINT nFlags, CPoint point)
{//OnMouseMove函数处理鼠标消息,显示当前鼠标所在直方图上的灰度值等信息;
CWnd *pWnd,*pWndText;//定义两个窗口对象;
CPoint point1;//定义个一个点对象;
point1=point;//存放当前鼠标的位置信息;
CRect rect;//矩形对象;
CString string ;//字符串对象;
pWnd=GetDlgItem(IDC_Graphic);//得到显示直方图的框架窗口对象指针;
pWndText=GetDlgItem(IDC_NUM);//得到指向文本框对象(IDC_NUM)窗口的指针;
pWnd->GetWindowRect(&rect);//获取pWnd窗口对象窗口区域位置;
file://屏幕坐标转换为客户区坐标;
ScreenToClient(&rect);
file://判断当前鼠标是否指在直方图内;
if(rect.PtInRect (point))
{
int x=point1.x-rect.left;
file://当前鼠标位置减去区域的起始位置恰好为当前鼠标所指位置所表示的灰度级;
string.Format("%d",x);
file://显示当前位置对应的图像的灰度级;
pWndText->SetWindowText((LPCTSTR)string);
}
CDialog::OnMouseMove(nFlags, point);
}
////////////////////////////////////////
void CDibView::OnImagehorgm()
file://在程序的"视"类对象内处理显示图像直方图的函数;
{
CDibDoc *pDoc=GetDocument();
HDIB hdib;
hdib=pDoc->GetHDIB();
BITMAPINFOHEADER *lpDIBHdr;//位图信息头结构指针;
BYTE *lpDIBBits;//指向位图像素灰度值的指针;
lpDIBHdr=( BITMAPINFOHEADER *)GlobalLock(hdib);//得到图像的位图头信息
lpDIBBits=(BYTE*)lpDIBHdr+sizeof(BITMAPINFOHEADER)+256*sizeof(RGBQUAD);
file://获取图像像素值
ZFT dialog;//直方图对话框模板对象;
int i,j;
int wImgWidth=lpDIBHdr->biWidth;
int wImgHeight=lpDIBHdr->biHeight;
file://a[]数组用来存放各个灰度级出现的概率;
float a[256];
for(i=0;i<256;i++)//初始化数组;
{
a
}
file://统计各个灰度级出现的次数;
for(i=0;i
(a)LENA图像
(b)直方图 图一
上图为LENA的原始图像和其对应的直方图,在(b)图中的135表示当前鼠标在直方图中所指的位置对应的灰度级为135。从该直方图可以看出,LENA图像的灰度主要分布在中高灰度级上,在低灰度级上图像的像素数几乎为零。
影响系统图像清晰程度的因素很多,例如室外光照度不够均匀就会造成图像灰度过于集中;由CCD(摄像头)获得的图像经过A/D(数/模转换,该功能在图像系统中由数字采集卡来实现)转换、线路传送都会产生噪声污染等等。因此图像质量不可避免的降低了,轻者表现为图像不干净,难于看清细节;重者表现为图像模糊不清,连概貌也看不出来。因此,在对图像进行分析之前,必须要对图像质量进行改善,一般情况下改善的方法有两类:图像增强和图像复原。图像增强不考虑图像质量下降的原因,只将图像中感兴趣的特征有选择的突出,而衰减不需要的特征,它的目的主要是提高图像的可懂度。图像增强的方法分为空域法和频域法两类,空域法主要是对图像中的各个像素点进行操作;而频域法是在图像的某个变换域内,对图像进行操作,修改变换后的系数,例如付立叶变换、DCT变换等的系数,然后再进行反变换得到处理后的图像。图像复原技术与增强技术不同,它需要了解图像质量下降的原因,首先要建立"降质模型",再利用该模型,恢复原始图像。本期讲座我们主要介绍各种增强技术在图象处理系统中的实际应用。
简单的说,灰度变换就是指对图像上各个像素点的灰度值x按某个函数T()变换到y。例如为了提高图像的清晰度,需要将图像的灰度级整个范围或其中某一段(A,B)扩展或压缩到(A
对于图像的灰度变换,我们这里介绍一种稍微复杂一点的方法,既直方图均衡化。直方图均衡化是灰度变换的一个重要应用,广泛应用在图像增强处理中,它是以累计分布函数变换为基础的直方图修正法,可以产生一幅灰度级分布具有均匀概率密度的图像,扩展了像素的取值动态范围。若像素点的原灰度为R,变换后的灰度为S,需要注意的是R、S是归一化后的灰度值,其灰度变换函数T()为:
式中,
void CDibView::OnZftJh()
{
CClientDC pDC(this);
HDC hDC=pDC.GetSafeHdc();//获取当前设备上下文的句柄;
SetStretchBltMode(hDC,COLORONCOLOR);
CDibDoc *pDoc=GetDocument();
HDIB hdib;
hdib=pDoc->GetHDIB();
BITMAPINFOHEADER *lpDIBHdr;//位图信息头结构指针;
BYTE *lpDIBBits;//指向位图像素灰度值的指针;
lpDIBHdr=( BITMAPINFOHEADER *)GlobalLock(hdib);//得到图像的位图头信息
lpDIBBits=(BYTE*)lpDIBHdr+sizeof(BITMAPINFOHEADER)+256*sizeof(RGBQUAD);
file://获取图像像素值
float p[256],p1[256],num[256];
int i,j,k;
for(i=0;i<256;i++)//清空三个数组;
{ num
p
p1
}
file://num[]存放图象各个灰度级出现的次数;
int Height=lpDIBHdr->biHeight;
int Width=lpDIBHdr->biWidth;
for(i=0;i
(a)LENA原图
(b)直方图均衡化后的效果图
(c)原始图象的直方图
(d)均衡化后的直方他图
图 二
从上述效果图可以看出,经过直方图均衡化处理后,图像变的清晰了,从直方图来看,处理后的LENA的图像直方图分布更均匀了,在每个灰度级上图像都有像素点。但是直方图均衡化存在着两个缺点:
1)变换后图像的灰度级减少,某些细节消失;
2)某些图像,如直方图有高峰,经处理后对比度不自然的过分增强。
为此M.Kamel和Lian Guan等人从图像相邻像素一般高度相关这一事实出发,将灰度概率分布和空间相关性联系在一起,提出了用二维条件概率密度函数取代一维概率密度函数作为均衡化条件,很好的解决了这个问题,有兴趣的朋友可以参阅一些图像处理书籍和资料。
图像平滑主要是为了消除噪声。噪声并不限于人眼所能看的见的失真和变形,有些噪声只有在进行图像处理时才可以发现。图像的常见噪声主要有加性噪声、乘性噪声和量化噪声等。图像中的噪声往往和信号交织在一起,尤其是乘性噪声,如果平滑不当,就会使图像本身的细节如边界轮廓、线条等变的模糊不清,如何既平滑掉噪声有尽量保持图像细节,是图像平滑主要研究的任务。
一般来说,图像的能量主要集中在其低频部分,噪声所在的频段主要在高频段,同时系统中所要提取的汽车边缘信息也主要集中在其高频部分,因此,如何去掉高频干扰又同时保持边缘信息,是我们研究的内容。为了去除噪声,有必要对图像进行平滑,可以采用低通滤波的方法去除高频干扰。图像平滑包括空域法和频域法两大类,在空域法中,图像平滑的常用方法是采用均值滤波或中值滤波,对于均值滤波,它是用一个有奇数点的滑动窗口在图像上滑动,将窗口中心点对应的图像像素点的灰度值用窗口内的各个点的灰度值的平均值代替,如果滑动窗口规定了在取均值过程中窗口各个像素点所占的权重,也就是各个像素点的系数,这时候就称为加权均值滤波;对于中值滤波,对应的像素点的灰度值用窗口内的中间值代替。实现均值或中值滤波时,为了简便编程工作,可以定义一个n*n的模板数组。另外,读者需要注意一点,在用窗口扫描图像过程中,对于图像的四个边缘的像素点,可以不处理;也可以用灰度值为"0"的像素点扩展图像的边缘。下面给出了采用加权均值滤波的图像平滑函数代码和效果图:
void CDibView::OnImagePh()
{
CClientDC pDC(this);
HDC hDC=pDC.GetSafeHdc();//获取当前设备上下文的句柄;
SetStretchBltMode(hDC,COLORONCOLOR);
HANDLE data1handle;
LPBITMAPINFOHEADER lpBi;
CDibDoc *pDoc=GetDocument();
HDIB hdib;
unsigned char *hData;
unsigned char *data;
hdib=pDoc->GetHDIB();
BeginWaitCursor();
lpBi=(LPBITMAPINFOHEADER)GlobalLock((HGLOBAL)hdib);
hData=(unsigned char*)FindDIBBits((LPSTR)lpBi);
pDoc->SetModifiedFlag(TRUE);
data1handle=GlobalAlloc(GMEM_SHARE,WIDTHBYTES(lpBi->biWidth*8)*lpBi->biHeight);
data=(unsigned char*)GlobalLock((HGLOBAL)data1handle);
AfxGetApp()->BeginWaitCursor();
int i,j,s,t,ms=1;
int sum=0,sumw=0;
int mask[3][3]={{1,1,1},{1,2,1},{1,1,1}};//定义的3x3加权平滑模板;
for(i=0; ibiHeight; i++)
for(j=0; jbiWidth; j++)
{
sumw=0; sum=0;
for(s=(-ms); s<=ms; s++)
for(t=(-ms); t<=ms; t++) if(((i+s)>=0)&&((j+t)>=0)&&((i+s)biHeight)&&((j+t)biWidth))
{
sumw += mask[1+s][1+t];
sum+=*(hData+(i+s)*WIDTHBYTES(lpBi->biWidth*8)+(j+t))*mask[1+s][1+t];
}
if(sumw==0) sumw=1;
sum/=sumw;
if(sum>255)sum=255;
if(sum<0)sum=0;
*(data+i*WIDTHBYTES(lpBi->biWidth*8)+j)=sum;
}
for( j=0; jbiHeight; j++)
for(i=0;ibiWidth;i++) *(hData+i*WIDTHBYTES(lpBi->biWidth*8)+j)=*(data+i*WIDTHBYTES(lpBi->biWidth*8)+j);
StretchDIBits (hDC,0,0,lpBi->biWidth,lpBi->biHeight,0,0,
lpBi->biWidth,lpBi->biHeight,
hData,(LPBITMAPINFO)lpBi,
DIB_RGB_COLORS,
SRCCOPY);//显示图像;
}
(a)LENA原图
(b)平滑后的效果图
图三
中值或均值平滑有时处理图像的效果并不是很好,它虽然去除了一定的噪声,但同时使图像中的边缘变的模糊,这主要和所选取的窗口大小有关,为此下面介绍了一种既能保持边缘清晰又能消除噪声的方法,其算法如图四所示:
(a)
(b)
(c)
图 四 图像平滑模板
上图的含义是在图像中取5*5的区域,包含点(i,j)的五边形和六边形各四个,3*3的区域一个,计算这九个区域的标准差和灰度的平均值,取标准差最小区域的灰度平均值作为点(i,j)的灰度。由于该算法的实现代码和上述代码大同小异,所以代码部分就不再赘述。
图像平滑往往使图像中的边界、轮廓变的模糊,为了减少这类不利效果的影响,这就需要利用图像鋭化技术,使图像的边缘变的清晰。图像銳化处理的目的是为了使图像的边缘、轮廓线以及图像的细节变的清晰,经过平滑的图像变得模糊的根本原因是因为图像受到了平均或积分运算,因此可以对其进行逆运算(如微分运算)就可以使图像变的清晰。从频率域来考虑,图像模糊的实质是因为其高频分量被衰减,因此可以用高通滤波器来使图像清晰。
为了要把图像中间任何方向伸展的的边缘和轮廓线变得清晰,我们希望对图像的某种运算是各向同性的。可以证明偏导平方和的运算是各向同性的,既:
式中(
为了突出物体的边缘,常常采用梯度值的改进算法,将图像各个点的梯度值与某一阈值作比较,如果大于阈值,该像素点的灰度用梯度值表示,否则用一个固定的灰度值表示。
我们在对图像增强的过程中,采用的是一种简单的高频滤波增强方法:
式中f,g分别为锐化前后的图像,
void CDibView::OnMenuitem32785()
{
CClientDC pDC(this);
HDC hDC=pDC.GetSafeHdc();//获取当前设备上下文的句柄;
SetStretchBltMode(hDC,COLORONCOLOR);
CDibDoc *pDoc=GetDocument();
HDIB hdib;
hdib=pDoc->GetHDIB();
BITMAPINFOHEADER *lpDIBHdr;//位图信息头结构指针;
BYTE *lpDIBBits;//指向位图像素灰度值的指针;
lpDIBHdr=( BITMAPINFOHEADER *)GlobalLock(hdib);//得到图像的位图头信息 lpDIBBits=(BYTE*)lpDIBHdr+sizeof(BITMAPINFOHEADER)+256*sizeof(RGBQUAD);//获取图像像素值
BYTE* pData1;
static int a[3][3]={{1,4,1},{4,-20,4},{1,4,1}};//拉普拉斯算子模板;
int m,n,i,j,sum;
int Width=lpDIBHdr->biWidth;
int Height=lpDIBHdr->biHeight;
pData1=(BYTE*)new char[WIDTHBYTES(Width*8)*Height];
file://进行拉普拉斯滤波运算;
for(i=1;i
(a)LENA原图
(b)拉普拉斯锐化图
图 五
本文主要讲解了图像直方图的基本概念和图像点处理运算中的增强、平滑、锐化概念和实现算法,并给处理实现代码和处理效果图和广大读者朋友们交流,希望达到抛砖引玉的作用。
摘要:本文以VC++ 6.0为编程工具,讲述了采取逆滤波和维纳滤波两种图像恢复算法对退化图像的恢复实现过程。
图像恢复技术是图像处理领域一类重要的处理技术,与图像增强等其他基本图像处理技术类似,该技术也是以获取视觉质量得到某种程度改善为目的的,所不同的是图像恢复过程需要根据指定的图像退化模型来完成,根据这个退化模型对在某种情况下退化或恶化了的退化图像进行恢复,以获取到原始的、未经过退化的原始图像。换句话说,图像恢复的处理过程实际是对退化图像品质的提升,并通过图像品质的提升来达到图像在视觉上的改善。本文以VC++作为开发工具,讲述了对退化图像进行逆滤波和维纳滤波处理算法。
对图像进行恢复处理通常需要根据一定的图像退化模型来进行,一个简单的通用图像退化模型可将图像的退化过程模型化为一个作用在原始图像f(x,y)上的退化系统H,作用结果与一个加性噪声n(x,y)的联合作用导致产生出了退化图像g(x,y),表现为数学形式为g(x,y)=H[f(x,y)]+n(x,y)。根据上述退化系统H可以从给定的退化图像g(x,y)得到原始图像f(x,y)的一个近似结果。逆滤波处理就是其中一种无约束恢复的图像恢复技术,其恢复过程的数学形式可表示为F (u,v)=G(u,v)/H(u,v) (u,v=0,1,…,M-1),其中F(u,v)和G(u,v)分别为图像f(x,y)和g(x,y)的频域变换,H(u,v)可看作是一个滤波函数。由于图像在退化过程中存在噪声的干扰,因此通常情况下的滤波器往往不是正好的1/H(u,v),而是关于u和v的某个非线形的恢复转移函数M(u,v)。经过以上的分析,图像的退化和恢复过程(模型)大致可用下图来表示:
一种简便的恢复方法是在选取恢复转移函数M(u,v) 时,如果u2+v2≤w2,则取值1/H(u,v),否则为1。这样处理虽然简单,但是恢复后的图像往往存在较明显的振铃现象,通常为了消除振铃现象,以H(u,v)的值作为判据,如不大于d(0
由于恢复过程需要在频域进行,因此需要通过二维傅立叶变换将图像由空域变换到频域。二维的傅立叶变换较一维傅立叶变换要复杂的多,一般采取连续2次运用一维离散快速傅立叶变换的方法来实现,即先沿f(x,y)的每一个x对y求变换再乘以N得到F(x,v),完成第一步变换。然后再将得到的F(x,v)沿f(x,v)的每一个v对x求变换即可得到f(x,y)的最终变换F(u,v),这两步的数学表达式如下:
F(x,v)=N*[(1/N)*
F(u,v)=(1/N)*
类似也可以得出二维离散傅立叶变换逆变换用一维变换计算的表达式:
F(x,v)=
f(x,y)=(1/N)*
在分布进行一维傅立叶变换时,多采用"蝴蝶图"的快速算法(详见信号处理方面资料),其核心算法如下:
int N=(int)pow(2,M); file://N:序列长度(2的整数次幂)
ReverseOrder(A,N); file://对空间序列进行倒序
for(int i=1;i<=M;i++){
int b=(int)pow(2,(i-1));
for(int j=0;j<=(b-1);j++) {
float p=(float)(pow(2,(M-i))*j*2.0*PI/(float)N);
for(int k=j;k<=(N-1);){
float tr=(float)(A[k+b].Re*cos(p)+A[k+b].Im*sin(p)); file://计算复数运算A*U
float ti=(float)(A[k+b].Im*cos(p)-A[k+b].Re*sin(p));
A[k+b].Re=A[k].Re-tr; file://复数运算A-tr
A[k+b].Im=A[k].Im-ti;
A[k].Re+=tr; file://复数运算A+tr
A[k].Im+=ti;
k+=b*2;
}
}
}
傅立叶逆变换的同傅立叶变换比较相似,只是在计算exp[j2πvy/N]时同正变换有符号的区别,以及在计算完成后逆变换需要将值除以N,因此不难写出一维傅立叶逆变换的实现代码。在进行二维傅立叶变换将图像由空域变换到频域之前,首先需要通过补0的手段将点数非2的整数次幂的非正方型网格采样构造为一个长宽均为2的整数次幂的正方型网格:
int WM=(int)(log(W)/log(2)+1.0f); file://计算图像宽应为2的多少次幂
int HM=(int)(log(H)/log(2)+1.0f); file://计算图像高应为2的多少次幂
WM=HM=max(WM,HM); file://取二者大值
int WN=(int)pow(2,WM); file://构造网格宽度
int HN=(int)pow(2,HM); file://构造网格高度
for{int i=0;i;for(int j=0;j if(i U[i*WN*3+j].Re=D[i*W*3+j]; file://D为图像序列
U[i*WN*3+j].Im=0.0f;
}else file://缺位补0
U[i*WN*3+j].Re=U[i*WN*3+j].Im=0.0f;
}
}
预处理完毕后,可对构造网格的每一列分别进行一维快速傅立叶变换,并将结果存放在原位置,结果乘以N,完成第一步的转换,求得F(x,v):
for(i=0;i for(int j=0;j UH[j].Re=U[j*WN*3+i].Re;
UH[j].Im=U[j*WN*3+i].Im;
}
DFT_FFT(UH,HM); file://对UH进行快速离散傅立叶变换
for(j=0;j U[j*WN*3+i].Re=HN*UH[j].Re; file://N=HN
U[j*WN*3+i].Im=HN*UH[j].Im;
}
}
随即对构造网格的每一行进行傅立叶变换,得到最终的变换结果F(u,v):
for(i=0;i UW[j].Re=U[i*WN*3+j*3+k].Re;
UW[j].Im=U[i*WN*3+j*3+k].Im;
}
DFT_FFT(UW,WM); file://对UW序列进行快速离散傅立叶变换
for(j=0;j U[i*WN*3+j*3+k].Re=UW[j].Re;
U[i*WN*3+j*3+k].Im=UW[j].Im;
}
}
}
至于二维傅立叶逆变换则基本上是上述过程的逆过程,在此就不再赘述。根据逆滤波图像恢复的设计方案,先通过前面的二维傅立叶变换将退化图像g(x,y)从空域变换到频域得到G(u,v),然后在频域经过恢复转移函数M(u,v)的恢复处理并经过二维傅立叶逆变换将结果由频域转换回空域,就可得到经过恢复处理的近似原始图像:
……
dsp.DFT_2D_FFT(m_cpBuffer+54,m_nWidth,m_nHeight,U); file://进行二维傅立叶变换
for(int i=0;i
这里的逆滤波处理算法采用的是经过改进的恢复转移函数M(u,v),因此恢复后的图像不会出现振铃现象。以标准检测图像Lina为处理对象应用以上恢复处理算法,效果如下图所示。其中间图像为未经过改进的简单算法,在胳膊和脸部存在较明显的振铃现象,而采取了改进措施的图像则没有任何振铃现象出现,图像得到了较好的恢复。
维纳(Wiener)滤波是对退化图像进行恢复处理的另一种常用算法,是一种有约束的恢复处理方法,其采用的维纳滤波器是一种最小均方误差滤波器,其数学形式比较复杂:
F(u,v)=[(1/H(u,v))*(|H(u,v)|2)/(|H(u,v)|2+s*[Sn(u,v)/Sf(u,v)])]*G(u,v)
当s为1时,上式就是普通的维纳滤波;如果s为变量,则为参数维纳滤波,如果没有噪声干扰,即Sn(u,v)=0时,上式实际就是前面的逆滤波。从其数学形式可以看出:维纳滤波比逆滤波在对噪声的处理方面要强一些。以上只是理论上的数学形式,在进行实际处理时,往往不知道噪声函数Sn(u,v)和Sf(u,v)的分布情况,因此在实际应用时多用下式进行近似处理:
F(u,v)=[(1/H(u,v))* (|H(u,v)|2)/(|H(u,v)|2+K)]*G(u,v)
其中K是一个预先设定的常数。由此可以写出维纳滤波的实现代码:
……
float K=0.05f; file://预先设定常数K
dsp.DFT_2D_FFT(m_cpBuffer+54,m_nWidth,m_nHeight,U); file://转换到频域
for(int i=0;i int k=(int)(j/3);
D1=(float)sqrt(i*i+k*k);
float H=1.0f/(1+(D1/D0)*(D1/D0));//H(u,v)= 1/(1+(u2+v2)/D02))
U[i*3*WN+j].Re=(U[i*3*WN+j].Re*H)/(H*H+K); file://维纳滤波
U[i*3*WN+j].Im=(U[i*3*WN+j].Im*H)/(H*H+K);
}
}
dsp.DFT_2D_IFFT(m_cpBuffer+54,m_nWidth,m_nHeight,U);//返回到空域
对经过退化的Lina图像应用维纳滤波处理,可得到如右图所示的恢复效果图。由于维纳滤波在进行恢复时对噪声进行了处理,因此其恢复效果要比逆滤波要好,尤其是退化图像的噪声干扰较强时效果更为明显。
本文对比较常用的两种图像恢复算法逆滤波和维纳滤波的实现过程作了较为详细的讲述,通过对图像质量较低的退化图像应用上述算法可以使图像质量得到一定程度的改善,在视觉上可以得到较好的改观。类似的图像恢复算法还有有约束最小平方恢复算法等多种,应视具体情况灵活选择合适的算法以获取最佳的恢复效果。本文所述程序在Windows 98下,由Microsoft Visual C++ 6.0编译通过。
计算机图像处理系统从系统层次上可分为高、中、低档三个层次,目前一般比较普及的是低档次的系统,该系统由CCD(摄像头)、图像采集卡、计算机三个部分组成,其结构简单,应用方便,效果也比较不错,得到的图像较清晰。目前网上基于VC开发经验的文章不少,可是关于如何在VC开发平台上使用图像采集卡的文章确没发现,笔者针对在科研开发中积累的使用图像采集卡经验,介绍如何自己是如何将采集卡集成到图像开发系统中,希望能够给目前正需要利用图像采集卡开发自己的图像处理系统的朋友有所帮助。
笔者使用的摄像机采用台湾BENTECH INDUSTRIAL 有限公司生产的CV-155L黑白摄像机。该摄像机分辨率为752x582。图象采集卡我们采用北京中科院科技嘉公司开发的基于PCI 总线的CA-MPE 1000 黑白图象采集卡。使用图像采集卡分三步,首先安装采集卡的驱动程序,并将虚拟驱动文件VxD.vxd拷贝到Windows的SYSTEM目录下;这时候就可以进入开发状态了,进入VC开发平台,生成新的项目,由于生产厂家为图像采集卡提供了以mpew32.dll、mpew32.lib命名的库文件,库中提供了初始硬件、采集图像等函数,为使用这些函数,在新项目上连接该动态库;最后一步就是采集图像并显示处理了,这一步要设置系统调色板,因为采集卡提供的是裸图形式,既纯图像数据,没有图像的规格和调色板信息,这些需要开发者自己规定实现,下面是实现的部分代码:
CTestView::CTestView()
{
W32_Init_MPE1000();//初始化采集卡
W32_Modify_Contrast(50);//下面的函数是为了对采集卡进行预设置
W32_Modify_Brightness(45);//设置亮度
W32_Set_HP_Value(945);//设置水平采集点数
wCurrent_Frame = 1;//当前帧为1,获取的图像就是从这帧取得的
// 设置采集信号源,仅对MPE1000有效
W32_Set_Input_Source(1);
W32_CACardParam(AD_SETHPFREQ,hpGrabFreq);
W32_Set_PAL_Range(1250, 1024);//设置水平采集范围
W32_Set_VGA_Mode ( 1 );
wGrabWinX1 = 0; // 采集窗口的左上角的坐标
wGrabWinY1 = 0;
firstTime=TRUE;
bGrabMode = FRAME;
bZipMode = ZIPPLE;
/
lpDib=NULL;//存放获取的图像数据
}
CTestView::~CTestView()
{
W32_Close_MPE1000();//关闭采集卡
}
////显示采集的图象,双击鼠标采集停止
void CTestView::OnGraboneframe()
{
// TODO: Add your command handler code here
wCurrent_Frame = 1;
// 设置采集目标为内存
W32_CACardParam (AD_SETGRABDEST, CA_GRABMEM);
// 启动采集
if (lpDib != NULL)
{
GlobalUnlock( hglbDIB );
GlobalFree( hglbDIB );
}
// 分配内存
hglbDIB=GlobalAlloc(GHND, (DWORD)wImgWidth*(DWORD)wImgHeight );
lpDib = (BYTE *)GlobalLock( hglbDIB );
hdc = GetDC()->GetSafeHdc( ) ;
if(lpDib != NULL)
{
cxDib = wImgWidth;
cyDib = wImgHeight;
SetLogicPal( hdc, cxDib, cyDib, 8 );
SetStretchBltMode (hdc, COLORONCOLOR) ;
bGrabMark = TRUE;
while (bGrabMark == TRUE)
{
if(msg.message==WM_LBUTTONDBLCLK)
bGrabMark = FALSE;
W32_ReadXMS2Buf (wCurrent_Frame,lpDib) ;
SetDIBitsToDevice (hdc, 0, 0, cxDib, cyDib, 0, 0,
0, cyDib, (LPSTR) lpDib,
bmi,
DIB_RGB_COLORS) ;
}
// 停止采集
W32_CAStopCapture();
::ReleaseDC( GetSafeHwnd(), hdc );
return ;
}
////将下面这个函数添加在视图类的CTestView::OnSize()函数中,就可以对系统的调色板进行设置。
void WINAPI InitLogicPal( HDC hdc , short width, short height, WORD bitCount )
{
int j, i;
short cxDib, cyDib;
LOGPALETTE * pLogPal;
j=256 ;
if ((pLogPal=(LOGPALETTE *)malloc(sizeof(LOGPALETTE) + (j*sizeof(PALETTEENTRY)))) == NULL)
return ;
pLogPal->palVersion=0x300;
pLogPal->palNumEntries=j;
for (i=0;i pLogPal->palPalEntry
pLogPal->palPalEntry
pLogPal->palPalEntry
pLogPal->palPalEntry
}
hPal = ::CreatePalette(pLogPal);
delete pLogPal;
::SelectPalette(hdc,hPal,0);
::RealizePalette(hdc);
cxDib = width; cyDib = height;
if ( (bmi = (BITMAPINFO *)malloc(sizeof(BITMAPINFOHEADER) + j*sizeof(RGBQUAD))) == NULL )
return ;
//bmi为全局变量,用于显示图像时用
bmi->bmiHeader.biSize = 40;
bmi->bmiHeader.biWidth = cxDib;
bmi->bmiHeader.biHeight = cyDib;
bmi->bmiHeader.biPlanes = 1 ;
bmi->bmiHeader.biBitCount = bitCount ;
bmi->bmiHeader.biCompression = 0 ;
bmi->bmiHeader.biSizeImage = 0 ;
bmi->bmiHeader.biXPelsPerMeter = 0;
bmi->bmiHeader.biYPelsPerMeter = 0;
bmi->bmiHeader.biClrUsed = 0;
bmi->bmiHeader.biClrImportant = 0;
for (i=0;i bmi->bmiColors
bmi->bmiColors
bmi->bmiColors
bmi->bmiColors
}
}
"画中画"这个概念类似与彩色电视机"画中画",就是在一幅大的图像内显示另外一幅内容不同的小的图像,小图像的尺寸大小一般地说为大图像尺寸的1/4或1/9,显示位置在大图像的右上角。这种技术不仅在电视技术中,在可视电话系统也可以发现这种技术的身影,它们都是依靠硬件来实现的,但是如何在VC开发平台上用编程语言来将该功能添加到自己开发的视频监控软件,为使用者提供更大的信息量呢?也许读者最容易想到的是首先显示大图像,然后再在一个固定位置画第二幅小图像,这种技术技术如果对于静止图像当然没有问题,但是对于视频流,由于每一秒钟需要画25幀,即25幅图像,这样一来计算机需要不停的画不停的擦除,会给用户以闪烁的感觉,如何解决这个问题呢?有的参考书上将大小图像分快显示,这种方法要将待显示的图像数据与显示位置的关系对应起来,容易出错不说,而且麻烦,且速度慢,为此,我对该方法进行了改进,得到了满意的效果。实现的代码如下:
void pictureinpicture( )
{
………………………..
CBitmap bitmap,*oldmap;
pData1=(BYTE*)new char[biWidth*biHeight *3];//biWidth和biHeight为视频采集卡获取//的图像尺寸。
Read(pData1,bih.biWidth*bih.biHeight *3);//该函数从采集卡中获取数据
CClientDC dc(this);
m_pBMI1= new BITMAPINFO;//自定义的BMP文件信息结构,用于后面的图像显示
m_pBMI1->bmiHeader.biBitCount=24;
m_pBMI1->bmiHeader.biClrImportant=0;
m_pBMI1->bmiHeader.biClrUsed=0;
m_pBMI1->bmiHeader.biCompression=0;
m_pBMI1->bmiHeader.biHeight=biHeight;
m_pBMI1->bmiHeader.biPlanes=1;
m_pBMI1->bmiHeader.biSize=40;
m_pBMI1->bmiHeader.biSizeImage=WIDTHBYTES(biWidth*8)*biHeight*3;
m_pBMI1->bmiHeader.biWidth=biWidth;
m_pBMI1->bmiHeader.biXPelsPerMeter=0;
m_pBMI1->bmiHeader.biYPelsPerMeter=0;
////////////////////////////////////////////////////////////////////////
pData2=(BYTE*)new char[biWidth1*biHeight1 *3];//申请存放小图像的缓冲区
Read(pData2,biWidth1*biHeight1 *3);////向该缓冲区读数据
m_pBMI2= new BITMAPINFO;
m_pBMI2->bmiHeader.biBitCount=24;
m_pBMI2->bmiHeader.biClrImportant=0;
m_pBMI2->bmiHeader.biClrUsed=0;
m_pBMI2->bmiHeader.biCompression=0;
m_pBMI2->bmiHeader.biHeight=biHeight1;
m_pBMI2->bmiHeader.biPlanes=1;
m_pBMI2->bmiHeader.biSize=40;
m_pBMI2->bmiHeader.biSizeImage=WIDTHBYTES(biWidth1*8)*biHeight1*3;
m_pBMI2->bmiHeader.biWidth=biWidth1;
m_pBMI2->bmiHeader.biXPelsPerMeter=0;
m_pBMI2->bmiHeader.biYPelsPerMeter=0;
//下面实现画中画的显示
CDC MemDc;
MemDc.CreateCompatibleDC(&dc);
bitmap.CreateCompatibleBitmap(&dc,biWidth,biHeight);
oldmap=MemDc.SelectObject(&bitmap);
::StretchDIBits(MemDc.m_hDC,0,0,biWidth,biHeight,0,0,—biWidth,biHeight,pData1,m_pBMI1,DIB_RGB_COLORS,SRCCOPY);//首先将大图像画在内寸上下文中
::StretchDIBits(MemDc.m_hDC,20,20,biWidth1,biHeight1,_
0,0,biWidth1,biHeight1,pData2,m_pBMI2,DIB_RGB_COLORS,SRCCOPY);//再将小图像画在内寸上下文中
::StretchBlt(dc.m_hDC,0,0,bih.biWidth,bih.biHeight,_
MemDc.m_hDC,0,0,bih.biWidth,bih.biHeight,SRCCOPY);//将结果显示在屏幕上。
MemDc.SelectObject(oldmap);
delete pData1;
delete m_pBMI1;
delete pData2;
delete m_pBMI2;
}