2010年5月12日星期三

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:某清晨,以严厉著称的某长官问晨练小兵:“你冷吗?”
小兵答:“不冷!”
长官恼:“那你颤颤什么?”
小兵答:“冻的!”

2008年7月5日星期六

KLT

KLT: An Implementation of the
Kanade-Lucas-Tomasi Feature Tracker


KLT is an implementation, in the C programming language, of a feature tracker for the computer vision community. The source code is in the public domain, available for both commercial and non-commerical use.

The tracker is based on the early work of Lucas and Kanade [1], was developed fully by Tomasi and Kanade [2], and was explained clearly in the paper by Shi and Tomasi [3]. Later, Tomasi proposed a slight modification which makes the computation symmetric with respect to the two images -- the resulting equation is derived in the unpublished note by myself [4]. Briefly, good features are located by examining the minimum eigenvalue of each 2 by 2 gradient matrix, and features are tracked using a Newton-Raphson method of minimizing the difference between the two windows. Multiresolution tracking allows for relatively large displacements between images. The affine computation that evaluates the consistency of features between non-consecutive frames [3] was implemented by Thorsten Thormaehlen several years after the original code and documentation were written.

Some Matlab interface routines: klt_read_featuretable.m

Note: An alternate Lucas-Kanade implementation can be found in Intel's OpenCV library. This implementation, described in the note by Bouguet, does a better job of handling features near the image borders, and it is more computationally efficient (approximately 30% on my desktop system). However, it does not contain the affine consistency check. Another alternative is GPU_KLT, which is an implementation of KLT for a graphics processing unit (GPU), which speeds up the run time considerably. A Matlab implementation of a single template tracker is available at Lucas-Kanade 20 Years On.

References

[1] Bruce D. Lucas and Takeo Kanade. An Iterative Image Registration Technique with an Application to Stereo Vision. International Joint Conference on Artificial Intelligence, pages 674-679, 1981.

[2] Carlo Tomasi and Takeo Kanade. Detection and Tracking of Point Features. Carnegie Mellon University Technical Report CMU-CS-91-132, April 1991.

[3] Jianbo Shi and Carlo Tomasi. Good Features to Track. IEEE Conference on Computer Vision and Pattern Recognition, pages 593-600, 1994.

[4] Stan Birchfield. Derivation of Kanade-Lucas-Tomasi Tracking Equation. Unpublished, January 1997.


Website maintained by Stan Birchfield

2008年7月3日星期四

如何用摄像头来测距(opencv)



原 理

假设激光束是与摄像头的光轴完全平行,激光束的中心落点在在摄像头的视域中是最亮的点。激光束照射到摄像头视域中的跟踪目标上,那么摄像头可以捕捉到这个 点,通过简单的图像处理的方法,可以在这侦图像中找到激光束照射形成的最亮点,同时可以计算出Y轴上方向上从落点到图像中心的象素的个数。这个落点越接近 图像的中心,被测物体距离机器人就越远。由下图图可以计算距离D:

(1)

等式中h是一个常量,是摄像头与激光发射器之间的垂直距离,可以直接测量获得。

θ可通过下式计算:
θ=Num*Rop+Offset (2)
其中:Num是从图像中心到落点的像素个数
Rop是每个像素的弧度值
Offset是弧度误差
合并以上等式可以得到:
(3)
Num可以从图像上计算得到。Rop和Offset需要通过实验计算获得。首先测量出D的准确值,然后根据等式(1)可以计算出准确的θ,根据 等式(2)可到只含有参数Rop和Offset的方程。在不同的距离多次测量D的准确值计算θ,求解方程组可以求出Rop和Offset。这里 Rop=0.0030354,Offset=0.056514344。


程 序
头文件:
class LaserRange
{
public:
struct RangeResult * GetRange(IplImage * imgRange,IplImage * imgDst);
LaserRange();
virtual ~LaserRange();
private:
unsigned int maxW;
unsigned int maxH;
unsigned int MaxPixel;
RangeResult * strctResult;

// Values used for calculating range from captured image data
const double gain;
// Gain Constant used for converting pixel offset to angle in radians
const double offset; // Offset Constant
const double h_cm; // Distance between center of camera and laser
unsigned int pixels_from_center;
// Brightest pixel location from center
void Preprocess(void * img,IplImage * imgTemp);
};
cpp文件:

LaserRange::LaserRange():gain(0.0030354),offset(0),h_cm(4.542)
{
maxW=0;
maxH=0;
MaxPixel=0;

pixels_from_center=0;
// Brightest pixel location from center
strctResult=new RangeResult;

strctResult->maxCol=0;
strctResult->maxRow=0;
strctResult->maxPixel=0;
strctResult->Range=0.0;
}
LaserRange::~LaserRange()
{
if(NULL!=strctResult) delete strctResult;
}
struct RangeResult * LaserRange::GetRange(IplImage * imgRange,IplImage * imgDst)
{
if(NULL==imgRange) return strctResult;
Preprocess(imgRange,imgDst);

pixels_from_center = abs(120-maxH);
// Calculate range in cm based on bright pixel location, and setup specific constants
strctResult->Range= h_cm/tan(pixels_from_center * gain + offset);

strctResult->PixfromCent=pixels_from_center;
strctResult->maxCol=maxW;
strctResult->maxRow=maxH;
strctResult->maxPixel=MaxPixel;
//strctResult->Range=0.0;
return strctResult;
}

void LaserRange::Preprocess(void *img, IplImage * imgTemp)
{
MaxPixel=0;
//处理下一帧前 最大像素值清零;
IplImage* image = reinterpret_cast(img);

cvCvtPixToPlane( image,0 ,0 ,imgTemp , 0);

for( int j=((imgTemp->width-60)/2-1); j<(imgTemp->width-40)/2+59; j++)
{
for(int i=5; i
height-5; i++)
{

if((imgTemp->imageData[i*imgTemp->widthStep+j])>MaxPixel)
{
if( ((imgTemp->imageData[(i-1)*imgTemp->widthStep+j])>MaxPixel) && ((imgTemp->imageData[(i-1)*imgTemp->widthStep+j+1])>MaxPixel) &&((imgTemp->imageData[(i-1)*imgTemp->widthStep+j-1])>MaxPixel) )
{
if( ((imgTemp->imageData[(i+1)*imgTemp->widthStep+j])>MaxPixel) && ((imgTemp->imageData[(i+1)*imgTemp->widthStep+j+1])>MaxPixel) &&((imgTemp->imageData[(i+1)*imgTemp->widthStep+j-1])>MaxPixel) )
{
if((imgTemp->imageData[i*(imgTemp->widthStep)+j+1])>MaxPixel)
{
if((imgTemp->imageData[i*(imgTemp->widthStep)+j-1])>MaxPixel)
{
MaxPixel=imgTemp->imageData[i*imgTemp->widthStep+j] ;
maxW=j;
maxH=i;
}
}
}
}
}
}

}
调用函数:
int CLaserVisionDlg::CaptureImage()
{
// CvCapture* capture = 0;

// capture = cvCaptureFromCAM(0); //0表示设备号
if( !capture )
{
fprintf(stderr,"Could not initialize capturing...\n");
return -1;
}

// cvNamedWindow( "LaserRangeImage", 1 );
// cvvNamedWindow( "image", 1);
cvvNamedWindow( "Dimage", 1);

for(;;)
{
IplImage* frame = 0;

if(isStop) break;
frame = cvQueryFrame( capture ); //从摄像头抓取一副图像框架
if( !frame )
break;
if( !imgOrign )
{
//allocate all the buffers
imgOrign = cvCreateImage( cvGetSize(frame), 8, 3 ); //创建一副图像
imgOrign->origin = frame->origin;

}
cvCopy( frame, imgOrign, 0 ); //将图frame复制到image
//cvShowImage("LaserRangeImage",imgOrign);


if(!imgDest)
{
imgDest=cvCreateImage( cvSize( imgOrign->width,imgOrign->height),8,1);
cvZero( imgDest );
}
struct RangeResult * temp= laservsion.GetRange(imgOrign,imgDest);
cvLine( imgOrign,cvPoint(temp->maxCol,0), cvPoint(temp->maxCol,imgOrign->height),cvScalar(100,100,255,0),1,8,0);
cvLine( imgOrign,cvPoint(0,temp->maxRow), cvPoint(imgOrign->width,temp->maxRow),cvScalar(100,100,255,0),1,8,0);


// cvvShowImage( "image", imgOrign);
cvSaveImage("image.bmp", imgOrign);

cvvShowImage( "Dimage", imgDest);

//在PictureBox上显示图片
CDC* pDC = GetDlgItem(IDC_Picture)->GetDC();
CDC dcmemory;
BITMAP bm;
dcmemory.CreateCompatibleDC(pDC);
CBitmap* pBmp;
CString szFileName = "image.bmp";
HBITMAP hBk = (HBITMAP)::LoadImage(NULL,szFileName,IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
if(NULL!=hBk)
{
pBmp=CBitmap::FromHandle(hBk);
pBmp->GetObject(sizeof(BITMAP), &bm);
dcmemory.SelectObject(pBmp);
pDC->BitBlt(0, 0, bm.bmWidth, bm.bmHeight, &dcmemory, 0, 0, SRCCOPY);
}


char str[80];
// To print message
CDC *pDCp= GetDC();
char str2[80];

// Display frame coordinates as well as calculated range
sprintf(str, "Pix Max Value=%d At x= %u, y= %u, PixfromCent= %d",temp->maxPixel,temp->maxCol, temp->maxRow, temp->PixfromCent);
sprintf(str2, "Range= %f cm ",temp->Range);
pDCp->TextOut(30, 33, str);
pDCp->TextOut(50, 50, str2);
ReleaseDC(pDCp);

int c = cvWaitKey(10);
// if( c == 'q' )
// break;
}
//cvReleaseCapture( &capture );
//cvDestroyWindow("LaserRangeImage");
// cvDestroyWindow( "image");
cvDestroyWindow( "Dimage");
return 0;
}

2008年7月1日星期二

利用光流法计算人体运动的速度与方向

1.方向的计算
首先计算图像各个象素的光流(opencv LK),然后建立4*4窗口对X,Y方向分别做统计求和,
然后求得 atan(yy/xx)作为光流方向,即为运动的方向.
2.速度的计算
利用帧差分得到运动图像,然后建立4*4窗口对图像进行统计求和,求和值作为权重,表示速度的比例.
即运动区域白色(255)面积越大,速度越大.

3.结果
大部分运动方向计算正确,少部分有错误,还需要改进算法.(利用统计?)

4.代码:

WW_RETURN HumanMotion::ImgOpticalFlow(IplImage *pre_grey,IplImage *grey)
/*************************************************
Function:
Description: 光流法计算运动速度与方向
Date: 2006-6-14
Author:
Input:
Output:
Return:
Others:
*************************************************/
{

IplImage *velx = cvCreateImage( cvSize(grey->width ,grey->height),IPL_DEPTH_32F, 1 );
IplImage *vely = cvCreateImage( cvSize(grey->width ,grey->height),IPL_DEPTH_32F, 1 );

velx->origin = vely->origin = grey->origin;
CvSize winSize = cvSize(5,5);
cvCalcOpticalFlowLK( prev_grey, grey, winSize, velx, vely );

cvAbsDiff( grey,prev_grey, abs_img );
cvThreshold( abs_img, abs_img, 29, 255, CV_THRESH_BINARY);

CvScalar xc,yc;
for(int y =0 ;yheight; y++)
for(int x =0;xwidth;x++ )
{
xc = cvGetAt(velx,y,x);
yc = cvGetAt(vely,y,x);


float x_shift= (float)xc.val[0];
float y_shift= (float)yc.val[0];
const int winsize=5; //计算光流的窗口大小


if((x%(winsize*2)==0) && (y%(winsize*2)==0) )
{

if(x_shift!=0 || y_shift!=0)
{

if(x>winsize && y>winsize && x <(velx->width-winsize) && y<(velx->height-winsize) )
{

cvSetImageROI( velx, cvRect( x-winsize, y-winsize, 2*winsize, 2*winsize));
CvScalar total_x = cvSum(velx);
float xx = (float)total_x.val[0];
cvResetImageROI(velx);

cvSetImageROI( vely, cvRect( x-winsize, y-winsize, 2*winsize, 2*winsize));
CvScalar total_y = cvSum(vely);
float yy = (float)total_y.val[0];
cvResetImageROI(vely);

cvSetImageROI( abs_img, cvRect( x-winsize, y-winsize, 2*winsize, 2*winsize));
CvScalar total_speed = cvSum(abs_img);
float ss = (float)total_speed.val[0]/(4*winsize*winsize)/255;
cvResetImageROI(abs_img);

const double ZERO = 0.000001;
const double pi = 3.1415926;
double alpha_angle;

if(xx-ZERO)
alpha_angle = pi/2;
else
alpha_angle = abs(atan(yy/xx));

if(xx<0>0) alpha_angle = pi - alpha_angle ;
if(xx<0 alpha_angle =" pi">0 && yy<0) alpha_angle =" 2*pi">



CvScalar line_color;
float scale_factor = ss*100;
line_color = CV_RGB(255,0,0);
CvPoint pt1,pt2;
pt1.x = x;
pt1.y = y;
pt2.x = static_cast(x + scale_factor*cos(alpha_angle));
pt2.y = static_cast(y + scale_factor*sin(alpha_angle));

cvLine( image, pt1, pt2 , line_color, 1, CV_AA, 0 );
CvPoint p;
p.x = (int) (pt2.x + 6 * cos(alpha_angle - pi / 4*3));
p.y = (int) (pt2.y + 6 * sin(alpha_angle - pi / 4*3));
cvLine( image, p, pt2, line_color, 1, CV_AA, 0 );
p.x = (int) (pt2.x + 6 * cos(alpha_angle + pi / 4*3));
p.y = (int) (pt2.y + 6 * sin(alpha_angle + pi / 4*3));
cvLine( image, p, pt2, line_color, 1, CV_AA, 0 );

/*
line_color = CV_RGB(255,255,0);
pt1.x = x-winsize;
pt1.y = y-winsize;
pt2.x = x+winsize;
pt2.y = y+winsize;
cvRectangle(image, pt1,pt2,line_color,1,CV_AA,0);
*/

}
}
}
}


cvShowImage( "Contour", abs_img);
cvShowImage( "Contour2", vely);

cvReleaseImage(&velx);
cvReleaseImage(&vely);
cvWaitKey(20);

return WW_OK;

}

模糊数学(Fuzzy mathematics)及其应用

模糊数学(Fuzzy mathematics)及其应用
一 :引言
有一个古老的希腊悖论,是这样说的:“一粒种子肯定不叫一堆,两粒也不是,三粒也不是……另一方面,所有的人都同意,一亿粒种子肯定叫一堆。那么,适当的界限在哪里?我们能不能说,123585粒种子不叫一堆而123586粒就构成一堆?”
确 实,“一粒”和“一堆”是有区别的两个概念。但是,它们的区别是逐渐的,而不是突变的,两者之间并不存在明确的界限。换句话说,“一堆”这个概念带有某种 程度的模糊性。类似的概念,如“年老”、“高个子”、“年轻人”、“很大”、“聪明”、“漂亮的人”、“价廉物美”等等,不胜枚举。
经 典集合论中,在确定一个元素是否属于某集合时,只能有两种回答:“是”或者“不是”。我们可以用两个值0或1加以描述,属于集合的元素用1表示,不属于集 合的元素用0表示。然而上面提到的“年老”、“高个子”、“年轻人”、“很大”、“聪明”、“漂亮的人”、“价廉物美” 等情况要复杂得多。假如规定身高1.8米算属于高个子范围,那么,1.79米的算不算?照经典集合论的观点看:不算。但这似乎很有些悖于情理。如果用一个 圆,以圆内和圆周上的点表示集A,而且圆外的点表示不属于A。A的边界显然是圆周。这是经典集合的图示。现在,设想将高个子的集合用图表示,则它的边界将 是模糊的,即可变的。因为一个元素(例如身高1.75米的人)虽然不是100%的高个子,却还算比较高,在某种程度上属于高个子集合。这时一个元素是否属 于集合,不能光用0和1两个数字表示,而可以取0和1之间的任何实数。例如对1.75米的身高,可以说具有70%属于高个子集合的程度。这样做似乎罗嗦, 但却比较合乎实际。
精 确和模糊,是一对矛盾。根据不同情况有时要求精确,有时要求模糊。比如打仗,指挥员下达命令:“拂晓发起总攻。”这就乱套了。这时,一定要求精确:“×月 ×日清晨六时正发起总攻。”我们在一些旧电影中还能看到各个阵地的指挥员在接受命令前对对表的镜头,生怕出个半分十秒的误差。但是,物极必反。如果事事要 求精确,人们就简直无法顺利的交流思想——两人见面,问:“你好吗?”可是,什么叫“好”,又有谁能给“好”下个精确的定义?
有些现象本质上就是模糊的,如果硬要使之精确,自然难以符合实际。例如,考核学生成绩,规定满60分为合格。但是,59分和60分之间究竟有多大差异,仅据1分之差来区别及格和不及格,其根据是不充分的。
不 仅普遍存在着边界模糊的集合,就是人类的思维,也带有模糊的特色。有些现象是精确的,但是,适当的模糊化可能使问题得到简化,灵活性大为提高。例如,在地 里摘玉米,若要找一个最大的,那很麻烦,而且近乎迂腐。我们必须把玉米地里所有的玉米都测量一下,再加以比较才能确定。它的工作量跟玉米地面积成正比。土 地面积越大,工作越困难。然而,只要稍为改变一下问题的提法:不要求找最大的玉米,而是找比较大的,即按通常的说法,到地里摘个大玉米。这时,问题从精确 变成了模糊,但同时也从不必要的复杂变成意外的简单,挑不多的几个就可以满足要求。工作量甚至跟土地无关。因此,过分的精确实际成了迂腐,适当的模糊反而 灵活。
显 然,玉米的大小,取决于它的长度、体积和重量 。大小虽是模糊概念,但长度、体积、重量等在理论上都可以是精确的。然而,人们在实际判断玉米大小时,通常并不需要测定这些精确值。同样,模糊的“堆”的 概念是建立在精确的“粒”的基础上,而人们在判断眼前的东西叫不叫一堆时,从来不用去数“粒”。有时,人们把模糊性看成一种物理现象。近的东西看得清,远 的东西看不清,一般的说,越远越模糊。但是,也有例外的情况:站在海边,海岸线是模糊的;从高空向下眺望,海岸线却显得十分清晰。太高了,又模糊。精确与 模糊,有本质区别,但又有内在联系,两者相互矛盾、相互依存也可相互转化。所以,精确性的另一半是模糊。
对 模糊性的讨论,可以追溯得很早。20世纪的大哲学家罗素(B.Russel)在1923年一篇题为《含糊性》(Vagueness)的论文里专门论述过我 们今天称之为“模糊性”的问题(严格地说,两者稍有区别),并且明确指出:“认为模糊知识必定是靠不住的,这种看法是大错特错的。”尽管罗素声名显赫,但 这篇发表在南半球哲学杂志的文章并未引起当时学术界对模糊性或含糊性的很大兴趣。这并非是问题不重要,也不是因为文章写得不深刻,而是“时候未到”。罗素 精辟的观点是超前的。长期以来,人们一直把模糊看成贬义词,只对精密与严格充满敬意。20世纪初期社会的发展,特别是科学技术的发展,还未对模糊性的研究 有所要求。事实上,模糊性理论是电子计算机时代的产物。正是这种十分精密的机器的发明与广泛应用,使人们更深刻地理解了精密性的局限,促进了人们对其对立 面或者说它的“另一半”——模糊性的研究。
集 合是现代数学的基础,模糊集合一提出,“模糊”观念也渗透到许多数学分支。模糊数学的发展速度也是相当快的。从发表的论文看,几乎是指数般的增长。模糊数 学的研究可分三个方面:一是研究模糊数学的理论,以及它和精确数学、统计数学的关系;二是研究模糊语言和模糊逻辑;三是研究模糊数学的应用。在模糊数学的 研究中,目前已有模糊拓扑学、模糊群论、模糊凸论、模糊概率、模糊环论等分支。虽然模糊数学是一门新兴学科,但它已初步应用于自动控制、模式识别、系统理 论、信系检索、社会科学、心理学、医学和生物学等方面。将来还可能出现模糊逻辑电路、模糊硬件、模糊软件和模糊固件,出现能和人用自然语言对话、更接近于 人的智能的新的一类计算机。所以,模糊数学将越来越显示出它的巨大生命力。
二: 模糊数学的产生
二 十世纪六十年代,产生了模糊数学这门新兴学科。 现代数学是建立在集合论的基础上。集合论的重要意义就一个侧面看,在与它把数学的抽象能力延伸到人类认识 过程的深处。一组对象确定一组属性,人们可以通过说明属性来说明概念(内涵),也可以通过指明对象来说明它。符合概念的那些对象的全体叫做这个概念的外 延,外延其实就是集合。从这个意义上讲,集合可以表现概念,而集合论中的关系和运算又可以表现判断和推理,一切现实的理论系统都一可能纳入集合描述的数学 框架。
但是,数学的发展也是阶段性的。经典集合论只能把自己的表现力限制在那些有明确外延的概念和事物上,它明确地限定:每个集合都必须 由明确的元素构成,元素对集合的隶属关系必须是明确的,决不能模棱两可。对于那些外延不分明的概念和事物,经典集合论是暂时不去反映的,属于待发展的范 畴。
在较长时间里,精确数学及随机数学在描述自然界多种事物的运动规律中,获得显著效果。但是,在客观世界中还普遍存在着大量的模糊现象。以前人们回避它,但是,由于现代科技所面对的系统日益复杂,模糊性总是伴随着复杂性出现。
各门学科,尤其是人文、社会学科及其它“软科学”的数学化、定量化趋向把模糊性的数学处理问题推向中心地位。更重要的是,随着电子计算机、控制论、系统科学的迅速发展,要使计算机能像人脑那样对复杂事物具有识别能力,就必须研究和处理模糊性。
我们研究人类系统的行为,或者处理可与人类系统行为相比拟的复杂系统,如航天系统、人脑系统、社会系统等,参数和变量甚多,各种因素相互交错,系统很复杂,它的模糊性也很明显。从认识方面说,模糊性是指概念外延的不确定性,从而造成判断的不确定性。
在 日常生活中,经常遇到许多模糊事物,没有分明的数量界限,要使用一些模糊的词句来形容、描述。比如,比较年轻、高个、大胖子、好、漂亮、善、热、远……。 在人们的工作经验中,往往也有许多模糊的东西。例如,要确定一炉钢水是否已经炼好,除了要知道钢水的温度、成分比例和冶炼时间等精确信息外,还需要参考钢 水颜色、沸腾情况等模糊信息。因此,除了很早就有涉及误差的计算数学之外,还需要模糊数学。
人与计算机相比,一般来说,人脑具有处理模糊 信息的能力,善于判断和处理模糊现象。但计算机对模糊现象识别能力较差,为了提高计算机识别模糊现象的能力,就需要把人们常用的模糊语言设计成机器能接受 的指令和程序,以便机器能像人脑那样简洁灵活的做出相应的判断,从而提高自动识别和控制模糊现象的效率。这样,就需要寻找一种描述和加工模糊信息的数学工 具,这就推动数学家深入研究模糊数学。所以,模糊数学的产生是有其科学技术与数学发展的必然性。
模糊数学的研究内容
1965年,美国控制论专家、数学家查德发表了论文《模糊集合》,标志着模糊数学这门学科的诞生。
模糊数学的研究内容主要有以下三个方面:
第 一,研究模糊数学的理论,以及它和精确数学、随机数学的关系。察德以精确数学集合论为基础,并考虑到对数学的集合概念进行修改和推广。他提出用“模糊集合 ”作为表现模糊事物的数学模型。并在“模糊集合”上逐步建立运算、变换规律,开展有关的理论研究,就有可能构造出研究现实世界中的大量模糊的数学基础,能 够对看来相当复杂的模糊系统进行定量的描述和处理的数学方法。
在模糊集合中,给定范围内元素对它的隶属关系不一定只有“是”或“否”两种 情况,而是用介于0和1之间的实数来表示隶属程度,还存在中间过渡状态。比如“老人”是个模糊概念,70岁的肯定属于老人,它的从属程度是 1,40岁的人肯定不算老人,它的从属程度为 0,按照查德给出的公式,55岁属于“老”的程度为0.5,即“半老”,60岁属于“老”的程度0.8。查德认为,指明各个元素的隶属集合,就等于指定了 一个集合。当隶属于0和1之间值时,就是模糊集合。
第二,研究模糊语言学和模糊逻辑。人类自然语言具有模糊性,人们经常接受模糊语言与模糊信息,并能做出正确的识别和判断。
为了实现用自然语言跟计算机进行直接对话,就必须把人类的语言和思维过程提炼成数学模型,才能给计算机输入指令,建立和是的模糊数学模型,这是运用数学方法的关键。查德采用模糊集合理论来建立模糊语言的数学模型,使人类语言数量化、形式化。
如 果我们把合乎语法的标准句子的从属函数值定为1,那么,其他文法稍有错误,但尚能表达相仿的思想的句子,就可以用以0到1之间的连续数来表征它从属于“正 确句子”的隶属程度。这样,就把模糊语言进行定量描述,并定出一套运算、变换规则。目前,模糊语言还很不成熟,语言学家正在深入研究。
人们的思维活动常常要求概念的确定性和精确性,采用形式逻辑的排中律,既非真既假,然后进行判断和推理,得出结论。现有的计算机都是建立在二值逻辑基础上的,它在处理客观事物的确定性方面,发挥了巨大的作用,但是却不具备处理事物和概念的不确定性或模糊性的能力。
为了使计算机能够模拟人脑高级智能的特点,就必须把计算机转到多值逻辑基础上,研究模糊逻辑。目前,模糊罗基还很不成熟,尚需继续研究。
第 三,研究模糊数学的应用。模糊数学是以不确定性的事物为其研究对象的。模糊集合的出现是数学适应描述复杂事物的需要,查德的功绩在于用模糊集合的理论找到 解决模糊性对象加以确切化,从而使研究确定性对象的数学与不确定性对象的数学沟通起来,过去精确数学、随机数学描述感到不足之处,就能得到弥补。在模糊数 学中,目前已有模糊拓扑学、模糊群论、模糊图论、模糊概率、模糊语言学、模糊逻辑学等分支。
模糊数学的应用
模糊数学是一门新兴学 科,它已初步应用于模糊控制、模糊识别、模糊聚类分析、模糊决策、模糊评判、系统理论、信息检索、医学、生物学等各个方面。在气象、结构力学、控制、心理 学等方面已有具体的研究成果。然而模糊数学最重要的应用领域是计算机职能,不少人认为它与新一代计算机的研制有密切的联系。
目前,世界上 发达国家正积极研究、试制具有智能化的模糊计算机,1986年日本山川烈博士首次试制成功模糊推理机,它的推理速度是1000万次/秒。1988年,我国 汪培庄教授指导的几位博士也研制成功一台模糊推理机——分立元件样机,它的推理速度为1500万次/秒。这表明我国在突破模糊信息处理难关方面迈出了重要 的一步。
模糊数学还远没有成熟,对它也还存在着不同的意见和看法,有待实践去检验。
四:模糊数学的主要应用
1 模糊数学自身的理论研究进展迅速。我国模糊数学自身的理论研究仍占模糊数学及其应用学科的主导地位,所取得的研究成果在《模糊数学》、《模糊系统与数学》 等数十种学术期刊和全国高校学报中经常可见,模糊聚类分析理论、模糊神经网络理论和各种新的模糊定理及算法不断取得进展。
2.模糊数学目前在自动控制技术领域仍然得到最广泛的应用,所涉及的技术复杂繁多,从微观到宏观、从地下到太空无所不有,在机器人实时控制、电磁元件自适应控制、各种物理及力学参数反馈控制、逻辑控制等高新技术中均成功地应用了模糊数学理论和方法。
3.模糊数学在计算机仿真技术、多媒体辨识等领域的应用取得突破性进展,如图像和文字的自动辨识、自动学习机、人工智能、音频信号辨识与处理等领域均借助了模糊数学的基本原理和方法。
4. 模糊聚类分析理论和模糊综合评判原理等更多地被应用于经济管理、环境科学、安全与劳动保护等领域,如房地价格、期货交易、股市情报、资产评估、工程质量分 析、产品质量管理、可行性研究、人机工程设计、环境质量评价、资源综合评价、各种危险性预测与评价、灾害探测等均成功地应用了模糊数学的原理和方法。
5.地 矿、冶金、建筑等传统行业在处理复杂不确定性问题中也成功地应用了模糊数学的原理和方法,从而使过去凭经验和类比法等处理工程问题的传统做法转向数学化、 科学化,如矿床预测、矿体边界确定、油水气层的识别、采矿方法设计参数选择、冶炼工艺自动控制与优化、建筑物结构设计等都有应用模糊数学的成功实践。
6. 我国医药、生物、农业、文化教育、体育等过去看似与数学无缘的学科也开始应用模糊数学的原理和方法,如计算机模糊综合诊断、传染病控制与评估、人体心理及 生理特点分析、家禽孵养、农作物品种选择与种植、教学质量评估、语言词义查找、翻译辨识等均有一些应用模糊数学的实践,并取得很好效果。
模糊数学目前在自动控制技术领域仍然得到最广泛的应用,所涉及的技术复杂繁多,从微观到宏观、从地下到太空无所不有,在机器人实时控制、电磁元件自适应控制、各种物理及力学参数反馈控制、逻辑控制等高新技术中均成功地应用了模糊数学理论和方法。
模糊数学在计算机仿真技术、多媒体辨识等领域的应用取得突破性进展,如图像和文字的自动辨识、自动学习机、人工智能、音频信号辨识与处理等领域均借助了模糊数学的基本原理和方法。
模 糊聚类分析理论和模糊综合评判原理等更多地被应用于经济管理、环境科学、安全与劳动保护等领域,如房地价格、期货交易、股市情报、资产评估、工程质量分 析、产品质量管理、可行性研究、人机工程设计、环境质量评价、资源综合评价、各种危险性预测与评价、灾害探测等均成功地应用了模糊数学的原理和方法。
地 矿、冶金、建筑等传统行业在处理复杂不确定性问题中也成功地应用了模糊数学的原理和方法,从而使过去凭经验和类比法等处理工程问题的传统做法转向数学化、 科学化,如矿床预测、矿体边界确定、油水气层的识别、采矿方法设计参数选择、冶炼工艺自动控制与优化、建筑物结构设计等都有应用模糊数学的成功实践。
我 国医药、生物、农业、文化教育、体育等过去看似与数学无缘的学科也开始应用模糊数学的原理和方法,如计算机模糊综合诊断、传染病控制与评估、人体心理及生 理特点分析、家禽孵养、农作物品种选择与种植、教学质量评估、语言词义查找、翻译辨识等均有一些应用模糊数学的实践,并取得很好效果。
五:
但是一些概率论 学者认为模糊数学不过是概率论的一个应用而已。一些搞理论数学的人说这不是数学。搞应用的人则说道理说的很好,但真正的实际效果没有。然而,国际著名的应 用数学家考夫曼(A.Kauffman)教授在访华时说:“他们的攻击是毫无道理的,不必管人家说什么,我们努力去做就是。”
模糊数学是一门崭新的数学学科,它的产生不仅拓广了经典数学的基础,而且是使计算机科 学向人们的自然机理方面发展的重大突破。它在科学技术、经济发展和社会学等问题的广泛应用领域中显示了巨大的力量。它虽然只有二十多年的历史,但已被国内 外数学界以及信息、系统、计算机和自动控制科学、人员的普遍关注,它是正在迅速发展中的有着广阔应用前景的一门崭新学科。