2010年5月12日星期三

在WinAPI中使用MFC的类

MFC工程使用MFC库时,可参考以下步骤

1、工程设置中,将MFC的使用由原来的“使用标准windows库”改为“在共享DLL中使用MFC”(VC71

如果是英文版,相关选项是:

Microsoft Foundation Classes: Use MFC in a shared dll, no using MFCVC6

2、头文件包含

不同的MFC类需包含的头文件是不一样的。

常用的类,如Cstring, Cedit 等,包含afxwin.h就可以了

如果不清楚包含什么头文件的话,可以同msdn进行查询,msdn中,对于MFC类的介绍中,都会给出相应的header file requirement.

3、#include 语句一定要写在首行

这一点很重要,通常出现前面讲到的windows.h重复包含错误,都是因为#include 语句没有写在首行。

另外还要注意的是,如果#include语句是在一个头文件里,那么对应头文件的包含也要写在首行。示例如下:

=============

test.h文件的内容如下:

include //保证该语句在首行

test.cpp的文件内容如下:

#include “test.h” //同样也要保证该语句在首行

=============

如果要用cstring

就包含include

并且要保证这个包含处于首行
4.打开resource.h,找到并删除 #define IDC_STATIC -1一行(貌似可有可无)

resenham算法是计算机图形学典型的直线光栅化算法。

resenham算法是计算机图形学典型的直线光栅化算法。

  • 从另一个角度看直线光栅化显示算法的原理
    • 由直线的斜率确定选择在x方向或y方向上每次递增(减)1个单位,另一变量的递增(减)量为0或1,它取决于实际直线与最近光栅网格点的距离,这个距离的最大误差为0.5。

     

  • 1)Bresenham的基本原理

     

    • 假定直线斜率k在0~1之间。此时,只需考虑x方向每次递增1个单位,决定y方向每次递增0或1。

      直线当前点为(xi,y)
      直线当前光栅点为(xi,yi)

      下一个直线的点应为(xi+1,y+k)
      下一个直线的光栅点
      或为右光栅点(xi+1,yi)(y方向递增量0)
      或为右上光栅点(xi+1,yi+1)(y方向递增量1)

      记直线与它垂直方向最近的下光栅点的误差为d,有:d=(y+k)–yi,且

      0≤d≤1
      当d<0.5:下一个象素应取右光栅点(xi+1,yi)>

      如果直线的(起)端点在整数点上,误差项d的初值:d0=0,
      x坐标每增加1,d的值相应递增直线的斜率值k,即:d=d + k。
      一旦d≥1,就把它减去1,保证d的相对性,且在0-1之间。

      令e=d-0.5,关于d的判别式和初值可简化成:

      e的初值e0= -0.5,增量亦为k;
      e<0时,取当前象素(xi,yi)的右方象素(xi+1,yi);>0时,取当前象素(xi,yi)的右上方象素(xi+1,yi+1);
      e=0时,可任取上、下光栅点显示。

      Bresenham算法的构思巧妙:它引入动态误差e,当x方向每次递增1个单位,可根据e的符号决定y方向每次递增 0 或 1。

      e<0,y方向不递增>0,y方向递增1
      x方向每次递增1个单位,e = e + k

      因为e是相对量,所以当e>0时,表明e的计值将进入下一个参考点(上升一个光栅点),此时须:e = e - 1

       

  • 2)Bresenham算法的实施——Rogers 版

     

    • 通过(0,0)的所求直线的斜率大于0.5,它与x=1直线的交点离y=1直线较近,离y=0直线较远,因此取光栅点(1,1)比(1,0)更逼近直线;
      如果斜率小于0.5,则反之;
      当斜率等于0.5,没有确定的选择标准,但本算法选择(1,1)

      程序

       

      • //Bresenham's line resterization algorithm for the first octal.
        //The line end points are (xs,ys) and (xe,ye) assumed not equal.
        // Round is the integer function.
        // x,y, ∆x, ∆y are the integer, Error is the real.
        //initialize variables
        x=xs
        y=ys
        ∆x = xe -xs
        ∆y = ye -ys
        //initialize e to compensate for a nonzero intercept
        Error =∆y/∆x-0.5
        //begin the main loop
        for i=1 to ∆x
        WritePixel (x, y, value)
        if (Error ≥0) then
        y=y+1
        Error = Error -1
        end if
        x=x+1
        Error = Error +∆y/∆x
        next i
        finish

       

  • 3)整数Bresenham算法

     

    • 上述Bresenham算法在计算直线斜率和误差项时要用到浮点运算和除法,采用整数算术运算和避免除法可以加快算法的速度。

      由于上述Bresenham算法中只用到误差项(初值Error =∆y/∆x-0.5)的符号

      因此只需作如下的简单变换:

      NError = 2*Error*∆x

      即可得到整数算法,这使本算法便于硬件(固件)实现。

      程序

       

      • //Bresenham's integer line resterization algorithm for the first octal.
        //The line end points are (xs,ys) and (xe,ye) assumed not equal. All variables are assumed integer.
        //initialize variables
        x=xs
        y=ys
        ∆x = xe -xs
        ∆y = ye -ys
        //initialize e to compensate for a nonzero intercept
        NError =2*∆y-∆x //Error =∆y/∆x-0.5
        //begin the main loop
        for i=1 to ∆x
        WritePixel (x, y)
        if (NError >=0) then
        y=y+1
        NError = NError –2*∆x //Error = Error -1
        end if
        x=x+1
        NError = NError +2*∆y //Error = Error +∆y/∆x
        next i
        finish

       

  • 4)一般Bresenham算法

     

    • 要使第一个八卦的Bresenham算法适用于一般直线,只需对以下2点作出改造:
      当直线的斜率|k|>1时,改成y的增量总是1,再用Bresenham误差判别式确定x变量是否需要增加1;
      x或y的增量可能是“+1”或“-1”,视直线所在的象限决定。

      程序

       

      • //Bresenham's integer line resterization algorithm for all quadrnts
        //The line end points are (xs,ys) and (xe,ye) assumed not equal. All variables are assumed integer.
        //initialize variables
        x=xs
        y=ys
        ∆x = abs(xe -xs) //∆x = xe -xs
        ∆y = abs(ye -ys) //∆y = ye -ys
        sx = isign(xe -xs)
        sy = isign(ye -ys)
        //Swap ∆x and ∆y depending on the slope of the line.
        if ∆y>∆x then
        Swap(∆x,∆y)
        Flag=1
        else
        Flag=0
        end if
        //initialize the error term to compensate for a nonezero intercept
        NError =2*∆y-∆x
        //begin the main loop
        for i=1 to ∆x
        WritePixel(x, y , value)
        if (Nerror>=0) then
        if (Flag) then //∆y>∆x,Y=Y+1
        x=x+sx
        else
        y=y+sy
        end if // End of Flag
        NError = NError –2*∆x
        end if // End of Nerror
        if (Flag) then //∆y>∆x,X=X+1
        y=y+sy
        else
        x=x+sx
        end if
        NError = NError +2*∆y
        next i
        finish

       

  • 例子

中点画圆算法

为了能以任意点为圆心画圆,我们可以把圆心先设为视点(相当于于将其平移到坐标原点),然后通过中点法扫描转换后,再恢复原来的视点(相当于将圆心平移回原来的位置)。

圆心位于原点的圆有四条对称轴x=0,y=0,x=yx=-y,从而圆上一点(x,y),可得到其关于四条对称轴的七个对称点,这称为八对称性,下面的函数就用来显示(x,y)及其七个对称点.
200772802.jpg


void CirclePoints(int x,int y,long color,CDC *pDC)

{

//第1象限

pDC->SetPixel(x,y,color);

pDC->SetPixel(y,x,color);

//第2象限

pDC->SetPixel(-x,y,color);

pDC->SetPixel(-y,x,color);

//第3象限

pDC->SetPixel(-y,-x,color);

pDC->SetPixel(-x,-y,color);

//第4象限

pDC->SetPixel(x,-y,color);

pDC->SetPixel(y,-x,color);

}

中点画圆算法就是每部单位间隔取样并且计算离圆最近的位置。在继续之前,我这里补充一个关于圆对称性的知识点,通过在圆中计算考虑使用对称性计算开销可以减小到原来的1/8。对称性质原理:(1)圆是满足x轴对称的,这样只需要计算原来的1/2点的位置;(2)圆是满足y轴对称的,这样只需要计算原来的1/2点的位置;(3)圆是满足y = x or y = -x轴对称的,这样只需要计算原来的1/2点的位置;通过上面三个性质分析得知,对于元的计算只需要分析其中1/8的点即可。例如:分析出来目标点(x,y)必然存在(x,-y),(-x,y),(-x,-y),(y,x),(y,-x),(-y,x),(-y,-x)的另外7个点。关于中心画圆算法,通过计算x = 0到 x = y的1/8圆的范围,然后通过对称原理得到其他7/8个点的信息。这里和Bresenham算法有很多相似之处,同样有一个决定下一个位置的关键值P来做权衡处理。在中点画圆算法中,通过平移的方法将假设圆心在坐标原点,然后计算,最后再平移到真实原心位置。 如果我们构造函数 F(x,y)=x2+y2-R2,则对于圆上的点有F(x,y)=0,对于圆外的点有F(x,y)>0,对于圆内的点F(x,y)<0 d="F(M)=" d="F(xp+2,yp-0.5)=" r2="">

d≥0,则应取P2为下一象素,而且下一象素的判别式为

d=F(xp+2,yp-1.5)=(xp+2)2+(yp-1.5)2-R2=d+2(xp-yp)+5

我们这里讨论的第一个象素是(0,R),判别式d的初始值为:

d0=F(1,R-0.5)=1.25-R

200772803.jpg

中点画圆算法内容:

1,输入圆心位置和圆的半径,得到圆周上的第一个点Point1;

(假设起始点为坐标原点,后面将通过坐标平移来处理非圆心在圆点)

2,计算决策关键参数的初始值,P = 5/4 - r;

3,在每个Xn的位置,从n = 0开始,更具决策值P来判断:

如果P<0,下一个点的位置为(Xn+1,Yn);

并且执行P = P + 2*x+3;

如果P>=0,下一个点的位置为(Xn+1,Yn-1);

并且执行P = P + 2.0*(x-y)+5;

4,通过对称原理计算其他7个对称相关点;

5,移动坐标到圆心点(x1,y1)

X = X + x1;

Y = Y + y1;

6,如果X重复执行35的步骤,否则结束该算法

程序如下:

void Circle::Draw(CDC *pDC)
{//中点算法画圆
int x,y;
double p;
pDC->SetViewportOrg(pMid);
x=0;
y=radis;
p=1.25-radis;
while(x<=y+1) { CirclePoints(x,y,m_lPenColor,pDC); x++; if(p>=0)
{
y--;
p+=2.0*(x-y)+5;
}
else
p+=2*x+3;
}
pDC->SetViewportOrg(0,0);
}

bresenham画圆算法

bresenham画圆算法

中点画圆算法在一个方向上取单位间隔,在另一个方向的取值由两种可能取值的中点离圆的远近而定。实际处理中,用决策变量的符号来确定象素点的选择,因此算法效率较高。

  Bresenham画圆和椭圆程序 - 随风倒上 - 赵志刚--Greddy的个人主页一、中点画圆算法描述

  设要显示圆的圆心在原点(0,0),半径为R,起点在(0,R)处,终点在(Bresenham画圆和椭圆程序 - 随风倒上 - 赵志刚--Greddy的个人主页Bresenham画圆和椭圆程序 - 随风倒上 - 赵志刚--Greddy的个人主页)处,顺时针生成八分之一圆,利用对称性扫描转换全部圆。

  为了应用中点画圆法,我们定义一个圆函数

F(x,y)=x2+y2-R2(2-19)

  任何点(x,y)的相对位置可由圆函数的符号来检测:

F(x,y)Bresenham画圆和椭圆程序 - 随风倒上 - 赵志刚--Greddy的个人主页<0 style="line-height: 22px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; ">

=0 点(x,y)位于数学圆上

>0 点(x,y)位于数学圆外

(2-20)

  如下图所示,图中有两条圆弧A和B,假定当前取点为Pi(xi,yi),如果顺时针生成圆,那么下一点只能取正右方的点E(xi+1,yi)或右下方的点SE(xi+1,yi-1)两者之一。

Bresenham画圆和椭圆程序 - 随风倒上 - 赵志刚--Greddy的个人主页

中点画线算法

  假设M是E和SE的中点,即 Bresenham画圆和椭圆程序 - 随风倒上 - 赵志刚--Greddy的个人主页,则:  1、当F(M)<0时,m在圆内(圆弧a),这说明点e距离圆更近,应取点e作为下一象素点;

  2、当F(M)>0时,M在圆外(圆弧B),表明SE点离圆更近,应取SE点;

  3、当F(M)=0时,在E点与SE点之中随便取一个即可,我们约定取SE点。

  Bresenham画圆和椭圆程序 - 随风倒上 - 赵志刚--Greddy的个人主页二、中点画圆算法思想

  因此,我们用中点M的圆函数作为决策变量di,同时用增量法来迭代计算下一个中点M的决策变量di+1。

Bresenham画圆和椭圆程序 - 随风倒上 - 赵志刚--Greddy的个人主页(2-21)

  下面分两种情况来讨论在迭代计算中决策变量di+1的推导。

  1、见图(a),若di<0,则选择e点,接着下一个中点就是Bresenham画圆和椭圆程序 - 随风倒上 - 赵志刚--Greddy的个人主页,这时新的决策变量为:

Bresenham画圆和椭圆程序 - 随风倒上 - 赵志刚--Greddy的个人主页(2-22)

Bresenham画圆和椭圆程序 - 随风倒上 - 赵志刚--Greddy的个人主页

(a)(di<0)>

  式(2-22)减去(2-21)得:

di+1=di+2xi+3(2-23)

  2、见图(b),若di≥0,则选择SE点,接着下一个中点就是Bresenham画圆和椭圆程序 - 随风倒上 - 赵志刚--Greddy的个人主页 ,这时新的决策变量为:

Bresenham画圆和椭圆程序 - 随风倒上 - 赵志刚--Greddy的个人主页(2-24)

Bresenham画圆和椭圆程序 - 随风倒上 - 赵志刚--Greddy的个人主页

(b)(di≥0) 中点画线算法

  式(2-24)减去(2-21)得:

di+1=di+2(xi-yi)+5(2-25)

  我们利用递推迭代计算这八分之一圆弧上的每个点,每次迭代需要两步处理:

   (1)用前一次迭代算出的决策变量的符号来决定本次选择的点。

   (2)对本次选择的点,重新递推计算得出新的决策变量的值。

  剩下的问题是计算初始决策变量d0,如下图所示。对于初始点(0,R),顺时针生成八分之一圆,下一个中点M的坐标是Bresenham画圆和椭圆程序 - 随风倒上 - 赵志刚--Greddy的个人主页 ,所以:

Bresenham画圆和椭圆程序 - 随风倒上 - 赵志刚--Greddy的个人主页(2-26)

Bresenham画圆和椭圆程序 - 随风倒上 - 赵志刚--Greddy的个人主页

生成圆的初始条件和圆的生成方向

  Bresenham画圆和椭圆程序 - 随风倒上 - 赵志刚--Greddy的个人主页三、中点画圆算法实现

  1、输入:圆半径r、圆心(x0,y0);

  2、确定初值:x=0,y=r、d=5/4-r;

  3、While(x<=y)

   {

    ·利用八分对称性,用规定的颜色color画八个象素点(x,y);

    · 若d≥0

      {

       y=y-1; //wind:个人觉得这句应该置于下句

       d=d+2(x-y)+5);

      }

     否则

       d=d+2x+3;

    ·x=x+1;

   }

  Bresenham画圆和椭圆程序 - 随风倒上 - 赵志刚--Greddy的个人主页五、中点画圆算法完善

  在上述算法中,使用了浮点数来表示决策变量d。为了简化算法,摆脱浮点数,在算法中全部使用整数,我们使用e=d-1/4代替d。显然,初值d=5/4-r对应于e=1-r。决策变量d<0对应于e<-1/4。算法中其它与d有关的式子可把d直接换成e。又由于e的初值为整数,且在运算过程中的迭代值也是整数,故e始终是整数,所以e<-1/4等价于e<0。因此,可以写出完全用整数实现的中点画圆算法。

  要求:写出用整数实现的中点画圆算法程序,并上机调试,观看运行结果。

  Bresenham画圆和椭圆程序 - 随风倒上 - 赵志刚--Greddy的个人主页六、中点画圆算法程序

void MidpointCircle(int x0,int y0,int r,int color)

{

 int x,y;

 float d;

 x=0;

 y=r;

 d=5.0/4-r;

 while(x<=y)

 {

  putdot(x0,y0,x,y,color);

  if(d<0)

  d+=x*2.0+3;

  else

  {

   d+=2.0*(x-y)+5;

   y--;

  }

  x++;

 }

}

putdot(x0,y0,x,y,color)

{

 putpixel(x0+x,y0+y,color);

 putpixel(x0+x,y0-y,color);

 putpixel(x0-x,y0+y,color);

 putpixel(x0-x,y0-y,color);

 putpixel(x0+y,y0+x,color);

 putpixel(x0+y,y0-x,color);

 putpixel(x0-y,y0+x,color);

 putpixel(x0-y,y0-x,color);

}

DialogBox-函数功能

该宏根据对话框模板资源创建一个模态的对话框。DialogBOX函数直到指定的回调函数通过调用EndDialog函数中止模态的对话框才能返回控制。该宏使用DialogBoxParam函数。

函数原型:int DialogBox(HINSTANCE hlnstance,LPCTSTRIpTemplate,HWND hWndParent,DLGPROC IpDialogFunc);

参数:

hlnstance:标识一个模块的事例该模块的可执行文件含有对话框模板。

IpTemplate:标识对话框模板。此参数可以是指向一个以NULL结尾的字符串的指针,该字符串指定对话框模板名,或是指定对话框模板的资源标识符中的一个整型值。如果此参数指定了一个资源标识符则它的高位字一定为零,且低位字一定含有标识符。一定用MAKEINTRESOURDE宏指令创建此值。

hWndParent:指定拥有对话框的窗口。

IpDialogFunc:指向对话框过程的指针。有关更详细的关于对话框过程的信息,请参见DialogProc。

返回值:如果函数调用成功,则返回值为在对函数EndDialog的调用中的nResult参数.该函数用于中止对话框。如果函数调用失败,则返回值为C1。若想获得更多的错误信息,请调用GetLastError函数。


DialogBox-备注
DialOgBox宏用CreateWindowEx函数创建对话框。DialogBox函数然后把一个WM_INITDIALOG消息(和一个WM-SETFONT消息,如果模板指定DS_SETFONT类型)传递到对话框过程。不管模板是否指定WS_VISIBLE类型,函数显示对话框,并且使拥有该对话框的窗口(也称属主窗口)失效,且为对话框启动它本身的消息循环来检索和传递消息。

当对话框应用程序调用EndDialog函数时,DialogBox函数清除对话框户止消息循环,使属主窗口生效(如果以前有效),且返回函数EndDialog调用中的nReSUlt参数。

Windows 95和以后版本:系统可支持每个对话框模板中最多255个控制。为把大于255个的控制放入对话框,需要在WM_INITDIALOG消息处理器中创建控制,而不是把他们放入模板中。

Windows CE:lpTemplateName参数指向的对话框模板中DLGTEMPLATE结构并不支持所有的类型。

速查:Windows NT:3.1及以上版本;Windows:95及以上版本;Windows CE:1.0及以上版本;头文件:winuser.h;库文件:user32.lib Unicode:在Windows NT上实现为Unicode和ANSI两种版本。

VC中CDC与HDC的区别以及二者之间的转换

CDC是MFC的DC的一个类
HDC是DC的句柄,API中的一个类似指针的数据类型.
MFC类的前缀都是C开头的
H开头的大多数是句柄
这是为了助记,是编程读\写代码的好的习惯.
CDC中所有MFC的DC的基类.常用的CClientDC dc(this);就是CDC的子类(或称派生类).
CDC等设备上下分类,都含有一个类的成员变量:m_nHdc;即HDC类型的句柄.
记住下面的一句话,会有助于你的理解.
MFC的类,是在用window API语句开发出来的有一定功能的小程序.(也可称为类).使用它的默认方法,就是,记住它的名字与参数(可以用笔记,代替脑记).
如果将window api比做汇编语言
那么MFC就相当于Basic语言.
cdc是设备描述表的基类,clientDC指代客户区的设备描述表,PaintDC只用于OnPaint()函数中


CDC是MFC的DC的一个类
HDC是DC的句柄,API中的一个类似指针的数据类型.
MFC类的前缀都是C开头的
H开头的大多数是句柄
这是为了助记,是编程读\写代码的好的习惯.
CDC中所有MFC的DC的基类.常用的CClientDC dc(this);就是CDC的子类(或称派生类).
CDC等设备上下分类,都含有一个类的成员变量:m_nHdc;即HDC类型的句柄.
记住下面的一句话,会有助于你的理解.
MFC的类,是在用window API语句开发出来的有一定功能的小程序.(也可称为类).使用它的默认方法,就是,记住它的名字与参数(可以用笔记,代替脑记).
如果将window api比做汇编语言
那么MFC就相当于Basic语言.


HDC是WINDOWS的一种数据类型,是设备描述句柄。
而CDC是MFC里的一个类,它封装了几乎所有的关于
HDC的操作。
也可以这样说,HDC定义的变量指向一块内存,这块
内存用来描述一个设备的相关的内容,所以也可以
认为HDC定义的是一个指针;而CDC类定义一个对象,
这个对象拥有HDC定义的一个设备描述表,同时也包
含与HDC相关的操作的函数。
这与HPEN和CPen,POINT与CPoint之间的差别是一样
的。

CDC 到HDC 的转化:
2007-05-09 12:04
方法一: 此方法在设备结束时不会销毁原来的资源(即:hDC,hBitmap)
CDC *pDC = CDC::FromHandle(hDC);
CBitmap *pBitmap = CBitmap::FromHandle(hBitmap);

方法二: 此方法在设备结束时会销毁原来的资源(即:hDC,hBitmap)
CDC dc;
dc.Attach(hDC);
CBitmap bit;
bit.Attach(hBitmap);

在结束的时候加dc.detach()也不会销毁原来资源

HDC hdc;
CDC cdc;
cdc到hdc
hdc = cdc.GetSafeHdc();
hdc到cdc
cdc.Attach(hdc);

CDC 是MFC中的类
而HDC是Handle
使用
HDC GetDC()

HDC,CDC,CWindowDC,CClientDC,CPaintDC详解

VC/MFC的HDC,CDC,CWindowDC,CClientDC,CPaintDC详解:
首先说一下什么是DC(设备描述表)
解:Windows应用程序通过为指定设备(屏幕,打印机等)创建一个设备描述表(Device Context, DC)在DC表示的逻辑意义的“画布”上进行图形的绘制。DC是一种包含设备信息的数据结构,它包含了物理设备所需的各种状态信息。Win32程序在绘制图形之前需要获取DC的句柄HDC,并在不继续使用时释放掉。

在c++ 编程中常会见到HDC,CDC,CClientDC,CPaintDC,CWindowDC这样的类
HDC是DC的句柄,API中的一个类似指针的数据类型.
CDC是MFC的DC的一个类
CDC等设备上下分类,都含有一个类的成员变量:m_nHdc;即HDC类型的句柄.


CDC及其派生类的继承视图:
CObject
public |------CDC
public |------|------CClientDC
public |------|------CPaintDC
public |------|------CWindowDC
public |------|------CMetaFileDC
(注意: 除CMetaFileDC以外的三个派生类用于图形绘制.)


CDC类定义了一个设备描述表相关的类,其对象提供成员函数操作设备描述表进行工作,如显示器,打印机,以及显示器描述表相关的窗口客户区域。

通过CDC的成员函数可进行一切绘图操作。CDC提供成员函数进行设备描述表的基本操作,使用绘图工具,选择类型安全的图形设备结构(GDI),以及色彩,调色板。除此之外还提供成员函数获取和设置绘图属性,映射,控制视口,窗体范围,转换坐标,区域操作,裁减,划线以及绘制简单图形(椭圆,多边形等)。成员函数也提供绘制文本,设置字体,打印机换码,滚动, 处理元文件。



其派生类:
1.PaintDC: 封装BeginPaint和EndPaint两个API的调用。
(1)用于响应窗口重绘消息(WM_PAINT)是的绘图输出。
(2)CPaintDC 在构造函数中调用BeginPaint()取得设备上下文,在析构函数中调用EndPaint()释放设备上下文。EndPaint()除了释放设备上下文外,还负责从消息队列中清除WM_PAINT消息。因此,在处理窗口重画时,必须使用CPaintDC,否则WM_PAINT消息无法从消息队列中清除,将引起不断的窗口重画。
(3)CPaintDC也只能用在WM_PAINT消息处理之中。


2.CClientDC(客户区设备上下文): 处理显示器描述表的相关的窗体客户区域。
用于客户区的输出,与特定窗口关联,可以让开发者访问目标窗口中客户区,其构造函数中包含了GetDC,析构函数中包含了ReleaseDC。


3.CWindowDC: 处理显示器描述表相关的整个窗体区域,包括了框架和控 件(子窗体)。
(1)可在非客户区绘制图形,而CClientDC,CPaintDC只能在客户区绘制图形。
(2)坐标原点是在屏幕的左上角,CClientDC,CPaintDC下坐标原点是在客户区的左上角。
(3)关联一特定窗口,允许开发者在目标窗口的任何一部分进行绘图,包含边界与标题,这种DC同WM_NCPAINT消息一起发送。


4.CMetaFileDC: 与元文件相关的设备描述表关联。



CDC提供两个函数,GetLayout和SetLayout用于反转设备描述表的布局。用于方便阿拉伯,希伯来的书写文化习惯的设计,以及非欧洲表中的字体布局。

CDC 包含两个设备描述表,m_hDC和m_hAttribDC对应于相同的设备,CDC为m_hDC指定所有的输出GDI调用,大多数的GDI属性调用由 m_hAttribDC控制。(如,GetTextColor是属性调用,而SetTextColor是一种输出调用。)



下面用一些简单的代码看看如果使用这些类
HDC使用, 每次画线等操作都不MFC封装的类多了个HDC的参数
执行在哪个设备描述表操作
HDC hdc=::GetDC(m_hWnd);//m_hWnd == this->m_hWnd 即当前窗口句柄
MoveToEx(hdc,m_ptOrigin.x,m_ptOrigin.y,NULL);
LineTo(hdc,point.x,point.y);
::ReleaseDC(m_hWnd,hdc);//必须和GetDC配对
可以看到HDC的使用较麻烦, 而且如果::GetDC和::ReleaseDC不配对的话,会造成错误


CDC *pDC=GetDC();
pDC->MoveTo(m_ptOrigin);
pDC->LineTo(point);
ReleaseDC(pDC);

CClientDC dc(this);
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);

CWindowDC dc(this);
CWindowDC dc2(GetDesktopWindow());//获得整个桌面的句柄, 一些桌面特效程序使用
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);

CPaintDC dc(this);
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);

可以看到 MFC 的类使用方便很多, 因为它们都在构造函数和析构函数调用了响应的函数进行DC的获取和释放.


下面说下一些细点的知识点
CClientDC,CWindowDC 区别不大, 可以说 CWindowDC包含了CClientDC 就拿记事本来说
CClientDC 就只是白白的我们可以编辑文字的那个区域是 客户区
CWindowDC 除了上面说的白白区域, 还包括菜单栏和工具栏等

CClientDC和CWindowDC 与 CPaintDC 的区别大点
在DC的获取方面 CClientDC和CWindowDC 使用的是并只能是 GetDC 和 ReleaseDC
CPaintDC 使用的是并只能是 BeginPaint 和 EndPaint

CPaintDC 只能用在响应 WM_PAINT 事件
CClientDC,CWindowDC 只能用在响应 非WM_PAINT 事件


关于 WM_PAINT 事件
系统会在多个不同的时机发送WM_PAINT消息:当第一次创建一个窗口时,当改变窗口的大小时,当把窗口从另一个窗口背后移出时,当最大化或最小化窗口时,等等,这些动作都是由系统管理的,应用只是被动地接收该消息,在消息处理函数中进行绘制操作;大多数的时候应用也需要能够主动引发窗口中的绘制操作,比如当窗口显示的数据改变的时候,这一般是通过InvalidateRect和InvalidateRgn函数来完成的。InvalidateRect和 InvalidateRgn把指定的区域加到窗口的Update Region中,当应用的消息队列没有其他消息时,如果窗口的Update Region不为空时,系统就会自动产生WM_PAINT消息。


系统为什么不在调用Invalidate时发送 WM_PAINT消息呢?又为什么非要等应用消息队列为空时才发送WM_PAINT消息呢?这是因为系统把在窗口中的绘制操作当作一种低优先级的操作,于是尽可能地推后做。不过这样也有利于提高绘制的效率:两个WM_PAINT消息之间通过InvalidateRect和InvaliateRgn使之失效的区域就会被累加起来,然后在一个WM_PAINT消息中一次得到更新,不仅能避免多次重复地更新同一区域,也优化了应用的更新操作。像这种通过 InvalidateRect和InvalidateRgn来使窗口区域无效,依赖于系统在合适的时机发送WM_PAINT消息的机制实际上是一种异步工作方式,也就是说,在无效化窗口区域和发送WM_PAINT消息之间是有延迟的;有时候这种延迟并不是我们希望的,这时我们当然可以在无效化窗口区域后利用SendMessage 发送一条WM_PAINT消息来强制立即重画,但不如使用Windows GDI为我们提供的更方便和强大的函数:UpdateWindow和RedrawWindow。UpdateWindow会检查窗口的Update Region,当其不为空时才发送WM_PAINT消息;RedrawWindow则给我们更多的控制:是否重画非客户区和背景,是否总是发送 WM_PAINT消息而不管Update Region是否为空等。

2009年6月25日星期四

CamShift算法

1--Back Projection

CamShift算法,即"Continuously Apative Mean-Shift"算法,是一种运动跟踪算法。它主要通过视频图像中运动物体的颜色信息来达到跟踪的目的。我把这个算法分解成三个部分,便于理解:
1) Back Projection计算
2) Mean Shift算法
3) CamShift算法
在这里主要讨论Back Projection,在随后的文章中继续讨论后面两个算法。

Back Projection
计算Back Projection的步骤是这样的:
1. 计算被跟踪目标的色彩直方图。在各种色彩空间中,只有HSI空间(或与HSI类似的色彩空间)中的H分量可以表示颜色信息。所以在具体的计算过程中,首先将其他的色彩空间的值转化到HSI空间,然后会其中的H分量做1D直方图计算。
2. 根据获得的色彩直方图将原始图像转化成色彩概率分布图像,这个过程就被称作"Back Projection"。
在OpenCV中的直方图函数中,包含Back Projection的函数,函数原型是:
void cvCalcBackProject(IplImage** img, CvArr** backproject, const CvHistogram* hist);
传递给这个函数的参数有三个:
1. IplImage** img:存放原始图像,输入。
2. CvArr** backproject:存放Back Projection结果,输出。
3. CvHistogram* hist:存放直方图,输入

下面就给出计算Back Projection的OpenCV代码。
1.准备一张只包含被跟踪目标的图片,将色彩空间转化到HSI空间,获得其中的H分量:
IplImage* target=cvLoadImage("target.bmp",-1); //装载图片
IplImage* target_hsv=cvCreateImage( cvGetSize(target), IPL_DEPTH_8U, 3 );
IplImage* target_hue=cvCreateImage( cvGetSize(target), IPL_DEPTH_8U, 3 );
cvCvtColor(target,target_hsv,CV_BGR2HSV); //转化到HSV空间
cvSplit( target_hsv, target_hue, NULL, NULL, NULL ); //获得H分量
2.计算H分量的直方图,即1D直方图:
IplImage* h_plane=cvCreateImage( cvGetSize(target_hsv),IPL_DEPTH_8U,1 );
int hist_size[]={255}; //将H分量的值量化到[0,255]
float* ranges[]={ {0,360} }; //H分量的取值范围是[0,360)
CvHistogram* hist=cvCreateHist(1, hist_size, ranges, 1);
cvCalcHist(&target_hue, hist, 0, NULL);
在这里需要考虑H分量的取值范围的问题,H分量的取值范围是[0,360),这个取值范围的值不能用一个byte来表示,为了能用一个byte表示,需要将H值做适当的量化处理,在这里我们将H分量的范围量化到[0,255].
4.计算Back Projection:
IplImage* rawImage;
//----------------------------------------------
//get from video frame,unsigned byte,one channel
//----------------------------------------------
IplImage* result=cvCreateImage(cvGetSize(rawImage),IPL_DEPTH_8U,1);
cvCalcBackProject(&rawImage,result,hist);
5.结果:result即为我们需要的.

2--Mean Shift算法
这里来到了CamShift算法,OpenCV实现的第二部分,这一次重点讨论Mean Shift算法。
在讨论Mean Shift算法之前,首先讨论在2D概率分布图像中,如何计算某个区域的重心(Mass Center)的问题,重心可以通过以下公式来计算:
1.计算区域内0阶矩
for(int i=0;i< height;i++)
for(int j=0;j< width;j++)
M00+=I(i,j)
2.区域内1阶矩:
for(int i=0;i< height;i++)
for(int j=0;j< width;j++)
{
M10+=i*I(i,j);
M01+=j*I(i,j);
}
3.则Mass Center为:
Xc=M10/M00; Yc=M01/M00
接下来,讨论Mean Shift算法的具体步骤,Mean Shift算法可以分为以下4步:
1.选择窗的大小和初始位置.
2.计算此时窗口内的Mass Center.
3.调整窗口的中心到Mass Center.
4.重复2和3,直到窗口中心"会聚",即每次窗口移动的距离小于一定的阈值。

在OpenCV中,提供Mean Shift算法的函数,函数的原型是:
int cvMeanShift(IplImage* imgprob,CvRect windowIn,
CvTermCriteria criteria,CvConnectedComp* out);

需要的参数为:
1.IplImage* imgprob:2D概率分布图像,传入;
2.CvRect windowIn:初始的窗口,传入;
3.CvTermCriteria criteria:停止迭代的标准,传入;
4.CvConnectedComp* out:查询结果,传出。
(注:构造CvTermCriteria变量需要三个参数,一个是类型,另一个是迭代的最大次数,最后一个表示特定的阈值。例如可以这样构造 criteria:criteria=cvTermCriteria(CV_TERMCRIT_ITER|CV_TERMCRIT_EPS,10,0.1)。)

返回的参数:
1.int:迭代的次数。

3--CamShift算法

1.原理
在了解了MeanShift算法以后,我们将MeanShift算法扩展到连续图像序列(一般都是指视频图像序列),这样就形成了CamShift算法。CamShift算法的全称是"Continuously Apaptive Mean-SHIFT",它的基本思想是视频图像的所有帧作MeanShift运算,并将上一帧的结果(即Search Window的中心和大小)作为下一帧MeanShift算法的Search Window的初始值,如此迭代下去,就可以实现对目标的跟踪。整个算法的具体步骤分5步:
Step 1:将整个图像设为搜寻区域。
Step 2:初始话Search Window的大小和位置。
Step 3:计算Search Window内的彩色概率分布,此区域的大小比Search Window要稍微大一点。
Step 4:运行MeanShift。获得Search Window新的位置和大小。
Step 5:在下一帧视频图像中,用Step 3获得的值初始化Search Window的位置和大小。跳转到Step 3继续运行。

2.实现
在OpenCV中,有实现CamShift算法的函数,此函数的原型是:
cvCamShift(IplImage* imgprob, CvRect windowIn,
CvTermCriteria criteria,
CvConnectedComp* out, CvBox2D* box=0);
其中:
imgprob:色彩概率分布图像。
windowIn:Search Window的初始值。
Criteria:用来判断搜寻是否停止的一个标准。
out:保存运算结果,包括新的Search Window的位置和面积。
box:包含被跟踪物体的最小矩形。

说明:
1.在OpenCV 4.0 beta的目录中,有CamShift的例子。遗憾的是这个例子目标的跟踪是半自动的,即需要人手工选定一个目标。我正在努力尝试全自动的目标跟踪,希望可以和大家能在这方面与大家交流。

5.
运动目标跟踪与检测的源代码(CAMSHIFT 算法)
From http://blog.csdn.net/hunnish/archive/2004/09/07/97049.aspx

采用 CAMSHIFT 算法快速跟踪和检测运动目标的 C/C++ 源代码,OPENCV BETA 4.0 版本在其 SAMPLE 中给出了这个例子。算法的简单描述如下(英文):

This application demonstrates a fast, simple color tracking algorithm that can be used to track faces, hands . The CAMSHIFT algorithm is a modification of the Meanshift algorithm which is a robust statistical method of finding the mode (top) of a probability distribution. Both CAMSHIFT and Meanshift algorithms exist in the library. While it is a very fast and simple method of tracking, because CAMSHIFT tracks the center and size of the probability distribution of an object, it is only as good as the probability distribution that you produce for the object. Typically the probability distribution is derived from color via a histogram, although it could be produced from correlation, recognition scores or bolstered by frame differencing or motion detection schemes, or joint probabilities of different colors/motions etc.

In this application, we use only the most simplistic approach: A 1-D Hue histogram is sampled from the object in an HSV color space version of the image. To produce the probability image to track, histogram "back projection" (we replace image pixels by their histogram hue value) is used.

算法的详细情况,请看论文:

http://www.assuredigit.com/incoming/camshift.pdf

关于OPENCV B4.0 库的使用方法以及相关问题,请查阅下面的相关文章:

http://forum.assuredigit.com/display_topic_threads.asp?ForumID=11&TopicID=3471

运行文件下载:

http://www.assuredigit.com/product_tech/Demo_Download_files/camshiftdemo.exe

该运行文件在VC6.0环境下编译通过,是一个 stand-alone 运行程序,不需要OPENCV的DLL库支持。在运行之前,请先连接好USB接口的摄像头。然后可以用鼠标选定欲跟踪目标。

=====

#ifdef _CH_
#pragma package
#endif

#ifndef _EiC
#include "cv.h"
#include "highgui.h"
#include
#include
#endif

IplImage *image = 0, *hsv = 0, *hue = 0, *mask = 0, *backproject = 0, *histimg = 0;
CvHistogram *hist = 0;

int backproject_mode = 0;
int select_object = 0;
int track_object = 0;
int show_hist = 1;
CvPoint origin;
CvRect selection;
CvRect track_window;
CvBox2D track_box; // tracking 返回的区域 box,带角度
CvConnectedComp track_comp;
int hdims = 48; // 划分HIST的个数,越高越精确
float hranges_arr[] = {0,180};
float* hranges = hranges_arr;
int vmin = 10, vmax = 256, smin = 30;

void on_mouse( int event, int x, int y, int flags )
{
if( !image )
return;

if( image->origin )
y = image->height - y;

if( select_object )
{
selection.x = MIN(x,origin.x);
selection.y = MIN(y,origin.y);
selection.width = selection.x + CV_IABS(x - origin.x);
selection.height = selection.y + CV_IABS(y - origin.y);

selection.x = MAX( selection.x, 0 );
selection.y = MAX( selection.y, 0 );
selection.width = MIN( selection.width, image->width );
selection.height = MIN( selection.height, image->height );
selection.width -= selection.x;
selection.height -= selection.y;

}

switch( event )
{
case CV_EVENT_LBUTTONDOWN:
origin = cvPoint(x,y);
selection = cvRect(x,y,0,0);
select_object = 1;
break;
case CV_EVENT_LBUTTONUP:
select_object = 0;
if( selection.width > 0 && selection.height > 0 )
track_object = -1;
#ifdef _DEBUG
printf("\n # 鼠标的选择区域:");
printf("\n X = %d, Y = %d, Width = %d, Height = %d",
selection.x, selection.y, selection.width, selection.height);
#endif
break;
}
}


CvScalar hsv2rgb( float hue )
{
int rgb[3], p, sector;
static const int sector_data[][3]=
{{0,2,1}, {1,2,0}, {1,0,2}, {2,0,1}, {2,1,0}, {0,1,2}};
hue *= 0.033333333333333333333333333333333f;
sector = cvFloor(hue);
p = cvRound(255*(hue - sector));
p ^= sector & 1 ? 255 : 0;

rgb[sector_data[sector][0]] = 255;
rgb[sector_data[sector][1]] = 0;
rgb[sector_data[sector][2]] = p;

#ifdef _DEBUG
printf("\n # Convert HSV to RGB:");
printf("\n HUE = %f", hue);
printf("\n R = %d, G = %d, B = %d", rgb[0],rgb[1],rgb[2]);
#endif

return cvScalar(rgb[2], rgb[1], rgb[0],0);
}

int main( int argc, char** argv )
{
CvCapture* capture = 0;
IplImage* frame = 0;

if( argc == 1 || (argc == 2 && strlen(argv[1]) == 1 && isdigit(argv[1][0])))
capture = cvCaptureFromCAM( argc == 2 ? argv[1][0] - '0' : 0 );
else if( argc == 2 )
capture = cvCaptureFromAVI( argv[1] );

if( !capture )
{
fprintf(stderr,"Could not initialize capturing...\n");
return -1;
}

printf( "Hot keys: \n"
"\tESC - quit the program\n"
"\tc - stop the tracking\n"
"\tb - switch to/from backprojection view\n"
"\th - show/hide object histogram\n"
"To initialize tracking, select the object with mouse\n" );

//cvNamedWindow( "Histogram", 1 );
cvNamedWindow( "CamShiftDemo", 1 );
cvSetMouseCallback( "CamShiftDemo", on_mouse ); // on_mouse 自定义事件
cvCreateTrackbar( "Vmin", "CamShiftDemo", &vmin, 256, 0 );
cvCreateTrackbar( "Vmax", "CamShiftDemo", &vmax, 256, 0 );
cvCreateTrackbar( "Smin", "CamShiftDemo", &smin, 256, 0 );

for(;;)
{
int i, bin_w, c;

frame = cvQueryFrame( capture );
if( !frame )
break;

if( !image )
{
/* allocate all the buffers */
image = cvCreateImage( cvGetSize(frame), 8, 3 );
image->origin = frame->origin;
hsv = cvCreateImage( cvGetSize(frame), 8, 3 );
hue = cvCreateImage( cvGetSize(frame), 8, 1 );
mask = cvCreateImage( cvGetSize(frame), 8, 1 );
backproject = cvCreateImage( cvGetSize(frame), 8, 1 );
hist = cvCreateHist( 1, &hdims, CV_HIST_ARRAY, &hranges, 1 ); // 计算直方图
histimg = cvCreateImage( cvSize(320,200), 8, 3 );
cvZero( histimg );
}

cvCopy( frame, image, 0 );
cvCvtColor( image, hsv, CV_BGR2HSV ); // 彩色空间转换 BGR to HSV

if( track_object )
{
int _vmin = vmin, _vmax = vmax;

cvInRangeS( hsv, cvScalar(0,smin,MIN(_vmin,_vmax),0),
cvScalar(180,256,MAX(_vmin,_vmax),0), mask ); // 得到二值的MASK
cvSplit( hsv, hue, 0, 0, 0 ); // 只提取 HUE 分量

if( track_object < 0 )
{
float max_val = 0.f;
cvSetImageROI( hue, selection ); // 得到选择区域 for ROI
cvSetImageROI( mask, selection ); // 得到选择区域 for mask
cvCalcHist( &hue, hist, 0, mask ); // 计算直方图
cvGetMinMaxHistValue( hist, 0, &max_val, 0, 0 ); // 只找最大值
cvConvertScale( hist->bins, hist->bins, max_val ? 255. / max_val : 0., 0 ); // 缩放 bin 到区间 [0,255]
cvResetImageROI( hue ); // remove ROI
cvResetImageROI( mask );
track_window = selection;
track_object = 1;

cvZero( histimg );
bin_w = histimg->width / hdims; // hdims: 条的个数,则 bin_w 为条的宽度

// 画直方图
for( i = 0; i < hdims; i++ )
{
int val = cvRound( cvGetReal1D(hist->bins,i)*histimg->height/255 );
CvScalar color = hsv2rgb(i*180.f/hdims);
cvRectangle( histimg, cvPoint(i*bin_w,histimg->height),
cvPoint((i+1)*bin_w,histimg->height - val),
color, -1, 8, 0 );
}
}

cvCalcBackProject( &hue, backproject, hist ); // 使用 back project 方法
cvAnd( backproject, mask, backproject, 0 );

// calling CAMSHIFT 算法模块
cvCamShift( backproject, track_window,
cvTermCriteria( CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 10, 1 ),
&track_comp, &track_box );
track_window = track_comp.rect;

if( backproject_mode )
cvCvtColor( backproject, image, CV_GRAY2BGR ); // 使用backproject灰度图像
if( image->origin )
track_box.angle = -track_box.angle;
cvEllipseBox( image, track_box, CV_RGB(255,0,0), 3, CV_AA, 0 );
}

if( select_object && selection.width > 0 && selection.height > 0 )
{
cvSetImageROI( image, selection );
cvXorS( image, cvScalarAll(255), image, 0 );
cvResetImageROI( image );
}

cvShowImage( "CamShiftDemo", image );
cvShowImage( "Histogram", histimg );

c = cvWaitKey(10);
if( c == 27 )
break; // exit from for-loop
switch( c )
{
case 'b':
backproject_mode ^= 1;
break;
case 'c':
track_object = 0;
cvZero( histimg );
break;
case 'h':
show_hist ^= 1;
if( !show_hist )
cvDestroyWindow( "Histogram" );
else
cvNamedWindow( "Histogram", 1 );
break;
default:
;
}
}

cvReleaseCapture( &capture );
cvDestroyWindow("CamShiftDemo");

return 0;
}

#ifdef _EiC
main(1,"camshiftdemo.c");
#endif

2008年8月31日星期日

OpenCV 初级使用指导-1

矩阵操作

分配释放矩阵空间

  • 综述:
    • OpenCV有针对矩阵操作的C语言函数. 许多其他方法提供了更加方便的C++接口,其效率与OpenCV一样.
    • OpenCV将向量作为1维矩阵处理.
    • 矩阵按行存储,每行有4字节的校整.

  • 分配矩阵空间:
    CvMat* cvCreateMat(int rows, int cols, int type);
    

    type: 矩阵元素类型. 格式为CV_(S|U|F)C.
    例如: CV_8UC1 表示8位无符号单通道矩阵, CV_32SC2表示32位有符号双通道矩阵.

    例程:
    CvMat* M = cvCreateMat(4,4,CV_32FC1);

  • 释放矩阵空间:
    CvMat* M = cvCreateMat(4,4,CV_32FC1);
    
    cvReleaseMat(&M);

  • 复制矩阵:
    CvMat* M1 = cvCreateMat(4,4,CV_32FC1);
    
    CvMat* M2;
    M2=cvCloneMat(M1);

  • 初始化矩阵:
    double a[] = { 1,   2,   3,   4,
    
    5, 6, 7, 8,
    9, 10, 11, 12 };

    CvMat Ma=cvMat(3, 4, CV_64FC1, a);

    另一种方法:

    CvMat Ma;
    
    cvInitMatHeader(&Ma, 3, 4, CV_64FC1, a);

  • 初始化矩阵为单位阵:
    CvMat* M = cvCreateMat(4,4,CV_32FC1);
    
    cvSetIdentity(M); // 这里似乎有问题,不成功

存取矩阵元素

  • 假设需要存取一个2维浮点矩阵的第(i,j)个元素.

  • 间接存取矩阵元素:
    cvmSet(M,i,j,2.0); // Set M(i,j)
    
    t = cvmGet(M,i,j); // Get M(i,j)

  • 直接存取,假设使用4-字节校正:
    CvMat* M     = cvCreateMat(4,4,CV_32FC1);
    
    int n = M->cols;
    float *data = M->data.fl;

    data[i*n+j] = 3.0;

  • 直接存取,校正字节任意:
    CvMat* M     = cvCreateMat(4,4,CV_32FC1);
    
    int step = M->step/sizeof(float);
    float *data = M->data.fl;

    (data+i*step)[j] = 3.0;

  • 直接存取一个初始化的矩阵元素:
    double a[16];
    
    CvMat Ma = cvMat(3, 4, CV_64FC1, a);
    a[i*4+j] = 2.0; // Ma(i,j)=2.0;

矩阵/向量操作

  • 矩阵-矩阵操作:
    CvMat *Ma, *Mb, *Mc;
    
    cvAdd(Ma, Mb, Mc); // Ma+Mb -> Mc
    cvSub(Ma, Mb, Mc); // Ma-Mb -> Mc
    cvMatMul(Ma, Mb, Mc); // Ma*Mb -> Mc

  • 按元素的矩阵操作:
    CvMat *Ma, *Mb, *Mc;
    
    cvMul(Ma, Mb, Mc); // Ma.*Mb -> Mc
    cvDiv(Ma, Mb, Mc); // Ma./Mb -> Mc
    cvAddS(Ma, cvScalar(-10.0), Mc); // Ma.-10 -> Mc

  • 向量乘积:
    double va[] = {1, 2, 3};
    
    double vb[] = {0, 0, 1};
    double vc[3];

    CvMat Va=cvMat(3, 1, CV_64FC1, va);
    CvMat Vb=cvMat(3, 1, CV_64FC1, vb);
    CvMat Vc=cvMat(3, 1, CV_64FC1, vc);

    double res=cvDotProduct(&Va,&Vb); // 点乘: Va . Vb -> res
    cvCrossProduct(&Va, &Vb, &Vc); // 向量积: Va x Vb -> Vc
    end{verbatim}

    注意 Va, Vb, Vc 在向量积中向量元素个数须相同.


  • 单矩阵操作:
    CvMat *Ma, *Mb;
    
    cvTranspose(Ma, Mb); // transpose(Ma) -> Mb (不能对自身进行转置)
    CvScalar t = cvTrace(Ma); // trace(Ma) -> t.val[0]
    double d = cvDet(Ma); // det(Ma) -> d
    cvInvert(Ma, Mb); // inv(Ma) -> Mb

  • 非齐次线性系统求解:
    CvMat* A   = cvCreateMat(3,3,CV_32FC1);
    
    CvMat* x = cvCreateMat(3,1,CV_32FC1);
    CvMat* b = cvCreateMat(3,1,CV_32FC1);
    cvSolve(&A, &b, &x); // solve (Ax=b) for x

  • 特征值分析(针对对称矩阵):
    CvMat* A   = cvCreateMat(3,3,CV_32FC1);
    
    CvMat* E = cvCreateMat(3,3,CV_32FC1);
    CvMat* l = cvCreateMat(3,1,CV_32FC1);
    cvEigenVV(&A, &E, &l); // l = A的特征值 (降序排列)
    // E = 对应的特征向量 (每行)

  • 奇异值分解SVD:
    CvMat* A   = cvCreateMat(3,3,CV_32FC1);
    
    CvMat* U = cvCreateMat(3,3,CV_32FC1);
    CvMat* D = cvCreateMat(3,3,CV_32FC1);
    CvMat* V = cvCreateMat(3,3,CV_32FC1);
    cvSVD(A, D, U, V, CV_SVD_U_T|CV_SVD_V_T); // A = U D V^T

    标号使得 U 和 V 返回时被转置(若没有转置标号,则有问题不成功!!!).

视频序列操作

从视频序列中抓取一帧

  • OpenCV支持从摄像头或视频文件(AVI)中抓取图像.

  • 从摄像头获取初始化:
    CvCapture* capture = cvCaptureFromCAM(0); // capture from video device #0
    

  • 从视频文件获取初始化:
    CvCapture* capture = cvCaptureFromAVI("infile.avi");
    

  • 抓取帧:
    IplImage* img = 0;
    
    if(!cvGrabFrame(capture)){ // 抓取一帧
    printf("Could not grab a frame\n\7");
    exit(0);
    }
    img=cvRetrieveFrame(capture); // 恢复获取的帧图像

    要从多个摄像头同时获取图像, 首先从每个摄像头抓取一帧. 在抓取动作都结束后再恢复帧图像.

  • 释放抓取源:
    cvReleaseCapture(&capture);
    

    注意由设备抓取的图像是由capture函数自动分配和释放的. 不要试图自己释放它.

获取/设定帧信息

  • 获取设备特性:
    cvQueryFrame(capture); // this call is necessary to get correct
    
    // capture properties
    int frameH = (int) cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_HEIGHT);
    int frameW = (int) cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_WIDTH);
    int fps = (int) cvGetCaptureProperty(capture, CV_CAP_PROP_FPS);
    int numFrames = (int) cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_COUNT);

    所有帧数似乎只与视频文件有关. 用摄像头时不对,奇怪!!!.

  • 获取帧信息:
    float posMsec    =        cvGetCaptureProperty(capture, CV_CAP_PROP_POS_MSEC);
    
    int posFrames = (int) cvGetCaptureProperty(capture, CV_CAP_PROP_POS_FRAMES);
    float posRatio = cvGetCaptureProperty(capture, CV_CAP_PROP_POS_AVI_RATIO);

    获取所抓取帧在视频序列中的位置, 从首帧开始按[毫秒]算. 或者从首帧开始从0标号, 获取所抓取帧的标号. 或者取相对位置,首帧为0,末帧为1, 只对视频文件有效.

  • 设定所抓取的第一帧标号:
    // 从视频文件相对位置0.9处开始抓取
    
    cvSetCaptureProperty(capture, CV_CAP_PROP_POS_AVI_RATIO, (double)0.9);

    只对从视频文件抓取有效. 不过似乎也不成功!!!

存储视频文件

  • 初始化视频存储器:
    CvVideoWriter *writer = 0;
    
    int isColor = 1;
    int fps = 25; // or 30
    int frameW = 640; // 744 for firewire cameras
    int frameH = 480; // 480 for firewire cameras
    writer=cvCreateVideoWriter("out.avi",CV_FOURCC('P','I','M','1'),
    fps,cvSize(frameW,frameH),isColor);

    其他有效编码:

    CV_FOURCC('P','I','M','1')     = MPEG-1 codec
    
    CV_FOURCC('M','J','P','G') = motion-jpeg codec (does not work well)
    CV_FOURCC('M', 'P', '4', '2') = MPEG-4.2 codec
    CV_FOURCC('D', 'I', 'V', '3') = MPEG-4.3 codec
    CV_FOURCC('D', 'I', 'V', 'X') = MPEG-4 codec
    CV_FOURCC('U', '2', '6', '3') = H263 codec
    CV_FOURCC('I', '2', '6', '3') = H263I codec
    CV_FOURCC('F', 'L', 'V', '1') = FLV1 codec

    若把视频编码设为-1则将打开一个编码选择窗口(windows系统下).

  • 存储视频文件:
    IplImage* img = 0;
    
    int nFrames = 50;
    for(i=0;i cvGrabFrame(capture); // 抓取帧
    img=cvRetrieveFrame(capture); // 恢复图像
    cvWriteFrame(writer,img); // 将帧添加入视频文件
    }

    若想在抓取中查看抓取图像, 可在循环中加入下列代码:

    cvShowImage("mainWin", img);
    
    key=cvWaitKey(20); // wait 20 ms

    若没有20[毫秒]延迟,将无法正确显示视频序列.

  • 释放视频存储器:
    cvReleaseVideoWriter(&writer);