X-Git-Url: https://vcs.maemo.org/git/?a=blobdiff_plain;f=src%2Fcvaux%2Fcvbgfg_acmmm2003.cpp;fp=src%2Fcvaux%2Fcvbgfg_acmmm2003.cpp;h=bcb03b32da6e8fdbe24708185536426b34a6542a;hb=e4c14cdbdf2fe805e79cd96ded236f57e7b89060;hp=0000000000000000000000000000000000000000;hpb=454138ff8a20f6edb9b65a910101403d8b520643;p=opencv diff --git a/src/cvaux/cvbgfg_acmmm2003.cpp b/src/cvaux/cvbgfg_acmmm2003.cpp new file mode 100644 index 0000000..bcb03b3 --- /dev/null +++ b/src/cvaux/cvbgfg_acmmm2003.cpp @@ -0,0 +1,740 @@ +/*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 foreground/background pixel +// discrimination algorithm described in +// +// Foreground Object Detection from Videos Containing Complex Background +// Li, Huan, Gu, Tian 2003 9p +// http://muq.org/~cynbe/bib/foreground-object-detection-from-videos-containing-complex-background.pdf + + +#include "_cvaux.h" + +#include +#include +#include +//#include + +static double* _cv_max_element( double* start, double* end ) +{ + double* p = start++; + + for( ; start != end; ++start) { + + if (*p < *start) p = start; + } + + return p; +} + +static void CV_CDECL icvReleaseFGDStatModel( CvFGDStatModel** model ); +static int CV_CDECL icvUpdateFGDStatModel( IplImage* curr_frame, + CvFGDStatModel* model ); + +// Function cvCreateFGDStatModel initializes foreground detection process +// parameters: +// first_frame - frame from video sequence +// parameters - (optional) if NULL default parameters of the algorithm will be used +// p_model - pointer to CvFGDStatModel structure +CV_IMPL CvBGStatModel* +cvCreateFGDStatModel( IplImage* first_frame, CvFGDStatModelParams* parameters ) +{ + CvFGDStatModel* p_model = 0; + + CV_FUNCNAME( "cvCreateFGDStatModel" ); + + __BEGIN__; + + int i, j, k, pixel_count, buf_size; + CvFGDStatModelParams params; + + if( !CV_IS_IMAGE(first_frame) ) + CV_ERROR( CV_StsBadArg, "Invalid or NULL first_frame parameter" ); + + if (first_frame->nChannels != 3) + CV_ERROR( CV_StsBadArg, "first_frame must have 3 color channels" ); + + // Initialize parameters: + if( parameters == NULL ) + { + params.Lc = CV_BGFG_FGD_LC; + params.N1c = CV_BGFG_FGD_N1C; + params.N2c = CV_BGFG_FGD_N2C; + + params.Lcc = CV_BGFG_FGD_LCC; + params.N1cc = CV_BGFG_FGD_N1CC; + params.N2cc = CV_BGFG_FGD_N2CC; + + params.delta = CV_BGFG_FGD_DELTA; + + params.alpha1 = CV_BGFG_FGD_ALPHA_1; + params.alpha2 = CV_BGFG_FGD_ALPHA_2; + params.alpha3 = CV_BGFG_FGD_ALPHA_3; + + params.T = CV_BGFG_FGD_T; + params.minArea = CV_BGFG_FGD_MINAREA; + + params.is_obj_without_holes = 1; + params.perform_morphing = 1; + } + else + { + params = *parameters; + } + + CV_CALL( p_model = (CvFGDStatModel*)cvAlloc( sizeof(*p_model) )); + memset( p_model, 0, sizeof(*p_model) ); + p_model->type = CV_BG_MODEL_FGD; + p_model->release = (CvReleaseBGStatModel)icvReleaseFGDStatModel; + p_model->update = (CvUpdateBGStatModel)icvUpdateFGDStatModel;; + p_model->params = params; + + // Initialize storage pools: + pixel_count = first_frame->width * first_frame->height; + + buf_size = pixel_count*sizeof(p_model->pixel_stat[0]); + CV_CALL( p_model->pixel_stat = (CvBGPixelStat*)cvAlloc(buf_size) ); + memset( p_model->pixel_stat, 0, buf_size ); + + buf_size = pixel_count*params.N2c*sizeof(p_model->pixel_stat[0].ctable[0]); + CV_CALL( p_model->pixel_stat[0].ctable = (CvBGPixelCStatTable*)cvAlloc(buf_size) ); + memset( p_model->pixel_stat[0].ctable, 0, buf_size ); + + buf_size = pixel_count*params.N2cc*sizeof(p_model->pixel_stat[0].cctable[0]); + CV_CALL( p_model->pixel_stat[0].cctable = (CvBGPixelCCStatTable*)cvAlloc(buf_size) ); + memset( p_model->pixel_stat[0].cctable, 0, buf_size ); + + for( i = 0, k = 0; i < first_frame->height; i++ ) { + for( j = 0; j < first_frame->width; j++, k++ ) + { + p_model->pixel_stat[k].ctable = p_model->pixel_stat[0].ctable + k*params.N2c; + p_model->pixel_stat[k].cctable = p_model->pixel_stat[0].cctable + k*params.N2cc; + } + } + + // Init temporary images: + CV_CALL( p_model->Ftd = cvCreateImage(cvSize(first_frame->width, first_frame->height), IPL_DEPTH_8U, 1)); + CV_CALL( p_model->Fbd = cvCreateImage(cvSize(first_frame->width, first_frame->height), IPL_DEPTH_8U, 1)); + CV_CALL( p_model->foreground = cvCreateImage(cvSize(first_frame->width, first_frame->height), IPL_DEPTH_8U, 1)); + + CV_CALL( p_model->background = cvCloneImage(first_frame)); + CV_CALL( p_model->prev_frame = cvCloneImage(first_frame)); + CV_CALL( p_model->storage = cvCreateMemStorage()); + + __END__; + + if( cvGetErrStatus() < 0 ) + { + CvBGStatModel* base_ptr = (CvBGStatModel*)p_model; + + if( p_model && p_model->release ) + p_model->release( &base_ptr ); + else + cvFree( &p_model ); + p_model = 0; + } + + return (CvBGStatModel*)p_model; +} + + +static void CV_CDECL +icvReleaseFGDStatModel( CvFGDStatModel** _model ) +{ + CV_FUNCNAME( "icvReleaseFGDStatModel" ); + + __BEGIN__; + + if( !_model ) + CV_ERROR( CV_StsNullPtr, "" ); + + if( *_model ) + { + CvFGDStatModel* model = *_model; + if( model->pixel_stat ) + { + cvFree( &model->pixel_stat[0].ctable ); + cvFree( &model->pixel_stat[0].cctable ); + cvFree( &model->pixel_stat ); + } + + cvReleaseImage( &model->Ftd ); + cvReleaseImage( &model->Fbd ); + cvReleaseImage( &model->foreground ); + cvReleaseImage( &model->background ); + cvReleaseImage( &model->prev_frame ); + cvReleaseMemStorage(&model->storage); + + cvFree( _model ); + } + + __END__; +} + +// Function cvChangeDetection performs change detection for Foreground detection algorithm +// parameters: +// prev_frame - +// curr_frame - +// change_mask - +CV_IMPL int +cvChangeDetection( IplImage* prev_frame, + IplImage* curr_frame, + IplImage* change_mask ) +{ + int i, j, b, x, y, thres; + const int PIXELRANGE=256; + + if( !prev_frame + || !curr_frame + || !change_mask + || prev_frame->nChannels != 3 + || curr_frame->nChannels != 3 + || change_mask->nChannels != 1 + || prev_frame->depth != IPL_DEPTH_8U + || curr_frame->depth != IPL_DEPTH_8U + || change_mask->depth != IPL_DEPTH_8U + || prev_frame->width != curr_frame->width + || prev_frame->height != curr_frame->height + || prev_frame->width != change_mask->width + || prev_frame->height != change_mask->height + ){ + return 0; + } + + cvZero ( change_mask ); + + // All operations per colour + for (b=0 ; bnChannels ; b++) { + + // Create histogram: + + long HISTOGRAM[PIXELRANGE]; + for (i=0 ; iheight ; y++) + { + uchar* rowStart1 = (uchar*)curr_frame->imageData + y * curr_frame->widthStep + b; + uchar* rowStart2 = (uchar*)prev_frame->imageData + y * prev_frame->widthStep + b; + for (x=0 ; xwidth ; x++, rowStart1+=curr_frame->nChannels, rowStart2+=prev_frame->nChannels) { + int diff = abs( int(*rowStart1) - int(*rowStart2) ); + HISTOGRAM[diff]++; + } + } + + double relativeVariance[PIXELRANGE]; + for (i=0 ; i=0 ; thres--) + { + // fprintf(stderr, "Iter %d\n", thres); + double sum=0; + double sqsum=0; + int count=0; + // fprintf(stderr, "Iter %d entering loop\n", thres); + for (j=thres ; jheight ; y++) + { + uchar* rowStart1 = (uchar*)(curr_frame->imageData) + y * curr_frame->widthStep + b; + uchar* rowStart2 = (uchar*)(prev_frame->imageData) + y * prev_frame->widthStep + b; + uchar* rowStart3 = (uchar*)(change_mask->imageData) + y * change_mask->widthStep; + for (x = 0; x < curr_frame->width; x++, rowStart1+=curr_frame->nChannels, + rowStart2+=prev_frame->nChannels, rowStart3+=change_mask->nChannels) { + // OR between different color channels + int diff = abs( int(*rowStart1) - int(*rowStart2) ); + if ( diff > bestThres) + *rowStart3 |=255; + } + } + } + + return 1; +} + + +#define MIN_PV 1E-10 + + +#define V_C(k,l) ctable[k].v[l] +#define PV_C(k) ctable[k].Pv +#define PVB_C(k) ctable[k].Pvb +#define V_CC(k,l) cctable[k].v[l] +#define PV_CC(k) cctable[k].Pv +#define PVB_CC(k) cctable[k].Pvb + + +// Function cvUpdateFGDStatModel updates statistical model and returns number of foreground regions +// parameters: +// curr_frame - current frame from video sequence +// p_model - pointer to CvFGDStatModel structure +static int CV_CDECL +icvUpdateFGDStatModel( IplImage* curr_frame, CvFGDStatModel* model ) +{ + int mask_step = model->Ftd->widthStep; + CvSeq *first_seq = NULL, *prev_seq = NULL, *seq = NULL; + IplImage* prev_frame = model->prev_frame; + int region_count = 0; + int FG_pixels_count = 0; + int deltaC = cvRound(model->params.delta * 256 / model->params.Lc); + int deltaCC = cvRound(model->params.delta * 256 / model->params.Lcc); + int i, j, k, l; + + //clear storages + cvClearMemStorage(model->storage); + cvZero(model->foreground); + + // From foreground pixel candidates using image differencing + // with adaptive thresholding. The algorithm is from: + // + // Thresholding for Change Detection + // Paul L. Rosin 1998 6p + // http://www.cis.temple.edu/~latecki/Courses/CIS750-03/Papers/thresh-iccv.pdf + // + cvChangeDetection( prev_frame, curr_frame, model->Ftd ); + cvChangeDetection( model->background, curr_frame, model->Fbd ); + + for( i = 0; i < model->Ftd->height; i++ ) + { + for( j = 0; j < model->Ftd->width; j++ ) + { + if( ((uchar*)model->Fbd->imageData)[i*mask_step+j] || ((uchar*)model->Ftd->imageData)[i*mask_step+j] ) + { + float Pb = 0; + float Pv = 0; + float Pvb = 0; + + CvBGPixelStat* stat = model->pixel_stat + i * model->Ftd->width + j; + + CvBGPixelCStatTable* ctable = stat->ctable; + CvBGPixelCCStatTable* cctable = stat->cctable; + + uchar* curr_data = (uchar*)(curr_frame->imageData) + i*curr_frame->widthStep + j*3; + uchar* prev_data = (uchar*)(prev_frame->imageData) + i*prev_frame->widthStep + j*3; + + int val = 0; + + // Is it a motion pixel? + if( ((uchar*)model->Ftd->imageData)[i*mask_step+j] ) + { + if( !stat->is_trained_dyn_model ) { + + val = 1; + + } else { + + // Compare with stored CCt vectors: + for( k = 0; PV_CC(k) > model->params.alpha2 && k < model->params.N1cc; k++ ) + { + if ( abs( V_CC(k,0) - prev_data[0]) <= deltaCC && + abs( V_CC(k,1) - prev_data[1]) <= deltaCC && + abs( V_CC(k,2) - prev_data[2]) <= deltaCC && + abs( V_CC(k,3) - curr_data[0]) <= deltaCC && + abs( V_CC(k,4) - curr_data[1]) <= deltaCC && + abs( V_CC(k,5) - curr_data[2]) <= deltaCC) + { + Pv += PV_CC(k); + Pvb += PVB_CC(k); + } + } + Pb = stat->Pbcc; + if( 2 * Pvb * Pb <= Pv ) val = 1; + } + } + else if( stat->is_trained_st_model ) + { + // Compare with stored Ct vectors: + for( k = 0; PV_C(k) > model->params.alpha2 && k < model->params.N1c; k++ ) + { + if ( abs( V_C(k,0) - curr_data[0]) <= deltaC && + abs( V_C(k,1) - curr_data[1]) <= deltaC && + abs( V_C(k,2) - curr_data[2]) <= deltaC ) + { + Pv += PV_C(k); + Pvb += PVB_C(k); + } + } + Pb = stat->Pbc; + if( 2 * Pvb * Pb <= Pv ) val = 1; + } + + // Update foreground: + ((uchar*)model->foreground->imageData)[i*mask_step+j] = (uchar)(val*255); + FG_pixels_count += val; + + } // end if( change detection... + } // for j... + } // for i... + //end BG/FG classification + + // Foreground segmentation. + // Smooth foreground map: + if( model->params.perform_morphing ){ + cvMorphologyEx( model->foreground, model->foreground, 0, 0, CV_MOP_OPEN, model->params.perform_morphing ); + cvMorphologyEx( model->foreground, model->foreground, 0, 0, CV_MOP_CLOSE, model->params.perform_morphing ); + } + + + if( model->params.minArea > 0 || model->params.is_obj_without_holes ){ + + // Discard under-size foreground regions: + // + cvFindContours( model->foreground, model->storage, &first_seq, sizeof(CvContour), CV_RETR_LIST ); + for( seq = first_seq; seq; seq = seq->h_next ) + { + CvContour* cnt = (CvContour*)seq; + if( cnt->rect.width * cnt->rect.height < model->params.minArea || + (model->params.is_obj_without_holes && CV_IS_SEQ_HOLE(seq)) ) + { + // Delete under-size contour: + prev_seq = seq->h_prev; + if( prev_seq ) + { + prev_seq->h_next = seq->h_next; + if( seq->h_next ) seq->h_next->h_prev = prev_seq; + } + else + { + first_seq = seq->h_next; + if( seq->h_next ) seq->h_next->h_prev = NULL; + } + } + else + { + region_count++; + } + } + model->foreground_regions = first_seq; + cvZero(model->foreground); + cvDrawContours(model->foreground, first_seq, CV_RGB(0, 0, 255), CV_RGB(0, 0, 255), 10, -1); + + } else { + + model->foreground_regions = NULL; + } + + // Check ALL BG update condition: + if( ((float)FG_pixels_count/(model->Ftd->width*model->Ftd->height)) > CV_BGFG_FGD_BG_UPDATE_TRESH ) + { + for( i = 0; i < model->Ftd->height; i++ ) + for( j = 0; j < model->Ftd->width; j++ ) + { + CvBGPixelStat* stat = model->pixel_stat + i * model->Ftd->width + j; + stat->is_trained_st_model = stat->is_trained_dyn_model = 1; + } + } + + + // Update background model: + for( i = 0; i < model->Ftd->height; i++ ) + { + for( j = 0; j < model->Ftd->width; j++ ) + { + CvBGPixelStat* stat = model->pixel_stat + i * model->Ftd->width + j; + CvBGPixelCStatTable* ctable = stat->ctable; + CvBGPixelCCStatTable* cctable = stat->cctable; + + uchar *curr_data = (uchar*)(curr_frame->imageData)+i*curr_frame->widthStep+j*3; + uchar *prev_data = (uchar*)(prev_frame->imageData)+i*prev_frame->widthStep+j*3; + + if( ((uchar*)model->Ftd->imageData)[i*mask_step+j] || !stat->is_trained_dyn_model ) + { + float alpha = stat->is_trained_dyn_model ? model->params.alpha2 : model->params.alpha3; + float diff = 0; + int dist, min_dist = 2147483647, indx = -1; + + //update Pb + stat->Pbcc *= (1.f-alpha); + if( !((uchar*)model->foreground->imageData)[i*mask_step+j] ) + { + stat->Pbcc += alpha; + } + + // Find best Vi match: + for(k = 0; PV_CC(k) && k < model->params.N2cc; k++ ) + { + // Exponential decay of memory + PV_CC(k) *= (1-alpha); + PVB_CC(k) *= (1-alpha); + if( PV_CC(k) < MIN_PV ) + { + PV_CC(k) = 0; + PVB_CC(k) = 0; + continue; + } + + dist = 0; + for( l = 0; l < 3; l++ ) + { + int val = abs( V_CC(k,l) - prev_data[l] ); + if( val > deltaCC ) break; + dist += val; + val = abs( V_CC(k,l+3) - curr_data[l] ); + if( val > deltaCC) break; + dist += val; + } + if( l == 3 && dist < min_dist ) + { + min_dist = dist; + indx = k; + } + } + + + if( indx < 0 ) + { // Replace N2th elem in the table by new feature: + indx = model->params.N2cc - 1; + PV_CC(indx) = alpha; + PVB_CC(indx) = alpha; + //udate Vt + for( l = 0; l < 3; l++ ) + { + V_CC(indx,l) = prev_data[l]; + V_CC(indx,l+3) = curr_data[l]; + } + } + else + { // Update: + PV_CC(indx) += alpha; + if( !((uchar*)model->foreground->imageData)[i*mask_step+j] ) + { + PVB_CC(indx) += alpha; + } + } + + //re-sort CCt table by Pv + for( k = 0; k < indx; k++ ) + { + if( PV_CC(k) <= PV_CC(indx) ) + { + //shift elements + CvBGPixelCCStatTable tmp1, tmp2 = cctable[indx]; + for( l = k; l <= indx; l++ ) + { + tmp1 = cctable[l]; + cctable[l] = tmp2; + tmp2 = tmp1; + } + break; + } + } + + + float sum1=0, sum2=0; + //check "once-off" changes + for(k = 0; PV_CC(k) && k < model->params.N1cc; k++ ) + { + sum1 += PV_CC(k); + sum2 += PVB_CC(k); + } + if( sum1 > model->params.T ) stat->is_trained_dyn_model = 1; + + diff = sum1 - stat->Pbcc * sum2; + // Update stat table: + if( diff > model->params.T ) + { + //printf("once off change at motion mode\n"); + //new BG features are discovered + for( k = 0; PV_CC(k) && k < model->params.N1cc; k++ ) + { + PVB_CC(k) = + (PV_CC(k)-stat->Pbcc*PVB_CC(k))/(1-stat->Pbcc); + } + assert(stat->Pbcc<=1 && stat->Pbcc>=0); + } + } + + // Handle "stationary" pixel: + if( !((uchar*)model->Ftd->imageData)[i*mask_step+j] ) + { + float alpha = stat->is_trained_st_model ? model->params.alpha2 : model->params.alpha3; + float diff = 0; + int dist, min_dist = 2147483647, indx = -1; + + //update Pb + stat->Pbc *= (1.f-alpha); + if( !((uchar*)model->foreground->imageData)[i*mask_step+j] ) + { + stat->Pbc += alpha; + } + + //find best Vi match + for( k = 0; k < model->params.N2c; k++ ) + { + // Exponential decay of memory + PV_C(k) *= (1-alpha); + PVB_C(k) *= (1-alpha); + if( PV_C(k) < MIN_PV ) + { + PV_C(k) = 0; + PVB_C(k) = 0; + continue; + } + + dist = 0; + for( l = 0; l < 3; l++ ) + { + int val = abs( V_C(k,l) - curr_data[l] ); + if( val > deltaC ) break; + dist += val; + } + if( l == 3 && dist < min_dist ) + { + min_dist = dist; + indx = k; + } + } + + if( indx < 0 ) + {//N2th elem in the table is replaced by a new features + indx = model->params.N2c - 1; + PV_C(indx) = alpha; + PVB_C(indx) = alpha; + //udate Vt + for( l = 0; l < 3; l++ ) + { + V_C(indx,l) = curr_data[l]; + } + } else + {//update + PV_C(indx) += alpha; + if( !((uchar*)model->foreground->imageData)[i*mask_step+j] ) + { + PVB_C(indx) += alpha; + } + } + + //re-sort Ct table by Pv + for( k = 0; k < indx; k++ ) + { + if( PV_C(k) <= PV_C(indx) ) + { + //shift elements + CvBGPixelCStatTable tmp1, tmp2 = ctable[indx]; + for( l = k; l <= indx; l++ ) + { + tmp1 = ctable[l]; + ctable[l] = tmp2; + tmp2 = tmp1; + } + break; + } + } + + // Check "once-off" changes: + float sum1=0, sum2=0; + for( k = 0; PV_C(k) && k < model->params.N1c; k++ ) + { + sum1 += PV_C(k); + sum2 += PVB_C(k); + } + diff = sum1 - stat->Pbc * sum2; + if( sum1 > model->params.T ) stat->is_trained_st_model = 1; + + // Update stat table: + if( diff > model->params.T ) + { + //printf("once off change at stat mode\n"); + //new BG features are discovered + for( k = 0; PV_C(k) && k < model->params.N1c; k++ ) + { + PVB_C(k) = (PV_C(k)-stat->Pbc*PVB_C(k))/(1-stat->Pbc); + } + stat->Pbc = 1 - stat->Pbc; + } + } // if !(change detection) at pixel (i,j) + + // Update the reference BG image: + if( !((uchar*)model->foreground->imageData)[i*mask_step+j]) + { + uchar* ptr = ((uchar*)model->background->imageData) + i*model->background->widthStep+j*3; + + if( !((uchar*)model->Ftd->imageData)[i*mask_step+j] && + !((uchar*)model->Fbd->imageData)[i*mask_step+j] ) + { + // Apply IIR filter: + for( l = 0; l < 3; l++ ) + { + int a = cvRound(ptr[l]*(1 - model->params.alpha1) + model->params.alpha1*curr_data[l]); + ptr[l] = (uchar)a; + //((uchar*)model->background->imageData)[i*model->background->widthStep+j*3+l]*=(1 - model->params.alpha1); + //((uchar*)model->background->imageData)[i*model->background->widthStep+j*3+l] += model->params.alpha1*curr_data[l]; + } + } + else + { + // Background change detected: + for( l = 0; l < 3; l++ ) + { + //((uchar*)model->background->imageData)[i*model->background->widthStep+j*3+l] = curr_data[l]; + ptr[l] = curr_data[l]; + } + } + } + } // j + } // i + + // Keep previous frame: + cvCopy( curr_frame, model->prev_frame ); + + return region_count; +} + +/* End of file. */