Update to 2.0.0 tree from current Fremantle build
[opencv] / src / cvaux / cvbgfg_acmmm2003.cpp
diff --git a/src/cvaux/cvbgfg_acmmm2003.cpp b/src/cvaux/cvbgfg_acmmm2003.cpp
new file mode 100644 (file)
index 0000000..bcb03b3
--- /dev/null
@@ -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 <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+//#include <algorithm>
+
+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 ; b<prev_frame->nChannels ; b++) {
+
+        // Create histogram:
+
+        long HISTOGRAM[PIXELRANGE]; 
+        for (i=0 ; i<PIXELRANGE; i++) HISTOGRAM[i]=0;
+        
+        for (y=0 ; y<curr_frame->height ; 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 ; x<curr_frame->width ; 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<PIXELRANGE; i++) relativeVariance[i]=0;
+
+        for (thres=PIXELRANGE-2; thres>=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 ; j<PIXELRANGE ; j++) {
+                sum   += double(j)*double(HISTOGRAM[j]);
+                sqsum += double(j*j)*double(HISTOGRAM[j]);
+                count += HISTOGRAM[j];
+            }
+            count = count == 0 ? 1 : count;
+            //            fprintf(stderr, "Iter %d finishing loop\n", thres);
+            double my = sum / count;
+            double sigma = sqrt( sqsum/count - my*my);
+            //            fprintf(stderr, "Iter %d sum=%g sqsum=%g count=%d sigma = %g\n", thres, sum, sqsum, count, sigma);
+            //            fprintf(stderr, "Writing to %x\n", &(relativeVariance[thres]));
+            relativeVariance[thres] = sigma;
+            //            fprintf(stderr, "Iter %d finished\n", thres);
+        }
+
+        // Find maximum:
+        uchar bestThres = 0;
+
+        double* pBestThres = _cv_max_element(relativeVariance, relativeVariance+PIXELRANGE);
+        bestThres = (uchar)(*pBestThres); if (bestThres <10) bestThres=10;
+
+        for (y=0 ; y<prev_frame->height ; 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. */