最近老师让做一个处理图像的小软件,用了大概一个星期做出来了成品,MFC上我算是新手,一路摸索过来也算是收获不少吧,现在软件也做完了,给自己总结一下在学习过程中的收获和不足。
首先放出一下软件的运行截图,主要是做了灰度图像的几何变换、正交变换、图像增强、二值化处理、形态学处理、图像分割等功能。代码主要参考了《visual c++数字图象处理技术详解》以及网上的一些技术文章,完整的工程已上传至csdn:http://download.csdn.net/detail/zzucode/7672489
灰度图像处理主要操作的是设备无关位图(DIB),由于mfc本身没有提供DIB的一个完整的类,只是提供了一些API接口和结构体,因此需要自己创建一个DIB类,主要实现位图的载入,保存,绘制及返回位图信息等操作。
首先使用mfc向导创建一个单文档应用程序,不使用unicode编码,然后在源文件中添加DIB类的头文件Dib.h和Dib.cpp,具体的头文件如下:
class CDib
{
public:
CDib();
virtual ~CDib();
//operations
public:
// 用于操作DIB的函数声明
BOOL DrawDib(HDC, LPRECT,HGLOBAL, LPRECT,CPalette*);//显示位图
BOOL ConstructPalette(HGLOBAL,CPalette* ); //构造逻辑调色板
LPSTR GetBits(LPSTR); //取得位图数据的入口地址
DWORD GetWidth(LPSTR); //取得位图的宽度
DWORD GetHeight(LPSTR); //取得位图的高度
WORD GetPalSize(LPSTR); //取得调色板的大小
WORD GetColorNum(LPSTR); //取得位图包含的颜色数目
WORD GetBitCount(LPSTR); //取得位图的颜色深度
HGLOBAL CopyObject(HGLOBAL); //用于复制位图对象
BOOL SaveFile(HGLOBAL , CFile&); //存储位图为文件
HGLOBAL LoadFile(CFile&); //从文件中加载位图
BOOL IsEmpty();
// 在对图像进行处理时,针对位图的字节宽度必须是4的倍数的这一要求,
//我们设计了函数GetRequireWidth,来处理这种比较特殊的情况
int GetReqByteWidth(int ); //转换后的字节数
long GetRectWidth(LPCRECT ); //取得区域的宽度
long GetRectHeight(LPCRECT); //取得区域的高度
public:
void ClearMemory();
void InitMembers();
public:
LPBITMAPINFO lpbminfo;// 指向BITMAPINFO结构的指针
LPBITMAPINFOHEADER lpbmihrd; //指向BITMAPINFOHEADER结构的指针
BITMAPFILEHEADER<span style="white-space:pre"> </span>bmfHeader; //BITMAPFILEHEADER结构
LPSTR<span style="white-space:pre"> </span>lpdib; //指向DIB的指针
LPSTR<span style="white-space:pre"> </span>lpDIBBits; // DIB像素指针
DWORD<span style="white-space:pre"> </span>dwDIBSize; //DIB大小
HGLOBAL<span style="white-space:pre"> </span>m_hDi //DIB对象的句柄
RGBQUAD*<span style="white-space:pre"> </span>lpRgbQuag; //指向颜色表的指针
};
DIB类的实现文件太长,就不再贴出来了,《visual c++数字图象处理技术详解》上有完整的源码,有感兴趣的朋友可以自己去看。
将Dib的实现文件添加进来之后,接下来就需要载入图像并在文档客户区显示,首先在工程C**Doc类的头文件中声明一个Dib类的对象并定义一些操作:
// 特性
public:
CDib m_dib; //声明一个DIB对象
CString m_szPathName;//图片文件路径
// 操作
public:
HGLOBAL GetHObject() const //获取Dib对象的句柄
{ return m_hDIB;}
CPalette *GetDocPal() const //获取调色板指针
{ return m_palDIB;}
CSize GetDocDimension() const //
{ return m_sizeDoc;}
void UpdateObject(HGLOBAL hDIB); //更新DIB对象
// 实现
public:
void SetDib(); //获取Dib对象
public:
HGLOBAL m_hDIB;
CPalette *m_palDIB;
CSize m_sizeDoc;
然后在C**Doc类的实现文件的构造函数中添加
// TODO: 在此添加一次性构造代码
m_hDIB = NULL;
m_palDIB = NULL;
m_sizeDoc = CSize(1,1);
在析构函数中添加
if (m_hDIB != NULL) //判断是否有dib对象
{
::GlobalFree(m_hDIB);
}
if (m_palDIB != NULL) //判断是否有调色板
{
delete m_palDIB;
}
然后重载C**Doc类的OnOpenDocument函数
// TODO: 在此添加您专用的创建代码
CFile file;
if (!file.Open(lpszPathName,CFile::modeRead | CFile::shareDenyWrite))
{
//只读方式打开文件
return FALSE;
}
DeleteContents();//删除文档中数据,确保载入图像数据之前文档为空
m_hDIB = m_dib.LoadFile(file);
m_szPathName = lpszPathName;
if (m_hDIB == NULL)
{
return FALSE;
}
SetDib();
if (m_hDIB == NULL)
{
AfxMessageBox("读取图像时出错!");
return FALSE;
}
SetPathName(lpszPathName);//设置文件名称
return TRUE;
其中setdib函数主要是在载入位图数据后创建调色板,具体代码如下
void CImgProcessDoc::SetDib()
{
LPSTR lpdib = (LPSTR) ::GlobalLock(m_hDIB);//保持图片数据内存一直有效
if (m_dib.GetWidth(lpdib) > INT_MAX ||m_dib.GetHeight(lpdib) > INT_MAX)// 判断图像是否过大
{
::GlobalUnlock((HGLOBAL) m_hDIB);
::GlobalFree((HGLOBAL) m_hDIB); // 释放DIB对象
m_hDIB = NULL;// 设置DIB为空
AfxMessageBox("初始化失败!");
return;
}
m_sizeDoc = CSize((int)m_dib.GetWidth(lpdib), (int)m_dib.GetHeight(lpdib));// 设置文档大小
::GlobalUnlock((HGLOBAL) m_hDIB);
m_palDIB = new CPalette;// 创建新调色板
if (m_palDIB == NULL)// 判断是否创建成功
{
::GlobalFree((HGLOBAL) m_hDIB); // 失败
m_hDIB = NULL;// 设置DIB对象为空
return;
}
// 调用CreateDIBPalette来创建调色板
if (m_dib.ConstructPalette(m_hDIB, m_palDIB) == NULL)
{
delete m_palDIB;// 删除
m_palDIB = NULL;// 设置为空
return;// 返回空
}
}
这时候编译工程并运行,点击打开一个位图文件就可以将位图文件的信息和数据载入内存,但是打开之后并没有在客户区显示,这时候需要重载C**View类的OnDraw函数
// TODO: 在此处为本机数据添加绘制代码
HGLOBAL hDIB = pDoc->GetHObject();
// 判断DIB是否为空
if (hDIB != NULL)
{
LPSTR lpDibSection = (LPSTR) ::GlobalLock((HGLOBAL) hDIB);
// 获取DIB宽度
int cxDIB = (int) pDoc->m_dib.GetWidth(lpDibSection);
// 获取DIB高度
int cyDIB = (int) pDoc->m_dib.GetHeight(lpDibSection);
::GlobalUnlock((HGLOBAL) hDIB);
CRect rcDIB;
rcDIB.top = rcDIB.left = 0;
rcDIB.right = cxDIB;
rcDIB.bottom = cyDIB;
CRect rcDest= rcDIB;
// 输出DIB
pDoc->m_dib.DrawDib(pDC->m_hDC, &rcDest, pDoc->GetHObject(),
&rcDIB, pDoc->GetDocPal());
}
编译工程并运行,就可以打开文件并显示到程序的客户区了。