mirror of https://github.com/opencv/opencv.git
Open Source Computer Vision Library
https://opencv.org/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1064 lines
36 KiB
1064 lines
36 KiB
/*M/////////////////////////////////////////////////////////////////////////////////////// |
|
// |
|
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. |
|
// |
|
// By downloading, copying, installing or using the software you agree to this license. |
|
// If you do not agree to this license, do not download, install, |
|
// copy or use the software. |
|
// |
|
// |
|
// Intel License Agreement |
|
// |
|
// Copyright (C) 2000, Intel Corporation, all rights reserved. |
|
// Third party copyrights are property of their respective owners. |
|
// |
|
// Redistribution and use in source and binary forms, with or without modification, |
|
// are permitted provided that the following conditions are met: |
|
// |
|
// * Redistribution's of source code must retain the above copyright notice, |
|
// this list of conditions and the following disclaimer. |
|
// |
|
// * Redistribution's in binary form must reproduce the above copyright notice, |
|
// this list of conditions and the following disclaimer in the documentation |
|
// and/or other materials provided with the distribution. |
|
// |
|
// * The name of Intel Corporation may not be used to endorse or promote products |
|
// derived from this software without specific prior written permission. |
|
// |
|
// This software is provided by the copyright holders and contributors "as is" and |
|
// any express or implied warranties, including, but not limited to, the implied |
|
// warranties of merchantability and fitness for a particular purpose are disclaimed. |
|
// In no event shall the Intel Corporation or contributors be liable for any direct, |
|
// indirect, incidental, special, exemplary, or consequential damages |
|
// (including, but not limited to, procurement of substitute goods or services; |
|
// loss of use, data, or profits; or business interruption) however caused |
|
// and on any theory of liability, whether in contract, strict liability, |
|
// or tort (including negligence or otherwise) arising in any way out of |
|
// the use of this software, even if advised of the possibility of such damage. |
|
// |
|
//M*/ |
|
|
|
/* |
|
This file implements the virtual interface defined as "CvBlobDetector". |
|
This implementation based on a simple algorithm: |
|
A new blob is detected when several successive frames contains connected components |
|
which have uniform motion not at an unreasonably high speed. |
|
Separation from border and already tracked blobs are also considered. |
|
|
|
For an entrypoint into the literature see: |
|
|
|
Appearance Models for Occlusion Handling |
|
Andrew Senior &t al, 8p 2001 |
|
http://www.research.ibm.com/peoplevision/PETS2001.pdf |
|
|
|
*/ |
|
|
|
//#define USE_OBJECT_DETECTOR |
|
|
|
#include "precomp.hpp" |
|
|
|
static int CompareContour(const void* a, const void* b, void* ) |
|
{ |
|
float dx,dy; |
|
float h,w,ht,wt; |
|
CvPoint2D32f pa,pb; |
|
CvRect ra,rb; |
|
CvSeq* pCA = *(CvSeq**)a; |
|
CvSeq* pCB = *(CvSeq**)b; |
|
ra = ((CvContour*)pCA)->rect; |
|
rb = ((CvContour*)pCB)->rect; |
|
pa.x = ra.x + ra.width*0.5f; |
|
pa.y = ra.y + ra.height*0.5f; |
|
pb.x = rb.x + rb.width*0.5f; |
|
pb.y = rb.y + rb.height*0.5f; |
|
w = (ra.width+rb.width)*0.5f; |
|
h = (ra.height+rb.height)*0.5f; |
|
|
|
dx = (float)(fabs(pa.x - pb.x)-w); |
|
dy = (float)(fabs(pa.y - pb.y)-h); |
|
|
|
//wt = MAX(ra.width,rb.width)*0.1f; |
|
wt = 0; |
|
ht = MAX(ra.height,rb.height)*0.3f; |
|
return (dx < wt && dy < ht); |
|
} |
|
|
|
static void cvFindBlobsByCCClasters(IplImage* pFG, CvBlobSeq* pBlobs, CvMemStorage* storage) |
|
{ /* Create contours: */ |
|
IplImage* pIB = NULL; |
|
CvSeq* cnt = NULL; |
|
CvSeq* cnt_list = cvCreateSeq(0,sizeof(CvSeq),sizeof(CvSeq*), storage ); |
|
CvSeq* clasters = NULL; |
|
int claster_cur, claster_num; |
|
|
|
pIB = cvCloneImage(pFG); |
|
cvThreshold(pIB,pIB,128,255,CV_THRESH_BINARY); |
|
cvFindContours(pIB,storage, &cnt, sizeof(CvContour), CV_RETR_EXTERNAL); |
|
cvReleaseImage(&pIB); |
|
|
|
/* Create cnt_list. */ |
|
/* Process each contour: */ |
|
for(; cnt; cnt=cnt->h_next) |
|
{ |
|
cvSeqPush( cnt_list, &cnt); |
|
} |
|
|
|
claster_num = cvSeqPartition( cnt_list, storage, &clasters, CompareContour, NULL ); |
|
|
|
for(claster_cur=0; claster_cur<claster_num; ++claster_cur) |
|
{ |
|
int cnt_cur; |
|
CvBlob NewBlob; |
|
double M00,X,Y,XX,YY; /* image moments */ |
|
CvMoments m; |
|
CvRect rect_res = cvRect(-1,-1,-1,-1); |
|
CvMat mat; |
|
|
|
for(cnt_cur=0; cnt_cur<clasters->total; ++cnt_cur) |
|
{ |
|
CvRect rect; |
|
CvSeq* cont; |
|
int k = *(int*)cvGetSeqElem( clasters, cnt_cur ); |
|
if(k!=claster_cur) continue; |
|
cont = *(CvSeq**)cvGetSeqElem( cnt_list, cnt_cur ); |
|
rect = ((CvContour*)cont)->rect; |
|
|
|
if(rect_res.height<0) |
|
{ |
|
rect_res = rect; |
|
} |
|
else |
|
{ /* Unite rects: */ |
|
int x0,x1,y0,y1; |
|
x0 = MIN(rect_res.x,rect.x); |
|
y0 = MIN(rect_res.y,rect.y); |
|
x1 = MAX(rect_res.x+rect_res.width,rect.x+rect.width); |
|
y1 = MAX(rect_res.y+rect_res.height,rect.y+rect.height); |
|
rect_res.x = x0; |
|
rect_res.y = y0; |
|
rect_res.width = x1-x0; |
|
rect_res.height = y1-y0; |
|
} |
|
} |
|
|
|
if(rect_res.height < 1 || rect_res.width < 1) |
|
{ |
|
X = 0; |
|
Y = 0; |
|
XX = 0; |
|
YY = 0; |
|
} |
|
else |
|
{ |
|
cvMoments( cvGetSubRect(pFG,&mat,rect_res), &m, 0 ); |
|
M00 = cvGetSpatialMoment( &m, 0, 0 ); |
|
if(M00 <= 0 ) continue; |
|
X = cvGetSpatialMoment( &m, 1, 0 )/M00; |
|
Y = cvGetSpatialMoment( &m, 0, 1 )/M00; |
|
XX = (cvGetSpatialMoment( &m, 2, 0 )/M00) - X*X; |
|
YY = (cvGetSpatialMoment( &m, 0, 2 )/M00) - Y*Y; |
|
} |
|
NewBlob = cvBlob(rect_res.x+(float)X,rect_res.y+(float)Y,(float)(4*sqrt(XX)),(float)(4*sqrt(YY))); |
|
pBlobs->AddBlob(&NewBlob); |
|
|
|
} /* Next cluster. */ |
|
|
|
#if 0 |
|
{ // Debug info: |
|
IplImage* pI = cvCreateImage(cvSize(pFG->width,pFG->height),IPL_DEPTH_8U,3); |
|
cvZero(pI); |
|
for(claster_cur=0; claster_cur<claster_num; ++claster_cur) |
|
{ |
|
int cnt_cur; |
|
CvScalar color = CV_RGB(rand()%256,rand()%256,rand()%256); |
|
|
|
for(cnt_cur=0; cnt_cur<clasters->total; ++cnt_cur) |
|
{ |
|
CvSeq* cnt; |
|
int k = *(int*)cvGetSeqElem( clasters, cnt_cur ); |
|
if(k!=claster_cur) continue; |
|
cnt = *(CvSeq**)cvGetSeqElem( cnt_list, cnt_cur ); |
|
cvDrawContours( pI, cnt, color, color, 0, 1, 8); |
|
} |
|
|
|
CvBlob* pB = pBlobs->GetBlob(claster_cur); |
|
int x = cvRound(CV_BLOB_RX(pB)), y = cvRound(CV_BLOB_RY(pB)); |
|
cvEllipse( pI, |
|
cvPointFrom32f(CV_BLOB_CENTER(pB)), |
|
cvSize(MAX(1,x), MAX(1,y)), |
|
0, 0, 360, |
|
color, 1 ); |
|
} |
|
|
|
cvNamedWindow( "Clusters", 0); |
|
cvShowImage( "Clusters",pI ); |
|
|
|
cvReleaseImage(&pI); |
|
|
|
} /* Debug info. */ |
|
#endif |
|
|
|
} /* cvFindBlobsByCCClasters */ |
|
|
|
/* Simple blob detector. */ |
|
/* Number of successive frame to analyse: */ |
|
#define EBD_FRAME_NUM 5 |
|
class CvBlobDetectorSimple:public CvBlobDetector |
|
{ |
|
public: |
|
CvBlobDetectorSimple(); |
|
~CvBlobDetectorSimple(); |
|
int DetectNewBlob(IplImage* pImg, IplImage* pFGMask, CvBlobSeq* pNewBlobList, CvBlobSeq* pOldBlobList); |
|
void Release(){delete this;}; |
|
|
|
protected: |
|
IplImage* m_pMaskBlobNew; |
|
IplImage* m_pMaskBlobExist; |
|
/* Lists of connected components detected on previous frames: */ |
|
CvBlobSeq* m_pBlobLists[EBD_FRAME_NUM]; |
|
}; |
|
|
|
/* Blob detector creator (sole interface function for this file) */ |
|
CvBlobDetector* cvCreateBlobDetectorSimple(){return new CvBlobDetectorSimple;}; |
|
|
|
/* Constructor of BlobDetector: */ |
|
CvBlobDetectorSimple::CvBlobDetectorSimple() |
|
{ |
|
int i = 0; |
|
m_pMaskBlobNew = NULL; |
|
m_pMaskBlobExist = NULL; |
|
for(i=0;i<EBD_FRAME_NUM;++i)m_pBlobLists[i] = NULL; |
|
|
|
SetModuleName("Simple"); |
|
} |
|
|
|
/* Destructor of BlobDetector: */ |
|
CvBlobDetectorSimple::~CvBlobDetectorSimple() |
|
{ |
|
int i; |
|
if(m_pMaskBlobExist) cvReleaseImage(&m_pMaskBlobExist); |
|
if(m_pMaskBlobNew) cvReleaseImage(&m_pMaskBlobNew); |
|
for(i=0; i<EBD_FRAME_NUM; ++i) delete m_pBlobLists[i]; |
|
} /* cvReleaseBlobDetector */ |
|
|
|
/* cvDetectNewBlobs |
|
* return 1 and fill blob pNewBlob by blob parameters |
|
* if new blob is detected: |
|
*/ |
|
int CvBlobDetectorSimple::DetectNewBlob(IplImage* /*pImg*/, IplImage* pFGMask, CvBlobSeq* pNewBlobList, CvBlobSeq* pOldBlobList) |
|
{ |
|
int result = 0; |
|
CvSize S = cvSize(pFGMask->width,pFGMask->height); |
|
if(m_pMaskBlobNew == NULL ) m_pMaskBlobNew = cvCreateImage(S,IPL_DEPTH_8U,1); |
|
if(m_pMaskBlobExist == NULL ) m_pMaskBlobExist = cvCreateImage(S,IPL_DEPTH_8U,1); |
|
|
|
/* Shift blob list: */ |
|
{ |
|
int i; |
|
if(m_pBlobLists[0]) delete m_pBlobLists[0]; |
|
for(i=1;i<EBD_FRAME_NUM;++i)m_pBlobLists[i-1]=m_pBlobLists[i]; |
|
m_pBlobLists[EBD_FRAME_NUM-1] = new CvBlobSeq; |
|
} /* Shift blob list. */ |
|
|
|
/* Create exist blob mask: */ |
|
cvCopy(pFGMask, m_pMaskBlobNew); |
|
|
|
/* Create contours and add new blobs to blob list: */ |
|
{ /* Create blobs: */ |
|
CvBlobSeq Blobs; |
|
CvMemStorage* storage = cvCreateMemStorage(); |
|
|
|
#if 1 |
|
{ /* Glue contours: */ |
|
cvFindBlobsByCCClasters(m_pMaskBlobNew, &Blobs, storage ); |
|
} /* Glue contours. */ |
|
#else |
|
{ /**/ |
|
IplImage* pIB = cvCloneImage(m_pMaskBlobNew); |
|
CvSeq* cnts = NULL; |
|
CvSeq* cnt = NULL; |
|
cvThreshold(pIB,pIB,128,255,CV_THRESH_BINARY); |
|
cvFindContours(pIB,storage, &cnts, sizeof(CvContour), CV_RETR_EXTERNAL); |
|
|
|
/* Process each contour: */ |
|
for(cnt = cnts; cnt; cnt=cnt->h_next) |
|
{ |
|
CvBlob NewBlob; |
|
|
|
/* Image moments: */ |
|
double M00,X,Y,XX,YY; |
|
CvMoments m; |
|
CvRect r = ((CvContour*)cnt)->rect; |
|
CvMat mat; |
|
|
|
if(r.height < S.height*0.02 || r.width < S.width*0.02) continue; |
|
|
|
cvMoments( cvGetSubRect(m_pMaskBlobNew,&mat,r), &m, 0 ); |
|
M00 = cvGetSpatialMoment( &m, 0, 0 ); |
|
|
|
if(M00 <= 0 ) continue; |
|
|
|
X = cvGetSpatialMoment( &m, 1, 0 )/M00; |
|
Y = cvGetSpatialMoment( &m, 0, 1 )/M00; |
|
|
|
XX = (cvGetSpatialMoment( &m, 2, 0 )/M00) - X*X; |
|
YY = (cvGetSpatialMoment( &m, 0, 2 )/M00) - Y*Y; |
|
|
|
NewBlob = cvBlob(r.x+(float)X,r.y+(float)Y,(float)(4*sqrt(XX)),(float)(4*sqrt(YY))); |
|
|
|
Blobs.AddBlob(&NewBlob); |
|
|
|
} /* Next contour. */ |
|
|
|
cvReleaseImage(&pIB); |
|
|
|
} /* One contour - one blob. */ |
|
#endif |
|
|
|
{ /* Delete small and intersected blobs: */ |
|
int i; |
|
for(i=Blobs.GetBlobNum(); i>0; i--) |
|
{ |
|
CvBlob* pB = Blobs.GetBlob(i-1); |
|
|
|
if(pB->h < S.height*0.02 || pB->w < S.width*0.02) |
|
{ |
|
Blobs.DelBlob(i-1); |
|
continue; |
|
} |
|
if(pOldBlobList) |
|
{ |
|
int j; |
|
for(j=pOldBlobList->GetBlobNum(); j>0; j--) |
|
{ |
|
CvBlob* pBOld = pOldBlobList->GetBlob(j-1); |
|
if((fabs(pBOld->x-pB->x) < (CV_BLOB_RX(pBOld)+CV_BLOB_RX(pB))) && |
|
(fabs(pBOld->y-pB->y) < (CV_BLOB_RY(pBOld)+CV_BLOB_RY(pB)))) |
|
{ /* Intersection is present, so delete blob from list: */ |
|
Blobs.DelBlob(i-1); |
|
break; |
|
} |
|
} /* Check next old blob. */ |
|
} /* if pOldBlobList */ |
|
} /* Check next blob. */ |
|
} /* Delete small and intersected blobs. */ |
|
|
|
{ /* Bubble-sort blobs by size: */ |
|
int N = Blobs.GetBlobNum(); |
|
int i,j; |
|
for(i=1; i<N; ++i) |
|
{ |
|
for(j=i; j>0; --j) |
|
{ |
|
CvBlob temp; |
|
float AreaP, AreaN; |
|
CvBlob* pP = Blobs.GetBlob(j-1); |
|
CvBlob* pN = Blobs.GetBlob(j); |
|
AreaP = CV_BLOB_WX(pP)*CV_BLOB_WY(pP); |
|
AreaN = CV_BLOB_WX(pN)*CV_BLOB_WY(pN); |
|
if(AreaN < AreaP)break; |
|
temp = pN[0]; |
|
pN[0] = pP[0]; |
|
pP[0] = temp; |
|
} |
|
} |
|
|
|
/* Copy only first 10 blobs: */ |
|
for(i=0; i<MIN(N,10); ++i) |
|
{ |
|
m_pBlobLists[EBD_FRAME_NUM-1]->AddBlob(Blobs.GetBlob(i)); |
|
} |
|
|
|
} /* Sort blobs by size. */ |
|
|
|
cvReleaseMemStorage(&storage); |
|
|
|
} /* Create blobs. */ |
|
|
|
/* Analyze blob list to find best blob trajectory: */ |
|
{ |
|
int Count = 0; |
|
int pBLIndex[EBD_FRAME_NUM]; |
|
int pBL_BEST[EBD_FRAME_NUM]; |
|
int i; |
|
int finish = 0; |
|
double BestError = -1; |
|
int Good = 1; |
|
|
|
for(i=0; i<EBD_FRAME_NUM; ++i) |
|
{ |
|
pBLIndex[i] = 0; |
|
pBL_BEST[i] = 0; |
|
} |
|
|
|
/* Check configuration exist: */ |
|
for(i=0; Good && (i<EBD_FRAME_NUM); ++i) |
|
if(m_pBlobLists[i] == NULL || m_pBlobLists[i]->GetBlobNum() == 0) |
|
Good = 0; |
|
|
|
if(Good) |
|
do{ /* For each configuration: */ |
|
CvBlob* pBL[EBD_FRAME_NUM]; |
|
int good = 1; |
|
double Error = 0; |
|
CvBlob* pBNew = m_pBlobLists[EBD_FRAME_NUM-1]->GetBlob(pBLIndex[EBD_FRAME_NUM-1]); |
|
|
|
for(i=0; i<EBD_FRAME_NUM; ++i) pBL[i] = m_pBlobLists[i]->GetBlob(pBLIndex[i]); |
|
|
|
Count++; |
|
|
|
/* Check intersection last blob with existed: */ |
|
if(good && pOldBlobList) |
|
{ /* Check intersection last blob with existed: */ |
|
int k; |
|
for(k=pOldBlobList->GetBlobNum(); k>0; --k) |
|
{ |
|
CvBlob* pBOld = pOldBlobList->GetBlob(k-1); |
|
if((fabs(pBOld->x-pBNew->x) < (CV_BLOB_RX(pBOld)+CV_BLOB_RX(pBNew))) && |
|
(fabs(pBOld->y-pBNew->y) < (CV_BLOB_RY(pBOld)+CV_BLOB_RY(pBNew)))) |
|
good = 0; |
|
} |
|
} /* Check intersection last blob with existed. */ |
|
|
|
/* Check distance to image border: */ |
|
if(good) |
|
{ /* Check distance to image border: */ |
|
CvBlob* pB = pBNew; |
|
float dx = MIN(pB->x,S.width-pB->x)/CV_BLOB_RX(pB); |
|
float dy = MIN(pB->y,S.height-pB->y)/CV_BLOB_RY(pB); |
|
|
|
if(dx < 1.1 || dy < 1.1) good = 0; |
|
} /* Check distance to image border. */ |
|
|
|
/* Check uniform motion: */ |
|
if(good) |
|
{ |
|
int N = EBD_FRAME_NUM; |
|
float sum[2] = {0,0}; |
|
float jsum[2] = {0,0}; |
|
float a[2],b[2]; /* estimated parameters of moving x(t) = a*t+b*/ |
|
|
|
int j; |
|
for(j=0; j<N; ++j) |
|
{ |
|
float x = pBL[j]->x; |
|
float y = pBL[j]->y; |
|
sum[0] += x; |
|
jsum[0] += j*x; |
|
sum[1] += y; |
|
jsum[1] += j*y; |
|
} |
|
|
|
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); |
|
} |
|
|
|
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; |
|
|
|
} /* Check configuration. */ |
|
|
|
|
|
/* New best trajectory: */ |
|
if(good && (BestError == -1 || BestError > Error)) |
|
{ |
|
for(i=0; i<EBD_FRAME_NUM; ++i) |
|
{ |
|
pBL_BEST[i] = pBLIndex[i]; |
|
} |
|
BestError = Error; |
|
} /* New best trajectory. */ |
|
|
|
/* Set next configuration: */ |
|
for(i=0; i<EBD_FRAME_NUM; ++i) |
|
{ |
|
pBLIndex[i]++; |
|
if(pBLIndex[i] != m_pBlobLists[i]->GetBlobNum()) break; |
|
pBLIndex[i]=0; |
|
} /* Next time shift. */ |
|
|
|
if(i==EBD_FRAME_NUM)finish=1; |
|
|
|
} while(!finish); /* Check next time configuration of connected components. */ |
|
|
|
#if 0 |
|
{/**/ |
|
printf("BlobDetector configurations = %d [",Count); |
|
int i; |
|
for(i=0; i<EBD_FRAME_NUM; ++i) |
|
{ |
|
printf("%d,",m_pBlobLists[i]?m_pBlobLists[i]->GetBlobNum():0); |
|
} |
|
printf("]\n"); |
|
|
|
} |
|
#endif |
|
|
|
if(BestError != -1) |
|
{ /* Write new blob to output and delete from blob list: */ |
|
CvBlob* pNewBlob = m_pBlobLists[EBD_FRAME_NUM-1]->GetBlob(pBL_BEST[EBD_FRAME_NUM-1]); |
|
pNewBlobList->AddBlob(pNewBlob); |
|
|
|
for(i=0; i<EBD_FRAME_NUM; ++i) |
|
{ /* Remove blob from each list: */ |
|
m_pBlobLists[i]->DelBlob(pBL_BEST[i]); |
|
} /* Remove blob from each list. */ |
|
|
|
result = 1; |
|
|
|
} /* Write new blob to output and delete from blob list. */ |
|
} /* Analyze blob list to find best blob trajectory. */ |
|
|
|
return result; |
|
|
|
} /* cvDetectNewBlob */ |
|
|
|
|
|
|
|
|
|
/* Simple blob detector2. */ |
|
/* Number of successive frames to analyse: */ |
|
#define SEQ_SIZE_MAX 30 |
|
#define SEQ_NUM 1000 |
|
typedef struct |
|
{ |
|
int size; |
|
CvBlob* pBlobs[SEQ_SIZE_MAX]; |
|
} DefSeq; |
|
|
|
class CvBlobDetectorCC:public CvBlobDetector |
|
{ |
|
public: |
|
CvBlobDetectorCC(); |
|
~CvBlobDetectorCC(); |
|
int DetectNewBlob(IplImage* pImg, IplImage* pFGMask, CvBlobSeq* pNewBlobList, CvBlobSeq* pOldBlobList); |
|
void Release(){delete this;}; |
|
|
|
virtual void ParamUpdate() |
|
{ |
|
if(SEQ_SIZE<1)SEQ_SIZE=1; |
|
if(SEQ_SIZE>SEQ_SIZE_MAX)SEQ_SIZE=SEQ_SIZE_MAX; |
|
|
|
#ifdef USE_OBJECT_DETECTOR |
|
if( m_param_split_detector_file_name ) |
|
{ |
|
m_split_detector = new CvObjectDetector(); |
|
if( !m_split_detector->Load( m_param_split_detector_file_name ) ) |
|
{ |
|
delete m_split_detector; |
|
m_split_detector = 0; |
|
} |
|
else |
|
{ |
|
m_min_window_size = m_split_detector->GetMinWindowSize(); |
|
m_max_border = m_split_detector->GetMaxBorderSize(); |
|
} |
|
} |
|
#endif |
|
} |
|
|
|
private: |
|
/* Lists of connected components detected on previous frames: */ |
|
CvBlobSeq* m_pBlobLists[SEQ_SIZE_MAX]; |
|
DefSeq m_TrackSeq[SEQ_NUM]; |
|
int m_TrackNum; |
|
float m_HMin; |
|
float m_WMin; |
|
float m_MinDistToBorder; |
|
int m_Clastering; |
|
int SEQ_SIZE; |
|
|
|
/* If not 0 then the detector is loaded from the specified file |
|
* and it is applied for splitting blobs which actually correspond |
|
* to groups of objects: |
|
*/ |
|
char* m_param_split_detector_file_name; |
|
float m_param_roi_scale; |
|
int m_param_only_roi; |
|
|
|
CvObjectDetector* m_split_detector; |
|
CvSize m_min_window_size; |
|
int m_max_border; |
|
|
|
CvBlobSeq m_detected_blob_seq; |
|
CvSeq* m_roi_seq; |
|
|
|
CvBlobSeq m_debug_blob_seq; |
|
}; |
|
|
|
/* Blob detector creator (sole interface function for this file): */ |
|
CvBlobDetector* cvCreateBlobDetectorCC(){return new CvBlobDetectorCC;} |
|
|
|
/* Constructor for BlobDetector: */ |
|
CvBlobDetectorCC::CvBlobDetectorCC() : |
|
m_split_detector(0), |
|
m_detected_blob_seq(sizeof(CvDetectedBlob)), |
|
m_roi_seq(0), |
|
m_debug_blob_seq(sizeof(CvDetectedBlob)) |
|
{ |
|
/*CvDrawShape shapes[] = |
|
{ |
|
{ CvDrawShape::RECT, {{255,255,255}} }, |
|
{ CvDrawShape::RECT, {{0,0,255}} }, |
|
{ CvDrawShape::ELLIPSE, {{0,255,0}} } |
|
}; |
|
int num_shapes = sizeof(shapes) / sizeof(shapes[0]);*/ |
|
|
|
int i = 0; |
|
SEQ_SIZE = 10; |
|
AddParam("Latency",&SEQ_SIZE); |
|
for(i=0;i<SEQ_SIZE_MAX;++i)m_pBlobLists[i] = NULL; |
|
for(i=0;i<SEQ_NUM;++i)m_TrackSeq[i].size = 0; |
|
m_TrackNum = 0; |
|
|
|
m_HMin = 0.02f; |
|
m_WMin = 0.01f; |
|
AddParam("HMin",&m_HMin); |
|
AddParam("WMin",&m_WMin); |
|
m_MinDistToBorder = 1.1f; |
|
AddParam("MinDistToBorder",&m_MinDistToBorder); |
|
CommentParam("MinDistToBorder","Minimal allowed distance from blob center to image border in blob sizes"); |
|
|
|
m_Clastering=1; |
|
AddParam("Clastering",&m_Clastering); |
|
CommentParam("Clastering","Minimal allowed distance from blob center to image border in blob sizes"); |
|
|
|
m_param_split_detector_file_name = 0; |
|
#ifdef USE_OBJECT_DETECTOR |
|
AddParam("Detector", &m_param_split_detector_file_name); |
|
CommentParam("Detector", "Detector file name"); |
|
#endif |
|
|
|
m_param_roi_scale = 1.5F; |
|
AddParam("ROIScale", &m_param_roi_scale); |
|
CommentParam("ROIScale", "Determines the size of search window around a blob"); |
|
|
|
m_param_only_roi = 1; |
|
AddParam("OnlyROI", &m_param_only_roi); |
|
CommentParam("OnlyROI", "Shows the whole debug image (0) or only ROIs where the detector was applied (1)"); |
|
|
|
m_min_window_size = cvSize(0,0); |
|
m_max_border = 0; |
|
m_roi_seq = cvCreateSeq( 0, sizeof(*m_roi_seq), sizeof(CvRect), cvCreateMemStorage() ); |
|
|
|
SetModuleName("CC"); |
|
} |
|
|
|
/* Destructor for BlobDetector: */ |
|
CvBlobDetectorCC::~CvBlobDetectorCC() |
|
{ |
|
int i; |
|
for(i=0; i<SEQ_SIZE_MAX; ++i) |
|
{ |
|
if(m_pBlobLists[i]) |
|
delete m_pBlobLists[i]; |
|
} |
|
|
|
if( m_roi_seq ) |
|
{ |
|
cvReleaseMemStorage( &m_roi_seq->storage ); |
|
m_roi_seq = 0; |
|
} |
|
//cvDestroyWindow( "EnteringBlobDetectionDebug" ); |
|
} /* cvReleaseBlobDetector */ |
|
|
|
|
|
/* cvDetectNewBlobs |
|
* Return 1 and fill blob pNewBlob with |
|
* blob parameters if new blob is detected: |
|
*/ |
|
int CvBlobDetectorCC::DetectNewBlob(IplImage* /*pImg*/, IplImage* pFGMask, CvBlobSeq* pNewBlobList, CvBlobSeq* pOldBlobList) |
|
{ |
|
int result = 0; |
|
CvSize S = cvSize(pFGMask->width,pFGMask->height); |
|
|
|
/* Shift blob list: */ |
|
{ |
|
int i; |
|
if(m_pBlobLists[SEQ_SIZE-1]) delete m_pBlobLists[SEQ_SIZE-1]; |
|
|
|
for(i=SEQ_SIZE-1; i>0; --i) m_pBlobLists[i] = m_pBlobLists[i-1]; |
|
|
|
m_pBlobLists[0] = new CvBlobSeq; |
|
|
|
} /* Shift blob list. */ |
|
|
|
/* Create contours and add new blobs to blob list: */ |
|
{ /* Create blobs: */ |
|
CvBlobSeq Blobs; |
|
CvMemStorage* storage = cvCreateMemStorage(); |
|
|
|
if(m_Clastering) |
|
{ /* Glue contours: */ |
|
cvFindBlobsByCCClasters(pFGMask, &Blobs, storage ); |
|
} /* Glue contours. */ |
|
else |
|
{ /**/ |
|
IplImage* pIB = cvCloneImage(pFGMask); |
|
CvSeq* cnts = NULL; |
|
CvSeq* cnt = NULL; |
|
cvThreshold(pIB,pIB,128,255,CV_THRESH_BINARY); |
|
cvFindContours(pIB,storage, &cnts, sizeof(CvContour), CV_RETR_EXTERNAL); |
|
|
|
/* Process each contour: */ |
|
for(cnt = cnts; cnt; cnt=cnt->h_next) |
|
{ |
|
CvBlob NewBlob; |
|
/* Image moments: */ |
|
double M00,X,Y,XX,YY; |
|
CvMoments m; |
|
CvRect r = ((CvContour*)cnt)->rect; |
|
CvMat mat; |
|
if(r.height < S.height*m_HMin || r.width < S.width*m_WMin) continue; |
|
cvMoments( cvGetSubRect(pFGMask,&mat,r), &m, 0 ); |
|
M00 = cvGetSpatialMoment( &m, 0, 0 ); |
|
if(M00 <= 0 ) continue; |
|
X = cvGetSpatialMoment( &m, 1, 0 )/M00; |
|
Y = cvGetSpatialMoment( &m, 0, 1 )/M00; |
|
XX = (cvGetSpatialMoment( &m, 2, 0 )/M00) - X*X; |
|
YY = (cvGetSpatialMoment( &m, 0, 2 )/M00) - Y*Y; |
|
NewBlob = cvBlob(r.x+(float)X,r.y+(float)Y,(float)(4*sqrt(XX)),(float)(4*sqrt(YY))); |
|
Blobs.AddBlob(&NewBlob); |
|
|
|
} /* Next contour. */ |
|
|
|
cvReleaseImage(&pIB); |
|
|
|
} /* One contour - one blob. */ |
|
|
|
{ /* Delete small and intersected blobs: */ |
|
int i; |
|
for(i=Blobs.GetBlobNum(); i>0; i--) |
|
{ |
|
CvBlob* pB = Blobs.GetBlob(i-1); |
|
|
|
if(pB->h < S.height*m_HMin || pB->w < S.width*m_WMin) |
|
{ |
|
Blobs.DelBlob(i-1); |
|
continue; |
|
} |
|
|
|
if(pOldBlobList) |
|
{ |
|
int j; |
|
for(j=pOldBlobList->GetBlobNum(); j>0; j--) |
|
{ |
|
CvBlob* pBOld = pOldBlobList->GetBlob(j-1); |
|
if((fabs(pBOld->x-pB->x) < (CV_BLOB_RX(pBOld)+CV_BLOB_RX(pB))) && |
|
(fabs(pBOld->y-pB->y) < (CV_BLOB_RY(pBOld)+CV_BLOB_RY(pB)))) |
|
{ /* Intersection detected, delete blob from list: */ |
|
Blobs.DelBlob(i-1); |
|
break; |
|
} |
|
} /* Check next old blob. */ |
|
} /* if pOldBlobList. */ |
|
} /* Check next blob. */ |
|
} /* Delete small and intersected blobs. */ |
|
|
|
{ /* Bubble-sort blobs by size: */ |
|
int N = Blobs.GetBlobNum(); |
|
int i,j; |
|
for(i=1; i<N; ++i) |
|
{ |
|
for(j=i; j>0; --j) |
|
{ |
|
CvBlob temp; |
|
float AreaP, AreaN; |
|
CvBlob* pP = Blobs.GetBlob(j-1); |
|
CvBlob* pN = Blobs.GetBlob(j); |
|
AreaP = CV_BLOB_WX(pP)*CV_BLOB_WY(pP); |
|
AreaN = CV_BLOB_WX(pN)*CV_BLOB_WY(pN); |
|
if(AreaN < AreaP)break; |
|
temp = pN[0]; |
|
pN[0] = pP[0]; |
|
pP[0] = temp; |
|
} |
|
} |
|
|
|
/* Copy only first 10 blobs: */ |
|
for(i=0; i<MIN(N,10); ++i) |
|
{ |
|
m_pBlobLists[0]->AddBlob(Blobs.GetBlob(i)); |
|
} |
|
|
|
} /* Sort blobs by size. */ |
|
|
|
cvReleaseMemStorage(&storage); |
|
|
|
} /* Create blobs. */ |
|
|
|
{ /* 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. */ |
|
|
|
/* Analyze blob list to find best blob trajectory: */ |
|
{ |
|
double BestError = -1; |
|
int BestTrack = -1;; |
|
CvBlobSeq* pNewBlobs = m_pBlobLists[0]; |
|
int i; |
|
int NewTrackNum = 0; |
|
for(i=pNewBlobs->GetBlobNum(); i>0; --i) |
|
{ |
|
CvBlob* pBNew = pNewBlobs->GetBlob(i-1); |
|
int j; |
|
int AsignedTrack = 0; |
|
for(j=0; j<m_TrackNum; ++j) |
|
{ |
|
double dx,dy; |
|
DefSeq* pTrack = m_TrackSeq+j; |
|
CvBlob* pLastBlob = pTrack->size>0?pTrack->pBlobs[1]:NULL; |
|
if(pLastBlob == NULL) continue; |
|
dx = fabs(CV_BLOB_X(pLastBlob)-CV_BLOB_X(pBNew)); |
|
dy = fabs(CV_BLOB_Y(pLastBlob)-CV_BLOB_Y(pBNew)); |
|
if(dx > 2*CV_BLOB_WX(pLastBlob) || dy > 2*CV_BLOB_WY(pLastBlob)) continue; |
|
AsignedTrack++; |
|
|
|
if(pTrack->pBlobs[0]==NULL) |
|
{ /* Fill existed track: */ |
|
pTrack->pBlobs[0] = pBNew; |
|
pTrack->size++; |
|
} |
|
else if((m_TrackNum+NewTrackNum)<SEQ_NUM) |
|
{ /* Duplicate existed track: */ |
|
m_TrackSeq[m_TrackNum+NewTrackNum] = pTrack[0]; |
|
m_TrackSeq[m_TrackNum+NewTrackNum].pBlobs[0] = pBNew; |
|
NewTrackNum++; |
|
} |
|
} /* Next track. */ |
|
|
|
if(AsignedTrack==0 && (m_TrackNum+NewTrackNum)<SEQ_NUM ) |
|
{ /* Initialize new track: */ |
|
m_TrackSeq[m_TrackNum+NewTrackNum].size = 1; |
|
m_TrackSeq[m_TrackNum+NewTrackNum].pBlobs[0] = pBNew; |
|
NewTrackNum++; |
|
} |
|
} /* Next new blob. */ |
|
|
|
m_TrackNum += NewTrackNum; |
|
|
|
/* Check each track: */ |
|
for(i=0; i<m_TrackNum; ++i) |
|
{ |
|
int Good = 1; |
|
DefSeq* pTrack = m_TrackSeq+i; |
|
CvBlob* pBNew = pTrack->pBlobs[0]; |
|
if(pTrack->size != SEQ_SIZE) continue; |
|
if(pBNew == NULL ) continue; |
|
|
|
/* Check intersection last blob with existed: */ |
|
if(Good && pOldBlobList) |
|
{ |
|
int k; |
|
for(k=pOldBlobList->GetBlobNum(); k>0; --k) |
|
{ |
|
CvBlob* pBOld = pOldBlobList->GetBlob(k-1); |
|
if((fabs(pBOld->x-pBNew->x) < (CV_BLOB_RX(pBOld)+CV_BLOB_RX(pBNew))) && |
|
(fabs(pBOld->y-pBNew->y) < (CV_BLOB_RY(pBOld)+CV_BLOB_RY(pBNew)))) |
|
Good = 0; |
|
} |
|
} /* Check intersection last blob with existed. */ |
|
|
|
/* Check distance to image border: */ |
|
if(Good) |
|
{ /* Check distance to image border: */ |
|
float dx = MIN(pBNew->x,S.width-pBNew->x)/CV_BLOB_RX(pBNew); |
|
float dy = MIN(pBNew->y,S.height-pBNew->y)/CV_BLOB_RY(pBNew); |
|
if(dx < m_MinDistToBorder || dy < m_MinDistToBorder) Good = 0; |
|
} /* Check distance to image border. */ |
|
|
|
/* Check uniform motion: */ |
|
if(Good) |
|
{ /* Check uniform motion: */ |
|
double Error = 0; |
|
int N = pTrack->size; |
|
CvBlob** pBL = pTrack->pBlobs; |
|
float sum[2] = {0,0}; |
|
float jsum[2] = {0,0}; |
|
float a[2],b[2]; /* estimated parameters of moving x(t) = a*t+b*/ |
|
int j; |
|
|
|
for(j=0; j<N; ++j) |
|
{ |
|
float x = pBL[j]->x; |
|
float y = pBL[j]->y; |
|
sum[0] += x; |
|
jsum[0] += j*x; |
|
sum[1] += y; |
|
jsum[1] += j*y; |
|
} |
|
|
|
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); |
|
} |
|
|
|
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; |
|
|
|
/* New best trajectory: */ |
|
if(Good && (BestError == -1 || BestError > Error)) |
|
{ /* New best trajectory: */ |
|
BestTrack = i; |
|
BestError = Error; |
|
} /* New best trajectory. */ |
|
} /* Check uniform motion. */ |
|
} /* Next track. */ |
|
|
|
#if 0 |
|
{ /**/ |
|
printf("BlobDetector configurations = %d [",m_TrackNum); |
|
int i; |
|
for(i=0; i<SEQ_SIZE; ++i) |
|
{ |
|
printf("%d,",m_pBlobLists[i]?m_pBlobLists[i]->GetBlobNum():0); |
|
} |
|
printf("]\n"); |
|
} |
|
#endif |
|
|
|
if(BestTrack >= 0) |
|
{ /* Put new blob to output and delete from blob list: */ |
|
assert(m_TrackSeq[BestTrack].size == SEQ_SIZE); |
|
assert(m_TrackSeq[BestTrack].pBlobs[0]); |
|
pNewBlobList->AddBlob(m_TrackSeq[BestTrack].pBlobs[0]); |
|
m_TrackSeq[BestTrack].pBlobs[0] = NULL; |
|
m_TrackSeq[BestTrack].size--; |
|
result = 1; |
|
} /* Put new blob to output and mark in blob list to delete. */ |
|
} /* Analyze blod list to find best blob trajectory. */ |
|
|
|
{ /* Delete bad tracks: */ |
|
int i; |
|
for(i=m_TrackNum-1; i>=0; --i) |
|
{ /* Delete bad tracks: */ |
|
if(m_TrackSeq[i].pBlobs[0]) continue; |
|
if(m_TrackNum>0) |
|
m_TrackSeq[i] = m_TrackSeq[--m_TrackNum]; |
|
} /* Delete bad tracks: */ |
|
} |
|
|
|
#ifdef USE_OBJECT_DETECTOR |
|
if( m_split_detector && pNewBlobList->GetBlobNum() > 0 ) |
|
{ |
|
int num_new_blobs = pNewBlobList->GetBlobNum(); |
|
int i = 0; |
|
|
|
if( m_roi_seq ) cvClearSeq( m_roi_seq ); |
|
m_debug_blob_seq.Clear(); |
|
for( i = 0; i < num_new_blobs; ++i ) |
|
{ |
|
CvBlob* b = pNewBlobList->GetBlob(i); |
|
CvMat roi_stub; |
|
CvMat* roi_mat = 0; |
|
CvMat* scaled_roi_mat = 0; |
|
|
|
CvDetectedBlob d_b = cvDetectedBlob( CV_BLOB_X(b), CV_BLOB_Y(b), CV_BLOB_WX(b), CV_BLOB_WY(b), 0 ); |
|
m_debug_blob_seq.AddBlob(&d_b); |
|
|
|
float scale = m_param_roi_scale * m_min_window_size.height / CV_BLOB_WY(b); |
|
|
|
float b_width = MAX(CV_BLOB_WX(b), m_min_window_size.width / scale) |
|
+ (m_param_roi_scale - 1.0F) * (m_min_window_size.width / scale) |
|
+ 2.0F * m_max_border / scale; |
|
float b_height = CV_BLOB_WY(b) * m_param_roi_scale + 2.0F * m_max_border / scale; |
|
|
|
CvRect roi = cvRectIntersection( cvRect( cvFloor(CV_BLOB_X(b) - 0.5F*b_width), |
|
cvFloor(CV_BLOB_Y(b) - 0.5F*b_height), |
|
cvCeil(b_width), cvCeil(b_height) ), |
|
cvRect( 0, 0, pImg->width, pImg->height ) ); |
|
if( roi.width <= 0 || roi.height <= 0 ) |
|
continue; |
|
|
|
if( m_roi_seq ) cvSeqPush( m_roi_seq, &roi ); |
|
|
|
roi_mat = cvGetSubRect( pImg, &roi_stub, roi ); |
|
scaled_roi_mat = cvCreateMat( cvCeil(scale*roi.height), cvCeil(scale*roi.width), CV_8UC3 ); |
|
cvResize( roi_mat, scaled_roi_mat ); |
|
|
|
m_detected_blob_seq.Clear(); |
|
m_split_detector->Detect( scaled_roi_mat, &m_detected_blob_seq ); |
|
cvReleaseMat( &scaled_roi_mat ); |
|
|
|
for( int k = 0; k < m_detected_blob_seq.GetBlobNum(); ++k ) |
|
{ |
|
CvDetectedBlob* b = (CvDetectedBlob*) m_detected_blob_seq.GetBlob(k); |
|
|
|
/* scale and shift each detected blob back to the original image coordinates */ |
|
CV_BLOB_X(b) = CV_BLOB_X(b) / scale + roi.x; |
|
CV_BLOB_Y(b) = CV_BLOB_Y(b) / scale + roi.y; |
|
CV_BLOB_WX(b) /= scale; |
|
CV_BLOB_WY(b) /= scale; |
|
|
|
CvDetectedBlob d_b = cvDetectedBlob( CV_BLOB_X(b), CV_BLOB_Y(b), CV_BLOB_WX(b), CV_BLOB_WY(b), 1, |
|
b->response ); |
|
m_debug_blob_seq.AddBlob(&d_b); |
|
} |
|
|
|
if( m_detected_blob_seq.GetBlobNum() > 1 ) |
|
{ |
|
/* |
|
* Split blob. |
|
* The original blob is replaced by the first detected blob, |
|
* remaining detected blobs are added to the end of the sequence: |
|
*/ |
|
CvBlob* first_b = m_detected_blob_seq.GetBlob(0); |
|
CV_BLOB_X(b) = CV_BLOB_X(first_b); CV_BLOB_Y(b) = CV_BLOB_Y(first_b); |
|
CV_BLOB_WX(b) = CV_BLOB_WX(first_b); CV_BLOB_WY(b) = CV_BLOB_WY(first_b); |
|
|
|
for( int j = 1; j < m_detected_blob_seq.GetBlobNum(); ++j ) |
|
{ |
|
CvBlob* detected_b = m_detected_blob_seq.GetBlob(j); |
|
pNewBlobList->AddBlob(detected_b); |
|
} |
|
} |
|
} /* For each new blob. */ |
|
|
|
for( i = 0; i < pNewBlobList->GetBlobNum(); ++i ) |
|
{ |
|
CvBlob* b = pNewBlobList->GetBlob(i); |
|
CvDetectedBlob d_b = cvDetectedBlob( CV_BLOB_X(b), CV_BLOB_Y(b), CV_BLOB_WX(b), CV_BLOB_WY(b), 2 ); |
|
m_debug_blob_seq.AddBlob(&d_b); |
|
} |
|
} // if( m_split_detector ) |
|
#endif |
|
|
|
return result; |
|
|
|
} /* cvDetectNewBlob */ |
|
|
|
|
|
|