2010年5月12日星期三

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);

2008年7月19日星期六

冷笑话100则!

1:从前有个人钓鱼,钓到了只鱿鱼。
鱿鱼求他:你放了我吧,别把我烤来吃啊。
那个人说:好的,那么我来考问你几个问题吧。
鱿鱼很开心说:你考吧你考吧!
然后这人就把鱿鱼给烤了..
2:我曾经得过精神分裂症,但现在我们已经康复了。
3:一留学生在美国考驾照,前方路标提示左转,他不是很确定,问考官:
“turn left?”
答:“right”
于是……挂了..
4:有一天绿豆自杀从5楼跳下来,流了很多血,变成了红豆;一直流脓,又变成了黄豆;伤口结了疤,最后成了黑豆。
5:小明理了头发,第二天来到学校,同学们看到他的新发型,笑道:小明,你的头型好像个风筝哦!小明觉得很委屈,就跑到外面哭。哭着哭着~他就飞起来了…………
6:有个人长的像洋葱,走着走着就哭了…….
7:小企鹅有一天问他奶奶,“奶奶奶奶,我是不是一只企鹅啊?”“是啊,你当然是企鹅。”小企鹅又问爸爸,“爸爸爸爸,我是不是一只企鹅啊?”“是啊,你是企鹅啊,怎么了?”“可是,可是我怎么觉得那么冷呢?”
8:有一对玉米相爱了…
于是它们决定结婚…
结婚那天…
一个玉米找不到另一个玉米了…
这个玉米就问身旁的爆米花:你看到我们家玉米了吗?
爆米花:亲爱的,人家穿婚纱了嘛…….
9:音乐课上 老师弹了一首贝多芬的曲子
小明问小华:“你懂音乐吗?”
小华:“是的”
小明:“那你知道老师在弹什麼吗?”
小华: “钢琴。”
10:Q:有两个人掉到陷阱里了,死的人叫死人,活人叫什么?
A:叫救命啦!
11:提问:布和纸怕什么?
回答:布怕一万,纸怕万一。
原因:不(布)怕一万,只(纸)怕万一。
12:有一天有个婆婆坐车…
坐到中途婆婆不认识路了….
婆婆用棍子打司机屁股说:这是哪?
司机:这是我的屁股…..
13: 一个鸡蛋去茶馆喝茶,结果它变成了茶叶蛋;一个鸡蛋跑去松花江游泳,结果它变成了松花蛋;一有个鸡蛋跑到了山东,结果变成了鲁(卤)蛋;一个鸡蛋无家可 归,结果它变成了野鸡蛋;一个鸡蛋在路上不小心摔了一跤,倒在地上,结果变成了导弹;一个鸡蛋跑到人家院子里去了,结果变成了原子弹;一个鸡蛋跑到青藏高 原,结果变成了氢弹;一个鸡蛋生病了,结果变成了坏蛋;一个鸡蛋嫁人了,结果变成了混蛋;一个鸡蛋跑到河里游泳,结果变成了核弹;一个鸡蛋跑到花丛中去 了,结果变成了花旦;一个鸡蛋骑着一匹马,拿着一把刀,原来他是刀马旦;一个鸡蛋是母的,长的很丑,结果就变成了恐龙蛋;一个鸡蛋是公的,他老婆在外面和 别的鸡蛋通奸,结果他变成了王八蛋;一个鸡蛋……
14:主持人问:猫是否会爬树?老鹰抢答:会!主持人:举例说明!老鹰含泪:那年,我睡熟了,猫爬上了树…后来就有了猫头鹰…
15:俩屎壳螂讨论福利彩票,甲说:我要中了大奖就把方圆50里的厕所都买下来,每天吃个够!乙说:你丫太俗了!我要是中了大奖就包一活人,每天吃新鲜的!
16:why the chicken cross the street
答案 to get another side
17:甲:那个人在干什么?
乙:他在发抖。
甲:他为什么要发抖呢?
乙:他冷呀。
甲:哦,原来发抖就不会冷拉。
甲:……
18:有个香蕉先生和女朋友约会,走在街上,天气很热,香蕉先生就把衣服脱掉了,之后他的女朋友就摔倒了………
19:一个香肠被关在冰箱里
感觉很冷,然后看了看身边的另一根,有了点安慰,说:“看你都冻成这样了,全身都是冰!”结果那根说:“对不起,我是冰棒。”
20:.从前有一个棉花糖去打了球打了很长时间.他说:好累啊,我觉得我整个人都软下来了……….
21:这位跳水运动员的动作难度很大,他做了一个转体三周接前空翻三周半接后空翻一个月。
22:MM找大学迷路了。遇见一位文质彬彬的教授。
MM:请问,我怎样才能到大学去?
教授:只有努力读书,才可以上大学。
23:局长与科长共乘电梯,局长放一屁后对科长说:你放屁了!科长说:不是我放的…不久科长被免职,局长在会上说:屁大的事你都担待不起,要你何用?
24:小姐:现在生意不好做呀!
老大:为什么?
小姐:“禽流感…..”
25:一女遇劫匪颤抖曰:“俺是XX学校的,刚毕业,工作都没找到,真的没有钱……”
劫匪听后竟然痛哭流涕,“妹子,俺也是XX学校的,你拿好学生证,前面抢劫的还是XX学校的,你放心,阿拉绝不抢自己人!”
26:想和女友ML,女友曰不洗澡不行,应允天冷可洗“局部”,洗毕,女友极为娇羞道:“亲爱的,你好好懒呦,用哪洗哪……”偶听完晕倒,偶就是刷了个牙啊~~~(巨隐讳的冷笑话)
27:一个盲人乞丐戴着墨镜在街上行乞。
一个醉汉走过来,觉得他可怜,就扔了一百元给他。
走了一段路,醉汉一回头,恰好看见那个盲人正对着太阳分辨那张百元大抄的真假。
醉汉过来一把夺回钱道:“你TMD不想活了,竟敢骗老子!”
盲人乞丐一脸委屈说:“大哥,真对不起啊,我是替一个朋友在这看一下,他是个瞎子,去上厕所了,其实我是个哑巴。”
“哦,是这样子啊,”于是醉汉扔下钱,又摇摇晃晃地走了……
28:禽流感——都是“天屎”惹的祸!!!
有两种人得禽流感的几率极大——1.“禽兽” ;2.“禽兽不如”的人 …….
29:A:哎,你怎么学会抽烟了?
B:我从亚当夏娃偷吃禁果的时候就会了~
C:知道亚当夏娃为什么会偷吃禁果吗?
AB:不知!
C:因为亚当没有烟!(提示:谐音一个字)
30:某人刚被女友抛弃,碰巧在大街上撞见前女友和新欢调情,他越看越气,想羞辱他们一下。于是很有礼貌上前打了个招呼,并很鄙视地对女友新欢说:“我用过的旧货你也不嫌弃!”正当他为自己创意得意的时候,前女友却笑出声道:“外面一寸是旧的,里面全是崭新的!”
31:分手时,她给了我一个吻,那感觉——就好像人民日报一样真实……
32:刚刚看师姐的电脑屏幕上方有个类似新闻滚动条的东西,上面的文字过得非常快。
偶好奇问:这是歌词吗?
师姐:是呀!
师姐:怎么过得这么快?都没看清!
师姐:周杰伦的!!
33:妻:我真是瞎了眼踩到狗屎才会嫁给你。
夫:我才真是瞎了眼踩到狗屎才会娶你。
狗屎:我好倒霉喔!躺在那里都被你们俩给踩到……
34:高考化学题:A和B可以相互转化,B在沸水中可以生成C,C在空气 中氧化成D,D有臭鸡蛋气味,问A,B,C,D各是什么?
我答:A是鸡,B是生鸡蛋,C是熟鸡蛋,D当然是臭鸡蛋啦!
35:橡皮、老虎皮、狮子皮哪一个最不好?
答:橡皮。
因为橡皮擦(橡皮差)。
36:问:3个头一只脚的是什么东西???
答案:3个头一只脚的怪物!!!!!!
37:蚂蚁去沙漠,为什么沙子上没有留下他的脚印,而只留下一条线呢?
答案:因为它是骑脚踏车的!
蚂蚁从沙漠回家了,他没有通知任何人,但是他家人却知道他回来了!为什么啊!
答案:看见他停在楼下的脚踏车…….
38:有一天一个女吸毒犯被抓到警局,police看见她的手上有刺青,就问她你干嘛把你男朋友的名字刺在手上,他叫小良是不是…啊..是不是.快说,说..他有没有吸毒阿….快说
只见那个女吸毒犯抬起头带着愤怒的眼神
对police说
这是恨啦….
40:一天,小美和她男友开车出去兜风,
车快没油了,刚好旁边有个加油站,开过去的时候,突然一阵狂风把她男友的帽子刮跑了。
小美的男友对她说:
「我去捡帽子,你帮我加油。」
男友刚跑开不远,就听到小美在他后面大喊:
「加油!加油!」
41:一只猩猩经过树林,不小心采到了长臂猿的粪便,
好心的猩猩把猿打扫了分辩.
过了不久他们相爱了,别人问你们是怎么走到一起的?
猩猩回答说:”是猿粪(缘分).!”
42::有一个胖子……….
从高楼跳下…
结果变成了…….
死胖子..
43:有一只鸭子叫小黄,有一天它过马路时被车撞了一下,大叫:“呱!”从此它就变成了小黄瓜……
44: 有一只企鹅,他的家离北极熊家特别远,要是靠走的话,得走20年才能到。有一天,企鹅在家里呆着特别无聊,准备去找北极熊玩,与是他出门了,可是走到路的 一半的时候发现自己忘记锁门了,这就已经走了10年了,可是门还是得锁啊,于是企鹅又走回家去锁门。锁了门以后,企鹅再次出发去找北极熊,等于他花了40 年才到了北极熊他们家……然后企鹅就敲门说:“北极熊北极熊,企鹅找你玩来了!”结果北极熊开门以后你猜他说什么?“还是去你家玩吧~”
45: 小白兔蹦蹦跳跳到面包房,问:“老板,你们有没有一百个小面包啊?” 老板:“啊,真抱歉,没有那么多”“这样啊。。。”小白兔垂头丧气地走了。第二天,小白兔蹦蹦跳跳到面包房,“老板,有没有一百个小面包啊?” 老板:“对不起,还是没有啊”“这样啊。。。”小白兔又垂头丧气地走了。第三天,小白兔蹦蹦跳跳到面包房,“老板,有没有一百个小面包啊?”老板高兴的 说:“有了,有了,今天我们有一百个小面包了!!” 小白兔掏出钱:“太好了,我买两个!”
46:小明说:「阿康,问你“有一只鲨鱼吃下了一颗绿豆,结果它变成了什么“?」 阿康说:「我不知道,答案是什么?」小明说:「嘿!嘿!答案是“绿豆沙(绿豆鲨)“,你很笨喔!」
47:老师问一同学怎么减少白色污染? 同学答:把饭盒做成蓝色.
48:有个人,他肠胃不好.一天,他来到胃病医院看病,对医生说:“我吃什么拉什么,吃西 瓜拉西瓜,吃黄瓜拉黄瓜!“医生想了想,对他说:“我看你只有吃屎了!”
49:飞机上,一位空中小姐问一个小女孩说:“为什么飞机飞这么高都不会撞到星星呢?“ 小女孩回答到:“我知道,因为星星会’闪’啊!”
50:有一只北极熊和一只企鹅在一起耍,企鹅把身上的毛一根一根地拔了下来,拔完之后,对北极熊说:“好冷哦!“北极熊听了,也把自己身上的毛一根一根地拔了下来,转头对企鹅说:“果然很冷!”
51:Q:非洲食人族的酋长吃什么?
A:人啊!
Q:那有一天,酋长病了,医生告诉他要吃素,那他吃什么?
A:吃植物人!
52:冰箱里有两根香肠,过了很久,
一跟香肠抖了一下,哇!好冷啊~!
另一根香肠十分惊奇地说,咦?你是香肠怎么会说话?
53:有一天,
有只公鹿越跑越快,
跑到最后 ,
它就变成高速公鹿了。
54:有一天,老师带一群小朋友到山上采水果,
她宣布说:“小朋友,采完水果后,我们统一一起洗,洗完可以一起吃。”
所有小朋友都跑去采水果了。
集合时间一到,所有小朋友都集合了。
老师:“小华,你采到什么?”
小华:“我在洗苹果,因为我采到苹果。”
老师:“小美你呢?”
小美:“我在洗蕃茄,因为我采到蕃茄。”
老师:“小朋友都很棒哦!那阿明你呢?”
阿明:“我在洗布鞋,因为我踩到大便。”
55:老师在课堂上对小明提问,小明站起来却一声不吭。
老师:小明?
老师:小明??
老师:小明!你怎么回事啊?你到底知不知道答案啊?好歹吱一声啊!
小明:吱~
56:一只大象问骆驼:‘你的咪咪怎么长在背上?’
骆驼说:‘死远点,我不和鸡鸡长在脸上的东西讲话!
57:如何让饮料变大杯?
念大悲咒
58:小明:今天几度阿?
小华:零下3度阿!
小明:难怪这麼冷。
59:有个小男孩放学回家从窗外窥见一个女人躺在床上狂揉胸部喊到我要男人我要男人!
第二天小男孩再从窗外过时发现女人身上躺了个男人,
于是小男孩回家躺在床上狂揉胸部喊到我要自行车我要自行车!
60:从前从前有一只鸟,
他每天都会经过一片玉米田,
但是很不幸的,
有一天那片玉米田发生了火灾,
所有的玉米都变成了爆米花!!!
小鸟飞过去以后……
以为下雪,就冷死了…
61:说有一只北极熊,因为雪地太刺眼了,必须要戴墨镜才能看东西,
可是他找不到墨镜,于是闭着眼睛爬来爬去在地上找,爬呀爬呀,把 手脚都爬的脏兮兮的才找到墨镜。戴上墨镜,对着镜子一照,这才发现:哦,原来我是一只熊猫。
62:自然课老师问:为什么人死后身体是冷的?
没人回答。
老师又问:没人知道吗?
这时,有个同学站起来说:那是因为心静自然凉。
63:小明在一次车祸中失去了一条腿,
小明在一次车祸中又失去了一条腿,
又一次车祸中小明失去了他的另一条腿,
一次车祸中小明又失去了他的一条腿,
其实小明是一条狗.
64:一天,A,B,C三个人一起出去玩,走在路上闲晃了很久。
后来A就说,好无聊,我好想去打B。
然后C看了A一眼, 就把B拖到巷子里去打。
65:三只小兔拉便便
第一只是长条的 。
第二只是圆球的。
第三只居然是三角形的 。
问,它答:我用手捏的。
66:台湾什么时候会想要统一?
买方便面的时候
67:有一天小强问他爸爸:“爸爸,我是不是傻孩子啊?”
爸爸说:“傻孩子,你怎么会是傻孩子呢?”
68:小明回家时,隔壁的狗突然跑出来咬他,他一气之下拿起竹子要打它,
狗的主人看到小明打他的狗,就不高兴的说:打狗也要看主人,没听过吗?
这时小明就说:好!我会一边看着你,一边打你家的狗。
69:虫虫:小花,你用我的铅笔了吗?
小花:没有,我没用。
虫虫:你真没用?
小花:我真没用!
虫虫:唉,你是第17个承认自己没用的人了
70:蚂蚁从喜玛拉雅山上摔下来后是怎么死的?
答案:饿死的。因为太轻~所以飘下来要很久…
80:为什么小狗越变越小?
答:因为它越走越远。
81:从前,有一只马!它跑着跑着就掉进海里。
所以,它变成了一只“海马”!
这只马的另外一只马朋友,为了要去找掉到海里的马,结果却掉到河里。后来,他就就变成“河马”。
第三只马是只白马。它为了要找失踪的两个朋友,来到了交通混乱的城市。
它连续被好几台车子给辗过,使得身上出现好几条黑条纹。
结果,它变成“斑马”了!
第四只马为了找寻前面三个的同伴,有一天,它来到一间工厂,结果被改造成“铁马”。
但后来,那些马还是难逃被吃的命运,通通被作成了“沙其马”,肆虐所及,所有马儿无一幸免,成了一个无马的世界……
然后,有一群人看到这篇笑话后忍不住的说:“马的~真冷”。
最后,为了纪念这个笑话,有人将它编订成课,我们叫它“马赛课”!
82:小明欠地下钱庄20万,小明苦苦哀求他多宽限几天,
钱庄的人说:明天一定要还,不然的话……,剁掉2只手指;
后天的话……,在剁4只;第3天的话……
小明:是不是不用还了
钱庄的人:NO,到时候你就变成小叮当了。
83:有个人一天碰到上帝
上帝突然大发善心打算给那人一个愿望
上帝问:你有什么愿望吗?
那个人想了想说: 听说猫都有9条命,那请您赐给我9条命吧!
上帝说:你的愿望实现咯!
一天,那个人闲着无聊,
想说去死一死算了, 反正有9条命嘛
就躺在铁轨上,
结果一辆火车开过去,
那人还是死了.
这是为什么呢?
因为那台火车的车厢有10节.
84:一个家伙到医院去检查,并做了许多测试。
医生说:有好消息、也有坏消息!看过你的测试结果后,我发现你有潜在的同性恋倾向!!而且难以根治!
这个家伙说:我的天啊!那好消息呢?
医生腼腆的说:我发现你还蛮可爱的耶
85:一个猎人带着猎狗去打猎,在林子里溜了一天都没有猎物。
天黑了,不甘心的他还是不停骑马在林子里转,
马忽然说:‘你都不让我休息,想累死我啊!?’
猎人听到吓了一跳,立刻从马背上滚下来,拉着猎狗就逃跑,跑到一课大树下喘气时,狗拍拍胸口对他说:‘吓死我了,马居然会说话!’
于是猎人当场被吓死了
86:狼、老虎和狮子谁玩游戏一定会被淘汰? 狼
因为:桃太郎(淘汰狼)
87:一天A拣了一面镜子对着镜子照了照说;这里边的人好面熟啊
B说;是吗?我看看(接过镜子),我啊!我你都不认识了啊?
88:番茄A和番茄B去逛街。
B问A:我们去哪?
A不回答。
B又问:我们去哪?
A还是不回答。
B又问了一次。
番茄A转过头对番茄B说:我们不是番茄来的吗?为什么我们会说话呢?
89:从前,有一只白猫和一只黑猫
一天
白猫掉到水里去了
黑猫把 它救了上来
白猫对黑猫说了一句话
“Q:这句话是什么?
.
.
.
.
.
.
.
.
.
.
.
.
..
“喵”
90:A:“你知道我昨天晚上在网吧干嘛吗?”
B:“在干吗;”
A:“上网呗;”
B:“。。。”
91:两只苍蝇去吃饭。
小的问大的:大哥,为什么我们每天都要吃屎?
大的说:吃饭的时候不要说这么恶心的东西!!
92:草船中
鲁肃:“这样真的可以借到箭吗?孔明先生?”
诸葛亮:“相信我。”
鲁肃:“可是我还是有些担心……”
诸葛亮:“没必要。”
鲁肃:“可是,你不觉得船里越来越热么?”
诸葛亮:“这么说起来是有一点碍…有什么不对劲吗?”
鲁肃:“是啊,我担心敌人射的是火箭……”
诸葛亮:“哎!?子敬 ̄ ̄你会游泳么 ̄ ̄ ̄我不会 ̄ ̄ ̄”
93:一猴子吃花生前都要先塞进屁股再拿出来吃。
对此管理员解释道:曾有人喂它桃 子,
结果桃核拉不出来,猴子吓怕了,现在一定要量好再吃。
94:医院为防止病人出逃外设100道,两精神病患者仍欲逃出医院。于夜黑努力
翻墙。至第30道墙下,
“累了么?” ,
“不累。”于是二人继续向外翻。
至第60道墙下,
“你累了么?”
“不累。”于是二人继续向外翻,
至第99道墙下,
“你累了么? ”
“累了”
“那好,我们翻回去吧”
95:小明:在某个溪边,有大宝、大雄、大志、大伟共四个男孩脱光光在玩水,
突然有人在溪边电鱼,这四个男孩都被电到了!猜一种电器用品。
阿康:嗯…. 不知道耶~
小明:答案是”电视机”(电四鸡)!嘿嘿!
96:小骆:爸爸,为什麼我们要有驼峰呢?
驼爸:因为沙漠中没有水,有驼峰才可以储存水分啊!
小骆:爸爸,为什麼我们要有长长的毛呢?
驼爸:因为沙漠中风沙大,我们必须靠它阻挡风砂,才看得见啊!
小骆:爸爸,为什麼我们要有厚厚的蹄呢?
驼爸:因为沙漠中都是沙,这样我们才站得稳啊!
小骆:爸爸,最后一个问题,那我们在动物园干嘛呢?
97:母鸡在孵蛋,有个蛋从它屁屁钻出来了
母鸡:“你干吗?”
鸡蛋:“你放屁好臭……”
98:有个人的名字叫“杜子藤”
老师点名时问
“杜子藤呢?”
同学说:“他肚子疼。”
99:我女朋友约我去她家看电影。到了她家之后,
她用签字笔在墙上写了‘电影’两个字,
我们两个就坐在马桶上看了起来。
100:某清晨,以严厉著称的某长官问晨练小兵:“你冷吗?”
小兵答:“不冷!”
长官恼:“那你颤颤什么?”
小兵答:“冻的!”