2012-06-29 00:24:48 +08:00
/*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.
//
//
2012-06-29 04:42:26 +08:00
// License Agreement
2012-06-29 00:24:48 +08:00
//
// 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 class implements an algorithm described in " Visual Tracking of Human Visitors under
* Variable - Lighting Conditions for a Responsive Audio Art Installation , " A. Godbehere,
* A . Matsukawa , K . Goldberg , American Control Conference , Montreal , June 2012.
*
* Prepared and integrated by Andrew B . Godbehere .
*/
# include "precomp.hpp"
using namespace std ;
namespace cv
{
BackgroundSubtractorGMG : : BackgroundSubtractorGMG ( )
{
/*
* Default Parameter Values . Override with algorithm " set " method .
*/
maxFeatures = 64 ;
learningRate = 0.025 ;
numInitializationFrames = 120 ;
quantizationLevels = 16 ;
backgroundPrior = 0.8 ;
decisionThreshold = 0.8 ;
smoothingRadius = 7 ;
}
2012-06-29 04:42:26 +08:00
2012-07-01 04:47:09 +08:00
void BackgroundSubtractorGMG : : initializeType ( InputArray _image , double min , double max )
2012-06-29 00:24:48 +08:00
{
2012-06-29 04:42:26 +08:00
minVal = min ;
maxVal = max ;
if ( minVal = = maxVal )
{
CV_Error_ ( CV_StsBadArg , ( " minVal and maxVal cannot be the same. " ) ) ;
}
/*
* Parameter validation
*/
if ( maxFeatures < = 0 )
{
CV_Error_ ( CV_StsBadArg ,
( " maxFeatures parameter must be 1 or greater. Instead, it is %d. " , maxFeatures ) ) ;
}
if ( learningRate < 0.0 | | learningRate > 1.0 )
{
CV_Error_ ( CV_StsBadArg ,
( " learningRate parameter must be in the range [0.0,1.0]. Instead, it is %f. " ,
learningRate ) ) ;
}
if ( numInitializationFrames < 1 )
{
CV_Error_ ( CV_StsBadArg ,
( " numInitializationFrames must be at least 1. Instead, it is %d. " ,
numInitializationFrames ) ) ;
}
if ( quantizationLevels < 1 )
{
CV_Error_ ( CV_StsBadArg ,
( " quantizationLevels must be at least 1 (preferably more). Instead it is %d. " ,
quantizationLevels ) ) ;
}
if ( backgroundPrior < 0.0 | | backgroundPrior > 1.0 )
{
CV_Error_ ( CV_StsBadArg ,
( " backgroundPrior must be a probability, between 0.0 and 1.0. Instead it is %f. " ,
backgroundPrior ) ) ;
}
/*
* Detect and accommodate the image depth
*/
Mat image = _image . getMat ( ) ;
numChannels = image . channels ( ) ;
/*
* Color quantization [ 0 | | | | max ] - - > [ 0 | | max ]
* ( 0 ) Use double as intermediary to convert all types to int .
* ( i ) Shift min to 0 ,
* ( ii ) max / ( num intervals ) = factor . x / factor * factor = quantized result , after integer operation .
*/
/*
* Data Structure Initialization
*/
2012-07-01 04:47:09 +08:00
imWidth = image . cols ;
imHeight = image . rows ;
numPixels = image . total ( ) ;
2012-06-29 04:42:26 +08:00
pixels . resize ( numPixels ) ;
frameNum = 0 ;
// used to iterate through matrix of type unknown at compile time
2012-07-01 04:47:09 +08:00
//elemSize = image.elemSize();
//elemSize1 = image.elemSize1();
2012-06-29 04:42:26 +08:00
vector < PixelModelGMG > : : iterator pixel ;
vector < PixelModelGMG > : : iterator pixel_end = pixels . end ( ) ;
for ( pixel = pixels . begin ( ) ; pixel ! = pixel_end ; + + pixel )
{
pixel - > setMaxFeatures ( maxFeatures ) ;
}
2012-07-01 04:47:09 +08:00
fgMaskImage = Mat : : zeros ( imHeight , imWidth , CV_8UC1 ) ; // 8-bit unsigned mask. 255 for FG, 0 for BG
posteriorImage = Mat : : zeros ( imHeight , imWidth , CV_32FC1 ) ; // float for storing probabilities. Can be viewed directly with imshow.
2012-06-29 04:42:26 +08:00
isDataInitialized = true ;
2012-06-29 00:24:48 +08:00
}
void BackgroundSubtractorGMG : : operator ( ) ( InputArray _image , OutputArray _fgmask , double newLearningRate )
{
2012-06-29 04:42:26 +08:00
if ( ! isDataInitialized )
{
CV_Error ( CV_StsError , " BackgroundSubstractorGMG has not been initialized. Call initialize() first. \n " ) ;
}
/*
* Update learning rate parameter , if desired
*/
if ( newLearningRate ! = - 1.0 )
{
if ( newLearningRate < 0.0 | | newLearningRate > 1.0 )
{
CV_Error ( CV_StsOutOfRange , " Learning rate for Operator () must be between 0.0 and 1.0. \n " ) ;
}
this - > learningRate = newLearningRate ;
}
Mat image = _image . getMat ( ) ;
2012-07-01 04:47:09 +08:00
_fgmask . create ( imHeight , imWidth , CV_8U ) ;
2012-06-29 04:42:26 +08:00
fgMaskImage = _fgmask . getMat ( ) ; // 8-bit unsigned mask. 255 for FG, 0 for BG
/*
* Iterate over pixels in image
*/
// grab data at each pixel (1,2,3 channels, int, float, etc.)
// grab data as an array of bytes. Then, send that array to a function that reads data into vector of appropriate types... and quantizing... before saving as a feature, which is a vector of flexitypes, so code can be portable.
// multiple channels do have sequential storage, use mat::elemSize() and mat::elemSize1()
vector < PixelModelGMG > : : iterator pixel ;
vector < PixelModelGMG > : : iterator pixel_end = pixels . end ( ) ;
size_t i ;
2012-07-01 04:47:09 +08:00
//#pragma omp parallel
2012-06-29 04:42:26 +08:00
for ( i = 0 , pixel = pixels . begin ( ) ; pixel ! = pixel_end ; + + i , + + pixel )
{
HistogramFeatureGMG newFeature ;
newFeature . color . clear ( ) ;
2012-07-01 04:47:09 +08:00
int irow = int ( i / imWidth ) ;
int icol = i % imWidth ;
2012-06-29 04:42:26 +08:00
for ( size_t c = 0 ; c < numChannels ; + + c )
{
/*
* Perform quantization . in each channel . ( color - min ) * ( levels ) / ( max - min ) .
* Shifts min to 0 and scales , finally casting to an int .
*/
2012-07-01 04:47:09 +08:00
double color ;
switch ( image . depth ( ) )
2012-06-29 04:42:26 +08:00
{
2012-07-01 04:47:09 +08:00
case CV_8U : color = image . ptr < uchar > ( irow ) [ icol * numChannels + c ] ; break ;
case CV_8S : color = image . ptr < schar > ( irow ) [ icol * numChannels + c ] ; break ;
case CV_16U : color = image . ptr < ushort > ( irow ) [ icol * numChannels + c ] ; break ;
case CV_16S : color = image . ptr < short > ( irow ) [ icol * numChannels + c ] ; break ;
case CV_32S : color = image . ptr < int > ( irow ) [ icol * numChannels + c ] ; break ;
case CV_32F : color = image . ptr < float > ( irow ) [ icol * numChannels + c ] ; break ;
case CV_64F : color = image . ptr < double > ( irow ) [ icol * numChannels + c ] ; break ;
default : color = 0 ; break ;
2012-06-29 04:42:26 +08:00
}
2012-07-01 04:47:09 +08:00
size_t quantizedColor = ( size_t ) ( ( color - minVal ) * quantizationLevels / ( maxVal - minVal ) ) ;
2012-06-29 04:42:26 +08:00
newFeature . color . push_back ( quantizedColor ) ;
}
// now that the feature is ready for use, put it in the histogram
if ( frameNum > numInitializationFrames ) // typical operation
{
newFeature . likelihood = float ( learningRate ) ;
/*
* ( 1 ) Query histogram to find posterior probability of feature under model .
*/
float likelihood = ( float ) pixel - > getLikelihood ( newFeature ) ;
// see Godbehere, Matsukawa, Goldberg (2012) for reasoning behind this implementation of Bayes rule
float posterior = float ( ( likelihood * backgroundPrior ) / ( likelihood * backgroundPrior + ( 1 - likelihood ) * ( 1 - backgroundPrior ) ) ) ;
/*
* ( 2 ) feed posterior probability into the posterior image
*/
int row , col ;
col = i % imWidth ;
2012-07-01 04:47:09 +08:00
row = int ( i - col ) / imWidth ;
2012-06-29 04:42:26 +08:00
posteriorImage . at < float > ( row , col ) = ( 1.0f - posterior ) ;
}
pixel - > setLastObservedFeature ( newFeature ) ;
}
/*
* ( 3 ) Perform filtering and threshold operations to yield final mask image .
*
* 2 options . First is morphological open / close as before . Second is " median filtering " which Jon Barron says is good to remove noise
*/
Mat thresholdedPosterior ;
threshold ( posteriorImage , thresholdedPosterior , decisionThreshold , 1.0 , THRESH_BINARY ) ;
thresholdedPosterior . convertTo ( fgMaskImage , CV_8U , 255 ) ; // convert image to integer space for further filtering and mask creation
medianBlur ( fgMaskImage , fgMaskImage , smoothingRadius ) ;
fgMaskImage . copyTo ( _fgmask ) ;
+ + frameNum ; // keep track of how many frames we have processed
2012-06-29 00:24:48 +08:00
}
void BackgroundSubtractorGMG : : getPosteriorImage ( OutputArray _img )
{
2012-06-29 04:42:26 +08:00
_img . create ( Size ( imWidth , imHeight ) , CV_32F ) ;
Mat img = _img . getMat ( ) ;
posteriorImage . copyTo ( img ) ;
2012-06-29 00:24:48 +08:00
}
void BackgroundSubtractorGMG : : updateBackgroundModel ( InputArray _mask )
{
2012-06-29 04:42:26 +08:00
CV_Assert ( _mask . size ( ) = = Size ( imWidth , imHeight ) ) ; // mask should be same size as image
2012-06-29 00:24:48 +08:00
2012-06-29 04:42:26 +08:00
Mat maskImg = _mask . getMat ( ) ;
2012-06-29 00:24:48 +08:00
//#pragma omp parallel
2012-07-01 04:47:09 +08:00
for ( int i = 0 ; i < imHeight ; + + i )
2012-06-29 04:42:26 +08:00
{
2012-06-29 00:24:48 +08:00
//#pragma omp parallel
2012-07-01 04:47:09 +08:00
for ( int j = 0 ; j < imWidth ; + + j )
2012-06-29 04:42:26 +08:00
{
if ( frameNum < = numInitializationFrames + 1 )
{
// insert previously observed feature into the histogram. -1.0 parameter indicates training.
pixels [ i * imWidth + j ] . insertFeature ( - 1.0 ) ;
if ( frameNum > = numInitializationFrames + 1 ) // training is done, normalize
{
pixels [ i * imWidth + j ] . normalizeHistogram ( ) ;
}
}
// if mask is 0, pixel is identified as a background pixel, so update histogram.
else if ( maskImg . at < uchar > ( i , j ) = = 0 )
{
pixels [ i * imWidth + j ] . insertFeature ( learningRate ) ; // updates the histogram for the next iteration.
}
}
}
2012-06-29 00:24:48 +08:00
}
BackgroundSubtractorGMG : : ~ BackgroundSubtractorGMG ( )
{
}
BackgroundSubtractorGMG : : PixelModelGMG : : PixelModelGMG ( )
{
2012-06-29 04:42:26 +08:00
numFeatures = 0 ;
maxFeatures = 0 ;
2012-06-29 00:24:48 +08:00
}
BackgroundSubtractorGMG : : PixelModelGMG : : ~ PixelModelGMG ( )
{
}
void BackgroundSubtractorGMG : : PixelModelGMG : : setLastObservedFeature ( HistogramFeatureGMG f )
{
2012-06-29 04:42:26 +08:00
this - > lastObservedFeature = f ;
2012-06-29 00:24:48 +08:00
}
double BackgroundSubtractorGMG : : PixelModelGMG : : getLikelihood ( BackgroundSubtractorGMG : : HistogramFeatureGMG f )
{
2012-06-29 04:42:26 +08:00
std : : list < HistogramFeatureGMG > : : iterator feature = histogram . begin ( ) ;
std : : list < HistogramFeatureGMG > : : iterator feature_end = histogram . end ( ) ;
for ( feature = histogram . begin ( ) ; feature ! = feature_end ; + + feature )
{
// comparing only feature color, not likelihood. See equality operator for HistogramFeatureGMG
if ( f = = * feature )
{
return feature - > likelihood ;
}
}
return 0.0 ; // not in histogram, so return 0.
2012-06-29 00:24:48 +08:00
}
void BackgroundSubtractorGMG : : PixelModelGMG : : insertFeature ( double learningRate )
{
2012-06-29 04:42:26 +08:00
std : : list < HistogramFeatureGMG > : : iterator feature ;
std : : list < HistogramFeatureGMG > : : iterator swap_end ;
std : : list < HistogramFeatureGMG > : : iterator last_feature = histogram . end ( ) ;
/*
* If feature is in histogram already , add the weights , and move feature to front .
* If there are too many features , remove the end feature and push new feature to beginning
*/
if ( learningRate = = - 1.0 ) // then, this is a training-mode update.
{
/*
* ( 1 ) Check if feature already represented in histogram
*/
lastObservedFeature . likelihood = 1.0 ;
for ( feature = histogram . begin ( ) ; feature ! = last_feature ; + + feature )
{
if ( lastObservedFeature = = * feature ) // feature in histogram
{
feature - > likelihood + = lastObservedFeature . likelihood ;
// now, move feature to beginning of list and break the loop
HistogramFeatureGMG tomove = * feature ;
histogram . erase ( feature ) ;
histogram . push_front ( tomove ) ;
return ;
}
}
if ( numFeatures = = maxFeatures )
{
histogram . pop_back ( ) ; // discard oldest feature
histogram . push_front ( lastObservedFeature ) ;
}
else
{
histogram . push_front ( lastObservedFeature ) ;
+ + numFeatures ;
}
}
else
{
/*
* ( 1 ) Scale entire histogram by scaling factor
* ( 2 ) Scale input feature .
* ( 3 ) Check if feature already represented . If so , simply add .
* ( 4 ) If feature is not represented , remove old feature , distribute weight evenly among existing features , add in new feature .
*/
* this * = float ( 1.0 - learningRate ) ;
lastObservedFeature . likelihood = float ( learningRate ) ;
for ( feature = histogram . begin ( ) ; feature ! = last_feature ; + + feature )
{
if ( lastObservedFeature = = * feature ) // feature in histogram
{
lastObservedFeature . likelihood + = feature - > likelihood ;
histogram . erase ( feature ) ;
histogram . push_front ( lastObservedFeature ) ;
return ; // done with the update.
}
}
if ( numFeatures = = maxFeatures )
{
histogram . pop_back ( ) ; // discard oldest feature
histogram . push_front ( lastObservedFeature ) ;
normalizeHistogram ( ) ;
}
else
{
histogram . push_front ( lastObservedFeature ) ;
+ + numFeatures ;
}
}
2012-06-29 00:24:48 +08:00
}
BackgroundSubtractorGMG : : PixelModelGMG & BackgroundSubtractorGMG : : PixelModelGMG : : operator * = ( const float & rhs )
{
2012-06-29 04:42:26 +08:00
/*
* Used to scale histogram by a constant factor
*/
list < HistogramFeatureGMG > : : iterator feature ;
list < HistogramFeatureGMG > : : iterator last_feature = histogram . end ( ) ;
for ( feature = histogram . begin ( ) ; feature ! = last_feature ; + + feature )
{
feature - > likelihood * = rhs ;
}
return * this ;
2012-06-29 00:24:48 +08:00
}
void BackgroundSubtractorGMG : : PixelModelGMG : : normalizeHistogram ( )
{
2012-06-29 04:42:26 +08:00
/*
* First , calculate the total weight in the histogram
*/
list < HistogramFeatureGMG > : : iterator feature ;
list < HistogramFeatureGMG > : : iterator last_feature = histogram . end ( ) ;
double total = 0.0 ;
for ( feature = histogram . begin ( ) ; feature ! = last_feature ; + + feature )
{
total + = feature - > likelihood ;
}
/*
* Then , if weight is not 0 , divide every feature by the total likelihood to re - normalize .
*/
for ( feature = histogram . begin ( ) ; feature ! = last_feature ; + + feature )
{
if ( total ! = 0.0 )
feature - > likelihood = float ( feature - > likelihood / total ) ;
}
2012-06-29 00:24:48 +08:00
}
bool BackgroundSubtractorGMG : : HistogramFeatureGMG : : operator = = ( HistogramFeatureGMG & rhs )
{
2012-06-29 04:42:26 +08:00
CV_Assert ( color . size ( ) = = rhs . color . size ( ) ) ;
std : : vector < size_t > : : iterator color_a ;
std : : vector < size_t > : : iterator color_b ;
std : : vector < size_t > : : iterator color_a_end = this - > color . end ( ) ;
2012-08-06 19:49:07 +08:00
for ( color_a = color . begin ( ) , color_b = rhs . color . begin ( ) ; color_a ! = color_a_end ; + + color_a , + + color_b )
2012-06-29 04:42:26 +08:00
{
if ( * color_a ! = * color_b )
{
return false ;
}
}
return true ;
2012-06-29 00:24:48 +08:00
}
}