Commit 79ef454c authored by Vladislav Vinogradov's avatar Vladislav Vinogradov

Merge branch 'bgfg-gmg-optimized' into bgfg-gmg-merged

parents 827fa850 bfd9e610
...@@ -361,10 +361,9 @@ GPU_PERF_TEST(GMG, cv::gpu::DeviceInfo, std::string, Channels, MaxFeatures) ...@@ -361,10 +361,9 @@ GPU_PERF_TEST(GMG, cv::gpu::DeviceInfo, std::string, Channels, MaxFeatures)
cv::BackgroundSubtractorGMG gmg; cv::BackgroundSubtractorGMG gmg;
gmg.set("maxFeatures", maxFeatures); gmg.set("maxFeatures", maxFeatures);
gmg.initializeType(frame, 0.0, 255.0); gmg.initialize(frame.size(), 0.0, 255.0);
gmg(frame, fgmask); gmg(frame, fgmask);
gmg.updateBackgroundModel(zeros);
for (int i = 0; i < 150; ++i) for (int i = 0; i < 150; ++i)
{ {
...@@ -387,7 +386,6 @@ GPU_PERF_TEST(GMG, cv::gpu::DeviceInfo, std::string, Channels, MaxFeatures) ...@@ -387,7 +386,6 @@ GPU_PERF_TEST(GMG, cv::gpu::DeviceInfo, std::string, Channels, MaxFeatures)
startTimer(); next(); startTimer(); next();
gmg(frame, fgmask); gmg(frame, fgmask);
gmg.updateBackgroundModel(zeros);
stopTimer(); stopTimer();
} }
} }
......
...@@ -199,111 +199,20 @@ protected: ...@@ -199,111 +199,20 @@ protected:
*/ */
class CV_EXPORTS BackgroundSubtractorGMG: public cv::BackgroundSubtractor class CV_EXPORTS BackgroundSubtractorGMG: public cv::BackgroundSubtractor
{ {
protected:
/**
* Used internally to represent a single feature in a histogram.
* Feature is a color and an associated likelihood (weight in the histogram).
*/
struct CV_EXPORTS HistogramFeatureGMG
{
/**
* Default constructor.
* Initializes likelihood of feature to 0, color remains uninitialized.
*/
HistogramFeatureGMG(){likelihood = 0.0;}
/**
* Copy constructor.
* Required to use HistogramFeatureGMG in a std::vector
* @see operator =()
*/
HistogramFeatureGMG(const HistogramFeatureGMG& orig){
color = orig.color; likelihood = orig.likelihood;
}
/**
* Assignment operator.
* Required to use HistogramFeatureGMG in a std::vector
*/
HistogramFeatureGMG& operator =(const HistogramFeatureGMG& orig){
color = orig.color; likelihood = orig.likelihood; return *this;
}
/**
* Tests equality of histogram features.
* Equality is tested only by matching the color (feature), not the likelihood.
* This operator is used to look up an observed feature in a histogram.
*/
bool operator ==(HistogramFeatureGMG &rhs);
//! Regardless of the image datatype, it is quantized and mapped to an integer and represented as a vector.
vector<size_t> color;
//! Represents the weight of feature in the histogram.
float likelihood;
friend class PixelModelGMG;
};
/**
* Representation of the statistical model of a single pixel for use in the background subtraction
* algorithm.
*/
class CV_EXPORTS PixelModelGMG
{
public:
PixelModelGMG();
~PixelModelGMG();
/**
* Incorporate the last observed feature into the statistical model.
*
* @param learningRate The adaptation parameter for the histogram. -1.0 to use default. Value
* should be between 0.0 and 1.0, the higher the value, the faster the
* adaptation. 1.0 is limiting case where fast adaptation means no memory.
*/
void insertFeature(double learningRate = -1.0);
/**
* Set the feature last observed, to save before incorporating it into the statistical
* model with insertFeature().
*
* @param feature The feature (color) just observed.
*/
void setLastObservedFeature(BackgroundSubtractorGMG::HistogramFeatureGMG feature);
/**
* Set the upper limit for the number of features to store in the histogram. Use to adjust
* memory requirements.
*
* @param max size_t representing the max number of features.
*/
void setMaxFeatures(size_t max) {
maxFeatures = max; histogram.resize(max); histogram.clear();
}
/**
* Normalize the histogram, so sum of weights of all features = 1.0
*/
void normalizeHistogram();
/**
* Return the weight of a feature in the histogram. If the feature is not represented in the
* histogram, the weight returned is 0.0.
*/
double getLikelihood(HistogramFeatureGMG f);
PixelModelGMG& operator *=(const float &rhs);
//friend class BackgroundSubtractorGMG;
//friend class HistogramFeatureGMG;
private:
size_t numFeatures; //!< number of features in histogram
size_t maxFeatures; //!< max allowable features in histogram
std::list<HistogramFeatureGMG> histogram; //!< represents the histogram as a list of features
HistogramFeatureGMG lastObservedFeature;
//!< store last observed feature in case we need to add it to histogram
};
public: public:
BackgroundSubtractorGMG(); BackgroundSubtractorGMG();
virtual ~BackgroundSubtractorGMG(); virtual ~BackgroundSubtractorGMG();
virtual AlgorithmInfo* info() const; virtual AlgorithmInfo* info() const;
/**
* Validate parameters and set up data structures for appropriate image size.
* Must call before running on data.
* @param frameSize input frame size
* @param min minimum value taken on by pixels in image sequence. Usually 0
* @param max maximum value taken on by pixels in image sequence. e.g. 1.0 or 255
*/
void initialize(cv::Size frameSize, double min, double max);
/** /**
* Performs single-frame background subtraction and builds up a statistical background image * Performs single-frame background subtraction and builds up a statistical background image
* model. * model.
...@@ -313,28 +222,10 @@ public: ...@@ -313,28 +222,10 @@ public:
virtual void operator()(InputArray image, OutputArray fgmask, double learningRate=-1.0); virtual void operator()(InputArray image, OutputArray fgmask, double learningRate=-1.0);
/** /**
* Validate parameters and set up data structures for appropriate image type. Must call before * Releases all inner buffers.
* running on data.
* @param image One sample image from dataset
* @param min minimum value taken on by pixels in image sequence. Usually 0
* @param max maximum value taken on by pixels in image sequence. e.g. 1.0 or 255
*/ */
void initializeType(InputArray image, double min, double max); void release();
/**
* Selectively update the background model. Only update background model for pixels identified
* as background.
* @param mask Mask image same size as images in sequence. Must be 8UC1 matrix, 255 for foreground
* and 0 for background.
*/
void updateBackgroundModel(InputArray mask);
/**
* Retrieve the greyscale image representing the probability that each pixel is foreground given
* the current estimated background model. Values are 0.0 (black) to 1.0 (white).
* @param img The 32FC1 image representing per-pixel probabilities that the pixel is foreground.
*/
void getPosteriorImage(OutputArray img);
protected:
//! Total number of distinct colors to maintain in histogram. //! Total number of distinct colors to maintain in histogram.
int maxFeatures; int maxFeatures;
//! Set between 0.0 and 1.0, determines how quickly features are "forgotten" from histograms. //! Set between 0.0 and 1.0, determines how quickly features are "forgotten" from histograms.
...@@ -345,31 +236,25 @@ protected: ...@@ -345,31 +236,25 @@ protected:
int quantizationLevels; int quantizationLevels;
//! Prior probability that any given pixel is a background pixel. A sensitivity parameter. //! Prior probability that any given pixel is a background pixel. A sensitivity parameter.
double backgroundPrior; double backgroundPrior;
//! Value above which pixel is determined to be FG.
double decisionThreshold;
//! Smoothing radius, in pixels, for cleaning up FG image.
int smoothingRadius;
//! Perform background model update
bool updateBackgroundModel;
double decisionThreshold; //!< value above which pixel is determined to be FG. private:
int smoothingRadius; //!< smoothing radius, in pixels, for cleaning up FG image. double maxVal_;
double minVal_;
double maxVal, minVal;
/* cv::Size frameSize_;
* General Parameters int frameNum_;
*/
int imWidth; //!< width of image.
int imHeight; //!< height of image.
size_t numPixels;
unsigned int numChannels; //!< Number of channels in image.
bool isDataInitialized; cv::Mat_<int> nfeatures_;
//!< After general parameters are set, data structures must be initialized. cv::Mat_<int> colors_;
cv::Mat_<float> weights_;
/* cv::Mat buf_;
* Data Structures
*/
vector<PixelModelGMG> pixels; //!< Probabilistic background models for each pixel in image.
int frameNum; //!< Frame number counter, used to count frames in training mode.
Mat posteriorImage; //!< Posterior probability image.
Mat fgMaskImage; //!< Foreground mask image.
}; };
} }
......
...@@ -48,12 +48,7 @@ ...@@ -48,12 +48,7 @@
#include "precomp.hpp" #include "precomp.hpp"
using namespace std; cv::BackgroundSubtractorGMG::BackgroundSubtractorGMG()
namespace cv
{
BackgroundSubtractorGMG::BackgroundSubtractorGMG()
{ {
/* /*
* Default Parameter Values. Override with algorithm "set" method. * Default Parameter Values. Override with algorithm "set" method.
...@@ -65,391 +60,293 @@ BackgroundSubtractorGMG::BackgroundSubtractorGMG() ...@@ -65,391 +60,293 @@ BackgroundSubtractorGMG::BackgroundSubtractorGMG()
backgroundPrior = 0.8; backgroundPrior = 0.8;
decisionThreshold = 0.8; decisionThreshold = 0.8;
smoothingRadius = 7; smoothingRadius = 7;
updateBackgroundModel = true;
} }
void BackgroundSubtractorGMG::initializeType(InputArray _image, double min, double max) cv::BackgroundSubtractorGMG::~BackgroundSubtractorGMG()
{ {
minVal = min; }
maxVal = max;
if (minVal == maxVal)
{
CV_Error_(CV_StsBadArg,("minVal and maxVal cannot be the same."));
}
/* void cv::BackgroundSubtractorGMG::initialize(cv::Size frameSize, double min, double max)
* Parameter validation {
*/ CV_Assert(min < max);
if (maxFeatures <= 0) CV_Assert(maxFeatures > 0);
{ CV_Assert(learningRate >= 0.0 && learningRate <= 1.0);
CV_Error_(CV_StsBadArg, CV_Assert(numInitializationFrames >= 1);
("maxFeatures parameter must be 1 or greater. Instead, it is %d.",maxFeatures)); CV_Assert(quantizationLevels >= 1 && quantizationLevels <= 255);
} CV_Assert(backgroundPrior >= 0.0 && backgroundPrior <= 1.0);
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));
}
/* minVal_ = min;
* Detect and accommodate the image depth maxVal_ = max;
*/
Mat image = _image.getMat();
numChannels = image.channels();
/* frameSize_ = frameSize;
* Color quantization [0 | | | | max] --> [0 | | max] frameNum_ = 0;
* (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.
*/
/* nfeatures_.create(frameSize_);
* Data Structure Initialization colors_.create(frameSize_.area(), maxFeatures);
*/ weights_.create(frameSize_.area(), maxFeatures);
imWidth = image.cols;
imHeight = image.rows;
numPixels = image.total();
pixels.resize(numPixels);
frameNum = 0;
// used to iterate through matrix of type unknown at compile time
//elemSize = image.elemSize();
//elemSize1 = image.elemSize1();
vector<PixelModelGMG>::iterator pixel;
vector<PixelModelGMG>::iterator pixel_end = pixels.end();
for (pixel = pixels.begin(); pixel != pixel_end; ++pixel)
{
pixel->setMaxFeatures(maxFeatures);
}
fgMaskImage = Mat::zeros(imHeight, imWidth, CV_8UC1); // 8-bit unsigned mask. 255 for FG, 0 for BG nfeatures_.setTo(cv::Scalar::all(0));
posteriorImage = Mat::zeros(imHeight, imWidth, CV_32FC1); // float for storing probabilities. Can be viewed directly with imshow.
isDataInitialized = true;
} }
void BackgroundSubtractorGMG::operator()(InputArray _image, OutputArray _fgmask, double newLearningRate) namespace
{ {
if (!isDataInitialized) float findFeature(int color, const int* colors, const float* weights, int nfeatures)
{ {
CV_Error(CV_StsError,"BackgroundSubstractorGMG has not been initialized. Call initialize() first.\n"); for (int i = 0; i < nfeatures; ++i)
}
/*
* 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"); if (color == colors[i])
return weights[i];
} }
this->learningRate = newLearningRate;
}
Mat image = _image.getMat();
_fgmask.create(imHeight,imWidth,CV_8U); // not in histogram, so return 0.
fgMaskImage = _fgmask.getMat(); // 8-bit unsigned mask. 255 for FG, 0 for BG return 0.0f;
}
/* void normalizeHistogram(float* weights, int nfeatures)
* 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;
//#pragma omp parallel
for (i = 0, pixel=pixels.begin(); pixel != pixel_end; ++i,++pixel)
{ {
HistogramFeatureGMG newFeature; float total = 0.0f;
newFeature.color.clear(); for (int i = 0; i < nfeatures; ++i)
int irow = int(i / imWidth); total += weights[i];
int icol = i % imWidth;
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.
*/
double color;
switch(image.depth())
{
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;
}
size_t quantizedColor = (size_t)((color-minVal)*quantizationLevels/(maxVal-minVal));
newFeature.color.push_back(quantizedColor);
}
// now that the feature is ready for use, put it in the histogram
if (frameNum > numInitializationFrames) // typical operation if (total != 0.0f)
{ {
newFeature.likelihood = float(learningRate); for (int i = 0; i < nfeatures; ++i)
/* weights[i] /= total;
* (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;
row = int(i-col)/imWidth;
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); bool insertFeature(int color, float weight, int* colors, float* weights, int& nfeatures, int maxFeatures)
++frameNum; // keep track of how many frames we have processed
}
void BackgroundSubtractorGMG::getPosteriorImage(OutputArray _img)
{
_img.create(Size(imWidth,imHeight),CV_32F);
Mat img = _img.getMat();
posteriorImage.copyTo(img);
}
void BackgroundSubtractorGMG::updateBackgroundModel(InputArray _mask)
{
CV_Assert(_mask.size() == Size(imWidth,imHeight)); // mask should be same size as image
Mat maskImg = _mask.getMat();
//#pragma omp parallel
for (int i = 0; i < imHeight; ++i)
{ {
//#pragma omp parallel int idx = -1;
for (int j = 0; j < imWidth; ++j) for (int i = 0; i < nfeatures; ++i)
{ {
if (frameNum <= numInitializationFrames + 1) if (color == colors[i])
{
// 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. // feature in histogram
weight += weights[i];
idx = i;
break;
} }
} }
}
}
BackgroundSubtractorGMG::~BackgroundSubtractorGMG() if (idx >= 0)
{ {
// move feature to beginning of list
}
BackgroundSubtractorGMG::PixelModelGMG::PixelModelGMG() ::memmove(colors + 1, colors, idx * sizeof(int));
{ ::memmove(weights + 1, weights, idx * sizeof(float));
numFeatures = 0;
maxFeatures = 0;
}
BackgroundSubtractorGMG::PixelModelGMG::~PixelModelGMG() colors[0] = color;
{ weights[0] = weight;
}
else if (nfeatures == maxFeatures)
{
// discard oldest feature
} ::memmove(colors + 1, colors, (nfeatures - 1) * sizeof(int));
::memmove(weights + 1, weights, (nfeatures - 1) * sizeof(float));
void BackgroundSubtractorGMG::PixelModelGMG::setLastObservedFeature(HistogramFeatureGMG f) colors[0] = color;
{ weights[0] = weight;
this->lastObservedFeature = f; }
} else
{
colors[nfeatures] = color;
weights[nfeatures] = weight;
double BackgroundSubtractorGMG::PixelModelGMG::getLikelihood(BackgroundSubtractorGMG::HistogramFeatureGMG f) ++nfeatures;
{
std::list<HistogramFeatureGMG>::iterator feature = histogram.begin();
std::list<HistogramFeatureGMG>::iterator feature_end = histogram.end();
for (feature = histogram.begin(); feature != feature_end; ++feature) return true;
{
// 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. return false;
}
} }
void BackgroundSubtractorGMG::PixelModelGMG::insertFeature(double learningRate) namespace
{ {
template <int cn> struct Quantization_
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.
{ {
/* template <typename T>
* (1) Check if feature already represented in histogram static inline int apply(T val, double minVal, double maxVal, int quantizationLevels)
*/
lastObservedFeature.likelihood = 1.0;
for (feature = histogram.begin(); feature != last_feature; ++feature)
{ {
if (lastObservedFeature == *feature) // feature in histogram int res = 0;
{ res |= static_cast<int>((val[0] - minVal) * quantizationLevels / (maxVal - minVal));
feature->likelihood += lastObservedFeature.likelihood; res |= static_cast<int>((val[1] - minVal) * quantizationLevels / (maxVal - minVal)) << 8;
// now, move feature to beginning of list and break the loop res |= static_cast<int>((val[2] - minVal) * quantizationLevels / (maxVal - minVal)) << 16;
HistogramFeatureGMG tomove = *feature; return res;
histogram.erase(feature);
histogram.push_front(tomove);
return;
}
} }
if (numFeatures == maxFeatures) };
template <> struct Quantization_<1>
{
template <typename T>
static inline int apply(T val, double minVal, double maxVal, int quantizationLevels)
{ {
histogram.pop_back(); // discard oldest feature return static_cast<int>((val - minVal) * quantizationLevels / (maxVal - minVal));
histogram.push_front(lastObservedFeature);
} }
else };
template <typename T> struct Quantization
{
static int apply(const void* src_, int x, double minVal, double maxVal, int quantizationLevels)
{ {
histogram.push_front(lastObservedFeature); const T* src = static_cast<const T*>(src_);
++numFeatures; return Quantization_<cv::DataType<T>::channels>::apply(src[x], minVal, maxVal, quantizationLevels);
} }
} };
else
class GMG_LoopBody : public cv::ParallelLoopBody
{ {
/* public:
* (1) Scale entire histogram by scaling factor GMG_LoopBody(const cv::Mat& frame, const cv::Mat& fgmask, const cv::Mat_<int>& nfeatures, const cv::Mat_<int>& colors, const cv::Mat_<float>& weights,
* (2) Scale input feature. int maxFeatures, double learningRate, int numInitializationFrames, int quantizationLevels, double backgroundPrior, double decisionThreshold,
* (3) Check if feature already represented. If so, simply add. double maxVal, double minVal, int frameNum, bool updateBackgroundModel) :
* (4) If feature is not represented, remove old feature, distribute weight evenly among existing features, add in new feature. frame_(frame), fgmask_(fgmask), nfeatures_(nfeatures), colors_(colors), weights_(weights),
*/ maxFeatures_(maxFeatures), learningRate_(learningRate), numInitializationFrames_(numInitializationFrames),
*this *= float(1.0-learningRate); quantizationLevels_(quantizationLevels), backgroundPrior_(backgroundPrior), decisionThreshold_(decisionThreshold),
lastObservedFeature.likelihood = float(learningRate); maxVal_(maxVal), minVal_(minVal), frameNum_(frameNum), updateBackgroundModel_(updateBackgroundModel)
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)
void operator() (const cv::Range& range) const;
private:
const cv::Mat frame_;
mutable cv::Mat_<uchar> fgmask_;
mutable cv::Mat_<int> nfeatures_;
mutable cv::Mat_<int> colors_;
mutable cv::Mat_<float> weights_;
int maxFeatures_;
double learningRate_;
int numInitializationFrames_;
int quantizationLevels_;
double backgroundPrior_;
double decisionThreshold_;
bool updateBackgroundModel_;
double maxVal_;
double minVal_;
int frameNum_;
};
void GMG_LoopBody::operator() (const cv::Range& range) const
{
typedef int (*func_t)(const void* src_, int x, double minVal, double maxVal, int quantizationLevels);
static const func_t funcs[6][4] =
{ {
histogram.pop_back(); // discard oldest feature {Quantization<uchar>::apply, 0, Quantization<cv::Vec3b>::apply, Quantization<cv::Vec4b>::apply},
histogram.push_front(lastObservedFeature); {0,0,0,0},
normalizeHistogram(); {Quantization<ushort>::apply, 0, Quantization<cv::Vec3w>::apply, Quantization<cv::Vec4w>::apply},
} {0,0,0,0},
else {0,0,0,0},
{Quantization<float>::apply, 0, Quantization<cv::Vec3f>::apply, Quantization<cv::Vec4f>::apply},
};
const func_t func = funcs[frame_.depth()][frame_.channels() - 1];
CV_Assert(func != 0);
for (int y = range.start, featureIdx = y * frame_.cols; y < range.end; ++y)
{ {
histogram.push_front(lastObservedFeature); const uchar* frame_row = frame_.ptr(y);
++numFeatures; int* nfeatures_row = nfeatures_[y];
uchar* fgmask_row = fgmask_[y];
for (int x = 0; x < frame_.cols; ++x, ++featureIdx)
{
int nfeatures = nfeatures_row[x];
int* colors = colors_[featureIdx];
float* weights = weights_[featureIdx];
int newFeatureColor = func(frame_row, x, minVal_, maxVal_, quantizationLevels_);
bool isForeground = false;
if (frameNum_ >= numInitializationFrames_)
{
// typical operation
const double weight = findFeature(newFeatureColor, colors, weights, nfeatures);
// see Godbehere, Matsukawa, Goldberg (2012) for reasoning behind this implementation of Bayes rule
const double posterior = (weight * backgroundPrior_) / (weight * backgroundPrior_ + (1.0 - weight) * (1.0 - backgroundPrior_));
isForeground = ((1.0 - posterior) > decisionThreshold_);
// update histogram.
if (updateBackgroundModel_)
{
for (int i = 0; i < nfeatures; ++i)
weights[i] *= 1.0f - learningRate_;
bool inserted = insertFeature(newFeatureColor, learningRate_, colors, weights, nfeatures, maxFeatures_);
if (inserted)
{
normalizeHistogram(weights, nfeatures);
nfeatures_row[x] = nfeatures;
}
}
}
else if (updateBackgroundModel_)
{
// training-mode update
insertFeature(newFeatureColor, 1.0f, colors, weights, nfeatures, maxFeatures_);
if (frameNum_ == numInitializationFrames_ - 1)
normalizeHistogram(weights, nfeatures);
}
fgmask_row[x] = (uchar)(-isForeground);
}
} }
} }
} }
BackgroundSubtractorGMG::PixelModelGMG& BackgroundSubtractorGMG::PixelModelGMG::operator *=(const float &rhs) void cv::BackgroundSubtractorGMG::operator ()(InputArray _frame, OutputArray _fgmask, double newLearningRate)
{ {
/* cv::Mat frame = _frame.getMat();
* 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;
}
void BackgroundSubtractorGMG::PixelModelGMG::normalizeHistogram() CV_Assert(frame.depth() == CV_8U || frame.depth() == CV_16U || frame.depth() == CV_32F);
{ CV_Assert(frame.channels() == 1 || frame.channels() == 3 || frame.channels() == 4);
/*
* 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;
}
/* if (newLearningRate != -1.0)
* 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) CV_Assert(newLearningRate >= 0.0 && newLearningRate <= 1.0);
feature->likelihood = float(feature->likelihood / total); learningRate = newLearningRate;
} }
}
bool BackgroundSubtractorGMG::HistogramFeatureGMG::operator ==(HistogramFeatureGMG &rhs) if (frame.size() != frameSize_)
{ initialize(frame.size(), 0.0, frame.depth() == CV_8U ? 255.0 : frame.depth() == CV_16U ? std::numeric_limits<ushort>::max() : 1.0);
CV_Assert(color.size() == rhs.color.size());
_fgmask.create(frameSize_, CV_8UC1);
cv::Mat fgmask = _fgmask.getMat();
std::vector<size_t>::iterator color_a; GMG_LoopBody body(frame, fgmask, nfeatures_, colors_, weights_,
std::vector<size_t>::iterator color_b; maxFeatures, learningRate, numInitializationFrames, quantizationLevels, backgroundPrior, decisionThreshold,
std::vector<size_t>::iterator color_a_end = this->color.end(); maxVal_, minVal_, frameNum_, updateBackgroundModel);
for (color_a = color.begin(), color_b = rhs.color.begin(); color_a != color_a_end; ++color_a, ++color_b) cv::parallel_for_(cv::Range(0, frame.rows), body);
if (smoothingRadius > 0)
{ {
if (*color_a != *color_b) cv::medianBlur(fgmask, buf_, smoothingRadius);
{ cv::swap(fgmask, buf_);
return false;
}
} }
return true;
// keep track of how many frames we have processed
++frameNum_;
} }
void cv::BackgroundSubtractorGMG::release()
{
frameSize_ = cv::Size();
nfeatures_.release();
colors_.release();
weights_.release();
buf_.release();
} }
...@@ -78,7 +78,9 @@ CV_INIT_ALGORITHM(BackgroundSubtractorGMG, "BackgroundSubtractor.GMG", ...@@ -78,7 +78,9 @@ CV_INIT_ALGORITHM(BackgroundSubtractorGMG, "BackgroundSubtractor.GMG",
obj.info()->addParam(obj, "smoothingRadius", obj.smoothingRadius,false,0,0, obj.info()->addParam(obj, "smoothingRadius", obj.smoothingRadius,false,0,0,
"Radius of smoothing kernel to filter noise from FG mask image."); "Radius of smoothing kernel to filter noise from FG mask image.");
obj.info()->addParam(obj, "decisionThreshold", obj.decisionThreshold,false,0,0, obj.info()->addParam(obj, "decisionThreshold", obj.decisionThreshold,false,0,0,
"Threshold for FG decision rule. Pixel is FG if posterior probability exceeds threshold.")); "Threshold for FG decision rule. Pixel is FG if posterior probability exceeds threshold.");
obj.info()->addParam(obj, "updateBackgroundModel", obj.updateBackgroundModel,false,0,0,
"Perform background model update."));
bool initModule_video(void) bool initModule_video(void)
{ {
......
...@@ -115,43 +115,43 @@ void CV_BackgroundSubtractorTest::run(int) ...@@ -115,43 +115,43 @@ void CV_BackgroundSubtractorTest::run(int)
{ {
rng.fill(simImage,RNG::UNIFORM,(unsigned char)(minuc/2+maxuc/2),maxuc); rng.fill(simImage,RNG::UNIFORM,(unsigned char)(minuc/2+maxuc/2),maxuc);
if (i == 0) if (i == 0)
fgbg->initializeType(simImage,minuc,maxuc); fgbg->initialize(simImage.size(),minuc,maxuc);
} }
else if (type == CV_8S) else if (type == CV_8S)
{ {
rng.fill(simImage,RNG::UNIFORM,(char)(minc/2+maxc/2),maxc); rng.fill(simImage,RNG::UNIFORM,(char)(minc/2+maxc/2),maxc);
if (i==0) if (i==0)
fgbg->initializeType(simImage,minc,maxc); fgbg->initialize(simImage.size(),minc,maxc);
} }
else if (type == CV_16U) else if (type == CV_16U)
{ {
rng.fill(simImage,RNG::UNIFORM,(unsigned int)(minui/2+maxui/2),maxui); rng.fill(simImage,RNG::UNIFORM,(unsigned int)(minui/2+maxui/2),maxui);
if (i==0) if (i==0)
fgbg->initializeType(simImage,minui,maxui); fgbg->initialize(simImage.size(),minui,maxui);
} }
else if (type == CV_16S) else if (type == CV_16S)
{ {
rng.fill(simImage,RNG::UNIFORM,(int)(mini/2+maxi/2),maxi); rng.fill(simImage,RNG::UNIFORM,(int)(mini/2+maxi/2),maxi);
if (i==0) if (i==0)
fgbg->initializeType(simImage,mini,maxi); fgbg->initialize(simImage.size(),mini,maxi);
} }
else if (type == CV_32F) else if (type == CV_32F)
{ {
rng.fill(simImage,RNG::UNIFORM,(float)(minf/2.0+maxf/2.0),maxf); rng.fill(simImage,RNG::UNIFORM,(float)(minf/2.0+maxf/2.0),maxf);
if (i==0) if (i==0)
fgbg->initializeType(simImage,minf,maxf); fgbg->initialize(simImage.size(),minf,maxf);
} }
else if (type == CV_32S) else if (type == CV_32S)
{ {
rng.fill(simImage,RNG::UNIFORM,(long int)(minli/2+maxli/2),maxli); rng.fill(simImage,RNG::UNIFORM,(long int)(minli/2+maxli/2),maxli);
if (i==0) if (i==0)
fgbg->initializeType(simImage,minli,maxli); fgbg->initialize(simImage.size(),minli,maxli);
} }
else if (type == CV_64F) else if (type == CV_64F)
{ {
rng.fill(simImage,RNG::UNIFORM,(double)(mind/2.0+maxd/2.0),maxd); rng.fill(simImage,RNG::UNIFORM,(double)(mind/2.0+maxd/2.0),maxd);
if (i==0) if (i==0)
fgbg->initializeType(simImage,mind,maxd); fgbg->initialize(simImage.size(),mind,maxd);
} }
/** /**
...@@ -159,7 +159,6 @@ void CV_BackgroundSubtractorTest::run(int) ...@@ -159,7 +159,6 @@ void CV_BackgroundSubtractorTest::run(int)
*/ */
(*fgbg)(simImage,fgmask); (*fgbg)(simImage,fgmask);
Mat fullbg = Mat::zeros(simImage.rows, simImage.cols, CV_8U); Mat fullbg = Mat::zeros(simImage.rows, simImage.cols, CV_8U);
fgbg->updateBackgroundModel(fullbg);
//! fgmask should be entirely background during training //! fgmask should be entirely background during training
code = cvtest::cmpEps2( ts, fgmask, fullbg, 0, false, "The training foreground mask" ); code = cvtest::cmpEps2( ts, fgmask, fullbg, 0, false, "The training foreground mask" );
......
...@@ -7,91 +7,76 @@ ...@@ -7,91 +7,76 @@
#include <opencv2/opencv.hpp> #include <opencv2/opencv.hpp>
#include <iostream> #include <iostream>
#include <sstream>
using namespace cv; using namespace cv;
static void help() static void help()
{ {
std::cout << std::cout <<
"\nA program demonstrating the use and capabilities of a particular BackgroundSubtraction\n" "\nA program demonstrating the use and capabilities of a particular BackgroundSubtraction\n"
"algorithm described in A. Godbehere, A. Matsukawa, K. Goldberg, \n" "algorithm described in A. Godbehere, A. Matsukawa, K. Goldberg, \n"
"\"Visual Tracking of Human Visitors under Variable-Lighting Conditions for a Responsive\n" "\"Visual Tracking of Human Visitors under Variable-Lighting Conditions for a Responsive\n"
"Audio Art Installation\", American Control Conference, 2012, used in an interactive\n" "Audio Art Installation\", American Control Conference, 2012, used in an interactive\n"
"installation at the Contemporary Jewish Museum in San Francisco, CA from March 31 through\n" "installation at the Contemporary Jewish Museum in San Francisco, CA from March 31 through\n"
"July 31, 2011.\n" "July 31, 2011.\n"
"Call:\n" "Call:\n"
"./BackgroundSubtractorGMG_sample\n" "./BackgroundSubtractorGMG_sample\n"
"Using OpenCV version " << CV_VERSION << "\n"<<std::endl; "Using OpenCV version " << CV_VERSION << "\n"<<std::endl;
} }
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
help(); help();
setUseOptimized(true);
setNumThreads(8); initModule_video();
setUseOptimized(true);
Ptr<BackgroundSubtractorGMG> fgbg = Algorithm::create<BackgroundSubtractorGMG>("BackgroundSubtractor.GMG"); setNumThreads(8);
if (fgbg == NULL)
{ Ptr<BackgroundSubtractorGMG> fgbg = Algorithm::create<BackgroundSubtractorGMG>("BackgroundSubtractor.GMG");
CV_Error(CV_StsError,"Failed to create Algorithm\n"); if (fgbg.empty())
} {
fgbg->set("smoothingRadius",7); std::cerr << "Failed to create BackgroundSubtractor.GMG Algorithm." << std::endl;
fgbg->set("decisionThreshold",0.7); return -1;
}
VideoCapture cap;
if( argc > 1 ) fgbg->set("initializationFrames", 20);
fgbg->set("decisionThreshold", 0.7);
VideoCapture cap;
if (argc > 1)
cap.open(argv[1]); cap.open(argv[1]);
else else
cap.open(0); cap.open(0);
if (!cap.isOpened()) if (!cap.isOpened())
{ {
std::cout << "error: cannot read video. Try moving video file to sample directory.\n"; std::cerr << "Cannot read video. Try moving video file to sample directory." << std::endl;
return -1; return -1;
} }
Mat img, downimg, downimg2, fgmask, upfgmask, posterior, upposterior; Mat frame, fgmask, segm;
bool first = true; namedWindow("FG Segmentation", WINDOW_NORMAL);
namedWindow("posterior");
namedWindow("fgmask"); for (;;)
namedWindow("FG Segmentation"); {
int i = 0; cap >> frame;
for (;;)
{ if (frame.empty())
std::stringstream txt; break;
txt << "frame: ";
txt << i++; (*fgbg)(frame, fgmask);
cap >> img; frame.copyTo(segm);
putText(img,txt.str(),Point(20,40),FONT_HERSHEY_SIMPLEX,0.8,Scalar(1.0,0.0,0.0)); add(frame, Scalar(100, 100, 0), segm, fgmask);
resize(img,downimg,Size(160,120),0,0,INTER_NEAREST); // Size(cols, rows) or Size(width,height) imshow("FG Segmentation", segm);
if (first)
{
fgbg->initializeType(downimg,0,255);
first = false;
}
if (img.empty())
{
return 0;
}
(*fgbg)(downimg,fgmask);
fgbg->updateBackgroundModel(Mat::zeros(120,160,CV_8U));
fgbg->getPosteriorImage(posterior);
resize(fgmask,upfgmask,Size(640,480),0,0,INTER_NEAREST);
Mat coloredFG = Mat::zeros(480,640,CV_8UC3);
coloredFG.setTo(Scalar(100,100,0),upfgmask);
resize(posterior,upposterior,Size(640,480),0,0,INTER_NEAREST);
imshow("posterior",upposterior);
imshow("fgmask",upfgmask);
resize(img, downimg2, Size(640, 480),0,0,INTER_LINEAR);
imshow("FG Segmentation",downimg2 + coloredFG);
int c = waitKey(30); int c = waitKey(30);
if( c == 'q' || c == 'Q' || (c & 255) == 27 ) if (c == 'q' || c == 'Q' || (c & 255) == 27)
break; break;
} }
return 0;
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment