经常看电视的朋友们不知注意到没有,最近的电视连续剧在每集片头或片尾部分都有显示一些特殊效果的图像,比如前一阵子中央一套放的《长征》、目前中央八套正在播放的《康熙王朝》,这些特效称为"图像的浮雕效果"和"图像的雕刻效果",经过这些特效处理后的图像增强了观众们的视觉效果,它们看上去仿佛是使用3D技术作的,这也是为什么这种技术那么流行的原因吧。其实,我们完全可以用一些简单的数字图像处理算法来实现这些看似复杂高深的显示效果。我们手头上的一些关于利用VC开发数字图像资料大都是讲解如何控制图像的每一行或每一列像素的显示时间或顺序来实现一些简单的图像显示效果,涉及到图像算法的文章很少,本文针对上述的这两种图像特效处理技术并加以引伸,讲解了一些实现图像特效算法,以一个标准的Lena灰度图像为原图,给出了处理后的效果图,同时给出了VC开发平台上的部分实现源代码。
"浮雕"图象效果是指图像的前景前向凸出背景。所谓的"浮雕"概念是指标绘图像上的一个像素和它左上方的那个像素之间差值的一种处理过程,为了使图像保持一定的亮度并呈现灰色,我在处理过程中为这个差值加了一个数值为128的常量。需要读者注意的是,当设置一个像素值的时候,它和它左上方的像素都要被用到,为了避免用到已经设置过的像素,应该从图像的右下方的像素开始处理,下面是实现的源代码:
void CDibView::OnFDImage() //产生"浮雕"效果图函数
{
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,buf;
for( i=lpBi->biHeight; i>=2; i--)
for( j=lpBi->biWidth; j>=2; j--)
{
//"浮雕"处理
buf=*(hData+(lpBi->biHeight-i)*WIDTHBYTES(lpBi->biWidth*8)+j)-*(hData+(lpBi->biHeight-i+1)*WIDTHBYTES(lpBi->biWidth*8)+j-1)+128;
if(buf>255) buf=255;
if(buf<0)buf=0;
*(data+(lpBi->biHeight-i)*WIDTHBYTES(lpBi->biWidth*8)+j)=(BYTE)buf;
}
for( j=0; jbiHeight; j++)
for( i=0; ibiWidth; i++)
//重新写回原始图像的数据缓冲区
*(hData+i*WIDTHBYTES(lpBi->biWidth*8)+j)=*(data+i*WIDTHBYTES(lpBi->biWidth*8)+j);
AfxGetApp()->EndWaitCursor();
GlobalUnlock((HGLOBAL)hdib);
GlobalUnlock(data1handle);
EndWaitCursor();
Invalidate(TRUE);//显示图像
}
上面讲述了通过求一个像素和它左上方像素之间的差值并加上一个常数的方法生成"浮雕"效果的灰度图像,"雕刻"图像与之相反,它是通过取一个像素和它右下方的像素之间的差值并加上一个常数,这里我也取128,经过这样处理,就可以得到"雕刻"图像,这时候图像的前景凹陷进背景之中。同样需要读者注意的是为了避免重复使用处理过的图像像素,处理图像时要从图像的左上方的像素开始处理。实现代码如下:
void CDibView::OnDKImage()
{
// TODO: Add your command handler code here
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,buf;
//图像的"雕刻"处理
for( i=0;i<=lpBi->biHeight-2; i++)
for( j=0;j<=lpBi->biWidth-2; j++)
{
buf=*(hData+(lpBi->biHeight-i)*WIDTHBYTES(lpBi->biWidth*8)+j)-*(hData+(lpBi->biHeight-i-1)*WIDTHBYTES(lpBi->biWidth*8)+j+1)+128;
if(buf>255) buf=255;
if(buf<0)buf=0;
*(data+(lpBi->biHeight-i)*WIDTHBYTES(lpBi->biWidth*8)+j)=(BYTE)buf;
}
for( j=0; jbiHeight; j++)
for( i=0; ibiWidth; i++)
//重新将处理后的图像数据写入原始的图像缓冲区内
*(hData+i*WIDTHBYTES(lpBi->biWidth*8)+j)=*(data+i*WIDTHBYTES(lpBi->biWidth*8)+j);
AfxGetApp()->EndWaitCursor();
GlobalUnlock((HGLOBAL)hdib);
GlobalUnlock(data1handle);
EndWaitCursor();
Invalidate(TRUE);
}
Lena原图
"雕刻"效果图
"浮雕"效果图
大家学习了VC的MFC的一些基础知识后,如果能用VC开发一个比较实用的软件,对熟悉VC各方面编程和面向对象的软件设计和开发都是很有帮助的。
本文旨在通过对一个作者自己开发的小型矢量图形系统全面讲述而达到让读者了解一个小软件从设计到实现的阶段的解决的问题。同时也从界面和功能上对MFC和Windows系统功能的挖掘,同样,对于学习计算机图形学的读者,也可以看到本文有很多对图形学算法和实现的有益探讨。
首先,让大家对一个本软件功能的大概了解。当你着手开发一个软件时,首先要解决的当然是本软件的功能(软件工程常称作用例,具体概念可以参考有关资料,不妨简单理解为用户使用它能完成哪些工作)。由于写这篇文章时,本软件已经具有比较完整的原型。我们可以结合它的界面(图1)来介绍软件设计的过程。
图 1 软件界面
可以看到,本软件是实现了一个绘图功能的子集。最初就确定了开发环境为VC6.0,界面采用IE风格。在使用上为了给用户最大的便利,采用了三种工具条(普通文件、打印操作等标准工具,对图形对象属性设置的工具条式对话框,带文字说明的大按钮式可浮动或任意船坞- Dock定位的绘图工具条)。
操作上采用左键点击建立图形对象起始点,移动动态调整图形大小和位置(随手画采用按住左键拖动的方式,再次点击左键确定位置,右键取消操作,双击确定(结束)多步图形对象(如多边形)的绘制。在功能设计方面基本符合一般图形软件的惯例,但出于作者的便利和保护鼠标的考虑,整个功能体现了基本无需按住左键拖动的思想。这也是很容易让人接受的,因为即便习惯拖动的用户拖动时也会产生位置调整,只是释放后还是出于拖动状态,再次点击或双击才最终确定。
功能上选择了画线、框、圆、多边形、立体、文字、曲线、填充以及删除的功能,根据是否填充和光照又增加了几个类别,填充方式根据图形学的概念提供了两种方式(以后介绍)。根据对图形属性取了线宽、线型(很容易实现简单的线型,由于想加入更多的特性,作者先没有具体实现它,以后作者会提到它的实现,读者有兴趣可以试着实现)、边框色、填充色和字体几个属性。当然,这些功能在面向对象的方法中都是可以很方便扩展的(如画椭圆,选取对象,对象的位移和旋转操作,根据填充算法实现同色选取,即Photoshop等软件的魔棒功能等),对于橡皮擦功能可以很简单的实现特定工具或告诉用户如何实现此功能(即用背景色利用已有功能绘图)。
内部实现上,要求单独记录各图形的关键属性(如位置、色彩等,这些是矢量图区别于位图的特点)。由于各对象可以形成对象链表,因此,也要求实现多步撤消(Undo)和重做(Redo)的功能,这往往是用户所十分期待的功能(Window自带的画笔附件程序在这点上就很欠缺)。
面向对象的程序设计方法都支持三种基本的活动:识别对象和类,描述对象和类之间的关系,以及通过描述每个类的功能定义对象的行为。
首先介绍一下对象(Object)和类(Class)的区别,类是同类对象数据凸δ艿拿枋龊褪迪郑–++中用Class关键字定义的是类),对象是类的在内存中的具体形态(用类名声明或用new操作生成的是对象变量),一般称对象为类的实例(Instance)。
对于图形对象的对象设计由于它们的较强的相关性,往往在很多面向对象编程书都提到过,故相信读者识别对象和类不会很困难。但是,要充分利用继承和多态的特性来描述对象和类之间的关系,以及通过描述每个类的功能定义还是要具体问题具体分析的。
下面还是以一副图来说明。图2是采用北航软件所的软件分析与测试工具——SafePro生成的本软件的类图局部。
由图2中可以清晰看到,我们的绘图子系统实现部分主要利用了几个从MFC可序列化的基类CObject继承的四个类:MFC已有类CArray,CObList,CDC以及我们自己需要实现的类CGraph。CDC对象封装了我们可以利用Windows系统绘图功能的设备无关的几乎全部绘图功能。CArray类和CObList 类用于实现基于CObject类的对象的数组和链表存储的辅助类。CGraph是抽象类,所有图形对象都由它继承而来。值得注意的是,由于多边形和框都是直线的组合,本软件采用了从CLine继承的方法,可以充分利用它的功能。
现在并不想把所有类的功能定义(以后会逐步介绍大部分)。下面介绍一些关系全局的类的设计。
图 2本软件的图形对象类的设计
在图1可以看到,本软件是基于多文档界面(MDI)的。由AppWizard选取多文档界面后,它会帮助我们生成基本的基于文档-视图结构的类。本软件使用DrawGraph为应用程序名,故有以下类:CMainFrame,CChildFrame,CDrawGraphApp,CDrawGraphDoc ,CDrawGraphView。
其中:CDrawGraphApp(以后我用是应用程序类,支持应用程序的建立和基本交互,我们可以不必改它。CChildFrame类是视图文档的容器,除了在显示图标上的定制外,我们也可以不修改它。
CMainFrame,CDrawGraphDoc ,CDrawGraphView用于分别实现主窗口、文档、视图的功能。
1).
class CMainFrame : public CMDIFrameWnd
{
DECLARE_DYNAMIC(CMainFrame)//支持动态建立
public:
CMainFrame();
// Attributes
public:
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CMainFrame)
public:
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
//}}AFX_VIRTUAL
// Implementation
public:
int m_Depth;//立体深度
COLORREF m_fillcolor;//填充色
COLORREF m_pencolor;//边框色
LOGFONT m_font;//字体
int m_penstyle;//线型
UINT m_penwidth;//笔宽
void SaveToReg();//记录退出前的窗口状态
void ReadFromReg();//读取退出前的窗口状态
objecttype GetDrawType();//返回当前选中的绘图工具类别
virtual ~CMainFrame();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
protected: // control bar embedded members
CStatusBar m_wndStatusBar;//状态栏
CReBar m_wndReBar;//标准栏和属性栏的容器工具条
CDialogBar m_wndDlgBar;//属性栏
CToolBar m_wndToolBar;//标准栏
CToolBar m_wndDrawTool;//绘图工具条
UINT objtype;//选中工具的ID号
// Generated message map functions
protected:
afx_msg void OnDropDown(NMHDR* pNotifyStruct,LRESULT* result);
//{{AFX_MSG(CMainFrame)
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnShowdrawtool();//显隐工具条
afx_msg void OnUpdateShowdrawtool(CCmdUI* pCmdUI);
afx_msg void OnFont();
afx_msg void OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized);
afx_msg void OnColor();
afx_msg void OnUpdateColor(CCmdUI* pCmdUI);
afx_msg void OnFillcolor();
afx_msg void OnUpdateFillcolor(CCmdUI* pCmdUI);
//}}AFX_MSG
afx_msg void OnSelectTool(UINT ID);//选中工具
afx_msg void OnUpdateButtons(CCmdUI* pCmdUI);//处理按钮按下状态
afx_msg void onchangedpenwidth();
DECLARE_MESSAGE_MAP()
};
2).
class CDrawGraphDoc : public CDocument
{
protected: // create from serialization only
CDrawGraphDoc();
DECLARE_DYNCREATE(CDrawGraphDoc)
// Attributes
public:
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CDrawGraphDoc)
public:
virtual BOOL OnNewDocument();
virtual void Serialize(CArchive& ar);
virtual BOOL OnOpenDocument(LPCTSTR lpszPathName);
virtual void DeleteContents();
//}}AFX_VIRTUAL
// Implementation
public:
CMainFrame* GetMainFrame();//获得对主框架窗口的指针
BOOLEAN m_fillmode;//两种填充方式
void Cancel();//删除当前正在建立的绘图对象
COLORREF m_color;
COLORREF m_filledcolor;
UINT m_PenWidth;
CGraph* NewDrawing();
CObList m_graphoblist;//绘图对象列表
CObList m_redolist;//为redo功能提供的历史记录对象列表
//以后可以添加下面的功能,把图形存储为流行的图形交互格式。
//SaveAsBitmap();
//SaveAsWMF();
//SaveAsJPEG();
//SaveAsGIF();
virtual ~CDrawGraphDoc();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
protected:
// Generated message map functions
protected:
void Refresh();//用于更新视图
void InitDocument();
//{{AFX_MSG(CDrawGraphDoc)
afx_msg void OnFillinborder();
afx_msg void OnUpdateFillinborder(CCmdUI* pCmdUI);
afx_msg void OnFilloncolor();
afx_msg void OnUpdateFilloncolor(CCmdUI* pCmdUI);
afx_msg void OnPenwidth();
afx_msg void OnEditUndo();
afx_msg void OnUpdateEditUndo(CCmdUI* pCmdUI);
afx_msg void OnClear();
afx_msg void OnUpdateClear(CCmdUI* pCmdUI);
afx_msg void OnEditRedo();
afx_msg void OnUpdateEditRedo(CCmdUI* pCmdUI);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
3).
class CDrawGraphView : public CView
{
protected: // create from serialization only
CDrawGraphView();
DECLARE_DYNCREATE(CDrawGraphView)
// Attributes
public:
CDrawGraphDoc* GetDocument();
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CDrawGraphView)
public:
virtual void OnDraw(CDC* pDC); // overridden to draw this view
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
protected:
virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);
virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);
virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);
//}}AFX_VIRTUAL
// Implementation
public:
virtual ~CDrawGraphView();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
protected:
// Generated message map functions
protected:
CPoint m_ptPrev;//前面一次点击的位置
CGraph* m_curGraph;//当前正在绘制的图形对象
state bdrawbegin;//绘制状态
//{{AFX_MSG(CDrawGraphView)
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg void OnRButtonDown(UINT nFlags, CPoint point);
afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point);
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
视图类在界面上改得少,主要是处理鼠标事件和调用各图形对象的绘制方法,实现上也尽量统一,充分利用图形对象的多态性。
4).
它定义了绘图类别和绘制状态两个枚举类型。当你把它定义好后,可以在stdafx.h加上#include “graph.h”来使得所有文件都能自由引用它,并且获得预编译。
enum state{notstart=0,startstroke,continuedrag,enddraw};
typedef enum {line ,bezier,solid,light,stroke,circle,rectangle,filledrectangle,&
polyline,filledcircle,filledpolygon,ellipse,fill,text} objecttype;
class CGraph : public CObject
{protected:
CGraph( ){};
DECLARE_DYNAMIC( CGraph )
// Attributes
protected:
COLORREF m_color;//所有图形对象都有颜色
public:
// Operations
public:
virtual state SetNext(CPoint pt)=0;//再次点击,由返回值确定是否结束绘制
virtual void SetStart(CPoint pt)=0;//一次点击,产生第一点的位置
virtual void Draw( CDC* pDC )=0;//图形对象绘制自己的方法
inline void SetColor(COLORREF color){m_color=color;};//设置图形对象颜色
virtual void DrawXOR(CDC*pDC,CPoint pt)=0;//在拖动状态,图形对象绘制自己的方法
virtual void Serialize( CArchive& ar );//图形对象序列化的方法
//以后可以扩展以下功能
// virtual void IsHit(CPoint pt);//确定对象是否被点击
// virtual void Highlight();//被点击后突出显示
// virtual CRect GetBoundRect();//获得图形矩阵,可以用线索的方法局部更新视图,免除闪烁和时延等。
// virtual void Move(CPoint shift);//移动
// virtual void Rotate(int Degree);//旋转
// virtual void Scale(int scalecoef);//缩放
// virtual void Copy();//拷贝、粘贴、剪切功能
// virtual void Paste();
// virtual void Cut();
};
虽然是个小软件,“开发过程”还是可以和“软件工程”的步骤基本对应的。
本软件的“需求分析”是人们需要一个比Windows画笔功能强大,但十分小巧易用的小而精的基于矢量的(易于编辑)的图形工具。而且,另一方面,这个小软件的开发是一个典型的基于VC的面向对象软件开发的尝试,很有教学意义。
至此,基本完成了“概要设计”。以后将把“详细设计”和“编码”结合起来讲。至于“测试”和“维护”(改错、升级)有兴趣的读者可以自己完成。
数字图像的边缘检测是图像分割、目标区域的识别、区域形状提取等图像分析领域十分重要的基础,图像理解和分析的第一步往往就是边缘检测,目前它以成为机器视觉研究领域最活跃的课题之一,在工程应用中占有十分重要的地位。本文向读者简单介绍一下这个技术,并给出了在Visual C++环境下实现的代码。
所谓边缘就是指图像局部亮度变化最显著的部分,它是检测图像局部变化显著变化的最基本的运算。对于数字图像,图像灰度灰度值的显著变化可以用梯度来表示,以边缘检测Sobel算子为例来讲述数字图像处理中边缘检测的实现:
对于数字图像,可以用一阶差分代替一阶微分;
△xf(x,y)=f(x,y)-f(x-1,y);
△yf(x,y)=f(x,y)-f(x,y-1)
求梯度时对于平方和运算及开方运算,可以用两个分量的绝对值之和表示,即:
G[f(x,y)]={[△xf(x,y)] +[△yf(x,y)] } |△xf(x,y)|+|△yf(x,y)|;
Sobel梯度算子是先做成加权平均,再微分,然后求梯度,即:
△xf(x,y)= f(x-1,y+1) + 2f(x,y+1) + f(x+1,y+1)- f(x-1,y-1) - 2f(x,y-1) - f(x+1,y-1);
△yf(x,y)= f(x-1,y-1) + 2f(x-1,y) + f(x-1,y+1)- f(x+1,y-1) - 2f(x+1,y) - f(x+1,y+1);
G[f(x,y)]=|△xf(x,y)|+|△yf(x,y)|;
上述各式中的像素之间的关系见图
f(x-1,y-1)f(x,y-1) f(x+1,y-1)f(x-1,y)f(x,y)f(x+1,y)f(x-1,y+1)f(x,y+1)f(x+1,y+1)
我在视图类中定义了响应菜单命令的边缘检测Sobel算子实现灰度图像边缘检测的函数:
void CDibView::OnMENUSobel()
//灰度图像数据的获得参见天极网9.10日发表的拙作//VC数字图像处理一文
{
HANDLE data1handle;
LPBITMAPINFOHEADER lpBi;
CDibDoc *pDoc=GetDocument();
HDIB hdib;
unsigned char *hData;
unsigned char *data;
hdib=pDoc->m_hDIB;
BeginWaitCursor();
lpBi=(LPBITMAPINFOHEADER)GlobalLock((HGLOBAL)hdib);
hData= lpbi +* (LPDWORD)lpbi + 256*sizeof(RGBQUAD);
//得到指向位图像素值的指针
pDoc->SetModifiedFlag(TRUE);//设修改标志为"TRUE"
data1handle=GlobalAlloc(GMEM_SHARE,WIDTHBYTES(lpBi->biWidth*8)*lpBi->biHeight);
//申请存放处理后的像素值的缓冲区
data=(unsigned char*)GlobalLock((HGLOBAL)data1handle);
AfxGetApp()->BeginWaitCursor();
int i,j,buf,buf1,buf2;
for( j=0; jbiHeight; j++)//以下循环求(x,y)位置的灰度值
for( i=0; ibiWidth; i++)
{
if(((i-1)>=0)&&((i+1)biWidth)&&((j-1)>=0)&&((j+1)biHeight))
{//对于图像四周边界处的向素点不处理
buf1=(int)*(hData+(i+1)*WIDTHBYTES(lpBi->biWidth*8)+(j-1))
+2*(int)*(hData+(i+1)*WIDTHBYTES(lpBi->biWidth*8)+(j))
+(int)(int)*(hData+(i+1)*WIDTHBYTES(lpBi->biWidth*8)+(j+1));
buf1=buf1-(int)(int)*(hData+(i-1)*WIDTHBYTES(lpBi->biWidth*8)+(j-1))
-2*(int)(int)*(hData+(i-1)*WIDTHBYTES(lpBi->biWidth*8)+(j))
-(int)(int)*(hData+(i-1)*WIDTHBYTES(lpBi->biWidth*8)+(j+1));
//x方向加权微分
buf2=(int)(int)*(hData+(i-1)*WIDTHBYTES(lpBi->biWidth*8)+(j+1))
+2*(int)(int)*(hData+(i)*WIDTHBYTES(lpBi->biWidth*8)+(j+1))
+(int)(int)*(hData+(i+1)*WIDTHBYTES(lpBi->biWidth*8)+(j+1));
buf2=buf2-(int)(int)*(hData+(i-1)*WIDTHBYTES(lpBi->biWidth*8)+(j-1))
-2*(int)(int)*(hData+(i)*WIDTHBYTES(lpBi->biWidth*8)+(j-1))
-(int)(int)*(hData+(i+1)*WIDTHBYTES(lpBi->biWidth*8)+(j-1));
//y方向加权微分
buf=abs(buf1)+abs(buf2);//求梯度
if(buf>255) buf=255;
if(buf<0){buf=0;
*(data+i*WIDTHBYTES(lpBi->biWidth*8)+j)=(BYTE)buf;
}
else *(data+i*lpBi->biWidth+j)=(BYTE)0;
}
for( j=0; jbiHeight; j++)
for( i=0; ibiWidth; i++)
*(hData+i*WIDTHBYTES(lpBi->biWidth*8)+j)=*(data+i*WIDTHBYTES(lpBi->biWidth*8)+j);
//处理后的数据写回原缓冲区
AfxGetApp()->EndWaitCursor();
GlobalUnlock((HGLOBAL)hdib);
GlobalUnlock(data1handle);
GlobalFree(date1handle);
EndWaitCursor();
Invalidate(TRUE);
}
上述的数学分析读者可能看起来有些吃力,不过不要紧,对与边缘检测,大家只要知道有若干个检测模板(既边缘检测矩阵)可以直接实现检测功能就行了,现在将常用的检测实现公式列出如下:
Roberts算子:G[i,i]=|f[i,j]-f[i+1,j+1]|+|f[i+1,j]-f[i,j+1]|;
Sobe算子:G[i,i]=|f[i-1,j+1]+2f[i,j+1]+f[i+1,j+1]-f[i-1,j-1]-2f[i,j-1]-f[i+1,j-1]|
+|f[i-1,j-1]+2f[i-1,j]+f[i-1,j+1]-f[i+1,j-1]-2f[i+1,j]-f[i+1,j+1]|;
拉普拉斯算子:G[I,j]=|f[i+1,j]+f[i-1,j]+f(i,j+1)+f[i,j-1]-4f[i,j]|;
其中G[i,j]表示处理后(i,j)点的灰度值,f[i,j]表示处理前该点的灰度值。
笔者开发的该图像处理程序在Windows2000环境下编译通过,下面图2给出了依据图像处理算法得到的图像二值化、高通滤波、Sobel边缘算子的处理结果,读者需要注意的是我在进行Sobel算子进行处理后,又对它进行了二值化处理,这才得到C图。关于如何实现二值化图像,我会后续撰文对相关知识进行介绍。
在Windows应用程序中,对话框是应用最广泛也是比较难控制其风格(外表)的一类窗口。相信用过Windows 的朋友在享受其强大功能的同时,一定也为它所提供的具有立体感的界面而感叹吧。通常情况下,对话框的弹出和消隐都是瞬时的,下面将介绍如何实现对话框的动画弹出和消隐,增强程序的美观性。
请按以下步骤实现:
第一步:生成我们的工程(基于对话框)FlashDlg,所有的选项都取默认值,在对话框上随意添加几个控件。
第二步:在对话框的类头文件中定义如下变量,如下:
CPoint point;
int nWidth,nHeight;
int dx,dy;
int dx1,dy1;
第三步:在OnInitDialog()中添加如下代码:
BOOL CFlashDlgDlg::OnInitDialog()
{
CDialog::OnInitDialog();
CRect dlgRect;
GetWindowRect(dlgRect);
CRect desktopRect;
GetDesktopWindow()->GetWindowRect(desktopRect);
MoveWindow(
(desktopRect.Width() - dlgRect.Width()) / 2,
(desktopRect.Height() - dlgRect.Height()) / 2,
0,
0 );
nWidth=dlgRect.Width();
nHeight=dlgRect.Height();
dx=2;
dy=4;
dx1=2;
dy1=2;
SetTimer(1,10 , NULL);
return TRUE;
}
第四步:添加OnTimer函数,添加如下代码:
void CFlashDlgDlg::OnTimer(UINT nIDEvent)
{
// TODO: Add your message handler code here and/or call default
CRect dlgRect;
GetWindowRect(dlgRect);
CRect desktopRect;
GetDesktopWindow()->GetWindowRect(desktopRect);
if(nIDEvent == 1)
{
MoveWindow(
(-dx+desktopRect.Width() - dlgRect.Width()) / 2,
(-dy+desktopRect.Height() - dlgRect.Height()) / 2,
+dx+dlgRect.Width(),
+dy+dlgRect.Height() );
if(dlgRect.Width() >=nWidth)
dx=0; // do not over grow
if(dlgRect.Height() >=nHeight)
dy=0; // do not over grow
if((dlgRect.Width() >=nWidth) && (dlgRect.Height() >=nHeight))
KillTimer(1); //Stop the timer
}
if((dlgRect.Width() >=nWidth) && (dlgRect.Height() >=nHeight))
KillTimer(1); //Stop the timer
if(nIDEvent == 2)
{
MoveWindow((+dx+desktopRect.Width() - dlgRect.Width()) / 2,
(+dy+desktopRect.Height() - dlgRect.Height()) / 2,
-dx1+dlgRect.Width(),
-dy1+dlgRect.Height() );
if(dlgRect.Width() <= 0)
dx1=0; // do not over grow
if(dlgRect.Height() <= 0 )
dy1=0; // do not over grow
if((dlgRect.Width() <= 0 ) && (dlgRect.Height() <=0))
{
KillTimer(2); //Stop the timer
CDialog::OnOK();
}
}
CDialog::OnTimer(nIDEvent);
}
好了,对话框的动画出现和消隐实现了,运行程序我们会发现对话框平滑的划出,关闭程序我们会发现对话框平滑的消失。
PhotoShop的图像处理功能很强,其中有一个功能是将灰度图像转换为彩色图像,数字图像处理中,也经常要遇到灰度图像与彩色图像相互转换的问题,如何自己解决这个问题,值得大家探讨,现将我解决这类问题的方法陈述如下:
工程应用中经常要遇到需要把彩色图像到灰度图像的变换的问题,采集卡过来的图像为彩色图像,为加快处理速度,要把彩色图像转换为黑白图象,这个问题比较好解决,一般情况下彩色图像每个像素用三个字节表示,每个字节对应着R、G、B分量的亮度(红、绿、蓝),转换后的黑白图像的一个像素用一个字节表示该点的灰度值,它的值在0~255之间,数值越大,该点越白,既越亮,越小则越黑。转换公式为Gray(i,j)=0.11*R(i,j)+0.59*G(i,j)+0.3*B(i,j),其中Gray(i,j)为转换后的黑白图像在(i,j)点处的灰度值,我们可以观察该式,其中绿色所占的比重最大,所以转换时可以直接使用G值作为转换后的灰度。
至于灰度图像转换为彩色图像,技术上称为灰度图像的伪彩色处理,这是一种视觉效果明显而技术又不是很复杂的图像增强技术。灰度图像中,如果相邻像素点的灰度相差不大,但包含了丰富的信息的话,人眼则无法从图像中提取相应的信息,因为人眼分辨灰度的能力很差,一般只有几十个数量级,但是人眼对彩色信号的分辨率却很强,这样将黑白图像转换为彩色图像人眼可以提取更多的信息量。在转换过程中,经常采用的技术是灰度级-彩色变换,意思就是对黑白图像上的每一个像素点,取得该点的灰度值并送入三个通道经过实施不同的变换,产生相应的R、G、B的亮度值,即所求彩色图像对应像素点的彩色值,具体变换公式很多,我采用的是最常用的一种,变换曲线图如下:
上图中,三个图分别代表了三个变换通道,R、G、B指的是变换后对应点的R、G、B分量值,L指的是各个分量的最大值为255,G(x,y)为相应点的灰度值。理论上就这些,下面是我用VC实现的源代码,图一为我的灰度位图,图二为伪彩色处理后的结果图。我这个实现函数中是如何得到灰度位图的数据的就不多讲了,有兴趣的朋友可参考我在天极网上九月十号发表的《VC灰度位图处理》一文,那里应该讲的很清楚了。需要读者注意的是彩色图像中每个象素中的三个字节分别代表的分量,第一个字节为B,第二个为G值、最后一个为R值,这个顺序不要搞错了。代码实现如下:
void CDibView::OnMenuchange() file://图像转换实现函数
{
// TODO: Add your command handler code here
HANDLE data1handle;
LPBITMAPINFOHEADER lpBi;
BITMAPINFO *m_pBMI;
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);
m_pBMI=new BITMAPINFO;//生成彩色图像的信息头
m_pBMI->bmiHeader.biBitCount=24;
m_pBMI->bmiHeader.biClrImportant=0;
m_pBMI->bmiHeader.biClrUsed=0;
m_pBMI->bmiHeader.biCompression=BI_RGB;
m_pBMI->bmiHeader.biHeight=lpBi->biHeight;
m_pBMI->bmiHeader.biWidth=lpBi->biWidth;
m_pBMI->bmiHeader.biPlanes=1;
m_pBMI->bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
m_pBMI->bmiHeader.biXPelsPerMeter=0;
m_pBMI->bmiHeader.biYPelsPerMeter=0;
m_pBMI->bmiHeader.biSizeImage=WIDTHBYTES(lpBi->biWidth*8)*lpBi->biHeight*3;
file://data=hData;
int R,G,B,i,j;
data1handle=GlobalAlloc(GMEM_SHARE,WIDTHBYTES(lpBi->biWidth*8)*lpBi->biHeight*3);
file://生成存储彩色图象数据的缓冲区
data=(unsigned char*)GlobalLock((HGLOBAL)data1handle);
for(i=0;ibiHeight;i++)//实现灰度到彩色变换
for(j=0;jbiWidth*8);j++)
{
if(*(hData+i*WIDTHBYTES(lpBi->biWidth*8)+j)<=64)
{R=0;
G=(int)4*(*(hData+i*WIDTHBYTES(lpBi->biWidth*8)+j));
B=255;
}
if(*(hData+i*WIDTHBYTES(lpBi->biWidth*8)+j)>64
&& *(hData+i*WIDTHBYTES(lpBi->biWidth*8)+j)<=128)
{R=0;
G=255;
B=(int)4*(128-*(hData+i*WIDTHBYTES(lpBi->biWidth*8)+j));
}
if(*(hData+i*WIDTHBYTES(lpBi->biWidth*8)+j)>128
&& *(hData+i*WIDTHBYTES(lpBi->biWidth*8)+j)<=192)
{R=(int)4*(*(hData+i*WIDTHBYTES(lpBi->biWidth*8)+j)-128);
G=255;
B=0;
}
if(*(hData+i*WIDTHBYTES(lpBi->biWidth*8)+j)>192
&& *(hData+i*WIDTHBYTES(lpBi->biWidth*8)+j)<=255)
{R=255;
G=(int)4*(255-*(hData+i*WIDTHBYTES(lpBi->biWidth*8)+j));
B=0;
}
file://将生成的R、G、B分量存入目标缓冲区
*(data+i*WIDTHBYTES(lpBi->biWidth*8)*3+j*3)=B;
*(data+i*WIDTHBYTES(lpBi->biWidth*8)*3+j*3+1)=G;
*(data+i*WIDTHBYTES(lpBi->biWidth*8)*3+j*3+2)=R;
}
GlobalUnlock((HGLOBAL)hdib);
GlobalUnlock(data1handle);
EndWaitCursor();
CClientDC pDC(this);
file://显示真彩色图像
StretchDIBits(pDC.GetSafeHdc(),0,0,lpBi->biWidth,lpBi->biHeight,0,0,
lpBi->biWidth, lpBi->biHeight,data,m_pBMI,DIB_RGB_COLORS,
SRCCOPY);
delete m_pBMI;
}
图 一
图 二
数字图像处理技术博大精深,我真诚的希望和广大朋友探讨
图像处理技术已经渗透到人类生活的各个领域并得到越来越多的应用,图像处理所涉及的图像格式有很多种,如TIF、JEMP、BMP等等,工程应用中经常要处理256级的灰度BMP图像,如通过黑白采集卡采集得到的图像。BMP灰度图像作为Windows环境下主要的图像格式之一,以其格式简单,适应性强而倍受欢迎。在进行图像处理时,操作图像中的像素值就要得到图像阵列;经过处理后的图像的像素值存储起来;显示图像时要正确实现调色板,结合这些问题,文章针对性的给出了操作灰度BMP图像时的部分函数实现代码及注释。
一、 BMP位图操作
BMP位图包括位图文件头结构BITMAPFILEHEADER、位图信息头结构BITMAPINFOHEADER、位图颜色表RGBQUAD和位图像素数据四部分。处理位图时要根据文件的这些结构得到位图文件大小、位图的宽、高、实现调色板、得到位图像素值等等。对于256级灰度图像每个像素用8bit表示颜色的索引值,这里要注意的一点是在BMP位图中,位图的每行像素值要填充到一个四字节边界,即位图每行所占的存储长度为四字节的倍数,不足时将多余位用0填充。
在处理图像应用程序的文档类(CdibDoc.h)中声明如下宏及公有变量:
#define WIDTHBYTES(bits) (((bits) + 31) / 32 * 4)//计算图像每行象素所占的字节数目
HANDLE m_hDIB;//存放位图数据的句柄
CPalette* m_palDIB;//指向调色板Cpalette类的指针
CSize m_sizeDoc; file://初始化视图的尺寸
1、 读取灰度BMP位图
根据BMP位图文件的结构,操作BMP位图文件读入数据,重载了文挡类的OnOpenDocument函数如下:
BOOL CDibDoc::OnOpenDocument(LPCTSTR lpszPathName)
{
CFile file;
CFileException fe;
if (!file.Open(lpszPathName, CFile::modeRead | CFile::shareDenyWrite, &fe))
{
AfxMessageBox("文件打不开");
return FALSE;
}//打开文件
DeleteContents();//删除文挡
BeginWaitCursor();
BITMAPFILEHEADER bmfHeader;//定义位图文件头结构
DWORD dwBitsSize;
HANDLE hDIB;
LPSTR pDIB;
BITMAPINFOHEADER *bmhdr;//指向位图信息头结构的指针
dwBitsSize = file.GetLength();//得到文件长度
if (file.Read((LPSTR)&bmfHeader, sizeof(bmfHeader)) !=
sizeof(bmfHeader))
return FALSE;
if (bmfHeader.bfType != 0x4d42) file://检查是否为BMP文件
return FALSE;
hDIB=(HANDLE) ::GlobalAlloc(GMEM_MOVEABLE |
GMEM_ZEROINIT, dwBitsSize);
file://申请缓冲区
if (hDIB == 0)
{
return FALSE;
}
pDIB = (LPSTR) ::GlobalLock((HGLOBAL)hDIB);
file://得到申请的缓冲区的指针
if (file.ReadHuge(pDIB, dwBitsSize - sizeof(BITMAPFILEHEADER)) !=
dwBitsSize - sizeof(BITMAPFILEHEADER) )
{
::GlobalUnlock((HGLOBAL)hDIB);
hDIB=NULL;
return FALSE;
}//读数据,包括位图信息、位图颜色表、图像像素的灰度值
bmhdr=(BITMAPINFOHEADER*)pDIB;//为指向位图信息头结构的指针付值
::GlobalUnlock((HGLOBAL)hDIB);
if ((*bmhdr).biBitCount!=8) file://验证是否为8bit位图
return FALSE;
m_hDIB=hDIB;
InitDIBData();
file://自定义函数,根据读入的数据得到位图的宽、高、颜色表
file:// 来得到初始化视的尺寸、生成调色板
EndWaitCursor();
SetPathName(lpszPathName);//设置存储路径
SetModifiedFlag(FALSE); // 设置文件修改标志为FALSE
return TRUE;
}
2、 灰度位图数据的存储
为了将图像处理后所得到的像素值保存起来,重载了文档类的OnSaveDocument函数,其具体实现如下:
BOOL CDibDoc::OnSaveDocument(LPCTSTR lpszPathName)
{
CFile file;
CFileException fe;
BITMAPFILEHEADER bmfHdr; // 位图文件头结构
LPBITMAPINFOHEADER lpBI; file://指向位图信息结构的指针
DWORD dwDIBSize;
if (!file.Open(lpszPathName, CFile::modeCreate |
CFile::modeReadWrite | CFile::shareExclusive, &fe))
{
AfxMessageBox("文件打不开");
}//打开文件
BOOL bSuccess = FALSE;
BeginWaitCursor();
lpBI = (LPBITMAPINFOHEADER) ::GlobalLock((HGLOBAL) m_hDIB);
if (lpBI == NULL)
return FALSE;
dwDIBSize = *(LPDWORD)lpBI + 256*sizeof(RGBQUAD);
// Partial Calculation
DWORD dwBmBitsSize;//BMP文件信息结构所占的字节数
dwBmBitsSize=WIDTHBYTES((lpBI->biWidth)*((DWORD)lpBI->biBitCount)) *lpBI->biHeight;// 存储时位图所有像素所占的总字节数
dwDIBSize += dwBmBitsSize;
lpBI->biSizeImage = dwBmBitsSize; // 位图所有像素所占的总字节数
file://以下五句为文件头结构填充值
bmfHdr.bfType =0x4d42; // 文件为"BMP"类型
bmfHdr.bfSize = dwDIBSize + sizeof(BITMAPFILEHEADER);//文件总长度
bmfHdr.bfReserved1 = 0;
bmfHdr.bfReserved2 = 0;
bmfHdr.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + lpBI->biSize
+ 256*sizeof(RGBQUAD);
file://位图数据距问件头的偏移量
file.Write((LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER));//写文件头
file.WriteHuge(lpBI, dwDIBSize);
file://将位图信息(信息头结构、颜色表、像素数据)写入文件
::GlobalUnlock((HGLOBAL) m_hDIB);
EndWaitCursor();
SetModifiedFlag(FALSE); // back to unmodified
return TRUE;
}
二、 调色板的操作
灰度图像要正确显示,必须实现逻辑调色板和系统调色板 ,通过在主框架类中处理Windows定义的消息WM_QUERYNEWPALETTE 、WM_PALETTECHANGED及视图类中处理自定义消息WM_DOREALIZE(该消息在主框架窗口定义如下:#define WM_REALIZEPAL (WM_USER+100))来实现调色板的操作。
void CMainFrame::OnPaletteChanged(CWnd* pFocusWnd)
{ file://总实现活动视的调色板
CMDIFrameWnd::OnPaletteChanged(pFocusWnd);
CMDIChildWnd* pMDIChildWnd = MDIGetActive();
if (pMDIChildWnd == NULL)
return
CView* pView = pMDIChildWnd->GetActiveView();
ASSERT(pView != NULL);
SendMessageToDescendants(WM_DOREALIZE, (WPARAM)pView->m_hWnd);
file://通知所有子窗口系统调色板已改变
}
BOOL CMainFrame::OnQueryNewPalette()//提供实现系统调色板的机会
{
// 实现活动视的调色板
CMDIChildWnd* pMDIChildWnd = MDIGetActive();
if (pMDIChildWnd == NULL)
return FALSE; // no active MDI child frame (no new palette)
CView* pView = pMDIChildWnd->GetActiveView();
ASSERT(pView != NULL);
file://通知活动视图实现系统调色板
pView->SendMessage(WM_DOREALIZE, (WPARAM)pView->m_hWnd);
return TRUE;
}
LRESULT CDibView::OnDoRealize(WPARAM wParam, LPARAM)//实现系统调色板
{
ASSERT(wParam != NULL);
CDibDoc* pDoc = GetDocument();
if (pDoc->m_hDIB == NULL)
return 0L; // must be a new document
CPalette* pPal = pDoc->m_palDIB;
file://调色板的颜色表数据在InitDIBData()函数中实现
if (pPal != NULL)
{
CMainFrame* pAppFrame = (CMainFrame*) AfxGetApp()->m_pMainWnd;
ASSERT_KINDOF(CMainFrame, pAppFrame);
CClientDC appDC(pAppFrame);
CPalette* oldPalette = appDC.SelectPalette(pPal, ((HWND)wParam) != m_hWnd);
file://只有活动视才可以设为"FALSE",
// 即根据活动视的调色板设为"前景"调色板
if (oldPalette != NULL)
{
UINT nColorsChanged = appDC.RealizePalette();//实现系统调色板
if (nColorsChanged > 0)
pDoc->UpdateAllViews(NULL);//更新视图
appDC.SelectPalette(oldPalette, TRUE);
file://将原系统调色板置为逻辑调色板
}
else
{
TRACE0("\tSelectPalette failed in
CDibView::OnPaletteChanged\n");
}
}
注:在调用API函数显示位图时,不要忘记设置逻辑调色板,即"背景"调色板,否则位图将无法正确显示。
三、图像的数字化处理
通过以上读文件的操作,已经得到图像数据,由于得到的数据包括多余信息,所以在进行数字图像处理时要进一步删除多余信息,只对位图的像素进行操作,以基于模板的高通滤波为例来讲述数字图像处理的实现 :
void CDibView::OnMENUHighPass()
{ 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,9,-1},{-1,-1,-1}};
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);
AfxGetApp()->EndWaitCursor();
GlobalUnlock((HGLOBAL)hdib);
GlobalUnlock(data1handle);
EndWaitCursor();
Invalidate(TRUE);
}
四、图像的基本操作处理
1、图像平移
图像平移只是改变图像在屏幕上的位置,图像本身并不发生变化。假设原图像区域左上角坐标为(x0, y0),右下角坐标为(x1, y1),将图像分别沿x和y轴平移dx和dy,则新图像的左上角坐标为(x0+dx, y0+dy),右下角坐标为(x1+dx, y1+dy)。坐标平移变换公式为:
x1 = x + dx
y1 = y + dy
在屏幕上实现图像的移动分为四个步骤:
⑴ 读原图像到缓冲区;
⑵ 擦除视图上原图像;
⑶ 计算平移后的新坐标。
⑷ 利用API函数::StretchDIBits()在新的左上角坐标位置处重新显示原图像。
其中,擦除原图像的方法与图形变换中擦除原图形的方法一致,在实现中仍采用XOR异或方式画图擦除原图像。对于新坐标值的计算还需要考虑边界情况,不要在图像平移后超出允许的屏幕范围。
2、图像颠倒
图像颠倒是指把定义好的图像区域上下翻转地显示在屏幕上。分析图像颠倒的过程,可发现每行的图像信息都保持不变,而只是改变了行的顺序,将第一行与最后的第n行相互交换,第二行与第n - 1行交换……,依此类推,从而实现了图像的颠倒。只需采用按行交换的方式,即可方便地修改缓冲区内容,实现图像的颠倒。基本步骤如下:
(1)将原图像读入缓冲区,并擦除原图像;
(2) 计算图像的高度,即行数height;计算图像宽度width;根据宽度、高度生成新缓冲区;
(3)把第一行与最末行交换,第2行与第n-1行交换……,依此类推,直至全部交换完毕。既原图中的(x、y)点,在新生成的图象中对应为x1=x,y1=height-1-y。把原图中的象素值读入新缓冲区的(x1,y1)点处。
(4)把交换后的图像缓冲区内容重新显示在屏幕上。
3、图像镜像变换
镜像变换是指将指定区域的图像左右翻转地显示在屏幕。分析镜像变换过程可以发现:每行图像信息的处理方式是相同的,而且行顺序不发生变化,只是每一行的像素信息按从左到右的顺序进行了左右颠倒,从而实现了镜像变换。因此,采用按行逐点变换的方式实现图像的镜像。
给出原图中的任意点(x, y)镜像变换后的新坐标(x1, y1)的坐标变换公式:
x1 = width-x-1
y1 = y
根据以上公式,对各个像素点计算新坐标后,把原图中的象素值读入新缓冲区的(x1,y1)点处。
4、图像任意角度的旋转
图像旋转是指把定义的图像绕某一点以逆时针或顺时针方向旋转一定的角度,通常是指绕图像的中心以逆时针方向旋转。
首先根据旋转的角度、图象对角线的长度计算旋转后的图像的最大宽度、高度,根据旋转后图象最大的宽度、高度生成新的缓冲区,假设图像的左上角为(left, top),右下角为(right, bottom),则图像上任意点(x, y)绕其中心(xcenter, ycenter)逆时针旋转angle角度后,新的坐标位置(x1, y1)的计算公式为:
xcenter = (width+1)/2+left;
ycenter = (height+1)/2+top;
x1 = (x-xcenter) cosθ- (y - ycenter) sinθ+xcenter;
y1 = (x-xcenter) sinθ+ (y- ycenter) cosθ+ ycenter;
与图像的镜像变换相类似,把原图中的象素值读入新缓冲区的(x1,y1)点处。注意在新缓冲区中与原图没有对应的象素点的值用白色代替。
五、小结
笔者开发的该图像处理程序在Windows98环境下编译通过,本文主要讲述了8bit灰度图像的处理,读者可以本文的基础上开发自己的针对二值、真彩色格式的图像处理系统。
对多媒体数据的检索,早期的方法是用文本将多媒体数据进行标识,这显然不是基于多媒体信息本身内容的检索,对多媒体数据中包含的信息是一中及大的浪费;
基于内容的检索是多媒体数据库的关键技术,如何实现这块技术,是值得商榷的,而最好的方法是使用无需领域知识的检索方法,因此,基于颜色的方法就是实现的关键;
本文介绍了颜色直方图和颜色对方法在基于内容检索时的实现思路和理论;
其实颜色直方图简单来说,就是统计图像中具有某一特定颜色的象素点数目而形成的各颜色的直方图表示,不同的直方图代表不同图片的特征。
该方法也可以应用于视频数据库的查询中,有以下三种方式:
(1)指明颜色组成--该法需要用户对图像中的颜色非常敏感,而且使用起来也不方便,检索的查准率和查全率并不高,因此文章中并未介绍该法的实现思路
(2)指明一幅示例图像--通过与用户确定的图像的颜色直方图的相似性匹配得到查询结果,这是文章介绍的两种方法的根本
(3)指明图像中一个子图--分割图像为各个小块,然后利用选择小块来确定图像中感兴趣的对象的轮廓,通过建立更复杂的颜色关系(如颜色对方法)来查询图像,该方法是文章的重心所在
两图片是否相似可以采用欧氏距离来描述:
Ed=(G,S)=
检索后,全图直方图的相似度的定量度量可以用如下公式表示:
Sim(G,S)=
(N为颜色级数,Sim越靠近1两幅图片越相似)
可以对上面2中的公式加改进对某些相对重要的颜色乘上一个权重,就可以做寻找某一前景或组合的查询。
全图的颜色直方图算法过于简单,因此带来很多问题,如:可能会有两幅根本不同的图像具有完全一样的颜色直方图,不反映颜色位置信息,这样导致查准率和查全率都不高,因此问文章提出了一个改进,即将图像进行了分割,形成若干子块,这样就提供了一定程度的位置信息,而且可以对含用户感兴趣的子块加大权重,提高检索的查询智能性和查准查全率,相应的公式有,子块Gij与Sij的相似性度量为:
(P为所选颜色空间的样点数)
再引入子块权重Wij,选取L个最大的Sim值作Simk(Gk,Sk),就有:
(Wk 的选取应根据图像的特点决定,可以使图像中间或用户指定的区域权重大,以反映图像的位置信息)
主要目的:借助图像中相邻子块之间的颜色直方图的配对建模,实现对图像中的具体对象的查询,支持对象的移位、旋转和部分变形;
颜色对方法特别适合于对边界明显的对象的查询;
实现思路:计算用户输入图像的子块直方图片à用户选定包含查询对象的子块à计算这些子块与周围相邻的子块的颜色对表à将这些颜色对中差值小于某一域值的颜色对删除以消除颜色噪声à选取颜色对表中数值最大的几个颜色对做为图片的代表特征à搜索目标图像的每一子块的颜色对表寻找与这写代表颜色对的匹配à统计单一匹配次数à若有某一比例以上的颜色对匹配到,图像即被检索到。
相似性度量:
(N为所用查询颜色对数目)
qj、gj:颜色对j在查询图像Q和目标图像G中出现的次数
查询时颜色对的匹配应该是不精确的,应该允许的误差为2%以内