OpenCV运动检测跟踪(blob track)框架组成模块详解 - 小新786

时间:2024-02-23 19:05:08
在..\opencv\doc\vidsurv文件夹中有三个doc文件,

Blob_Tracking_Modules、Blob_Tracking_Tests、TestSeq,其中Blob_Tracking_Modules必须需要详读的。

FG/BG Detection” module performsforeground/background segmentation for each pixel.

Blob Entering Detection” module uses theresult (FG/BG mask) of “FG/BG Detection” module to detect new blob objectentered to a scene on each frame.

Blob Tracking” module initialized by“Blob Entering Detection” results and tracks each new entered blob.

Trajectory Generation” module performs asaving function. It collects all blobs positions and save each whole blobtrajectory to hard disk when it finished (for example tracking is lost).

Trajectory PostProcessing” moduleperforms a blob trajectory smoothing function. This module is optional and cannot be included in specific pipeline.

OpenCV所提供的这个运动物体跟踪框架只是一个基本的框架,开发者可以根据自己的实际需要对其中的一些模块进行自定义扩展,以满足实际应用中的具体要求。

1、  前景检测模块CvFGDetector:它的输入数据为当前帧图像,输出结果数据为当前帧图像的前景图像(mask)。前景图像是一个和输入的视频帧具有同样大小的二值图像,即如果当前帧中的像素点被判断为运动前景,则前景掩码中相应位置的像素点值为1,否则,相应的像素点值为0。

开发者需要继承CvFGDetector类,并实现其中的纯虚函数。其中在函数Virtual void process(IplImage *pImg){}中写入自己开发的运动目标检测算法。而函数Virtual IplImage *GetMask()是得到前景检测的结果图像,并负责传递到后续的模块中。而函数Virtual void Release()负责一些动态分配内存的释放。

2、  新团块检测模块CvBlobDetector:该模块的作用是检测进入监控范围的新目标的位置和大小。模块的输入是当前帧的前景图像(前景检测模块的结果)和已经检测并标定的团块,输出的结果是新检测到的团块。

开发者可以将虚类实例化,然后将自己的新团块检测算法写入到相应的函数中。

新团块检测模块的处理流程为:首先从前景图像中检测出所有团块,然后将较小的团块(可能是由噪声引起的)和与已经被跟踪团块有重叠的团块丢弃,并对剩余的团块按照大小顺序排列,只保留其中几个比较大的团块(默认为10)。最后利用特定规则筛选,筛选不合标准的团块,将真正的新团块保存到团块列表中。

3、  团块跟踪模块CvBlobTracker:该模块的作用就是在前面两个模块(前景检测模块、新团块检测模块)对运动目标检测的基础上,实现对运动目标的跟踪。此模块的输入为当前帧的前景图像和团块列表以及当前帧图像,输出结果是当前视频帧中所有运动目标的信息,以团块表示(ID,pos,size)。使用新团块检测模块的结果初始化该模块,并跟踪新进入的团块。

开发者根据自己的算法开发相应的跟踪系统时,可以继承该类,然后用自己的算法实现函数Virtural void process(IplImage *pImg,IplImage *pImgFG=NULL)。此虚类中还定义了许多其他的辅助处理函数接口,例如跟踪索引或ID返回指定团块指针的函数,根据索引或ID为指定团块设置参数函数等。

团块跟踪模块的处理流程为:首先从前景图像提取所有团块,并计算团块的质心、宽度和高度;然后对每一个已被跟踪的轨迹,利用卡尔曼滤波器预测该轨迹在当前帧的团块的位置和大小;最后对每个跟踪的轨迹进行处理,寻找离上一帧里的团块最近的当前帧的团块,将此团块添加到跟踪轨迹。

4、轨迹生成模块CvBlobTrackGen:该模块的作用是生成运动目标的运动轨迹,然后将轨迹导出到指定数据库或文件中(如.txt、.csv文件)。该模块的输入是代表当前处理视频帧中各个运动目标的团块,输出结果是存储在指定位置下的轨迹文件。该模块主要是保存操作,它收集所有团块的位置,并在每条轨迹结束时(例如跟踪丢失时或者物体离开场景时)将其保存到硬盘上,同时也可以为每个团块计算一些特征并保存。

5、轨迹后处理模块CvBlobTrackPostProc:该模块的作用是在前一个模块所产生的团块轨迹上做一些处理,例如采用Kalman滤波或平滑滤波处理等。此模块是可选的,可以不包含在处理流程中。它的输入是当前处理图像的所有团块,输出结果是处理后所处理图像的团块列表。

6、轨迹分析模块CvBlobTrackAnalysis:当某个目标跟踪结束后,会产生一个轨迹,CvBlobTrackAnalysis的子类用于对轨迹进行数据分析;

7、跟踪流程模块CvBlobTracterAuto:为了方便开发者开发自己的系统,同时也为了保证系统的模块化设计,OpenCV设计了此虚类描述整个跟踪流程,这个代表整个跟踪流程的虚类将各个模块相互联系起来成为一个有机的整体。本模块将前面提到的五个模块连接起来,形成一个完整的处理流程。

此类中的函数Process负责调用其它各个子模块,首先对背景图像进行更新并检测前景,将获取的前景图像保存于成员变量m_pFG中。获取前景图像后,便依次调用团块跟踪模块(注意:而不是新团块检测模块,这样做的主要目的是先执行跟踪可将当前帧的跟踪结果传入新团块检测模块,以提供新团块检测的准确度。如果团块跟踪在后,则新团块跟踪模块只能与上一帧的团块列表进行比较,新团块检测的准确度将会有所降低),轨迹后处理模块,团块检测模块,轨迹生成模块,轨迹分析模块。

如果想将自己实现的算法加入到以上模块中也很方便,如将背景差分算法加入到前景检测模块中,只要继承CvFGDetector类,然后主要在Process函数中实现自己的算法就可以了。

原始代码中,用红色标注运动目标的表示跟踪不稳定,绿色则表示稳定跟踪。

轨迹生成模块中默认有两种方法实现数据的保存,其中一种是”RawTracks”方法,每行存放一个运动目标数据,单位为像素,依次为运动目标出现的起始帧,运动目标中心x坐标,运动目标中心y坐标,运动目标宽度,运动目标高度,运动目标中心x坐标,运动目标中心y坐标,运动目标宽度,运动目标高度,……。

参考文献:

1、http://www.doc88.com/p-896576154875.html

2、http://blog.csdn.net/wk119911/article/details/7664478

3、http://www.opencv.org.cn/forum/viewtopic.php?t=11128

 

Block Track 调试过程

 

1.在空工程中加入opencv中blobtrack.cpp编译遇到的错误

fatal error C1010: unexpected end of file while looking for precompiled header directive

Error executing cl.exe.

blobtrack.obj - 1 error(s), 0 warning(s)

预编译头文件(precompiled header)

解决方法:

1、如果发生错误的文件是由其他的C代码文件添加进入当前工程而引起的,则Alt+F7进入当前工程的 Settings,选择C/C++选项卡,从Category组合框中选中Precompiled Headers,选择Not Using Precompiled headers。确定。

2、在文件开头添加:

#include "stdafx.h"

对预编译头文件说明如下:

所谓头文件预编译,就是把一个工程(Project)中使用的一些MFC标准头文件(如Windows.H、Afxwin.H)预先编译,以后该工程编译时,不再编译这部分头文件,仅仅使用预编译的结果。这样可以加快编译速度,节省时间。

预编译头文件通过编译stdafx.cpp生成,以工程名命名,由于预编译的头文件的后缀是“pch”,所以编译结果文件是projectname.pch。

编译器通过一个头文件stdafx.h来使用预编译头文件。stdafx.h这个头文件名是可以在project的编译设置里指定的。编译器认为,所有在指令#include   "stdafx.h"前的代码都是预编译的,它跳过#include   "stdafx.   h"指令,使用projectname.pch编译这条指令之后的所有因此,所有的CPP实现文件第一条语句都是:#include   "stdafx.h"。

------------------------------------

unexpected end of file while looking for precompiled header directive Error executing cl.exe.我在文件头上添加了:

#include "stdafx.h" 还是不能运行,请多指教!

Alt+F7进入当前工程的Settings,选择C/C++选项卡,从Category组合框中选中Precompiled Headers,选择Not Using Precompiled headers。确定。

如果发生错误的文件原本是该工程中的,则检查该文件头部有没有#i nclude "stdafx.h"语句,没有的话添加。

如果还不行,也有可能是定义的类或结构体等最后忘了加分号,注意一下

------------------------------------------

问题:我在编译程序中老出现“fatal error C1010: unexpected end of file while looking for precompiled header directive”这一句,但我查看了程序并没有错,请问这是怎么一回事?

A回答:

肯定是一个新添加的类的.cpp文件开头没包含stdafx.h,在该文件最前面加上即可。

BOBO的意见:

有时可以使用右键点击项目工程中的该cpp文件,选择setting,在c/c++栏,选择PreCompiled headers,然后设置第一选项,选择不使用预编译头,解决这个问题。

---------------------------------------

编译出现问题:unexpected end of file while looking for precompiled header directive

解决方案:

需要在文件头上添加一句:

#include "stdafx.h"

这个文件定义了源程序为C++格式。

否则文件需要保存为.C格式

--------------------------------

错误:fatal error C1083: Cannot open precompiled header file: \'Debug\test4.pch\': No such file or directory

解决方案:

或rebuild all,或重新编译一下stdafx.cpp就OK了,呵呵,又长知识了。

--------------------

代码。

----------------总结------

如果工程很大,头文件很多,而有几个头文件又是经常要用的,那么

1。把这些头文件全部写到一个头文件里面去,比如写到preh.h

2。写一个preh.c,里面只一句话:#include "preh.h"

3。对于preh.c,在project setting里面设置creat precompiled headers,对于其他.c文件,设置use precompiled header file

 

 

 

关于blobtracker的一些解析

帖子由 secondwang » 2010-08-05 10:43

virtual CvBlob* AddBlob(CvBlob* pB, IplImage* pImg, IplImage* pImgFG = NULL ) 
//这个函数的主要作用是在m_BlobList中加入新的blob,该结构包含了新创建的预测器和跟踪器 
//pPredictor 采用卡尔曼线性滤波器来预测blob的位置 
//pResolver 采用meanshift或者/和 particle filter粒子滤波器来进行跟踪 
virtual void Process(IplImage* pImg, IplImage* pImgFG = NULL) 
//这个函数先通过轮廓找到contour blob 
//然后对前一帧的blob进行预测位置 
//再将预测到的新位置与新产生的contour blob进行距离相关处理, 
//如果是一个contour blob对应多个预测值,则认为有碰撞 
//如果是多个contour blob对应一个预测数值,这里程序未做处理,个人认为应该有个分离处理 
//接着对上述信息综合处理,m_BlobList列表中某个blob如果有碰撞,则采用meanshift 或者/和 
// particle filter进行跟踪的结果(位置信息,预测器,跟踪器)更新该blob 
//如果没碰撞,如果是一个contour blob对应一个m_BlobList列表中的某个blob,则用contour blob更新 
//该blob,如果多个contour blob对应一个m_BlobList列表中的某个blob,则找最佳的contour blob 
//更新该blob。最佳blob有两中方式去判断,第一是根据meanshift算法中直方图最小距离法Bhattacharyya 
//获得一个可信度,最大可信度的最匹配;第二采用空间上最小距离法,距离最小最匹配 
//这里需要说明的是在处理碰撞情况下pResolver调用的process,由于上层的默认采用meanshift+Partical filter 
//进行跟踪,所以运算量有时候非常大 
//最终结果是用跟踪法更新了m_BlobList表中blob的位置 
//新contour blob与以前多个blob的预测位置有交集则认为有碰撞 
if(m_BlobList.GetBlobNum()>0 && m_BlobListNew.GetBlobNum()>0) 
}/* check intersection */ 
// 
//这里如果没有找到相关物体,则认为被隐藏, 
//程序未做处理也 
//可以判断是否到图像边界,如果在图像边界则认为消失,如果不在图像边界,则认为被遮挡, 
//可以考虑继续跟踪,直到预测计数达到某个数值的时候认为该物体消失 
//判断依据很简单:pFLast是否为NULL 
}/* check next new blob */ 
if(pBT->Collision) 
{/* tracking in collision */ 
//多个blob对应一个contour blob 
if(pBT->pResolver) 

//这个函数太耗资源了,恐怖哦,又有粒子跟踪又有meanshift,肯定很慢 
//pBT->pResolver->Process(&(pBT->BlobPredict),pImg, pImgFG)[0]; 
pB[0] = pBT->BlobPredict;//pBT->pResolver->Process(&(pBT->BlobPredict),pImg, pImgFG)[0]; 

}/* tracking in collision */ 
/这里还有一种情况是 no contour blob对应的blob,此时pBT->pBlobHyp->GetBlobNum()应该为0 
//所以CvBlob NewCC = pBT->BlobPredict;就是预测值 
/* one blob several CC */ 
CvBlob* pBBest = NULL; 
int CvBlobDetectorCC::DetectNewBlob(IplImage* /*pImg*/, IplImage* pFGMask, CvBlobSeq* pNewBlobList, CvBlobSeq* pOldBlobList) 
/这个函数首先通过轮廓获得contour blob 
//然后筛掉与其关联的已经存在的blob,该blob应该是通过预测,跟踪后的blob 
//然后将剩下的blob与存在的track进行关联操作,如果符合关联条件则计数加1, 
//这里的track类似于预备blob的历史轨迹表,他们可能成为新blob的,需要符合计数达到SEQ_SIZE 
//轨迹符合线形拟合 
//如果多个contour blob对应一个track则复制这个contour 
//如果多个track对应一个contour blob,则通过计数和线形拟合进行最后判断 
/* cvDetectNewBlobs return 1 and fill blob pNewBlob by blob parameters if new blob is detected */ 
/SEQ_SIZE应该是控制是否符合新blob条件的计数上限, 
//比如一个contour blob在一个track里连续SEQ_SIZE次符合新blob条件,则判断是否该contour blob是一个新的blob, 
//符合新blob条件有三个条件: 
//1,物体高宽分别大于门限 
//2,前后帧质心X,Y方向距离与以前blob列表中的所有blob分别都大于两个物体宽度和的平均高度和平均 
//3,对连续SEQ_SIZE个物体的质心进行最小二乘法的线性拟合看是否符合一定运动轨迹规律 
//前面的blob都是coutour blob,通过找轮廓方法获得的blob, 
//coutour blob通过筛选确定是否为新物体 
//m_TrackSeq用于存放可能为新blob的contour blob,每一个DefSeq 
//都保存了符合条件contour blob的历史轨迹 当track计数达到了SEQ_SIZE的时候 
//就与已有的 blob进行比对,看是否与已有的blob关联,关联条件主要是看两个blob的质心X,Y方向的距离是否 
//大于两个blob宽度和的平均高度和平均,个人 认为这里多余了,因为前面已经检测过了 
//还有就是判断物体是否在边界附近,如果在边界附近也可以不用处理跟踪了 
//当所有的都符合,则用最小二乘法来最终确定track中SEQ_SIZE个历史blob的轨迹是否合理 
{/* shift each track */ 
int j; 
for(j=0;j<m_TrackNum;++j) 

int i; 
DefSeq* pTrack = m_TrackSeq+j; 
for(i=SEQ_SIZE-1;i>0;--i)pTrack->pBlobs[i]=pTrack->pBlobs[i-1]; 
pTrack->pBlobs[0] = NULL; 
if(pTrack->size == SEQ_SIZE) 
pTrack->size--; 

}/* shift each track */ 
else if((m_TrackNum+NewTrackNum)<SEQ_NUM) 
{ /* duplicate existed track */ 
//这里表示前面已有contour blob符合要求,这样 
//两个contour blob分开成两个track,类似一个物体分离出两个物体情况, 
m_TrackSeq[m_TrackNum+NewTrackNum] = pTrack[0];//pTrack[0]前面已经pTrack->size++;这里直接赋值过去就可以了 
//如果contour blob没有和任何的track关联则生成新的track 
if(AsignedTrack==0 && (m_TrackNum+NewTrackNum)<SEQ_NUM ) 
{/* init new track */ 
//这里采用了最小二乘法进行线性拟合 
//y=a*x+b,这里x是从0~N-1的数值,y分别表示运动轨迹的坐标值x,y,将他们分别进行线性拟合 
//根据最小二乘法的线性拟合公式 
//a=((x*y)\'-x\'*y\')/((x*x)\'-x\'*x\'); 
//b=y\'-a*x\'; 
//这里\'是表示平均的意思 
//首先有 1^2 + 2^2 + …… + n^2 = n*(n+1)*(2n+1)/6 
//有 1 + 2 + …… + n = n*(n+1)/2; 
//考虑x,是从0开始则, 
//0 + 1^2 + 2^2 + …… + (n-1)^2=n*(n+1)*(2n+1)/6 -n*n=n*(n-1)*(2n-1)/6; 
//0 + 1 + 2 + …… + n-1 = n*(n-1)/2; 
//公式推导过程 
// a = (jsum/N-(N-1)/2*sum/N)/((N*(N+1)*(2*N+1)/6 -N*N)/N-(N-1)/2*(N-1)/2) 
// = (jsum-(N-1)/2*sum)/((N-1)*(2N-1)/6-(N-1)*(N-1)/4)/N 
// = 6*(2*jsum-(N-1)*sum)/((N-1)*(N+1)*N) 
// = 6*(2*jsum-(N-1)*sum)/((N*N-1)*N) 
// b = sum/N-a*(N-1)/2; 
// = sum/N-3*(2*jsum-(N-1)*sum)/(N*(N+1)); 
// = ((N+1)*sum-6*jsum-3*(N-1)*sum)/(N*(N+1)); 
// = ((6*N-3)*sum-6*jsum)/(N*(N+1)); 
//与程序公式符合 
a[0] = 6*((1-N)*sum[0]+2*jsum[0])/(N*(N*N-1)); 
b[0] = -2*((1-2*N)*sum[0]+3*jsum[0])/(N*(N+1)); 
a[1] = 6*((1-N)*sum[1]+2*jsum[1])/(N*(N*N-1)); 
b[1] = -2*((1-2*N)*sum[1]+3*jsum[1])/(N*(N+1)); 
for(j=0;j<N;++j) 

Error += 
pow(a[0]*j+b[0]-pBL[j]->x,2)+ 
pow(a[1]*j+b[1]-pBL[j]->y,2); 

//a[0],a[1]是斜率,如果斜率过大,表示速度快,这里过滤了快速运动 
// 
Error = sqrt(Error/N); 
if( Error > S.width*0.01 || 
fabs(a[0])>S.width*0.1 || 
fabs(a[1])>S.height*0.1) 

Good = 0; 
}