new optimized version of BackgroundSubtractorGMG
authorVladislav Vinogradov <vlad.vinogradov@itseez.com>
Wed, 8 Aug 2012 14:15:06 +0000 (18:15 +0400)
committerVladislav Vinogradov <vlad.vinogradov@itseez.com>
Wed, 8 Aug 2012 14:15:06 +0000 (18:15 +0400)
modules/video/include/opencv2/video/background_segm.hpp
modules/video/src/bgfg_gmg.cpp
modules/video/test/test_backgroundsubtractor_gbh.cpp
samples/cpp/bgfg_gmg.cpp

index a71cf3e..9a5497c 100644 (file)
@@ -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<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:
     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_<int> nfeatures_;
+    cv::Mat_<int> colors_;
+    cv::Mat_<float> weights_;
 
-    unsigned int numChannels; //!< Number of channels in image.
-
-    bool    isDataInitialized;
-    //!< After general parameters are set, data structures must be initialized.
-
-    /*
-     * 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.
+    cv::Mat buf_;
 };
 
 }
index b4e9824..f75dc9b 100644 (file)
 
 #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<PixelModelGMG>::iterator pixel;
-    vector<PixelModelGMG>::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<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)
+    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<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
+        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<float>(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<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
 
-}
+            ::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<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 true;
         }
-    }
 
-    return 0.0; // not in histogram, so return 0.
+        return false;
+    }
 }
 
-void BackgroundSubtractorGMG::PixelModelGMG::insertFeature(double learningRate)
+namespace
 {
-
-    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 <int cn> struct Quantization_
     {
-        /*
-         * (1) Check if feature already represented in histogram
-         */
-        lastObservedFeature.likelihood = 1.0;
-
-        for (feature = histogram.begin(); feature != last_feature; ++feature)
+        template <typename T>
+        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<int>((val[0] - minVal) * quantizationLevels / (maxVal - minVal));
+            res |= static_cast<int>((val[1] - minVal) * quantizationLevels / (maxVal - minVal)) << 8;
+            res |= static_cast<int>((val[2] - minVal) * quantizationLevels / (maxVal - minVal)) << 16;
+            return res;
         }
-        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
-            histogram.push_front(lastObservedFeature);
+            return static_cast<int>((val - minVal) * quantizationLevels / (maxVal - minVal));
         }
-        else
+    };
+    template <typename T> 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<const T*>(src_);
+            return Quantization_<cv::DataType<T>::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_<int>& nfeatures, const cv::Mat_<int>& colors, const cv::Mat_<float>& 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_<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_;
+
+        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<uchar>::apply, 0, Quantization<cv::Vec3b>::apply, Quantization<cv::Vec4b>::apply},
+            {0,0,0,0},
+            {Quantization<ushort>::apply, 0, Quantization<cv::Vec3w>::apply, Quantization<cv::Vec4w>::apply},
+            {0,0,0,0},
+            {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);
-            ++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<HistogramFeatureGMG>::iterator feature;
-    list<HistogramFeatureGMG>::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<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;
-    }
+    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<ushort>::max() : 1.0);
 
-    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();
-    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_;
+}
index 8486e54..c858e4a 100644 (file)
@@ -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" );
index b6a2852..3d2da44 100644 (file)
@@ -7,91 +7,76 @@
 
 #include <opencv2/opencv.hpp>
 #include <iostream>
-#include <sstream>
 
 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"<<std::endl;
+    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"<<std::endl;
 }
 
 int main(int argc, char** argv)
 {
-       help();
-       setUseOptimized(true);
-       setNumThreads(8);
-
-       Ptr<BackgroundSubtractorGMG> fgbg = Algorithm::create<BackgroundSubtractorGMG>("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<BackgroundSubtractorGMG> fgbg = Algorithm::create<BackgroundSubtractorGMG>("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;
 }