- Visual C++数字图像模式识别典型案例详解
- 冯伟兴 梁洪 王臣业编著
- 7393字
- 2025-03-16 03:50:20
1.3.2 Visual C++数字图像处理类
本节将首先介绍典型的BMP图像文件格式,并围绕BMP图像文件格式给出Visual C++平台下的数字图像处理基本类的一个实现实例,以此作为后续数字图像模式识别实际案例的编程基础。
1.BMP图像文件
BMP位图文件格式是Windows系统交换图像数据的一种标准图像文件存储格式,在Windows环境下运行的所有图像处理软件都支持这种格式。Windows 3.0以前的BMP位图文件格式与显示设备有关,因此称其为设备相关位图(Device-dependent Bitmap,DDB)文件格式。Windows 3.0以后的BMP位图文件格式与显示设备无关,因此把这种BMP位图文件格式称为设备无关位图(Device-independet Bitmap,DIB)格式,目的是为了让Windows能够在任何类型的显示设备上显示BMP位图文件。BMP位图文件默认的文件扩展名是.bmp。
BMP位图文件由4部分组成:位图文件头(Bitmap-file Header)、位图信息头(Bitmap-information Header)、调色板数据(Palette Data)和像素数据(Image Data),如图1-6所示。

图1-6 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数据结构定义位图信息头,它包含位图的大小、压缩类型和颜色格式等信息,其结构如下:
typedef struct 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结构,其定义如下:
typedef struct tagRGBQUAD{ BYTE rgbBlue; /*指定蓝色分量(值范围为0~255)*/ BYTE rgbGreen; /*指定绿色分量(值范围为0~255)*/ BYTE rgbRed; /*指定红色分量(值范围为0~255)*/ BYTE rgbReserved; /*保留值,必须为0*/ }RGBQUAD;
对于24位真彩色图像,其不使用调色板,因为位图中的RGB值就代表了每个像素的颜色,因此BITMAPINFOHEADER后直接是像素数据。
4)像素数据
紧跟在调色板之后的是图像数据字节阵列,用BYTE数据结构存储。图像的每一扫描行由表示图像的连续像素字节组成,每一行的字节数取决于图像的颜色数目和用像素表示的图像宽度。扫描行是由底向上存储的,这就是说,数据存放是从下到上,从左到右。从文件中最先读到的图像数据是位图最下面的最左边的第一个像素,然后是最左边的第二个像素,而最后读到的图像数据是位图最上面一行的最右边的一个像素。
2.数字图像处理类
本书所采用的数字图像处理类的类名为ImageDib。在ImageDib类的头文件ImageDib.h中编辑该类的结构,如代码1-3所示。
代码1-3 ImageDib类头文件
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(); //清理空间 };
ImageDib类的函数体文件ImageDib.cpp中编辑该类的实现如代码1-4所示。
代码1-4 ImageDib类函数体
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); } ImageDib::ImageDib(CSize size, int nBitCount, LPRGBQUAD lpColorTable, unsigned char *pImgData) { //如果没有位图数据传入,我们认为是空的DIB,此时不分配DIB内存 if(pImgData==NULL){ m_lpDib=NULL; m_lpColorTable=NULL; m_pImgData=NULL; // 图像数据 m_lpBmpInfoHead=NULL; // 图像信息头 m_hPalette = NULL; } else{//如果有位图数据传入 //则为图像的宽、高、每像素位数等成员变量赋值 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 [sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * m_nColorTableLength+imgBufSize]; //填写BITMAPINFOHEADER结构 m_lpBmpInfoHead = (LPBITMAPINFOHEADER) m_lpDib; m_lpBmpInfoHead->biSize = sizeof(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; //调色板句柄初始化为空,有颜色表时,MakePalette()函数要生成新的调色板 m_hPalette = NULL; //如果有颜色表,则将颜色表复制进DIB的颜色表位置 if(m_nColorTableLength!=0){ //m_lpColorTable指向DIB颜色表的起始位置 m_lpColorTable=(LPRGBQUAD)(m_lpDib+sizeof (BITMAPINFOHEADER)); //颜色表复制 memcpy(m_lpColorTable,lpColorTable,sizeof(RGBQUAD)* m_nColorTableLength); //创建逻辑调色板 MakePalette(); } //m_pImgData指向DIB位图数据起始位置 m_pImgData = (LPBYTE)m_lpDib+sizeof(BITMAPINFOHEADER)+ sizeof(RGBQUAD) * m_nColorTableLength; //复制图像数据进DIB位图数据区 memcpy(m_pImgData,pImgData,imgBufSize); } } 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, sizeof(BITMAPFILEHEADER)); //为m_lpDib分配空间,读取DIB进内存 if(m_lpDib!=NULL) delete []m_lpDib; m_lpDib=new BYTE[file.GetLength() -sizeof(BITMAPFILEHEADER)]; file.Read(m_lpDib, file.GetLength() -sizeof(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+sizeof(BITMAPINFOHEADER)); MakePalette(); } //m_pImgData指向DIB的位图数据起始位置 m_pImgData = (LPBYTE)m_lpDib+sizeof(BITMAPINFOHEADER) + sizeof(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 = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * m_nColorTableLength; try { //文件头结构写进文件 file.Write((LPVOID) &bmfh, sizeof(BITMAPFILEHEADER)); //文件信息头结构写进文件 file.Write(m_lpBmpInfoHead, sizeof(BITMAPINFOHEADER)); //如果有颜色表的话,颜色表写进文件 if(m_nColorTableLength!=0) file.Write(m_lpColorTable, sizeof(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 * sizeof(WORD) + m_nColorTableLength * sizeof(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::Empty() { //释放DIB内存缓冲区 if(m_lpDib != NULL) { delete [] m_lpDib; m_lpDib=NULL; m_lpColorTable=NULL; m_pImgData=NULL; m_lpBmpInfoHead=NULL; } //释放逻辑调色板缓冲区 if(m_hPalette != NULL){ ::DeleteObject(m_hPalette); m_hPalette = NULL; } } 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 [sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * m_nColorTableLength+imgBufSize]; //填写位图信息头BITMAPINFOHEADER结构 m_lpBmpInfoHead = (LPBITMAPINFOHEADER) m_lpDib; m_lpBmpInfoHead->biSize = sizeof(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+sizeof(BITMAPINFOHEADER)); memcpy(m_lpColorTable,lpColorTable,sizeof(RGBQUAD)* m_nColorTableLength); MakePalette(); } //m_pImgData指向DIB的位图数据起始位置 m_pImgData = (LPBYTE)m_lpDib+sizeof(BITMAPINFOHEADER)+ sizeof(RGBQUAD) * m_nColorTableLength; //将新位图数据复制至新的DIB中 memcpy(m_pImgData,pImgData,imgBufSize); }
3.数字图像处理类的应用
本实例将实现在多文档应用程序中打开一个位图文件并显示的功能。
设计步骤
[1] 在文档类CDemo1Doc类的头文件Demo1Doc.h中包含ImageDib类的声明文件ImageDib.h。
#include "ImageDib.h"
[2] 在Visual C++集成开发环境(IDE)的“View”菜单中选择“ClassWizard”命令,在操作类名(Class name)中选择文档类CDemo1Doc,操作对象(Object IDs)选择CDemo1Doc,对应消息(Messages)选择OnOpenDocument,该消息在单击应用程序中“文件”菜单的“打开”菜单项时产生。
单击添加函数 按钮,在类CDemo1Doc中加入OnOpenDocument成员函数,该函数在程序中出现OnOpenDocument消息时执行。该过程如图1-7所示。

图1-7 类编辑对话框
该成员函数默认生成代码如下:
BOOL CDemo1Doc::OnOpenDocument(LPCTSTR lpszPathName) { if (!CDocument::OnOpenDocument(lpszPathName)) return FALSE; // TODO: Add your specialized creation code here return TRUE; }
[3] 在Visual C++集成开发环境(IDE)的工作区(Workspace)中选中类CDemo1Doc,右击,弹出的快捷菜单如图1-8所示。

图1-8 添加类新成员操作框
在弹出的快捷菜单中单击“Add Member Variable”命令,弹出类新成员对话框,如图1-9所示。

图1-9 添加类新成员对话框
在变量类型“Variable Type”中输入“ImageDib”,变量名称“Variable Name”中输入“m_dib”,其他缺省。单击按钮即可完成在CDemo1Doc类中添加ImageDib类成员变量m_dib的操作。
[4] 随后对CDemo1Doc类的CDemo1Doc和OnOpenDocument函数代码重新编写,重新编写后的代码如下:
CDemo1Doc::CDemo1Doc() { // TODO: add one-time construction code here m_dib = new ImageDib; } BOOL CDemo1Doc::OnOpenDocument(LPCTSTR lpszPathName) { if (m_dib.Read(lpszPathName) == TRUE) { SetModifiedFlag(FALSE); // start off with unmodified return TRUE; } else return 0; }
[5] OnOpenDocument函数仅实现将数字图像读入内存,如果在文档所对应视窗内进行数字图像显示,还需对视窗类CDemo1View的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 }
[6] 运行程序,选择菜单“文件”中的“打开”命令,打开本书附带光盘中的demo.bmp文件。运行结果如图1-10所示。

图1-10 打开位图文件程序运行结果
4.数字图像处理类的扩展
在数字图像基础类上,常用的数字图像功能进行了如下扩展。
(1)图像灰度变换类
灰度变换可以按照预定的方式改变一幅图像的灰度直方图。除了灰度级的改变是根据某种特定的灰度变换函数进行之外,灰度变换可以看作是“从像素到像素”的复制操作。如果输入图像为A(x, y),输出图像为B(x, y),则灰度变换可表示为:B(x, y)= f[A(x, y)]。其中函数f( )称为灰度变换函数,它描述了输入灰度值和输出灰度值之间的转换关系。一旦灰度变换函数确定,该灰度变换就被完全确定。
本节设计的图像灰度变换类名为GrayTrans。在GrayTrans类的头文件GrayTrans.h中编辑该类的结构,如代码1-5所示。
代码1-5 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); };
各子函数代码的实现可参见本书所附光盘。
(2)图像几何变换类
本节将设计一个图像几何变换类,其目的是将关于图像的几何变换操作的所有函数封装到该类中。图像几何变换类名为GeometryTrans。在GeometryTrans类的头文件GeometryTrans.h中编辑该类的结构,如代码1-6所示。
代码1-6 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旋转角度 };
各子函数代码的实现可参见本书所附光盘。
(3)图像空域增强类
图像的空间信息可以反映图像中物体的位置、形状、大小等特征,而这些特征可以通过一定的物理模式来描述。例如,物体的边缘轮廓由于灰度值变化剧烈一般出现高频率特征,而一个比较平滑的物体内部由于灰度值比较均一则呈现低频率特征。因此,根据需要可以分别增强图像的高频和低频特征。对图像的高频增强可以突出物体的边缘轮廓,从而起到锐化图像的作用,例如,对于人脸的比对查询,就需要通过高频增强技术突出五官的轮廓。相应地,对图像的低频部分进行增强可以对图像进行平滑处理,一般用于图像的噪声消除。
图像的空域增强是应用模板卷积方法对每一像素的邻域进行处理完成的,一般可分为线性和非线性两类。无论采用什么样的增强方法,其实现步骤大体相同,具体过程如下:
(a)将模板在图像中漫游移动,并将模板中心与每个像素依次重合(边缘像素除外);
(b)将模板中的各个系数和与其对应的像素一一相乘,并将所有结果相加(或进行其他四则运算);
(c)将步骤(b)中的结果赋给图像中对应模板中心位置的像素。
图1-11为应用模板进行滤波的示意图。1-11a所示是一幅图像的一小部分,共9个像素,Pi(i=0,1,…,8)表示像素的灰度值。1-11b所示表示一个3×3的模板,Ki(i=0,1,…,8)称为模板系数,模板的大小一般取奇数(如3×3,5×5等)。现将模板在图像中漫游,并使K0与图1-11a所示的P0像素重合,即可由下式计算输出图像(增强图像)中与P0相对应的像素的灰度值r:

图1-11 空域模板滤波示意图

对每个像素按上式进行计算即可得到增强图像中所有像素的灰度值。
本节将设计一个图像空域增强类,其目的是将关于图像的空域变换操作的所有函数封装到该类中。图像空域增强类名为ImageEnhance。在ImageEnhance类的头文件ImageEnhance.h中编辑该类的结构,如代码1-7所示。
代码1-7 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)图像频域增强类
图像空域增强一般只是对数字图像进行局部增强,而图像频域增强则可以对图像进行全局增强。
频域增强技术是在数字图像的频率域空间对图像进行滤波,因此需要将图像从空间域变换到频率域,一般通过傅里叶变换即可实现。在频率域空间的滤波与空域滤波一样可以通过卷积实现,因此傅里叶变换和卷积理论是频域滤波技术的基础。
假定函数f(x, y)与线性位不变算子h(x, y)的卷积结果是g(x, y),即
g(x, y) = h(x, y)*f(x, y)
相应的,由卷积定理可得到下述频域关系:
G(u, v) = H(u, v)×F(u, v)
式中,G、H、F分别是函数g、h、f的傅里叶变换;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)的低频分量,就可以使图像显得比较平滑,即低通滤波。
在介绍具体的滤波器之前,先根据以上的描述给出频域滤波的主要步骤:
(a)对原始图像f(x, y)进行傅里叶变换得到F(u, v);
(b)将F(u, v)与传递函数H(u, v)进行卷积运算得到G(u, v);
(c)将G(u, v)进行傅里叶反变换得到增强图像g(x, y)。
本节设计一个图像频域增强类,其目的是将有关图像的频域变换操作的所有函数封装到该类中。图像空域增强类名为ImageFreqEnhance。在ImageFreqEnhance类的头文件ImageFreqEnhance.h中编辑该类的结构,如代码1-8所示。
代码1-8 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(); };
各子函数代码的实现可参见本书所附光盘。
(5)图像形态学处理类
形态学是生物学的一个分支,常用来处理动物和植物的形状和结构。数学形态学是一种应用于图像处理和模式识别领域的新的方法。数学形态学是建立在严格的数学理论基础上的科学。用于描述数学形态学的语言是集合论,利用数学形态学对物体几何结构的分析过程就是主客体相互逼近的过程。利用数学形态学的几个基本概念和运算,可将结构元素灵活地组合、分解,应用形态变换序列达到分析的目的。
数学形态学是以集合代数为基础的,用集合的方法定量描述几何结构的科学。数学形态学应用于数字图像处理以后,开始用形态学来处理图像,去描述某些区域的形状,如边界曲线、骨架结构和凸形外壳。另外,还可用形态学技术进行预测和快速处理如形态过滤、形态细化、形态修饰等。而这些处理都是基于一些基本运算实现的。
本节将设计一个图像形态学类,其目的是将关于图像的形态学变换操作的所有函数封装到该类中。图像形态学类名为Morphology。在Morphology类的头文件Morphology.h中编辑该类的结构,如代码1-9所示。
代码1-9 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); //击中、击不中变换 };
各子函数代码的实现可参见本书所附光盘。
(6)图像分割类
图像分割就是将图像分成具有不同特性的区域,并提取出感兴趣的区域的过程。图像分割是一种重要的图像处理技术,是图像分析和理解的第一步。图像分割在很多领域都有着广泛的应用,如工业图像处理、军事图像处理、生物医学图像处理、图像传输、文本图像分析处理和识别、身份鉴定、机器人视觉等。不同类型的图像,有不同的分割方法,其中,一些分割方法也只适用于某些特殊类型的图像分割。
早期的图像分割方法可以分成两大类:一是边界法,应用这种方法时一般假设图像分割结果的某个子区域在原来图像中一定会有边缘存在;二是区域法,应用这种方法时一般假设图像分割结果的某个子区域一定会有相同的性质,而不同区域的像素则没有共同的性质。现在,随着计算机处理能力的提高,涌现出了很多其他的方法,如基于模型的图像分割,基于彩色分量、纹理的图像分割以及基于人工智能的图像分割方法等。
图1-12所示的是图像分割方法的框架。

图1-12 图像分割方法的框架
本节将设计一个图像分割类,其目的是将关于图像的分割操作的所有函数封装到该类中。图像分割类名为ImgSegment。在ImgSegment类的头文件ImgSegment.h中编辑该类的结构,如代码1-10所示。
代码1-10 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(); };
各子函数代码的实现可参见本书所附光盘。