Visual C++数字图像模式识别技术详解
上QQ阅读APP看书,第一时间看更新

2.2 Visual C++数字图像处理

本节将在2.1节基础上介绍如何用Visual C++进行数字图像处理应用程序的开发。重点介绍Windows下BMP图像文件格式,以及如何用Visual C++对该数字图像文件进行读取,为后续内容打下基础。

2.2.1 BMP图像文件

BMP位图文件格式是Windows系统交换图像数据的一种标准图像文件存储格式,在Windows环境下运行的所有图像处理软件都支持这种格式。Windows 3.0以前的BMP位图文件格式与显示设备有关,因此把它称为设备相关位图(Device-dependent Bitmap,DDB)文件格式。Windows 3.0以后的BMP位图文件格式与显示设备无关,因此把这种BMP位图文件格式称为设备无关位图(Deviceindependet Bitmap,DIB)格式,目的是为了让Windows能够在任何类型的显示设备上显示BMP位图文件。BMP位图文件默认的文件扩展名是bmp。

BMP位图文件是由4个部分组成:位图文件头(Bitmapfile Header)、位图信息头(Bitmap-information Header)、调色板(Palette)和像素数据(Image Data)。如图2-15所示。

图2-15 BMP图像文件结构

1.位图头文件

Visual C++中用BITMAPFILEHEADER数据结构定义位图头文件,它包含文件类型、文件大小和存放位置等信息,结构如下。

typedef struct  tagBITMAPFILEHEADER{
    WORD             bfType;        /*说明文件的类型*/
    DWORD            bfSize;        /*说明文件的大小,用字节为单位*/
    WORD             bfReserved1;   /*保留,设置为0*/
    WORD             bfReserved2;   /*保留,设置为0*/
    DWORD            bfOffBits;     /*说明从BITMAPFILEHEADER结构开始到实际图
                                    像数据阵列字节间的字节偏移量*/
}BITMAPFILEHEADER;

这个结构的长度是固定的,为14个字节,其中WORD为无符号16位二进制数,DWORD为无符号32位二进制整数。

2.位图信息头

Visual C++中用BITMAPINFOHEADER数据结构定义位图信息头,它包含位图的大小、压缩类型和颜色格式等信息,其结构定义如下。

typedefstruct BITMAPINFOHEADER{
    DWORD         biSize;         /*BITMAPINFOHEADER结构所需要的字节数*/
    LONG          biWidth;        /*图像的宽度,以像素为单位*/
    LONG          biHeight;       /*图像的高度,以像素为单位*/
    WORD          biPlanes;       /*目标设备位平面数,其值设置为l*/
    WORD          biBitCount;     /*每像素位数,为1、4、8或24*/
    DWORD         biCompression;  /*压缩类型,0为不压缩*/
    DWORD         biSizeImage;    /*压缩图像大小的字节数,非压缩图像为0*/
    LONG          biXPelsPerMeter;/*水平分辨率*/
    LONG          biYPelsPerMeter;/*垂直分辨率*/
    DWORD         biClrUsed;      /*使用的色彩数*/
    DWORD         biClrImportant; /*重要色彩数,0表示都重要*/
} BITMAPINFOHEADER;

3.调色板

Visual C++中,调色板实际上定义为一个数组,共有biClrUsed个元素,每个元素的类型是一个RGBQUAD结构,其定义如下。

typedefstruct tagRGBQUAD{
    BYTE           rgbBlue;      /*指定蓝色分量*/
    BYTE           rgbGreen;     /*指定绿色分量*/
    BY7E           rgbRed;       /*指定红色分量*/
    BYgE           rgbReserved;  /*保留值*/
}RGBQUAD;

对于24位真彩色图像不使用调色板,因为位图中的RGB值就代表了每个像素的颜色。因此BITMAPINFOHEADER后直接是像素数据。

4.像素数据

紧跟在调色板之后的是图像数据字节阵列,用BYTE数据结构存储。图像的每一扫描行由表示图像的连续像素字节组成,每一行的字节数取决于图像的颜色数目和用像素表示的图像宽度。扫描行是由底向上存储的,这就是说,数据存放是从下到上,从左到右。从文件中最先读到的图像数据是位图最下面的左边的第一个像素,然后是左边的第二个像素,而最后读到的图像数据是位图最上面一行的最右边的一个像素。

2.2.2 位图文件读取

本节将实现在多文档应用程序中打开一个位图文件的功能。这里将用到Visual C++提供的另一个强大工具ClassWizard。

【例2-2】位图文件的读取

本例的目的是讲解如何基于Visual C++ 6.0进行图像处理应用程序的开发。这里构造了一个标准图像处理类并以打开一幅数字图像为例解释了如何基于该类构造图像处理应用程序。

(1)文档类中添加成员函数

设计步骤

[1]在Visual C++集成开发环境(IDE)的【View】菜单中选择【ClassWizard】,在操作类名(Class name)中选择文档类“CDemo1Doc”,操作对象(object IDs)选择“CDemo1Doc”,对应消息(Messages)选择“OnOpenDocument”,该消息在点击应用程序中“文件”菜单的“打开”菜单项时产生。该过程如图2-16所示。

[2]点击添加函数【Add Function】按钮,在类C Demo1Doc加入OnOpenDocument成员函数,该函数在程序中出现“OnOpenDocument”消息时执行。该过程如图2-16所示。

图2-16 类编辑对话框

该成员函数默认生成代码如下。

BOOL CDemo1Doc::OnOpenDocument(LPCTSTR lpszPathName)
{
    if(!CDocument::OnOpenDocument(lpszPathName))
        return FALSE;
        // TODO: Add your specialized creation code here
        return TRUE;
}

(2)数字图像处理类的创建

设计步骤

由于MFC没有提供合适的数字图像处理类,因此,需要定义一个用于数字图像处理的新类。

[1]首先在Visual C++集成开发环境(IDE)的【Insert】菜单选择创建新类【New Class】。新类类型【Class type】选择一般类“Generic Class”。类名【Name】框键入“ImageDib”。

[2]单击【ok】按钮即可生成该类。该过程如图2-17所示。

图2-17 生成新类对话框

[3]在“ImageDib”类的头文件“ImageDib.h”中编辑该类的结构如下。

class ImageDib
{
//成员变量
public:
    unsigned char * m_pImgData;                      //图像数据指针
    LPRGBQUAD m_lpColorTable;                        //图像颜色表指针
    int m_nBitCount;                                 //每像素占的位数
private:
    LPBYTE m_lpDib;                                  //指向DIB的指针
    HPALETTE m_hPalette;                             //逻辑调色板句柄
    int m_nColorTableLength;                         //颜色表长度(多少个表项)
public:
    int m_imgWidth;                                  //图像的宽,像素为单位
    int m_imgHeight;                                 //图像的高,像素为单位
    LPBITMAPINFOHEADER m_lpBmpInfoHead;              //图像信息头指针
//成员函数
public:
    ImageDib();                                      //构造函数
    ~ImageDib();                                     //析构函数
    BOOL Read(LPCTSTR lpszPathName);                 //DIB读函数
    BOOL Write(LPCTSTR lpszPathName);                //DIB写函数
    int ComputeColorTabalLength(int nBitCount);      //计算颜色表的长度
    BOOL Draw(CDC* pDC,CPoint origin,CSize size);  //图像绘制
    CSize GetDimensions();                           //读取图像维数
    void ReplaceDib(CSize size,int nBitCount,LPRGBQUAD lpColorTable,
    unsigned char *pImgData);                        //用新的数据替换DIB
private:
    void MakePalette();                              //创建逻辑调色板
    void Empty();                                    //清理空间
};

[4]在“ImageDib”类的代码文件“ImageDib.cpp”中编辑该类中函数的定义如下。

ImageDib::ImageDib()
{
    m_lpDib=NULL;                         //初始化m_lpDib为空
    m_lpColorTable=NULL;                  //颜色表指针为空
    m_pImgData=NULL;                      //图像数据指针为空
    m_lpBmpInfoHead=NULL;                 //图像信息头指针为空
    m_hPalette = NULL;                    //调色板为空
}
ImageDib::~ImageDib()
{
    //释放m_lpDib所指向的内存缓冲区
    if(m_lpDib != NULL)
    delete [] m_lpDib;
    //如果有调色板,释放调色板缓冲区
    if(m_hPalette != NULL)
    ::DeleteObject(m_hPalette);
}
BOOL ImageDib::Read(LPCTSTR lpszPathName)
{
    //读模式打开图像文件
    CFile file;
    if(!file.Open(lpszPathName,CFile::modeRead | CFile::shareDenyWrite))
            return FALSE;
    BITMAPFILEHEADER bmfh;
    //读取BITMAPFILEHEADER结构到变量bmfh中
    int nCount=file.Read((LPVOID) &bmfh,size of(BITMAPFILEHEADER));
    //为m_lpDib分配空间,读取DIB进内存
    if(m_lpDib!=NULL)delete []m_lpDib;
    m_lpDib=new BYTE[file.GetLength() -size of(BITMAPFILEHEADER)];
    file.Read(m_lpDib,file.GetLength() -size of(BITMAPFILEHEADER));
    //m_lpBmpInfoHead位置为m_lpDib起始位置
    m_lpBmpInfoHead = (LPBITMAPINFOHEADER)m_lpDib;
    //为成员变量赋值
    m_imgWidth=m_lpBmpInfoHead->biWidth;
    m_imgHeight=m_lpBmpInfoHead->biHeight;
    m_nBitCount=m_lpBmpInfoHead->biBitCount;
    //计算颜色表长度
    m_nColorTableLength= ComputeColorTabalLength(m_lpBmpInfoHead->biBitCount);
    //如果有颜色表,则创建逻辑调色板
    m_hPalette = NULL;
    if(m_nColorTableLength!=0){m_lpColorTable=
    (LPRGBQUAD)(m_lpDib+size of(BITMAPINFOHEADER));
        MakePalette();
    }
    //m_pImgData指向DIB的位图数据起始位置
    m_pImgData = (LPBYTE)m_lpDib+size of(BITMAPINFOHEADER) +
        size of(RGBQUAD) * m_nColorTableLength;
    return TRUE;
}
BOOL ImageDib::Write(LPCTSTR lpszPathName)
{
    //写模式打开文件
    CFile file;
    if(!file.Open(lpszPathName,CFile::modeCreate | CFile::modeReadWrite
        | CFile::shareExclusive))
        return FALSE;
    //填写文件头结构
    BITMAPFILEHEADER bmfh;
    bmfh.bfType =0x4d42; // 'BM'
    bmfh.bfSize =0;
    bmfh.bfReserved1 = bmfh.bfReserved2 =0;
    bmfh.bfOffBits = size of(BITMAPFILEHEADER) + size of(BITMAPINFOHEADER) +
        size of(RGBQUAD) * m_nColorTableLength;
    try {
        //文件头结构写进文件
        file.Write((LPVOID) &bmfh,size of(BITMAPFILEHEADER));
        //文件信息头结构写进文件
        file.Write(m_lpBmpInfoHead,size of(BITMAPINFOHEADER));
        //如果有颜色表的话,颜色表写进文件
        if(m_nColorTableLength!=0)
            file.Write(m_lpColorTable,size of(RGBQUAD) * m_nColorTableLength);
        //位图数据写进文件
        int imgBufSize=(m_imgWidth*m_nBitCount/8+3)/4*4*m_imgHeight;
        file.Write(m_pImgData,imgBufSize);
    }
    catch(CException* pe) {
        pe->Delete();
        AfxMessageBox("write error");
        return FALSE;
    }
    //函数返回
    return TRUE;
}
void ImageDib::MakePalette()
{
    //如果颜色表长度为0,则不创建逻辑调色板
    if(m_nColorTableLength ==0)
        return;
    //删除旧的逻辑调色板句柄
    if(m_hPalette != NULL) ::DeleteObject(m_hPalette);
    //申请空间,根据颜色表生成LOGPALETTE结构
    LPLOGPALETTE pLogPal = (LPLOGPALETTE) new char[2 * size of(WORD) +
        m_nColorTableLength * size of(PALETTEENTRY)];
    pLogPal->palVersion =0x300;
    pLogPal->palNumEntries = m_nColorTableLength;
    LPRGBQUAD m_lpDibQuad = (LPRGBQUAD) m_lpColorTable;
    for(int i =0; i <m_nColorTableLength; i++) {
        pLogPal->palPalEntry[i].peRed = m_lpDibQuad->rgbRed;
        pLogPal->palPalEntry[i].peGreen = m_lpDibQuad->rgbGreen;
        pLogPal->palPalEntry[i].peBlue = m_lpDibQuad->rgbBlue;
        pLogPal->palPalEntry[i].peFlags =0;
        m_lpDibQuad++;
    }
    //创建逻辑调色板
    m_hPalette = ::CreatePalette(pLogPal);
    //释放空间
    delete pLogPal;
}
int ImageDib::ComputeColorTabalLength(int nBitCount)
{
    int colorTableLength;
    switch(nBitCount) {
    case 1:
        colorTableLength = 2;
        break;
    case 4:
        colorTableLength = 16;
        break;
    case 8:
        colorTableLength = 256;
        break;
    case 16:
    case 24:
    case 32:
        colorTableLength =0;
        break;
    default:
        ASSERT(FALSE);
    }
    ASSERT((colorTableLength >=0) && (colorTableLength <= 256));
    return colorTableLength;
}
BOOL ImageDib::Draw(CDC* pDC,CPoint origin,CSize size)
{
    HPALETTE hOldPal=NULL;             //旧的调色板句柄
    if(m_lpDib == NULL) return FALSE;  //如果DIB为空,则返回0
    if(m_hPalette != NULL) {           //如果DIB有调色板
                                       //将调色板选进设备环境中
        hOldPal=::SelectPalette(pDC->GetSafeHdc(),m_hPalette,TRUE);
        pDC->RealizePalette();
    }
    pDC->SetStretchBltMode(COLORONCOLOR); //设置位图伸缩模式
    //将DIB在pDC所指向的设备上进行显示
    ::StretchDIBits(pDC->GetSafeHdc(),origin.x,origin.y,size.cx,size.cy,
       0,0,m_lpBmpInfoHead->biWidth,m_lpBmpInfoHead->biHeight,m_pImgData,
        (LPBITMAPINFO) m_lpBmpInfoHead,DIB_RGB_COLORS,SRCCOPY);
    if(hOldPal!=NULL)                   //恢复旧的调色板
        ::SelectPalette(pDC->GetSafeHdc(),hOldPal,TRUE);
    return TRUE;
}
CSize ImageDib::GetDimensions()
{
    if(m_lpDib == NULL) return CSize(0,0);
    return CSize(m_imgWidth,m_imgHeight);
    }
void ImageDib::ReplaceDib(CSize size,int nBitCount,
                          LPRGBQUAD lpColorTable,unsigned char *pImgData)
{
    //释放源DIB所占空间
    Empty();
    //成员变量赋值
    m_imgWidth=size.cx;
    m_imgHeight=size.cy;
    m_nBitCount=nBitCount;
    //计算颜色表的长度
    m_nColorTableLength=ComputeColorTabalLength(nBitCount);
    //每行像素所占字节数,扩展成4的倍数
    int lineByte=(m_imgWidth*nBitCount/8+3)/4*4;
    //位图数据的大小
    int imgBufSize=m_imgHeight*lineByte;
    //为m_lpDib重新分配空间,以存放新的DIB
    m_lpDib=new BYTE [size of(BITMAPINFOHEADER) +
        size of(RGBQUAD) * m_nColorTableLength+imgBufSize];
    //填写位图信息头BITMAPINFOHEADER结构
    m_lpBmpInfoHead = (LPBITMAPINFOHEADER) m_lpDib;
    m_lpBmpInfoHead->biSize = size of(BITMAPINFOHEADER);
    m_lpBmpInfoHead->biWidth = m_imgWidth;
    m_lpBmpInfoHead->biHeight = m_imgHeight;
    m_lpBmpInfoHead->biPlanes = 1;
    m_lpBmpInfoHead->biBitCount = m_nBitCount;
    m_lpBmpInfoHead->biCompression = BI_RGB;
    m_lpBmpInfoHead->biSizeImage =0;
    m_lpBmpInfoHead->biXPelsPerMeter =0;
    m_lpBmpInfoHead->biYPelsPerMeter =0;
    m_lpBmpInfoHead->biClrUsed = m_nColorTableLength;
    m_lpBmpInfoHead->biClrImportant = m_nColorTableLength;
    //调色板置空
    m_hPalette = NULL;
    //如果有颜色表,则将颜色表复制到新生成的DIB,并创建逻辑调色板
    if(m_nColorTableLength!=0){
        m_lpColorTable=(LPRGBQUAD)(m_lpDib+size of(BITMAPINFOHEADER));
        memcpy(m_lpColorTable,lpColorTable,size of(RGBQUAD) * m_nColorTableLength);
        MakePalette();
    }
    //m_pImgData指向DIB的位图数据起始位置
    m_pImgData = (LPBYTE)m_lpDib+size of(BITMAPINFOHEADER)+
        size of(RGBQUAD) * m_nColorTableLength;
    //将新位图数据拷贝至新的DIB中
    memcpy(m_pImgData,pImgData,imgBufSize);
}

(3)数字图像显示功能实现

设计步骤

[1]首先,在“CDemo1Doc”类的头文件“Demo1Doc.h”中包含“ImageDib类”的声明文件“ImageDib.h”。

#include "ImageDib.h"

[2]然后,在Visual C++集成开发环境的工作区(Workspace)中选中类“CDemo1Doc”,单击鼠标右键,弹出操作框如图2-18所示。

图2-18 添加类新成员操作框

[3]在弹出的操作框中选中【ADD Member Variable】,弹出类新成员对话框,如图2-19所示。

图2-19 添加类新成员对话框

[4]在变量类型【Variable Type】输入“ImageDib”,变量名称【Variable Name】输入“m_dib”,其他默认。单击【ok】按钮即可完成在“CDemo1Doc”类中添加“ImageDib”类成员变量“m_dib”。

[5]随后对“CDemo1Doc”类的“OnOpenDocument”函数代码重新编写如下。

BOOL CDemo1Doc::OnOpenDocument(LPCTSTR lpszPathName)
{
    if(m_dib.Read(lpszPathName) == TRUE) {
          SetModifiedFlag(FALSE);     // start offwith unmodified
          return TRUE;
    }
    else
          return0;
}

[6]OnOpenDocument函数仅是实现将数字图像读入内存,如果在文档所对应视窗内进行数字图像显示,还需对视窗类C Demo1View的OnDraw函数进行编程。首先在类“CDemo1View”的头文件“Demo1View.h”中包含“ImageDib”类的声明文件“ImageDib.h”。然后对“OnDraw”函数进行编程。

void CDemo1View::OnDraw(CDC* pDC)
{
    CDemo1Doc* pDoc = GetDocument();                  //获取文档类指针
    ImageDib pDib=pDoc->m_dib;                        //返回m_dib的指针
    CSize sizeFileDib = pDib.GetDimensions();         //获取DIB的尺寸
    pDib.Draw(pDC,CPoint(0,0),sizeFileDib);        //显示DIB
}

[7]运行程序;选择【文件】菜单中的【打开】,打开示例图像“demo.bmp”。运行结果如图2-20所示。

图2-20 打开位图文件程序运行结果

2.2.3 图像增强

图像增强是图像模式识别中非常重要的图像预处理过程。图像增强的目的是通过对图像中的信息进行处理,使得有利于模式识别的信息得到增强,不利于模式识别的信息得到抑制,扩大图像中不同物体特征之间的差别,为图像的信息提取及其识别奠定良好的基础。图像增强按实现方法不同可分为点增强、空域增强和频域增强。

1.点增强

点增强主要指图像灰度变换和几何变换。

图像的灰度变换也称为点运算、对比度增强或对比度拉伸,它是图像数字化软件和图像显示软件的重要组成部分。

灰度变换是一种既简单又重要的技术,它能让用户改变图像数据占据的灰度范围。一幅输入图像经过灰度变换后将产生一幅新的输出图像,由输入像素点的灰度值决定相应的输出像素点的灰度值。灰度变换不会改变图像内的空间关系。

图像的几何变换是图像处理中的另一种基本变换。它通常包括图像的平移、图像的镜像、图像的缩放和图像的旋转。通过图像的几何变换可以实现图像的最基本的坐标变换及缩放功能。

(1)图像灰度变换类

灰度变换可以按照预定的方式改变一幅图像的灰度直方图。除了灰度级的改变是根据某种特定的灰度变换函数进行之外,灰度变换可以看作是“从像素到像素”的复制操作。如果输入图像为A(x,y),输出图像为B(x,y),则灰度变换可表示为:B(x,y)=f[A(x,y)]。其中,函数f(D)称为灰度变换函数,它描述了输入灰度值和输出灰度值之间的转换关系。一旦灰度变换函数确定,该灰度变换就完全被确定下来了。

本节设计一个图像灰度变换类,其目的是将关图像的灰度变换操作的所有函数封装到该类中。图像灰度变换的具体成员函数将在后几节中依次介绍。首先介绍该类的创建。

设计步骤

[1]打开2.1.5节创建的工程文件demo1。

[2]在工作区(Workspace)的类视图(Class View)编辑区中,选中【demo classes】后单击鼠标右键并选择【New Class】。添加继承自“ImageDib”类的“GrayTrans”类进行有关图像的灰度变换操作的算法编程与实现,如图2-21所示。

图2-21 添加GrayTrans类示意图

[3]在文件“GrayTrans.h”中定义该类如下。

代码2-3 GrayTrans函数

class GrayTrans:public ImageDib
{
public:
    //输出图像每像素位数
    int m_nBitCountOut;
    //输出图像位图数据指针
    unsigned char * m_pImgDataOut;
    //输出图像颜色表
    LPRGBQUAD m_lpColorTableOut;
    //输出图像的宽,像素为单位
    int m_imgWidthOut;
    //输出图像的高,像素为单位
    int m_imgHeightOut;
    //输出图像颜色表长度
    int m_nColorTableLengthOut;
public:
    //不带参数的构造函数
    GrayTrans();
    //带参数的构造函数
    GrayTrans(CSize size,int nBitCount,LPRGBQUAD lpColorTable,unsigned char *pImgData);
    //析构函数
    ~GrayTrans();
    //以像素为单位返回输出图像的宽和高
    CSize GetDimensions();
    //二值化
    void BinaryImage(int threshold=128);
    //反转
    void RevImage();
    //窗口变换
    void ThresholdWindow(int bTop,int bBottom);
    //分段线性拉伸
    void LinearStrech(CPoint point1,CPoint point2);
private:
    //单通道数据线性拉伸
    void LinearStrechForSnglChannel(unsigned char *pImgDataIn,
         unsigned char *pImgDataOut,int imgWidth,int imgHeight,
         CPoint point1,CPoint point2);
};

[4]将“GrayTrans.h”头文件包含进“demoView.cpp”文件中。

[5]利用资源管理器,在菜单条上加入【灰度变换】菜单及其子菜单【二值化】、【直方图】、【直方图均衡】、【反转】、【阈值变换】、【窗口变换】、【分段线性拉伸】,如图2-22所示。

[6]双击上述菜单项,设置其ID值,如图2-23所示。上述菜单项中,【二值化】ID值定义为id_Binary;【直方图】ID值定义为id_HistogramDraw;【直方图均衡】定义为id_HistgramAver;【反转】ID值定义为id_ImageReverse;【阈值变换】ID值定义为id_ImgThresh;【窗口变换】ID值定义为id_ThresholdWindow;【分段线性拉伸】ID值定义为id_LinearStrecth。

图2-22 灰度变换菜单

图2-23 设置菜单项ID值

各子函数代码的实现可参见本书附录所附程序。

(2)图像几何变换类

本节设计一个图像几何变换类,其目的是将关图像的几何变换操作的所有函数封装到该类中。图像几何变换的具体成员函数将在后几节中依次介绍。首先介绍该类的创建。

设计步骤

[1]打开2.1.5节创建的工程文件demo1。

[2]在工作区(Workspace)的类视图(Class View)编辑区中,选中【demo classes】后单击鼠标右键并选择【New Class】。添加继承自“ImageDib”类的“GeometryTrans”类进行有关图像的几何变换操作的算法编程与实现,如图2-24所示。

[3]在文件“GeometryTrans.h”中定义该类如下。

代码2-4 GeometryTrans函数

class GeometryTrans : public ImageDib
{
public:
    //输出图像每像素位数
    int m_nBitCountOut;
    //输出图像位图数据指针
    unsigned char * m_pImgDataOut;
    //输出图像颜色表
    LPRGBQUAD m_lpColorTableOut;
    //输出图像的宽
    int m_imgWidthOut;
    //输出图像的高
    int m_imgHeightOut;
    //输出图像颜色表长度
    int m_nColorTableLengthOut;
public:
    //构造函数
    GeometryTrans();
    //带参数的构造函数
    GeometryTrans(CSize size,int nBitCount,LPRGBQUAD lpColorTable,unsigned char *pImgData);
    //析构函数
    ~GeometryTrans();
    //以像素为单位返回输出图像的宽和高
    CSize GetDimensions();
    //平移
    void Move(int offsetx,int offsetY);
    //缩放
    void Zoom(float ratiox,float ratioY);//缩放
    //水平镜像
    void MirrorHorTrans();
    //垂直镜像
    void MirrorVerTrans();
    //顺时针旋转90度
    void Clockwise90();
    //逆时针旋转90度
    void Anticlockwise90();
    //旋转180
    void Rotate180();
    //0-360度之间任意角度旋转
    void Rotate(int angle);//angle旋转角度
};

图2-24 添加GeometryTrans类示意图

[4]将“GeometryTrans.h”头文件包含进“demoView.cpp”文件中。

[5]利用资源管理器,在菜单条上加入【几何变换】菜单及其子菜单【平移】、【水平镜像】、【垂直镜像】、【缩放】、【旋转】、【顺时针旋转90度】、【逆时针旋转90度】、【旋转180度】、【任意角度旋转】,如图2-25所示。

图2-25 几何变换菜单

[6]双击上述菜单项,设置其ID值,如图2-26所示。上述菜单项中,【平移】ID值定义为id_Move;【水平镜像】ID值定义为id_HorizontalMirror;【垂直镜像】定义为id_VerticalMirror;【缩放】ID值定义为id_Zoom;【顺时针旋转90度】ID值定义为id_Clockwise90;【逆时针旋转90度】ID值定义为id_Anticlockwise90;【旋转180度】ID值定义为id_Rotate180;【任意角度旋转】ID值定义为id_FreeRotate。

图2-26 设置菜单项ID值

各子函数代码的实现可参见本书附录所附程序。

2.空域增强

一幅数字图像包括光谱、空间、时间等三类基本信息。对于一幅灰度图像,其光谱信息是以像素的灰度值来体现的,对光谱信息的增强可以通过前面介绍的各种灰度变换方法来实现,如直方图均衡化和直方图规定化可以通过改变像素的灰度值以达到信息增强的目的。图像间的差值运算可以提供图像的动态信息(即时间信息)。本节将要介绍对图像的空间信息进行增强的方法。

(1)图像空域增强

图像的空间信息可以反映图像中物体的位置、形状、大小等特征,而这些特征可以通过一定的物理模式来描述。例如,物体的边缘轮廓由于灰度值变化剧烈一般出现高频率特征,而一个比较平滑的物体内部由于灰度值比较均一则呈现低频率特征。因此,根据需要可以分别增强图像的高频和低频特征。对图像的高频增强可以突出物体的边缘轮廓,从而起到锐化图像的作用,例如,对于人脸的比对查询,就需要通过高频增强技术突出五官的轮廓。相应地,对图像的低频部分进行增强可以对图像进行平滑处理,一般用于图像的噪声消除。

图像的空域增强是应用模板卷积方法对每一像素的邻域进行处理完成的,一般可分为线性和非线性两类。无论采用什么样的增强方法,其实现步骤大体相同,具体过程如下:

1)将模板在图像中漫游移动,并将模板中心与每个像素依次重合(边缘像素除外);

2)将模板中的各个系数与其对应的像素一一相乘,并将所有结果相加(或进行其他四则运算);

3)将2)中的结果赋给图像中对应模板中心位置的像素。

图2-27给出了应用模板进行滤波的示意图。2-27a是一幅图像的一小部分,共9个像素,Pi(i=0,1,...,8)表示像素的灰度值。2-27b表示一个3×3的模板,ki(i=0,1,...,8)称为模板系数,模板的大小一般取奇数(如3×3,5×5等)。现将模板在图像中漫游,并使k0与图2-27a所示的P0像素重合,即可由下式计算输出图像(增强图像)中与P0相对应的像素的灰度值r

对每个像素按上式进行计算即可得到增强图像中所有像素的灰度值。

图2-27 空域模板滤波示意图

(2)图像空域增强类

本节设计一个图像空域增强类,其目的是将关图像的空域变换操作的所有函数封装到该类中。图像空域增强的具体成员函数将在后几节中依次介绍。首先介绍该类的创建。

设计步骤

[1]打开2.1.5节创建的工程文件demo1。

[2]在工作区(Workspace)的类视图(Class View)编辑区中,选中【demo classes】后单击鼠标右键并选择【New Class】。添加继承自“ImageDib”类的“ImageEnhance”类进行有关图像的空域增强操作的算法编程与实现,如图2-28所示。

图2-28 添加ImageEnhance类示意图

[3]在文件“ImageEnhance.h”中定义该类如下:

代码2-5 ImageEnhance函数

class ImageEnhance:public ImageDib
{
public:
    int m_nBitCountOut;                  //输出图像每像素位数
    unsigned char * m_pImgDataOut;       //输出图像位图数据指针
    LPRGBQUAD m_lpColorTableOut;         //输出图像颜色表
    int m_nColorTableLengthOut;          //输出图像颜色表长度
public:
    ImageEnhance();                      //构造函数
    ImageEnhance(CSize size,int nBitCount,LPRGBQUAD lpColorTable,
    unsigned char *pImgData);            //带参数的构造函数
    ~ImageEnhance();                     //析构函数
    void NeiAveTemplate(int TempH,int TempW,int TempCx,int TempCY,
    float *fpTempArray,float fCoef);    //采用均值模板进行图像平滑
    //中值滤波
    BYTE FindMedianValue(unsigned char* lpbArray,int iArrayLen);
    void MedianSmooth(int iFilterH,int iFilterW,int iFilterCx,int iFilterCY);
    //拉普拉斯锐化转化为模板运算
    void LapTemplate(int TempH,int TempW,int TempCX,int TempCY,float *fpTempArray,float fCoef);
    //梯度锐化
    void GradeSharp(int Thresh);
    //选择掩模平滑
    void ChooseMaskSmooth();
};

[4]将“ImageEnhance.h”头文件包含进“demoView.cpp”文件中。

[5]利用资源管理器,在菜单条上加入【图像空域增强】菜单及其子菜单【邻域平均】、【中值平均】、【掩模平滑】、【梯度锐化】、【拉普拉斯锐化】,如图2-29所示。

[6]双击上述菜单项,设置其ID值,如图2-30所示。上述菜单项中,【高斯噪声】ID值定义为id_GaussNoise;【椒盐噪声】ID值定义为id_PepperSaltNosie;【邻域平均】ID值定义为id_PowerSmooth;【中值平均】ID值定义为id_MedianSmooth;【掩模平滑】ID值定义为id_ChooseMaskSmooth;【梯度锐化】ID值定义为id_GradeSharp;【拉普拉斯锐化】ID值定义为id_LaplaceSharp。

图2-29 图像空域增强菜单

图2-30 设置菜单项ID值

各子函数代码的实现可参见本书附录所附程序。

3.频域增强

图像空域增强一般只是对数字图像进行局部增强,而图像频域增强则可以对图像进行全局增强。

频域增强技术是在数字图像的频率域空间对图像进行滤波,因此需要将图像从空间域变换到频率域,一般通过傅里叶变换即可实现。在频率域空间的滤波与空域滤波一样可以通过卷积实现,因此傅里叶变换和卷积理论是频域滤波技术的基础。

假定函数f(x,y)与线性位不变算子h(x,y)的卷积结果是g(x,y),即

g(x,y)=h(x,y)* f(x,y)

相应的,由卷积定理可得到下述频域关系:

G(u,v)=H(u,vF(u,v)

其中,GHF分别是函数ghf的傅里叶变换,H(u,v)称为传递函数或滤波器函数。

在图像增强中,图像函数f(x,y)是已知的,即待增强的图像,因此F(u,v)可由图像的傅里叶变换得到。实际应用中,首先需要确定的是H(u,v),然后就可以求得G(u,v),对G(u,v)求傅里叶反变换后即可得到增强的图像g(x,y)。g(x,y)可以突出f(x,y)的某一方面的特征,如利用传递函数H(u,v)突出F(u,v)的高频分量,以增强图像的边缘信息,即高通滤波;反之,如果突出F(u,v)的低频分量,就可以使图像显得比较平滑,即低通滤波。

在介绍具体的滤波器之前,先根据以上的描述给出频域滤波的主要步骤:

1)对原始图像f(x,y)进行傅里叶变换得到F(u,v);

2)将F(u,v)与传递函数H(u,v)进行卷积运算得到G(u,v);

3)将G(u,v)进行傅里叶反变换得到增强图像g(x,y)。

图像频域增强类

本节设计一个图像频域增强类,其目的是将关图像的频域变换操作的所有函数封装到该类中。图像频域增强的具体成员函数将在后几节中依次介绍。首先介绍该类的创建。

设计步骤

[1]打开2.1.5节创建的工程文件demo1。

[2]在工作区(Workspace)的类视图(Class View)编辑区中,选中【demo classes】后单击鼠标右键并选择【New Class】。添加继承自“ImageDib”类的“ImageFreqEnhance”类进行有关图像的频域增强操作的算法编程与实现,如图2-31所示。

图2-31 添加ImageFreqEnhance类示意图

[3]在文件“ImageFreqEnhance.h”中定义该类如下。

代码2-6 ImageFreqEnhance函数

class ImageFreqEnhance:public ImageDib
{
public:
    //输出图像每像素位数
    int m_nBitCountOut;
    //输出图像位图数据指针
    unsigned char * m_pImgDataOut;
    //输出图像颜色表
    LPRGBQUAD m_lpColorTableOut;
    int m_imgWidthOut; //输出图像的宽
    int m_imgHeightOut;//输出图像的高
    int m_nColorTableLengthOut;//输出图像颜色表长度
public:
    //傅里叶变换类对象
    FourierTrans FFtTrans;
public:
    //构造函数
    ImageFreqEnhance();
    //带参数的构造函数
    ImageFreqEnhance(CSize size,int nBitCount,LPRGBQUAD lpColorTable,
        unsigned char *pImgData);
    CSize GetDimensions();        //以像素为单位返回输出图像的宽和高
    void InputImageData(CSize size,int nBitCount,LPRGBQUAD lpColorTable,
        unsigned char *pImgData); //输入原图像数据
    void IdealLowPassFilter(int nWidth,int nHeight,int nRadius);//理想低通滤波
    void ButterLowPassFilter(int nWidth,int nHeight,int nRadius);//巴特沃斯低通滤波
    void IdealHighPassFilter(int nWidth,int nHeight,int nRadius);//理想高通滤波
    void ButterHighPassFilter(int nWidth,int nHeight,int nRadius);//巴特沃斯高通滤波;
    //析构函数
    virtual ~ImageFreqEnhance();
};

[4]将“ImageFreqEnhance.h”头文件包含进“demoView.cpp”文件中。

[5]利用资源管理器,在菜单条上加入【图像频域增强】菜单及其子菜单【快速傅里叶变换】、【快速傅里叶反变换】、【理想低通滤波】、【巴特沃斯低通滤波】、【理想高通滤波】、【巴特沃斯高通滤波】、【哈尔小波变换】、【哈尔小波反变换】,如图2-32所示。

图2-32 图像频域增强菜单

[6]双击上述菜单项,设置其ID值,如图2-33所示。上述菜单项中,【快速傅里叶变换】ID值定义为id_QuickFFt;【快速傅里叶反变换】ID值定义为id_QuickFFt_Reverse;【理想低通滤波】ID值定义为id_IdealLowPass;【巴特沃斯低通滤波】ID值定义为id_ButterLowPass;【理想高通滤波】ID值定义为id_IdealHighPass;【ButterWorth高通滤波】ID值定义为id_ButterHighPass;【哈尔小波变换】ID值定义为id_HarrWaveletTrans;【哈尔小波反变换】ID值定义为id_HarrWavRevTrans。

各子函数代码的实现可参见本书附录所附程序。

图2-33 设置菜单项ID值

2.2.4 图像形态学处理

数学形态学是一种应用于图像处理和模式识别领域的新的方法。形态学是生物学的一个分支,常用来处理动物和植物的形状和结构。数学形态学是建立在严格的数学理论基础上的科学。用于描述数学形态学的语言是集合论,利用数学形态学对物体几何结构的分析过程就是主客体相互逼近的过程。利用数学形态学的几个基本概念和运算,将结构元素灵活地组合、分解,应用形态变换序列达到分析的目的。

数学形态学是以集合代数为基础的,用集合的方法定量描述几何结构的科学。数学形态学应用于数字图像处理以后,用形态学来处理图像去描述某些区域的形状如边界曲线、骨架结构和凸形外壳。另外,还可用形态学技术进行预测和快速处理如形态过滤、形态细化、形态修饰等。而这些处理都是基于一些基本运算实现的。

1.数学形态学的基本概念

用于描述形态学的语言是集合论。集合代表图像中物体的形状,例如:在二值图像中所有的黑色像素点的集合就是这幅图像的完整描述。在二值图像中,当前集合是指二维整形空间的成员,集合中的每个元素就是一个二维变量,用(x,y)表示。按规则代表图像中的一个黑色像素点。灰度数字图像可以用三维集合来表示。在这种情况下,集合中每个元素的前两个元素表示像素点的坐标,第三个变量代表离散的灰度值。在更高维的空间集合中可以包括其他的图像属性,如颜色和时间。

形态学运算的质量取决于所选取的结构元素和形态变换。结构元素的选择要根据具体情况来确定,而形态运算的选择必须满足一些基本的约束条件。这些约束条件称为图像定量分析的原则。下面列出了数学形态学的几条定量分析原则:

(1)平移不变性

设待分析的图像为XΦ表示某种图像变换或运算,Φ(X)表示X经变换或运算后的新图像。设h为一矢量,Xh表示将图像X平移一个位移矢量后的结果,那么,平移不变性原则可表示为

Φ(Xh)=[Φ(X)]h

此式说明,图像X先平移然后变换的结果与图像先变换后平移的结果相一致。

(2)尺度变换不变性

设缩放因子λ是一个正的实常数,λX表示对图像X所做的相似变换,则尺度变换不变性可表示为

如果设图像运算Φ为结构元素BX的腐蚀(记为XΘB),则Φλ为结构元素λBX的腐蚀,则上式具体化为

(3)局部知识原理

如果Z是一个图形(闭集),则相对于Z存在另一个闭集Z',使得对于图形X有下式成立:

(Φ (XZ))∩Z'=Φ(X)∩Z'

可以将Z理解为一个“掩模”。在实际中,观察某一个对象时,每次只能观察一个局部,即某一掩模覆盖的部分。该原则要求对每种确定的变换或运算Φ,当掩模Z选定以后,都能找到一个相应的模板Z',使得通过Z'所观察到的局部性质,即(Φ(XZ))∩Z'与整体性质Φ(X)∩Z'相一致。

(4)半连续原理

在研究一幅图像时,常采用逐步逼近的方法,即对图像X的研究往往需要通过一系列图像X1,X2,…,Xn,…的研究实现,其中诸个Xn逐步逼近X。半连续原理要求各种图像变换后应满足这样的性质:对真实图像X的处理结果应包含在对一系列图像Xn的处理结果内。

(5)形态运算的基本性质

除了一些特殊情况外,数学形态学处理一般都是不可逆的。实际上,对图像进行重构的思想在该情况下是不恰当的。任何形态处理的目的都是通过变换法去除不感兴趣的信息,保留感兴趣的信息。在形态运算中的几个关键性质如下。

■递增性:XYΦ(X)Φ(Y) X,Yξ(E)

■反扩展性:Φ(X)X Xξ(E)

■幂等性:Φ[Φ (X)]=Φ(X)

其中,ξ(E)表示欧几里得(Euclidean)空间E的幂级。

2.数学形态学的表示

集合论是数学形态学的基础,这里首先对集合论的一些基本概念作一总结性的概括介绍。对于形态处理的讨论,将从两个最基本的模加处理和模减处理开始。它们是以后大多数形态处理的基础。

(1)集合

具有某种性质的确定的有区别的事物的全体。如果某种事物不存在,称为空集。集合常用大写字母A,B,C,…表示,空集用Φ表示。

E为一自由空间,ξ(E)是由集合空间E所构成的幂集,集合XBξ(E),则集合XB之间只能有以下三种形式(如图2-34所示):

1)集合B1击中X,表示为B1X

图2-34 集合表示形式

2)集合B2离于X,表示为B2Xc

3)集合B3含于X,表示为B3X

(2)元素

构成集合的每一个事物称之为元素。元素常用小写字母A,B,C,…表示,应注意的是,任何事物都不是空集的元素。

(3)平移转换

AB是两个二维集合,AB中的元素分别是

a=(a1,a2),b=(b1,b2)

定义x=(x1,X2),对集合A的平移转换为

(A)x={c|c=a+x,aA}

(4)子集

当且仅当集合A的所有元素都属于B时,称AB的子集。

(5)补集

定义集合A的补集为

Ac={x | xA}

(6)差集

定义集合AB的差集为

Ab={x | xA,xB}=ABc

(7)映像

定义集合A的映像为Â,定义为Â

Â={x | x=−a,aA}

(8)并集

由集合AB的所有元素组成的集合称为AB的并集。

(9)交集

由集合AB的公共元素组成的集合称为AB的交集。

3.图像形态学类的设计

本节设计一个图像形态学类,其目的是将关图像的形态学变换操作的所有函数封装到该类中。图像形态学变换的具体成员函数将在后几节中依次介绍。首先介绍该类的创建。

设计步骤

[1]打开2.1.5节创建的工程文件demo1。

[2]在工作区(Workspace)的类视图(Class View)编辑区中,选中【demo classes】后单击鼠标右键并选择【New Class】。添加继承自“ImageDib”类的“Morphology”类进行有关图像的形态学变换操作的算法编程与实现,如图2-35所示。

[3]在文件“Morphology.h”中定义该类如下。

图2-35 添加Morphology类示意图

代码2-7 Morphology函数

//结构元素对,该结构专门为击中击不中变换而定义
struct ElementPair
{
    int hitElement[9];
    int missElement[9];
};
class Morphology:public ImageDib
{
public:
    //输出图像每像素位数
    int m_nBitCountOut;
    //输出图像位图数据指针
    unsigned char * m_pImgDataOut;
    //输出图像颜色表
    LPRGBQUAD m_lpColorTableOut;
    //输出图像的宽,像素为单位
    int m_imgWidthOut;
    //输出图像的高,像素为单位
    int m_imgHeightOut;
    //输出图像颜色表长度
    int m_nColorTableLengthOut;
    //结构元素(模板)指针
    int *m_maskBuf;
    //结构元素宽
    int m_maskW;
    //结构元素高
    int m_maskH;
    //定义8个方向的击中击不中变换结构元素对
    ElementPair m_hitMissTemp[8];
public:
    Morphology();             //不带参数的构造函数
    Morphology(CSize size,int nBitCount,LPRGBQUAD lpColorTable,unsigned
    char *pImgData);          //带参数的构造函数
    virtual ~Morphology();    //析构函数
public:
    CSize GetDimensions();    //返回输出图像的尺寸
    void ImgErosion(unsigned char *imgBufIn,unsigned char *imgBufOut,int imgWidth,
         int imgHeight,int *TempBuf,int TempW,int TempH);//腐蚀
    void ImgDilation(unsigned char *imgBufIn,unsigned char *imgBufOut,int imgWidth,
         int imgHeight,int *maskBuf,int maskW,int maskH);//膨胀
    void Open();              //二值开
    void Close();             //二值闭
    void ImgThinning();       //击中击不中细化
    void DefineElementPair(); //定义击中击不中变换的结构元素对
    void HitAndMiss(unsigned char *imgBufIn,unsigned char *imgBufOut,
         int imgWidth,int imgHeight,ElementPair hitMissMask);//击中击不中变换
};

[4]将“Morphology.h”头文件包含进“demoView.cpp”文件中。

[5]利用资源管理器,在菜单条上加入【形态学】菜单及其子菜单【腐蚀】、【膨胀】、【开运算】、【闭运算】、【击中击不中细化】,如图2-36所示。

图2-36 灰度变换菜单

[6]双击上述菜单项,设置其ID值,如图2-37所示。上述菜单项中,【腐蚀】ID值定义为id_Erosion;【膨胀】ID值定义为id_Dilation;【开运算】ID值定义为id_Open;【闭运算】ID值定义为id_Close;【击中击不中细化】ID值定义为id_Thinning。

图2-37 设置菜单项ID值

各子函数代码的实现可参见本书附录所附程序。

2.2.5 图像分割

图像分割是一种重要的图像处理技术,是图像分析和理解的第一步。图像分割在很多领域有着广泛的应用,如工业图像处理、军事图像处理、生物医学图像处理、图像传输、文本图像分析处理和识别、身份鉴定、机器人视觉等。不同类型的图像,有不同的分割方法对其进行分割,而同时,某些分割方法也只适用于某些特殊类型的图像分割。

图像分割就是将图像分成具有不同特性的区域,并提取出感兴趣的区域的过程。早期的图像分割方法可以分成两大类:一是边界法,应用这种方法时一般假设图像分割结果的某个子区域在原来图像中一定会有边缘存在;二是区域法,应用这种方法时一般假设图像分割结果的某个子区域一定会有相同的性质,而不同区域的像素则没有共同的性质。

现在,随着计算机处理能力的提高,涌现出了很多其他的方法,如基于模型的图像分割、基于彩色分量、纹理的图像分割,以及基于人工智能的图像分割方法等。

图2-38所示是图像分割的方法框架。

图2-38 图像分割方法框架

图像分割类的设计

本节设计一个图像分割类,其目的是将关图像的分割操作的所有函数封装到该类中。不同的图像分割方法及其具体成员函数将在后几节中依次介绍。首先介绍该类的创建。

设计步骤

[1]打开2.1.5节创建的工程文件demo1。

[2]在工作区(Workspace)的类视图(Class View)编辑区中,选中【demo classes】后单击鼠标右键并选择【New Class】。添加继承自ImageDib类的“ImgSegment”类进行有关图像分割操作的算法编程与实现,如图2-39所示。

[3]在文件“ImgSegment.h”中定义该类如下。

图2-39 添加ImgSegment类示意图

代码2-8 ImgSegment函数

class ImgSegment:public ImageDib
{
public:
    //输出图像每像素位数
    int m_nBitCountOut;
    //输出图像位图数据指针
    unsigned char * m_pImgDataOut;
    //输出图像颜色表
    LPRGBQUAD m_lpColorTableOut;
    //输出图像的宽
    int m_imgWidthOut;
    //输出图像的高
    int m_imgHeightOut;
    //输出图像颜色表长度
    int m_nColorTableLengthOut;
public:
    //不带参数的构造函数
    ImgSegment();
    //带参数的构造函数
    ImgSegment(CSize size,int nBitCount,LPRGBQUAD lpColorTable,
        unsigned char *pImgData);
    //析构函数
    virtual ~ImgSegment();
public:
    //以像素为单位返回输出图像的尺寸
    CSize GetDimensions();
    //自适应阈值分割
    void AdaptThreshSeg(unsigned char *pImgData);
    //Roberts算子
    void Roberts();
    //Sobel算子
    void Sobel();
    //Prewitt算子
    void Prewitt();
    //Laplacian算子
    void Laplacian();
public:
    //区域生长
    void RegionGrow(CPoint SeedPos,int thresh);
    //曲线跟踪
    void EdgeTrace();
};

[4]将“ImgSegment.h”头文件包含进“demoView.cpp”文件中。

[5]利用资源管理器,在菜单条上加入【图像分割】菜单及其子菜单【直方图阈值分割】【自适应阈值分割】、【Robert算子】、【Sobel算子】、【Prewitt算子】、【Laplacian算子】、【边界跟踪】、【区域生长】,如图2-40所示。

图2-40 图像分割菜单

[6]双击上述菜单项,设置其ID值,如图2-41所示。上述菜单项中,【直方图阈值分割】ID值定义为id_HistThreshSeg;【自适应阈值分割】ID值定义为id_AdaptiveThreshold;【Robert算子】ID值定义为id_Robert;【Sobel算子】ID值定义为id_Sobel;【Prewitt算子】ID值定义为id_Prewitt;【Laplacian算子】ID值定义为id_Laplacian;【边界跟踪】ID值定义为id_EdgeTrace;【区域生长】ID值定义为id_RegionGrow。

图2-41 设置菜单项ID值

各子函数代码的实现可参见本书附录所附程序。