• Visual C++实现数字图像增强处理


    前言

      对于一个图像处理系统来说,可以将流程分为三个阶段,在获取原始图像后,首先是图像预处理阶段、第二是特征抽取阶段、第三是识别分析阶段。图像预处理阶段尤为重要,如果这阶段处理不好,后面的工作根本无法展开。
      
       在实际应用中,我们的系统获取的原始图像不是完美的,例如对于系统获取的原始图像,由于噪声、光照等原因,图像的质量不高,所以需要进行预处理,以有利 于提取我们感兴趣的信息。图像的预处理包括图像增强、平滑滤波、锐化等内容。图像的预处理既可以在空间域实现,也可以在频域内实现,我们主要介绍在空间域 内对图像进行点运算,它是一种既简单又重要的图像处理技术,它能让用户改变图像上像素点的灰度值,这样通过点运算处理将产生一幅新图像。下面我们开始介绍 与图像点运算的相关知识。

      一、图像的直方图

       图像直方图是图像处理中一种十分重要的图像分析工具,它描述了一幅图像的灰度级内容,任何一幅图像的直方图都包含了丰富的信息,它主要用在图象分割,图 像灰度变换等处理过程中。从数学上来说图像直方图是图像各灰度值统计特性与图像灰度值的函数,它统计一幅图像中各个灰度级出现的次数或概率;从图形上来 说,它是一个二维图,横坐标表示图像中各个像素点的灰度级,纵坐标为各个灰度级上图像各个像素点出现的次数或概率。如果不特别说明,本讲座中的直方图的纵 坐标都对应着该灰度级在图像中出现的概率。我们的例子是在一个对话框中显示一个图像的直方图,为实现该目的,我们定义了一个名为"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[i]*30));
      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[i]=0;
     }
     file://统计各个灰度级出现的次数;
     for(i=0;i
     {
      for(j=0;j
      {
       a[*(lpDIBBits+WIDTHBYTES(wImgWidth*8)*i+j)]++;
      }
      file://统计各个灰度级出现的概率;
      for(i=0;i<256;i++)
      {
       a[i]=a[i]/(wImgHeight*wImgWidth);//得到每个灰度级的出现概率;
       memcpy(dialog.num,a,256*sizeof(float));
      }
     }
      dialog.DoModal();//显示直方图对话框;
     }
     return;
    }




    (a)LENA图像

    (b)直方图
                         图一

      上图为LENA的原始图像和其对应的直方图,在(b)图中的135表示当前鼠标在直方图中所指的位置对应的灰度级为135。从该直方图可以看出,LENA图像的灰度主要分布在中高灰度级上,在低灰度级上图像的像素数几乎为零。

        

    二、图像增强

      影响系 统图像清晰程度的因素很多,例如室外光照度不够均匀就会造成图像灰度过于集中;由CCD(摄像头)获得的图像经过A/D(数/模转换,该功能在图像系统中 由数字采集卡来实现)转换、线路传送都会产生噪声污染等等。因此图像质量不可避免的降低了,轻者表现为图像不干净,难于看清细节;重者表现为图像模糊不 清,连概貌也看不出来。因此,在对图像进行分析之前,必须要对图像质量进行改善,一般情况下改善的方法有两类:图像增强和图像复原。图像增强不考虑图像质 量下降的原因,只将图像中感兴趣的特征有选择的突出,而衰减不需要的特征,它的目的主要是提高图像的可懂度。图像增强的方法分为空域法和频域法两类,空域 法主要是对图像中的各个像素点进行操作;而频域法是在图像的某个变换域内,对图像进行操作,修改变换后的系数,例如付立叶变换、DCT变换等的系数,然后 再进行反变换得到处理后的图像。图像复原技术与增强技术不同,它需要了解图像质量下降的原因,首先要建立"降质模型",再利用该模型,恢复原始图像。本期 讲座我们主要介绍各种增强技术在图象处理系统中的实际应用。

      1.灰度变换

      简单的说,灰度变换就是指对图像上各个像素点的灰度值x按某个函数T()变换到y。例如为了提高图像的清晰度,需要将图像的灰度级整个范围或其中某一段(A,B)扩展或压缩到(A,B);需要显示出图像的细节部分等都要求采用灰度变换方法。灰度变换有时又被称为图像的对比度增强或对比度拉伸。假定输入图像中的一个像素的灰度级为Z,经过T(Z)函数变换后输出图像对应的灰度级为Z ,其中要求Z和Z 都要在图像的灰度范围之内。根据T()形式,可以将灰度变换分为线性变换和非线性变换。具体应用中采用何种T(),需要根据变换的要求而定。

       对于图像的灰度变换,我们这里介绍一种稍微复杂一点的方法,既直方图均衡化。直方图均衡化是灰度变换的一个重要应用,广泛应用在图像增强处理中,它是以 累计分布函数变换为基础的直方图修正法,可以产生一幅灰度级分布具有均匀概率密度的图像,扩展了像素的取值动态范围。若像素点的原灰度为R,变换后的灰度 为S,需要注意的是R、S是归一化后的灰度值,其灰度变换函数T()为:

    S=T (R); k=0,1…, ;

      式中, 是第j级灰度值的概率, 是图像中j级灰度的像素总数, 是图像中灰度级的总数目, 是图象中像素的总数。对变换后的S值取最靠近的一个灰度级的值,建立灰度级变换表,将原图像变换为直方图均衡的图像。下面是实现图像直方图均衡化函数的源代码和效果图:

    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[i]=0.0f;
      p[i]=0.0f;
      p1[i]=0.0f;
     }
     file://num[]存放图象各个灰度级出现的次数;
     int Height=lpDIBHdr->biHeight;
     int Width=lpDIBHdr->biWidth;
     for(i=0;i
      for(j=30;j
      {
       num[*(lpDIBBits+WIDTHBYTES(Width*8)*i+j)]++;
      }
      file://p[]存放图像各个灰度级的出现概率;
      for(i=0;i<256;i++)
      {
       p[i]=num[i]/(Width*Height);
      }
      file://p1[]存放各个灰度级之前的概率和,用于直方图变换;
      for(i=0;i<256;i++)
      {
       for(k=0;k<=i;k++)
       p1[i]+=p[k];
      }
      file://直方图变换;
      for(i=0;i
      for(j=30;j
      {   *(lpDIBBits+WIDTHBYTES(Width*8)*i+j)=(BYTE)(p1[*(lpDIBBits+WIDTHBYTES(Width*8)*i+j)]*255+0.5);
      }
      StretchDIBits (hDC,0,0,lpDIBHdr->biWidth,lpDIBHdr->biHeight,0,0,
             lpDIBHdr->biWidth,lpDIBHdr->biHeight,
             lpDIBBits,(LPBITMAPINFO)lpDIBHdr,
             DIB_RGB_COLORS,
             SRCCOPY);//显示图像;
    }





    (a)LENA原图
      
    (b)直方图均衡化后的效果图


    (c)原始图象的直方图
     
    (d)均衡化后的直方他图

                          图 二

      从上述效果图可以看出,经过直方图均衡化处理后,图像变的清晰了,从直方图来看,处理后的LENA的图像直方图分布更均匀了,在每个灰度级上图像都有像素点。但是直方图均衡化存在着两个缺点:

      1)变换后图像的灰度级减少,某些细节消失;

      2)某些图像,如直方图有高峰,经处理后对比度不自然的过分增强。

      为此M.Kamel和Lian Guan等人从图像相邻像素一般高度相关这一事实出发,将灰度概率分布和空间相关性联系在一起,提出了用二维条件概率密度函数取代一维概率密度函数作为均衡化条件,很好的解决了这个问题,有兴趣的朋友可以参阅一些图像处理书籍和资料。
         
    2.图像平滑

      图像平滑主要是为了消除噪声。噪声并不限于人眼所能看的见的失真和 变形,有些噪声只有在进行图像处理时才可以发现。图像的常见噪声主要有加性噪声、乘性噪声和量化噪声等。图像中的噪声往往和信号交织在一起,尤其是乘性噪 声,如果平滑不当,就会使图像本身的细节如边界轮廓、线条等变的模糊不清,如何既平滑掉噪声有尽量保持图像细节,是图像平滑主要研究的任务。

       一般来说,图像的能量主要集中在其低频部分,噪声所在的频段主要在高频段,同时系统中所要提取的汽车边缘信息也主要集中在其高频部分,因此,如何去掉高 频干扰又同时保持边缘信息,是我们研究的内容。为了去除噪声,有必要对图像进行平滑,可以采用低通滤波的方法去除高频干扰。图像平滑包括空域法和频域法两 大类,在空域法中,图像平滑的常用方法是采用均值滤波或中值滤波,对于均值滤波,它是用一个有奇数点的滑动窗口在图像上滑动,将窗口中心点对应的图像像素 点的灰度值用窗口内的各个点的灰度值的平均值代替,如果滑动窗口规定了在取均值过程中窗口各个像素点所占的权重,也就是各个像素点的系数,这时候就称为加 权均值滤波;对于中值滤波,对应的像素点的灰度值用窗口内的中间值代替。实现均值或中值滤波时,为了简便编程工作,可以定义一个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)的灰度。由于该算法的实现代码和上述代码大同小异,所以代码部分就不再赘述。
        
    3.图像锐化

      图像平滑往往使图像中的边界、轮廓变的模糊,为了减少这类不利效果 的影响,这就需要利用图像鋭化技术,使图像的边缘变的清晰。图像銳化处理的目的是为了使图像的边缘、轮廓线以及图像的细节变的清晰,经过平滑的图像变得模 糊的根本原因是因为图像受到了平均或积分运算,因此可以对其进行逆运算(如微分运算)就可以使图像变的清晰。从频率域来考虑,图像模糊的实质是因为其高频 分量被衰减,因此可以用高通滤波器来使图像清晰。

      为了要把图像中间任何方向伸展的的边缘和轮廓线变得清晰,我们希望对图像的某种运算是各向同性的。可以证明偏导平方和的运算是各向同性的,既:


      式中( )是图像旋转前的坐标,( )是图像旋转后的坐标。梯度运算就是在这个式子的基础上开方得到的。图像(x,y)点的梯度值:


      为了突出物体的边缘,常常采用梯度值的改进算法,将图像各个点的梯度值与某一阈值作比较,如果大于阈值,该像素点的灰度用梯度值表示,否则用一个固定的灰度值表示。

      我们在对图像增强的过程中,采用的是一种简单的高频滤波增强方法:


      式中f,g分别为锐化前后的图像, 是与扩散效应有关的系数。 表示对图像f进行二次微分的拉普拉斯算子。这表明不模糊的图像可以由模糊的图像减去乘上系数的模糊图像拉普拉斯算子来得到。 可以用下面的模板H={{1,4,1},{4,-20,4},{1,4,1}}来近似。在具体实现时,上述模板H中的各个系数可以改变, 这个系数的选择也很重要,太大了会使图像的轮廓过冲,太小了则图像锐化不明显。实验表明, 选取2-8之间往往可以达到比较满意的效果。下面给出 等于4的情况下的实现代码和效果图:

    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
    for(j=1;j
    {
    sum=0;
    for(m=-1;m<2;m++)
    for(n=-1;n<2;n++) sum+=*(lpDIBBits+WIDTHBYTES(Width*8)*(i+m)+j+n)*a[1+m][1+n];
    if(sum<0) sum=0;
    if(sum>255) sum=255;
    *(pData1+WIDTHBYTES(Width*8)*i+j)=sum;
    }
    file://原始图像pData减去拉普拉斯滤波处理后的图像pData1
    for(i=0;i
    for(j=0;j
    { sum=(int)(*(lpDIBBits+WIDTHBYTES(Width*8)*i+j)-4*(*(pData1+WIDTHBYTES(Width*8)*i+j)));
    if(sum<0) sum=0;
    if(sum>255) sum=255;
    *(lpDIBBits+WIDTHBYTES(Width*8)*i+j)=sum;
    }
    StretchDIBits (hDC,0,0,lpDIBHdr->biWidth,lpDIBHdr->biHeight,0,0,
    lpDIBHdr->biWidth,lpDIBHdr->biHeight,
    lpDIBBits,(LPBITMAPINFO)lpDIBHdr,
    DIB_RGB_COLORS,
    SRCCOPY);
    }






    (a)LENA原图

    (b)拉普拉斯锐化图

                        图 五

      本文主要讲解了图像直方图的基本概念和图像点处理运算中的增强、平滑、锐化概念和实现算法,并给处理实现代码和处理效果图和广大读者朋友们交流,希望达到抛砖引玉的作用。

  • 相关阅读:
    windows开启PostgreSQL数据库远程访问
    Git使用介绍
    linux 常用工具记录及简介
    ubuntu18 安装坑点记录(华硕飞行堡垒)
    快手自动视频随机点赞脚本
    接触手机脚本编程------基于触动精灵的lua编程
    使电脑蜂鸣器发声小脚本
    tensorflow--非线性回归
    python笔记--------numpy
    python笔记--------二
  • 原文地址:https://www.cnblogs.com/nsnow/p/2064906.html
Copyright © 2020-2023  润新知