运动物体跟踪与计数
Opencv有个算法:运动模板.理论可以参考<<学习opencv>>红色这本书,例程参考<<opencv基础>>绿色书上的例程.
一些经验:
1.前景检测:背景差分法,在例程上加入膨胀操作,可以将一些区域连接起来,另外要根据前景背景调整二值化的参数阈值,不然有的前景会检测不到.
2.计数原理:检测穿过某一区域的物体个数,某一团块中心进入前将该块保存到一个数组中,用于后续查找,没穿过前不断更新其特征,穿过后从表中查找是否有改团块,有则计数加1(计数过程中会有个别丢失,可以将帧数加入作为匹配条件,我的代码没用到帧数,有时间在搞一下)
3.方向判断:运动方向可借助运动模板中的运动历史图像计算.cvUpdateMotionHistory();利用角度可以知道运动方向
4.项目开发时,想用opencv中的blobTrack来做,但发现CPU使用太高
//======================MotionDetect.h================================================================================
#ifndef _MOTION_DETECT_H
#define _MOTION_DETECT_H
#include <opencv/cv.h>
#include <opencv/highgui.h>
#include <opencv/cvaux.h>
struct MyBlobTrack
{
int m_nFrame; //帧数;
int m_nCentreX; //中心x;
int m_nCentreY; //中心y;
};
class CMotionDetect
{
public:
CMotionDetect();
~CMotionDetect();
void InitMemory(CvSize size);
void DrawContours(IplImage* pGrayImg,IplImage* pShowImg);
void Process(IplImage* pSrc,IplImage* pDst);
void DrawText(IplImage* pImg,char* text);
void DrawCount(IplImage* pImg,int count);
void AddBlobTrack(MyBlobTrack blob);
bool FindBlobTrack(MyBlobTrack blob);
void CheckBlobTrack(CvSize size,MyBlobTrack blob);
private:
MyBlobTrack m_blobTrack[10]; //记录跟踪的团块;
int m_blobIndex;
IplImage *m_pMHI; // MHI: motion history image
IplImage *m_pOrient; // orientation
IplImage *m_pMask; // valid orientation mask
IplImage *m_pSegmask; // motion segmentation map
IplImage** m_ppImgBuf;
CvMemStorage* m_pMemStorage;
CvSeq* m_pSeq;
int m_nLastFrame;
int m_nCount;
};
#endif
//======================MotionDetect.cpp================================================================================
#include "stdafx.h"
#include "MotionDetect.h"
// various tracking parameters (in seconds)
const double MHI_DURATION = 0.5;
const double MAX_TIME_DELTA = 0.5;
const double MIN_TIME_DELTA = 0.05;
const int CONTOUR_MAX_AERA = 18;
CMotionDetect::CMotionDetect()
{
m_pMHI = NULL;
m_pOrient = NULL;
m_pMask = NULL;
m_pSegmask = NULL;
m_ppImgBuf = NULL;
m_pMemStorage = NULL;
m_pSeq = NULL;
m_nLastFrame = 0;
m_nCount = 0;
m_blobIndex = 0;
}
CMotionDetect::~CMotionDetect()
{
cvReleaseImage( &m_pMHI );
m_pMHI = NULL;
cvReleaseImage( &m_pOrient );
m_pOrient = NULL;
cvReleaseImage( &m_pSegmask );
m_pSegmask = NULL;
cvReleaseImage( &m_pMask );
m_pMask = NULL;
for(int i = 0; i < 2; i++ )
{
cvReleaseImage( &m_ppImgBuf[i] );
m_ppImgBuf[i] = NULL;
}
}
void CMotionDetect::InitMemory(CvSize size)
{
if( m_ppImgBuf == 0 )
{
m_ppImgBuf = (IplImage**)malloc(2*sizeof(m_ppImgBuf[0]));
memset( m_ppImgBuf, 0, 2*sizeof(m_ppImgBuf[0]));
}
for(int i = 0; i < 2; i++ )
{
cvReleaseImage( &m_ppImgBuf[i] );
m_ppImgBuf[i] = cvCreateImage( size, IPL_DEPTH_8U, 1 );
cvZero( m_ppImgBuf[i] );
}
cvReleaseImage( &m_pMHI );
cvReleaseImage( &m_pOrient );
cvReleaseImage( &m_pSegmask );
cvReleaseImage( &m_pMask );
m_pMHI = cvCreateImage( size, IPL_DEPTH_32F, 1 );
cvZero( m_pMHI ); // clear MHI at the beginning
m_pOrient = cvCreateImage( size, IPL_DEPTH_32F, 1 );
m_pSegmask = cvCreateImage( size, IPL_DEPTH_32F, 1 );
m_pMask = cvCreateImage( size, IPL_DEPTH_8U, 1 );
}
void CMotionDetect::DrawContours(IplImage* pGrayImg,IplImage* pShowImg)
{
// 下面的程序段用来找到轮廓;
// Create dynamic structure and sequence.
CvMemStorage *stor;
CvSeq *cont;
stor = cvCreateMemStorage(0);
cont = cvCreateSeq(CV_SEQ_ELTYPE_POINT, sizeof(CvSeq), sizeof(CvPoint) , stor);
// 找到所有轮廓;
cvFindContours( pGrayImg, stor, &cont, sizeof(CvContour),
CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0));
/*
for(;cont;cont = cont->h_next)
{
// Number point must be more than or equal to 6 (for cvFitEllipse_32f).
if( cont->total < 6 )
continue;
// Draw current contour.
cvDrawContours(pShowImg,cont,CV_RGB(255,0,0),CV_RGB(255,0,0),0,1, 8, cvPoint(0,0));
} // end of for-loop: "cont"
*/
// 直接使用CONTOUR中的矩形来画轮廓
for(;cont;cont = cont->h_next)
{
CvRect r = ((CvContour*)cont)->rect;
if(r.height * r.width > CONTOUR_MAX_AERA && r.height > 5 && r.width>5) // 面积小的方形抛弃掉
{
cvRectangle( pShowImg, cvPoint(r.x,r.y),
cvPoint(r.x + r.width, r.y + r.height),
CV_RGB(255,0,0), 1, CV_AA,0);
}
}
// free memory
cvReleaseMemStorage(&stor);
}
void CMotionDetect::DrawText(IplImage* pImg,char* text)
{
CvFont font;//初始化字体格式;
cvInitFont(&font, CV_FONT_HERSHEY_SIMPLEX, 0.5, 0.5, 0, 2, 8);
cvPutText( pImg, text, cvPoint(10,20), &font, cvScalar(0,0,255));
}
void CMotionDetect::DrawCount(IplImage* pImg,int count)
{
cvLine(pImg,cvPoint(pImg->width/2,0),cvPoint(pImg->width/2,pImg->height),cvScalar(255,0,0));;
char data[64];
memset(data,0,64);
sprintf(data,"Count:%d",count);
DrawText(pImg,data);
}
void CMotionDetect::Process(IplImage* pSrc,IplImage* pDst)
{
int idx1, idx2;
IplImage* pSilh;
CvRect comp_rect;
double count;
double angle;
CvPoint center;
double magnitude;
CvScalar color;
double timestamp = clock()/100.; // get current time in seconds
CvSize size = cvSize(pSrc->width,pSrc->height); // get current frame size
if( !m_pMHI || m_pMHI->width != size.width || m_pMHI->height != size.height )
{
InitMemory(size);
}
cvCvtColor( pSrc, m_ppImgBuf[m_nLastFrame], CV_BGR2GRAY ); // convert frame to grayscale
idx1 = m_nLastFrame;
idx2 = (m_nLastFrame+1)%2;
m_nLastFrame = idx2;
// 做帧差;
pSilh = m_ppImgBuf[idx2];
cvAbsDiff( m_ppImgBuf[idx1], m_ppImgBuf[idx2], pSilh ); // get difference between frames
// 对差图像做二值化;
cvThreshold( pSilh, pSilh, 20, 255, CV_THRESH_BINARY ); // and threshold it
// 向下采样,去掉噪声;
IplImage* pyr = cvCreateImage( cvSize((size.width & -2)/2, (size.height & -2)/2), 8, 1 );
cvPyrDown( pDst, pyr, 7 );
cvDilate( pyr, pyr, 0, 1 ); // 做膨胀操作,消除目标的不连续空洞;
cvPyrUp( pyr, pDst, 7 );
cvReleaseImage( &pyr );
cvUpdateMotionHistory( pSilh, m_pMHI, timestamp, MHI_DURATION ); // update MHI
//cvCvtScale( m_pMHI, pDst, 255./MHI_DURATION,(MHI_DURATION - timestamp)*255./MHI_DURATION );
//cvCvtScale( m_pMHI, pDst, 255./MHI_DURATION, 0 );
cvCvtScale( m_pMHI, m_pMask, 255./MHI_DURATION,(MHI_DURATION - timestamp)*255./MHI_DURATION );
// 中值滤波,消除小的噪声;
cvSmooth( pDst, pDst, CV_MEDIAN, 3, 0, 0, 0 );
// 计算运动的梯度方向以及正确的方向掩模mask;
// Filter size = 3
cvCalcMotionGradient( m_pMHI, m_pMask, m_pOrient, MAX_TIME_DELTA, MIN_TIME_DELTA, 3 );
if( !m_pMemStorage )
m_pMemStorage = cvCreateMemStorage(0);
else
cvClearMemStorage(m_pMemStorage);
// 运动分割: 获得运动部件的连续序列;
// segmask is marked motion components map. It is not used further
m_pSeq = cvSegmentMotion( m_pMHI, m_pSegmask, m_pMemStorage, timestamp, MAX_TIME_DELTA );
// iterate through the motion components,
// One more iteration (i == -1) corresponds to the whole image (global motion)
for( int i = 0; i < m_pSeq->total; i++ )
{
if( i < 0 )
{ // case of the whole image,对整幅图像做操作
comp_rect = cvRect( 0, 0, size.width, size.height );
color = CV_RGB(255,255,255); // white color
magnitude = 100; // 画线长度以及圆半径的大小控制
}
else
{ // i-th motion component
comp_rect = ((CvConnectedComp*)cvGetSeqElem( m_pSeq, i ))->rect;
// 去掉小的部分
if( comp_rect.width + comp_rect.height <110 )
continue;
color = CV_RGB(255,0,0); // red color
magnitude = 30;
//if(seq->total > 0) MessageBox(NULL,"Motion Detected",NULL,0);
}
// select component ROI
cvSetImageROI( pSilh, comp_rect );
cvSetImageROI( m_pMHI, comp_rect );
cvSetImageROI( m_pOrient, comp_rect );
cvSetImageROI( m_pMask, comp_rect );
// 在选择的区域内, 计算运动方向
angle = cvCalcGlobalOrientation( m_pOrient, m_pMask, m_pMHI, timestamp,MHI_DURATION);
// 在轮廓内计算点数
// Norm(L1) = sum of total pixel values
count = cvNorm( pSilh, 0, CV_L1, 0 );
// The function cvResetImageROI releases image ROI
cvResetImageROI( m_pMHI );
cvResetImageROI( m_pOrient );
cvResetImageROI( m_pMask );
cvResetImageROI( pSilh );
// check for the case of little motion
if( count < comp_rect.width*comp_rect.height * 0.05 ) // five percent of pixel
continue;
// draw a clock with arrow indicating the direction
center = cvPoint( (comp_rect.x + comp_rect.width/2),
(comp_rect.y + comp_rect.height/2) );
//计数
if((angle >0 && angle <90) || (angle > 270 && angle < 360))
{
/*
if(center.x > size.width/2 -3 && center.x < size.width/2 +3)
{
m_nCount++;
}
*/
MyBlobTrack blobTrack;
blobTrack.m_nCentreX = comp_rect.x + comp_rect.width/2;
blobTrack.m_nCentreY = comp_rect.y + comp_rect.height/2;
CheckBlobTrack(size,blobTrack);
}
cvCircle( pSrc, center, cvRound(magnitude*1.2), color, 1, CV_AA, 0 );
angle = 360.0 - angle; // adjust for images with top-left origin
cvLine( pSrc, center, cvPoint( cvRound( center.x + magnitude*cos(angle*CV_PI/180)),
cvRound( center.y - magnitude*sin(angle*CV_PI/180))), color, 3, CV_AA, 0 );
}
DrawCount(pSrc,m_nCount);
}
void CMotionDetect::CheckBlobTrack(CvSize size,MyBlobTrack blob)
{
if(blob.m_nCentreX > size.width/2 -20 && blob.m_nCentreX < size.width/2)
{
AddBlobTrack(blob);
}
else if(blob.m_nCentreX > size.width/2 && blob.m_nCentreX < size.width/2 + 20)
{
if(FindBlobTrack(blob))
{
m_nCount++;
}
}
}
void CMotionDetect::AddBlobTrack(MyBlobTrack blob)
{
bool bFind = false;
for(int i=0;i<10;i++)
{
if(m_blobTrack[i].m_nCentreX != 0 && abs(m_blobTrack[i].m_nCentreX - blob.m_nCentreX)< 15 && abs(m_blobTrack[i].m_nCentreY - blob.m_nCentreY)< 15 )
{
m_blobTrack[i].m_nCentreX = blob.m_nCentreX;
m_blobTrack[i].m_nCentreY = blob.m_nCentreY;
m_blobTrack[i].m_nFrame = blob.m_nFrame;
bFind = true;
break;
}
}
if(bFind != true)//表示没有找到车辆;
{
m_blobTrack[m_blobIndex].m_nCentreX = blob.m_nCentreX;
m_blobTrack[m_blobIndex].m_nCentreY = blob.m_nCentreY;
m_blobTrack[m_blobIndex].m_nFrame = blob.m_nFrame;
if(m_blobIndex==9)
{
m_blobIndex=0;
}
else
{
m_blobIndex++;
}
}
}
bool CMotionDetect::FindBlobTrack(MyBlobTrack blob)
{
for(int i=0;i<10;i++)
{
if(m_blobTrack[i].m_nCentreX != 0 && abs(m_blobTrack[i].m_nCentreX - blob.m_nCentreX)< 20 && abs(m_blobTrack[i].m_nCentreY - blob.m_nCentreY)< 30 )
{
//移除此块;
m_blobTrack[i].m_nCentreX = 0;
m_blobTrack[i].m_nCentreY = 0;
m_blobTrack[i].m_nFrame = 0;
return true;
}
}
return false;
}