core/vpdl/vpdt/vpdt_update_mog.h
Go to the documentation of this file.
00001 // This is core/vpdl/vpdt/vpdt_update_mog.h
00002 #ifndef vpdt_update_mog_h_
00003 #define vpdt_update_mog_h_
00004 //:
00005 // \file
00006 // \author Matt Leotta (mleotta@lems.brown.edu)
00007 // \date March 8, 2009
00008 // \brief Iterative updating of a mixture of Gaussians
00009 
00010 #include <vpdl/vpdt/vpdt_field_traits.h>
00011 #include <vpdl/vpdt/vpdt_gaussian.h>
00012 #include <vpdl/vpdt/vpdt_mixture_of.h>
00013 #include <vpdl/vpdt/vpdt_update_gaussian.h>
00014 #include <vpdl/vpdt/vpdt_mog_fitness.h>
00015 #include <vpdl/vpdt/vpdt_num_obs.h>
00016 #include <vcl_utility.h>
00017 #include <vcl_cassert.h>
00018 
00019 //: A mixture of Gaussians updater
00020 //  Base class for common functionality in adaptive updating schemes
00021 template <class mog_type>
00022 class vpdt_mog_updater
00023 {
00024  public:
00025   typedef mog_type distribution_type;
00026   typedef typename mog_type::field_type field_type;
00027 
00028  private:
00029   typedef typename mog_type::component_type gaussian_type;
00030   typedef typename gaussian_type::field_type F;
00031   typedef typename vpdt_field_traits<F>::scalar_type T;
00032 
00033  protected:
00034   //: Constructor
00035   vpdt_mog_updater(const gaussian_type& init_gaussian,
00036                    unsigned int max_cmp = 5)
00037   : init_gaussian_(init_gaussian), max_components_(max_cmp) {}
00038 
00039   //: insert a sample in the mixture
00040   // \param sample A Gaussian is inserted with a mean of \a sample and a
00041   //               covariance from \a init_gaussian_
00042   // \param init_weight The normalized weight the resulting sample
00043   //                    should have after insertion
00044   void insert(mog_type& mixture, const F& sample, T init_weight) const
00045   {
00046     // Ensure that the number of components does not exceed the maximum.
00047     // Remove the last component if necessary to make room for the new one.
00048     // Components should be sorted such that the last component is least important.
00049     bool removed = false;
00050     while (mixture.num_components() >= max_components_)
00051     {
00052       mixture.remove_last();
00053       removed = true;
00054     }
00055 
00056     // this correction accounts for the fact that the remaining components
00057     // are not normalized to 1-init_weight
00058     if (mixture.num_components() > 0)
00059       init_weight /= (1-init_weight)*mixture.norm_const();
00060     else
00061       init_weight = T(1);
00062 
00063     // the mean is moved to the sample point (initial covariance is fixed)
00064     init_gaussian_.mean = sample;
00065     mixture.insert(init_gaussian_,init_weight);
00066   }
00067 
00068   //: Return the index of the first Gaussian within the threshold distance
00069   //  The threshold \a gt2 is on the square distance.
00070   //  The computed square distance is returned by reference in \a sqr_dist
00071   //  If there are no matches return the number of components (last index + 1)
00072   unsigned int match( const mog_type& mix, const F& sample,
00073                       const T& gt2, T& sqr_dist ) const
00074   {
00075     const unsigned int mix_nc = mix.num_components();
00076     for (unsigned int i=0; i<mix_nc; ++i) {
00077       const gaussian_type& g = mix.distribution(i);
00078       sqr_dist = g.sqr_mahal_dist(sample);
00079       if (sqr_dist < gt2)
00080         return i;
00081     }
00082     return mix_nc;
00083   }
00084 
00085   //: A model for new Gaussians inserted
00086   mutable gaussian_type init_gaussian_;
00087   //: The maximum number of components in the mixture
00088   unsigned int max_components_;
00089 };
00090 
00091 
00092 //: A mixture of Gaussians adaptive updater based on Stauffer-Grimson.
00093 //  Using the S-G approximation to prior probabilities
00094 // This algorithm is based on: C. Stauffer and W.E.L. Grimson,
00095 // "Adaptive background mixture models for real-time tracking", CVPR 1999
00096 template <class mog_type>
00097 class vpdt_mog_sg_updater : public vpdt_mog_updater<mog_type>
00098 {
00099  public:
00100   typedef typename mog_type::component_type gaussian_type;
00101   typedef typename gaussian_type::field_type F;
00102   typedef typename vpdt_field_traits<F>::scalar_type T;
00103 
00104   //: Constructor
00105   vpdt_mog_sg_updater(const gaussian_type& init_gaussian,
00106                       unsigned int max_cmp = 5,
00107                       T g_thresh = T(2.5),
00108                       T alpha = T(0.1),
00109                       T init_weight = T(0.1),
00110                       T min_stdev = T(0.16) )
00111   : vpdt_mog_updater<mog_type>(init_gaussian, max_cmp),
00112     gt2_(g_thresh*g_thresh), alpha_(alpha), init_weight_(init_weight),
00113     min_var_(min_stdev*min_stdev) {}
00114 
00115   //: The main function
00116   void operator() ( mog_type& mix, const F& sample ) const
00117   {
00118     this->update(mix, sample, alpha_);
00119   }
00120 
00121  protected:
00122 
00123   //: Update the mixture with \a sample using learning rate \a alpha
00124   void update( mog_type& mix, const F& sample, T alpha ) const
00125   {
00126     T sqr_dist;
00127     unsigned int i = vpdt_mog_updater<mog_type>::match(mix,sample,gt2_,sqr_dist);
00128     if (i<mix.num_components())
00129     {
00130       gaussian_type& g = mix.distribution(i);
00131       // unlike the original paper, the normal distribution here is unnormalized
00132       // if normalized, rho can exceed 1 leading to divergence
00133       T rho = alpha * vcl_exp(-sqr_dist/2);
00134       if (min_var_ > T(0))
00135         vpdt_update_gaussian(g, rho, sample, min_var_);
00136       else
00137         vpdt_update_gaussian(g, rho, sample);
00138       // this is equivalent to
00139       // w_j = (1-alpha)*w_j + alpha*(j==i?1:0) for all j, but without normalization
00140       mix.set_weight(i, mix.weight(i) + alpha/((1-alpha)*mix.norm_const()));
00141     }
00142     else
00143     {
00144       vpdt_mog_updater<mog_type>::insert(mix,sample,init_weight_);
00145     }
00146     mix.sort(vpdt_mog_fitness<gaussian_type>::order);
00147   }
00148 
00149   //: Squared Gaussian Mahalanobis distance threshold
00150   T gt2_;
00151   //: The learning rate
00152   T alpha_;
00153   //: The initial weight for added Gaussians
00154   T init_weight_;
00155   //: Minimum variance allowed in each Gaussian component
00156   T min_var_;
00157 };
00158 
00159 
00160 //: A mixture of Gaussians adaptive updater based on D.-S. Lee.
00161 // Modification of the S-G method.  Each Gaussian has its own learning rate
00162 // that adjusts with the number of observations.
00163 // This algorithm requires that the Gaussians in the mixture are wrapped
00164 // in a vpdt_num_obs class to count the number of observations.
00165 // This algorithm is based on: D.-S. Lee.
00166 // "Effective gaussian mixture learning for video background subtraction",
00167 // PAMI, 27:827--832, May 2005.
00168 template <class mog_type>
00169 class vpdt_mog_lee_updater : public vpdt_mog_updater<mog_type>
00170 {
00171  public:
00172   typedef typename mog_type::component_type gaussian_type;
00173   typedef typename gaussian_type::field_type F;
00174   typedef typename vpdt_field_traits<F>::scalar_type T;
00175 
00176   //: Constructor
00177   vpdt_mog_lee_updater(const gaussian_type& init_gaussian,
00178                        unsigned int max_cmp = 5,
00179                        T g_thresh = T(2.5),
00180                        T min_stdev = T(0.16),
00181                        bool use_wta = false )
00182   : vpdt_mog_updater<mog_type>(init_gaussian, max_cmp),
00183   gt2_(g_thresh*g_thresh), min_var_(min_stdev*min_stdev),
00184   use_winner_take_all_(use_wta) {}
00185 
00186   //: The main function
00187   void operator() ( mog_type& mix, const F& sample ) const
00188   {
00189     T num_obs = total_num_obs(mix) + 1;
00190     this->update(mix, sample, T(1)/num_obs);
00191   }
00192 
00193  protected:
00194 
00195   //: Count the total number of observations in all components
00196   T total_num_obs( const mog_type& mix ) const
00197   {
00198     T num_obs = T(0);
00199     const unsigned int mix_nc = mix.num_components();
00200     for (unsigned int i=0; i<mix_nc; ++i) {
00201       const gaussian_type& g = mix.distribution(i);
00202       num_obs += g.num_observations;
00203     }
00204     return num_obs;
00205   }
00206 
00207   //: find all matches within the \c gt2_ threshold and compute the probability of each
00208   vcl_vector<vcl_pair<unsigned int,double> >
00209   matches(const mog_type& mix, const F& sample) const
00210   {
00211     const unsigned int mix_nc = mix.num_components();
00212     double sum_p = 0.0;
00213     vcl_vector<vcl_pair<unsigned int,double> > matchez;
00214     // find the square distance to all components, count those below gt2_
00215     for (unsigned int i=0; i<mix_nc; ++i) {
00216       const gaussian_type& g = mix.distribution(i);
00217       double sqr_dist = g.sqr_mahal_dist(sample);
00218       if (sqr_dist < gt2_)
00219         matchez.push_back(vcl_pair<unsigned int,double>(i,sqr_dist));
00220     }
00221     // if only one match, it has prob 1
00222     if (matchez.size() == 1) {
00223       matchez[0].second = 1.0;
00224     }
00225     // find the probability of each match
00226     else if (matchez.size() > 1) {
00227       for (unsigned int j=0; j<matchez.size(); ++j) {
00228         unsigned int& i = matchez[j].first;
00229         double& p = matchez[j].second;
00230         const gaussian_type& g = mix.distribution(i);
00231         p = mix.weight(i) * g.norm_const() * vcl_exp(-p/2);
00232         sum_p += p;
00233       }
00234       // normalize
00235       for (unsigned int j=0; j<matchez.size(); ++j) {
00236         matchez[j].second /= sum_p;
00237       }
00238     }
00239     return matchez;
00240   }
00241 
00242   //: Apply a winner-take-all strategy to the matches.
00243   //  Keep only the highest probability match and assign it probability 1
00244   void winner_take_all(vcl_vector<vcl_pair<unsigned int,double> >& m) const
00245   {
00246     double max_p = m[0].second;
00247     unsigned int max_j = 0;
00248     for (unsigned int j=1; j<m.size(); ++j) {
00249       if (m[j].second > max_p) {
00250         max_p = m[j].second;
00251         max_j = j;
00252       }
00253     }
00254     m[0].first = m[max_j].first;
00255     m[0].second = 1.0;
00256     m.resize(1);
00257   }
00258 
00259   //: Update the mixture with \a sample using learning rate \a alpha
00260   void update( mog_type& mix, const F& sample, T alpha ) const
00261   {
00262     const unsigned int mix_nc = mix.num_components();
00263     if (mix_nc == 0) {
00264       insert(mix,sample,alpha);
00265       return;
00266     }
00267     // find all matching components (sqr dist < gt2_) and their probabilites
00268     vcl_vector<vcl_pair<unsigned int,double> > m = matches(mix, sample);
00269 
00270     if (!m.empty())
00271     {
00272       if (use_winner_take_all_ && m.size() > 1)
00273         winner_take_all(m);
00274       T w_inc = alpha / ((T(1)-alpha)*mix.norm_const());
00275       for (unsigned int j=0; j<m.size(); ++j) {
00276         unsigned int i = m[j].first;
00277         double p = m[j].second;
00278         gaussian_type& g = mix.distribution(i);
00279         g.num_observations += T(p);
00280         T rho = (T(1)-alpha)/g.num_observations + alpha;
00281         rho *= p;
00282         if (min_var_ > T(0))
00283           vpdt_update_gaussian(g, rho, sample, min_var_);
00284         else
00285           vpdt_update_gaussian(g, rho, sample);
00286         // this is equivalent to
00287         // w_j = (1-alpha)*w_j + alpha*(j==i?1:0) for all j, but without normalization
00288         mix.set_weight(i, mix.weight(i)+p*w_inc);
00289       }
00290     }
00291     else
00292     {
00293       insert(mix,sample,alpha);
00294     }
00295     mix.sort(vpdt_mog_fitness<gaussian_type>::order);
00296   }
00297 
00298   //: Squared Gaussian Mahalanobis distance threshold
00299   T gt2_;
00300   //: Minimum variance allowed in each Gaussian component
00301   T min_var_;
00302 
00303   //: Use a winner-take_all strategy
00304   bool use_winner_take_all_;
00305 };
00306 
00307 
00308 //: A mixture of Gaussians adaptive updater base on Leotta-Mundy
00309 // Combines the greedy matching of the S-G method for speed with the
00310 // dynamic learning rate of Lee for faster learning.
00311 // Unnormalized weights serve a dual role with observation counts.
00312 // This algorithm is based on: M. Leotta and J. Mundy,
00313 // "Learning background and shadow appearance with 3-d vehicle models",
00314 // BMVC, 2:649--658, September 2006.
00315 template <class mog_type>
00316 class vpdt_mog_lm_updater : public vpdt_mog_updater<mog_type>
00317 {
00318  public:
00319   typedef typename mog_type::component_type gaussian_type;
00320   typedef typename gaussian_type::field_type F;
00321   typedef typename vpdt_field_traits<F>::scalar_type T;
00322 
00323   //: Constructor
00324   vpdt_mog_lm_updater(const gaussian_type& init_gaussian,
00325                       unsigned int max_cmp = 5,
00326                       T g_thresh = T(2.5),
00327                       T min_stdev = T(0.16),
00328                       unsigned int ws = 300)
00329   : vpdt_mog_updater<mog_type>(init_gaussian, max_cmp),
00330   gt2_(g_thresh*g_thresh), min_var_(min_stdev*min_stdev),
00331   window_size_(ws) {}
00332 
00333   //: The main function
00334   void operator() ( mog_type& mix, const F& sample, T sample_weight = T(1) ) const
00335   {
00336     this->update(mix, sample, sample_weight);
00337   }
00338 
00339  protected:
00340 
00341   //: Count the total mixture weight
00342   T total_weight( const mog_type& mix ) const
00343   {
00344     T tw = T(0);
00345     const unsigned int mix_nc = mix.num_components();
00346     for (unsigned int i=0; i<mix_nc; ++i) {
00347       tw += mix.weight(i);
00348     }
00349     return tw;
00350   }
00351 
00352   //: Update the mixture with \a sample using learning rate \a alpha
00353   void update( mog_type& mix, const F& sample, T samp_weight ) const
00354   {
00355     assert(samp_weight > 0.0);
00356     T tw = total_weight(mix);
00357     tw += samp_weight;
00358 
00359     T alpha = 1/tw;
00360     T sqr_dist;
00361     unsigned int i = match(mix,sample,gt2_,sqr_dist);
00362     if (i<mix.num_components())
00363     {
00364       gaussian_type& g = mix.distribution(i);
00365       T w = mix.weight(i);
00366       w += samp_weight;
00367       T rho = (T(1)-alpha)/w + alpha;
00368       if (min_var_ > T(0))
00369         vpdt_update_gaussian(g, rho, sample, min_var_);
00370       else
00371         vpdt_update_gaussian(g, rho, sample);
00372       mix.set_weight(i,w);
00373     }
00374     else
00375     {
00376       insert(mix,sample,alpha);
00377     }
00378     // scale down weights for a moving window effect
00379     if (tw > window_size_) {
00380       T scale = window_size_ / tw;
00381       const unsigned int mix_nc = mix.num_components();
00382       for (unsigned int i=0; i<mix_nc; ++i) {
00383         mix.set_weight(i, mix.weight(i)*scale);
00384       }
00385     }
00386     mix.sort(vpdt_mog_fitness<gaussian_type>::order);
00387   }
00388 
00389   //: Squared Gaussian Mahalanobis distance threshold
00390   T gt2_;
00391   //: Minimum variance allowed in each Gaussian component
00392   T min_var_;
00393   //: The moving window size in number of frames
00394   unsigned int window_size_;
00395 };
00396 
00397 
00398 #endif // vpdt_update_mog_h_