From 42c1d4f4b1880f229b9ae0b23f9331937848051b Mon Sep 17 00:00:00 2001 From: Vladislav Vinogradov Date: Wed, 8 Aug 2012 18:15:06 +0400 Subject: [PATCH] new optimized version of BackgroundSubtractorGMG --- .../include/opencv2/video/background_segm.hpp | 165 +------ modules/video/src/bgfg_gmg.cpp | 539 ++++++++------------- .../video/test/test_backgroundsubtractor_gbh.cpp | 15 +- samples/cpp/bgfg_gmg.cpp | 131 +++-- 4 files changed, 297 insertions(+), 553 deletions(-) diff --git a/modules/video/include/opencv2/video/background_segm.hpp b/modules/video/include/opencv2/video/background_segm.hpp index a71cf3e..9a5497c 100644 --- a/modules/video/include/opencv2/video/background_segm.hpp +++ b/modules/video/include/opencv2/video/background_segm.hpp @@ -199,112 +199,21 @@ protected: */ 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 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 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: BackgroundSubtractorGMG(); virtual ~BackgroundSubtractorGMG(); 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 * model. * @param image Input image @@ -312,28 +221,6 @@ public: */ 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 - * 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); - /** - * 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. int maxFeatures; @@ -345,31 +232,23 @@ protected: int quantizationLevels; //! Prior probability that any given pixel is a background pixel. A sensitivity parameter. double backgroundPrior; + //! value above which pixel is determined to be FG. + double decisionThreshold; + //! smoothing radius, in pixels, for cleaning up FG image. + int smoothingRadius; - double decisionThreshold; //!< value above which pixel is determined to be FG. - int smoothingRadius; //!< smoothing radius, in pixels, for cleaning up FG image. +private: + double maxVal_; + double minVal_; - double maxVal, minVal; + cv::Size frameSize_; + size_t frameNum_; - /* - * General Parameters - */ - int imWidth; //!< width of image. - int imHeight; //!< height of image. - size_t numPixels; + cv::Mat_ nfeatures_; + cv::Mat_ colors_; + cv::Mat_ weights_; - unsigned int numChannels; //!< Number of channels in image. - - bool isDataInitialized; - //!< After general parameters are set, data structures must be initialized. - - /* - * Data Structures - */ - vector 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. + cv::Mat buf_; }; } diff --git a/modules/video/src/bgfg_gmg.cpp b/modules/video/src/bgfg_gmg.cpp index b4e9824..f75dc9b 100644 --- a/modules/video/src/bgfg_gmg.cpp +++ b/modules/video/src/bgfg_gmg.cpp @@ -48,12 +48,7 @@ #include "precomp.hpp" -using namespace std; - -namespace cv -{ - -BackgroundSubtractorGMG::BackgroundSubtractorGMG() +cv::BackgroundSubtractorGMG::BackgroundSubtractorGMG() { /* * Default Parameter Values. Override with algorithm "set" method. @@ -67,389 +62,275 @@ BackgroundSubtractorGMG::BackgroundSubtractorGMG() smoothingRadius = 7; } -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.")); - } +} - /* - * 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)); - } +void cv::BackgroundSubtractorGMG::initialize(cv::Size frameSize, double min, double max) +{ + CV_Assert(min < max); + CV_Assert(maxFeatures > 0); + CV_Assert(learningRate >= 0.0 && learningRate <= 1.0); + CV_Assert(numInitializationFrames >= 1); + CV_Assert(quantizationLevels >= 1 && quantizationLevels <= 255); + CV_Assert(backgroundPrior >= 0.0 && backgroundPrior <= 1.0); - /* - * Detect and accommodate the image depth - */ - Mat image = _image.getMat(); - numChannels = image.channels(); + minVal_ = min; + maxVal_ = max; - /* - * 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. - */ + frameSize_ = frameSize; + frameNum_ = 0; - /* - * Data Structure Initialization - */ - 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::iterator pixel; - vector::iterator pixel_end = pixels.end(); - for (pixel = pixels.begin(); pixel != pixel_end; ++pixel) - { - pixel->setMaxFeatures(maxFeatures); - } + nfeatures_.create(frameSize_); + colors_.create(frameSize_.area(), maxFeatures); + weights_.create(frameSize_.area(), maxFeatures); - 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. - isDataInitialized = true; + nfeatures_.setTo(cv::Scalar::all(0)); } -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"); - } - - /* - * Update learning rate parameter, if desired - */ - if (newLearningRate != -1.0) - { - if (newLearningRate < 0.0 || newLearningRate > 1.0) + for (int i = 0; i < nfeatures; ++i) { - 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); - fgMaskImage = _fgmask.getMat(); // 8-bit unsigned mask. 255 for FG, 0 for BG + // not in histogram, so return 0. + return 0.0f; + } - /* - * 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::iterator pixel; - vector::iterator pixel_end = pixels.end(); - size_t i; - //#pragma omp parallel - for (i = 0, pixel=pixels.begin(); pixel != pixel_end; ++i,++pixel) + void normalizeHistogram(float* weights, int nfeatures) { - HistogramFeatureGMG newFeature; - newFeature.color.clear(); - int irow = int(i / imWidth); - 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(irow)[icol * numChannels + c]; break; - case CV_8S: color = image.ptr(irow)[icol * numChannels + c]; break; - case CV_16U: color = image.ptr(irow)[icol * numChannels + c]; break; - case CV_16S: color = image.ptr(irow)[icol * numChannels + c]; break; - case CV_32S: color = image.ptr(irow)[icol * numChannels + c]; break; - case CV_32F: color = image.ptr(irow)[icol * numChannels + c]; break; - case CV_64F: color = image.ptr(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 + float total = 0.0f; + for (int i = 0; i < nfeatures; ++i) + total += weights[i]; - if (frameNum > numInitializationFrames) // typical operation + if (total != 0.0f) { - 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; - row = int(i-col)/imWidth; - posteriorImage.at(row,col) = (1.0f-posterior); + for (int i = 0; i < nfeatures; ++i) + weights[i] /= total; } - 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 -} - -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) + bool insertFeature(int color, float weight, int* colors, float* weights, int& nfeatures, int maxFeatures) { -//#pragma omp parallel - for (int j = 0; j < imWidth; ++j) + int idx = -1; + 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(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 -} + ::memmove(colors + 1, colors, idx * sizeof(int)); + ::memmove(weights + 1, weights, idx * sizeof(float)); -BackgroundSubtractorGMG::PixelModelGMG::PixelModelGMG() -{ - numFeatures = 0; - maxFeatures = 0; -} + colors[0] = color; + weights[0] = weight; + } + else if (nfeatures == maxFeatures) + { + // discard oldest feature -BackgroundSubtractorGMG::PixelModelGMG::~PixelModelGMG() -{ + ::memmove(colors + 1, colors, (nfeatures - 1) * sizeof(int)); + ::memmove(weights + 1, weights, (nfeatures - 1) * sizeof(float)); -} + colors[0] = color; + weights[0] = weight; + } + else + { + colors[nfeatures] = color; + weights[nfeatures] = weight; -void BackgroundSubtractorGMG::PixelModelGMG::setLastObservedFeature(HistogramFeatureGMG f) -{ - this->lastObservedFeature = f; -} + ++nfeatures; -double BackgroundSubtractorGMG::PixelModelGMG::getLikelihood(BackgroundSubtractorGMG::HistogramFeatureGMG f) -{ - std::list::iterator feature = histogram.begin(); - std::list::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 true; } - } - return 0.0; // not in histogram, so return 0. + return false; + } } -void BackgroundSubtractorGMG::PixelModelGMG::insertFeature(double learningRate) +namespace { - - std::list::iterator feature; - std::list::iterator swap_end; - std::list::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 struct Quantization_ { - /* - * (1) Check if feature already represented in histogram - */ - lastObservedFeature.likelihood = 1.0; - - for (feature = histogram.begin(); feature != last_feature; ++feature) + template + static inline int apply(T val, double minVal, double maxVal, int quantizationLevels) { - 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; - } + int res = 0; + res |= static_cast((val[0] - minVal) * quantizationLevels / (maxVal - minVal)); + res |= static_cast((val[1] - minVal) * quantizationLevels / (maxVal - minVal)) << 8; + res |= static_cast((val[2] - minVal) * quantizationLevels / (maxVal - minVal)) << 16; + return res; } - if (numFeatures == maxFeatures) + }; + template <> struct Quantization_<1> + { + template + static inline int apply(T val, double minVal, double maxVal, int quantizationLevels) { - histogram.pop_back(); // discard oldest feature - histogram.push_front(lastObservedFeature); + return static_cast((val - minVal) * quantizationLevels / (maxVal - minVal)); } - else + }; + template struct Quantization + { + static int apply(const void* src_, int x, double minVal, double maxVal, int quantizationLevels) { - histogram.push_front(lastObservedFeature); - ++numFeatures; + const T* src = static_cast(src_); + return Quantization_::channels>::apply(src[x], minVal, maxVal, quantizationLevels); } - } - else + }; + + class GMG_LoopBody : public cv::ParallelLoopBody { - /* - * (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) + public: + GMG_LoopBody(const cv::Mat& frame, const cv::Mat& fgmask, const cv::Mat_& nfeatures, const cv::Mat_& colors, const cv::Mat_& weights, + int maxFeatures, double learningRate, int numInitializationFrames, int quantizationLevels, double backgroundPrior, double decisionThreshold, + double maxVal, double minVal, size_t frameNum) : + frame_(frame), fgmask_(fgmask), nfeatures_(nfeatures), colors_(colors), weights_(weights), + maxFeatures_(maxFeatures), learningRate_(learningRate), numInitializationFrames_(numInitializationFrames), + quantizationLevels_(quantizationLevels), backgroundPrior_(backgroundPrior), decisionThreshold_(decisionThreshold), + maxVal_(maxVal), minVal_(minVal), frameNum_(frameNum) { - 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_ fgmask_; + + mutable cv::Mat_ nfeatures_; + mutable cv::Mat_ colors_; + mutable cv::Mat_ weights_; + + int maxFeatures_; + double learningRate_; + int numInitializationFrames_; + int quantizationLevels_; + double backgroundPrior_; + double decisionThreshold_; + + double maxVal_; + double minVal_; + size_t 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 - histogram.push_front(lastObservedFeature); - normalizeHistogram(); - } - else + {Quantization::apply, 0, Quantization::apply, Quantization::apply}, + {0,0,0,0}, + {Quantization::apply, 0, Quantization::apply, Quantization::apply}, + {0,0,0,0}, + {0,0,0,0}, + {Quantization::apply, 0, Quantization::apply, Quantization::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); - ++numFeatures; + const uchar* frame_row = frame_.ptr(y); + 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_); + } + + fgmask_row[x] = (uchar)(-isForeground); + + if (frameNum_ <= numInitializationFrames_ + 1) + { + // training-mode update + + insertFeature(newFeatureColor, 1.0f, colors, weights, nfeatures, maxFeatures_); + + if (frameNum_ == numInitializationFrames_ + 1) + normalizeHistogram(weights, nfeatures); + } + else + { + // update histogram. + + 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; + } } } } -BackgroundSubtractorGMG::PixelModelGMG& BackgroundSubtractorGMG::PixelModelGMG::operator *=(const float &rhs) +void cv::BackgroundSubtractorGMG::operator ()(InputArray _frame, OutputArray _fgmask, double newLearningRate) { - /* - * Used to scale histogram by a constant factor - */ - list::iterator feature; - list::iterator last_feature = histogram.end(); - for (feature = histogram.begin(); feature != last_feature; ++feature) - { - feature->likelihood *= rhs; - } - return *this; -} + cv::Mat frame = _frame.getMat(); -void BackgroundSubtractorGMG::PixelModelGMG::normalizeHistogram() -{ - /* - * First, calculate the total weight in the histogram - */ - list::iterator feature; - list::iterator last_feature = histogram.end(); - double total = 0.0; - for (feature = histogram.begin(); feature != last_feature; ++feature) - { - total += feature->likelihood; - } + 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); - /* - * 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 (newLearningRate != -1.0) { - if (total != 0.0) - feature->likelihood = float(feature->likelihood / total); + CV_Assert(newLearningRate >= 0.0 && newLearningRate <= 1.0); + learningRate = newLearningRate; } -} -bool BackgroundSubtractorGMG::HistogramFeatureGMG::operator ==(HistogramFeatureGMG &rhs) -{ - CV_Assert(color.size() == rhs.color.size()); + if (frame.size() != frameSize_) + initialize(frame.size(), 0.0, frame.depth() == CV_8U ? 255.0 : frame.depth() == CV_16U ? std::numeric_limits::max() : 1.0); - std::vector::iterator color_a; - std::vector::iterator color_b; - std::vector::iterator color_a_end = this->color.end(); - for (color_a = color.begin(), color_b = rhs.color.begin(); color_a != color_a_end; ++color_a, ++color_b) - { - if (*color_a != *color_b) - { - return false; - } - } - return true; -} + _fgmask.create(frameSize_, CV_8UC1); + cv::Mat fgmask = _fgmask.getMat(); + GMG_LoopBody body(frame, fgmask, nfeatures_, colors_, weights_, + maxFeatures, learningRate, numInitializationFrames, quantizationLevels, backgroundPrior, decisionThreshold, + maxVal_, minVal_, frameNum_); + cv::parallel_for_(cv::Range(0, frame.rows), body); -} + cv::medianBlur(fgmask, buf_, smoothingRadius); + cv::swap(fgmask, buf_); + // keep track of how many frames we have processed + ++frameNum_; +} diff --git a/modules/video/test/test_backgroundsubtractor_gbh.cpp b/modules/video/test/test_backgroundsubtractor_gbh.cpp index 8486e54..c858e4a 100644 --- a/modules/video/test/test_backgroundsubtractor_gbh.cpp +++ b/modules/video/test/test_backgroundsubtractor_gbh.cpp @@ -115,43 +115,43 @@ void CV_BackgroundSubtractorTest::run(int) { rng.fill(simImage,RNG::UNIFORM,(unsigned char)(minuc/2+maxuc/2),maxuc); if (i == 0) - fgbg->initializeType(simImage,minuc,maxuc); + fgbg->initialize(simImage.size(),minuc,maxuc); } else if (type == CV_8S) { rng.fill(simImage,RNG::UNIFORM,(char)(minc/2+maxc/2),maxc); if (i==0) - fgbg->initializeType(simImage,minc,maxc); + fgbg->initialize(simImage.size(),minc,maxc); } else if (type == CV_16U) { rng.fill(simImage,RNG::UNIFORM,(unsigned int)(minui/2+maxui/2),maxui); if (i==0) - fgbg->initializeType(simImage,minui,maxui); + fgbg->initialize(simImage.size(),minui,maxui); } else if (type == CV_16S) { rng.fill(simImage,RNG::UNIFORM,(int)(mini/2+maxi/2),maxi); if (i==0) - fgbg->initializeType(simImage,mini,maxi); + fgbg->initialize(simImage.size(),mini,maxi); } else if (type == CV_32F) { rng.fill(simImage,RNG::UNIFORM,(float)(minf/2.0+maxf/2.0),maxf); if (i==0) - fgbg->initializeType(simImage,minf,maxf); + fgbg->initialize(simImage.size(),minf,maxf); } else if (type == CV_32S) { rng.fill(simImage,RNG::UNIFORM,(long int)(minli/2+maxli/2),maxli); if (i==0) - fgbg->initializeType(simImage,minli,maxli); + fgbg->initialize(simImage.size(),minli,maxli); } else if (type == CV_64F) { rng.fill(simImage,RNG::UNIFORM,(double)(mind/2.0+maxd/2.0),maxd); if (i==0) - fgbg->initializeType(simImage,mind,maxd); + fgbg->initialize(simImage.size(),mind,maxd); } /** @@ -159,7 +159,6 @@ void CV_BackgroundSubtractorTest::run(int) */ (*fgbg)(simImage,fgmask); Mat fullbg = Mat::zeros(simImage.rows, simImage.cols, CV_8U); - fgbg->updateBackgroundModel(fullbg); //! fgmask should be entirely background during training code = cvtest::cmpEps2( ts, fgmask, fullbg, 0, false, "The training foreground mask" ); diff --git a/samples/cpp/bgfg_gmg.cpp b/samples/cpp/bgfg_gmg.cpp index b6a2852..3d2da44 100644 --- a/samples/cpp/bgfg_gmg.cpp +++ b/samples/cpp/bgfg_gmg.cpp @@ -7,91 +7,76 @@ #include #include -#include using namespace cv; static void help() { - std::cout << - "\nA program demonstrating the use and capabilities of a particular BackgroundSubtraction\n" - "algorithm described in A. Godbehere, A. Matsukawa, K. Goldberg, \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" - "installation at the Contemporary Jewish Museum in San Francisco, CA from March 31 through\n" - "July 31, 2011.\n" - "Call:\n" - "./BackgroundSubtractorGMG_sample\n" - "Using OpenCV version " << CV_VERSION << "\n"< fgbg = Algorithm::create("BackgroundSubtractor.GMG"); - if (fgbg == NULL) - { - CV_Error(CV_StsError,"Failed to create Algorithm\n"); - } - fgbg->set("smoothingRadius",7); - fgbg->set("decisionThreshold",0.7); - - VideoCapture cap; - if( argc > 1 ) + help(); + + initModule_video(); + setUseOptimized(true); + setNumThreads(8); + + Ptr fgbg = Algorithm::create("BackgroundSubtractor.GMG"); + if (fgbg.empty()) + { + std::cerr << "Failed to create BackgroundSubtractor.GMG Algorithm." << std::endl; + return -1; + } + + fgbg->set("initializationFrames", 20); + fgbg->set("decisionThreshold", 0.7); + + VideoCapture cap; + if (argc > 1) cap.open(argv[1]); else cap.open(0); - - if (!cap.isOpened()) - { - std::cout << "error: cannot read video. Try moving video file to sample directory.\n"; - return -1; - } - - Mat img, downimg, downimg2, fgmask, upfgmask, posterior, upposterior; - - bool first = true; - namedWindow("posterior"); - namedWindow("fgmask"); - namedWindow("FG Segmentation"); - int i = 0; - for (;;) - { - std::stringstream txt; - txt << "frame: "; - txt << i++; - - cap >> img; - putText(img,txt.str(),Point(20,40),FONT_HERSHEY_SIMPLEX,0.8,Scalar(1.0,0.0,0.0)); - - resize(img,downimg,Size(160,120),0,0,INTER_NEAREST); // Size(cols, rows) or Size(width,height) - 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); + + if (!cap.isOpened()) + { + std::cerr << "Cannot read video. Try moving video file to sample directory." << std::endl; + return -1; + } + + Mat frame, fgmask, segm; + + namedWindow("FG Segmentation", WINDOW_NORMAL); + + for (;;) + { + cap >> frame; + + if (frame.empty()) + break; + + (*fgbg)(frame, fgmask); + + frame.copyTo(segm); + add(frame, Scalar(100, 100, 0), segm, fgmask); + + imshow("FG Segmentation", segm); + int c = waitKey(30); - if( c == 'q' || c == 'Q' || (c & 255) == 27 ) - break; - } + if (c == 'q' || c == 'Q' || (c & 255) == 27) + break; + } + + return 0; } -- 2.7.4