bgfg_gmg.cpp 12.2 KB
Newer Older
1 2 3 4 5 6 7 8 9
/*M///////////////////////////////////////////////////////////////////////////////////////
//
//  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
//  By downloading, copying, installing or using the software you agree to this license.
//  If you do not agree to this license, do not download, install,
//  copy or use the software.
//
//
10
//                          License Agreement
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
//
// Copyright (C) 2000, Intel Corporation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//   * Redistribution's of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//
//   * Redistribution's in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//
//   * The name of Intel Corporation may not be used to endorse or promote products
//     derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/

/*
 * This class implements an algorithm described in "Visual Tracking of Human Visitors under
 * Variable-Lighting Conditions for a Responsive Audio Art Installation," A. Godbehere,
 * A. Matsukawa, K. Goldberg, American Control Conference, Montreal, June 2012.
 *
 * Prepared and integrated by Andrew B. Godbehere.
 */

#include "precomp.hpp"

51
cv::BackgroundSubtractorGMG::BackgroundSubtractorGMG()
52 53 54 55 56 57 58 59 60 61 62
{
    /*
     * Default Parameter Values. Override with algorithm "set" method.
     */
    maxFeatures = 64;
    learningRate = 0.025;
    numInitializationFrames = 120;
    quantizationLevels = 16;
    backgroundPrior = 0.8;
    decisionThreshold = 0.8;
    smoothingRadius = 7;
63
    updateBackgroundModel = true;
64
}
65

66
cv::BackgroundSubtractorGMG::~BackgroundSubtractorGMG()
67
{
68
}
69

70 71 72 73 74 75 76 77
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);
78

79 80
    minVal_ = min;
    maxVal_ = max;
81

82 83
    frameSize_ = frameSize;
    frameNum_ = 0;
84

85 86 87
    nfeatures_.create(frameSize_);
    colors_.create(frameSize_.area(), maxFeatures);
    weights_.create(frameSize_.area(), maxFeatures);
88

89
    nfeatures_.setTo(cv::Scalar::all(0));
90 91
}

92
namespace
93
{
94
    float findFeature(int color, const int* colors, const float* weights, int nfeatures)
95
    {
96
        for (int i = 0; i < nfeatures; ++i)
97
        {
98 99
            if (color == colors[i])
                return weights[i];
100 101
        }

102 103 104
        // not in histogram, so return 0.
        return 0.0f;
    }
105

106
    void normalizeHistogram(float* weights, int nfeatures)
107
    {
108 109 110
        float total = 0.0f;
        for (int i = 0; i < nfeatures; ++i)
            total += weights[i];
111

112
        if (total != 0.0f)
113
        {
114 115
            for (int i = 0; i < nfeatures; ++i)
                weights[i] /= total;
116 117 118
        }
    }

119
    bool insertFeature(int color, float weight, int* colors, float* weights, int& nfeatures, int maxFeatures)
120
    {
121 122
        int idx = -1;
        for (int i = 0; i < nfeatures; ++i)
123
        {
124
            if (color == colors[i])
125
            {
126 127 128 129
                // feature in histogram
                weight += weights[i];
                idx = i;
                break;
130 131
            }
        }
132

133 134 135
        if (idx >= 0)
        {
            // move feature to beginning of list
136

137 138
            ::memmove(colors + 1, colors, idx * sizeof(int));
            ::memmove(weights + 1, weights, idx * sizeof(float));
139

140 141 142 143 144 145
            colors[0] = color;
            weights[0] = weight;
        }
        else if (nfeatures == maxFeatures)
        {
            // discard oldest feature
146

147 148
            ::memmove(colors + 1, colors, (nfeatures - 1) * sizeof(int));
            ::memmove(weights + 1, weights, (nfeatures - 1) * sizeof(float));
149

150 151 152 153 154 155 156
            colors[0] = color;
            weights[0] = weight;
        }
        else
        {
            colors[nfeatures] = color;
            weights[nfeatures] = weight;
157

158
            ++nfeatures;
159

160
            return true;
161 162
        }

163 164
        return false;
    }
165 166
}

167
namespace
168
{
169
    template <int cn> struct Quantization_
170
    {
171 172
        template <typename T>
        static inline int apply(T val, double minVal, double maxVal, int quantizationLevels)
173
        {
174 175 176 177 178
            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;
179
        }
180 181 182 183 184
    };
    template <> struct Quantization_<1>
    {
        template <typename T>
        static inline int apply(T val, double minVal, double maxVal, int quantizationLevels)
185
        {
186
            return static_cast<int>((val - minVal) * quantizationLevels / (maxVal - minVal));
187
        }
188 189 190 191
    };
    template <typename T> struct Quantization
    {
        static int apply(const void* src_, int x, double minVal, double maxVal, int quantizationLevels)
192
        {
193 194
            const T* src = static_cast<const T*>(src_);
            return Quantization_<cv::DataType<T>::channels>::apply(src[x], minVal, maxVal, quantizationLevels);
195
        }
196 197 198
    };

    class GMG_LoopBody : public cv::ParallelLoopBody
199
    {
200 201 202
    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,
203
                     double maxVal, double minVal, int frameNum, bool updateBackgroundModel) :
204 205 206
            frame_(frame), fgmask_(fgmask), nfeatures_(nfeatures), colors_(colors), weights_(weights),
            maxFeatures_(maxFeatures), learningRate_(learningRate), numInitializationFrames_(numInitializationFrames),
            quantizationLevels_(quantizationLevels), backgroundPrior_(backgroundPrior), decisionThreshold_(decisionThreshold),
207
            maxVal_(maxVal), minVal_(minVal), frameNum_(frameNum), updateBackgroundModel_(updateBackgroundModel)
208 209
        {
        }
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227

        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_;
228
        bool updateBackgroundModel_;
229 230 231

        double maxVal_;
        double minVal_;
232
        int frameNum_;
233 234 235 236 237 238
    };

    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] =
239
        {
240 241 242 243 244 245 246 247 248 249 250 251
            {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)
252
        {
253 254 255 256 257 258 259 260 261 262 263 264 265 266
            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;

267
                if (frameNum_ >= numInitializationFrames_)
268 269 270 271 272 273 274 275 276 277
                {
                    // 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_);

278
                    // update histogram.
279

280 281 282 283
                    if (updateBackgroundModel_)
                    {
                        for (int i = 0; i < nfeatures; ++i)
                            weights[i] *= 1.0f - learningRate_;
284

285
                        bool inserted = insertFeature(newFeatureColor, learningRate_, colors, weights, nfeatures, maxFeatures_);
286

287 288 289 290 291
                        if (inserted)
                        {
                            normalizeHistogram(weights, nfeatures);
                            nfeatures_row[x] = nfeatures;
                        }
292
                    }
293
                }
294
                else if (updateBackgroundModel_)
295
                {
296
                    // training-mode update
297

298
                    insertFeature(newFeatureColor, 1.0f, colors, weights, nfeatures, maxFeatures_);
299

300
                    if (frameNum_ == numInitializationFrames_ - 1)
301 302 303
                        normalizeHistogram(weights, nfeatures);
                }

304
                fgmask_row[x] = (uchar)(-isForeground);
305
            }
306 307
        }
    }
308 309
}

310
void cv::BackgroundSubtractorGMG::operator ()(InputArray _frame, OutputArray _fgmask, double newLearningRate)
311
{
312
    cv::Mat frame = _frame.getMat();
313

314 315
    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);
316

317
    if (newLearningRate != -1.0)
318
    {
319 320
        CV_Assert(newLearningRate >= 0.0 && newLearningRate <= 1.0);
        learningRate = newLearningRate;
321
    }
322

323 324
    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);
325

326 327
    _fgmask.create(frameSize_, CV_8UC1);
    cv::Mat fgmask = _fgmask.getMat();
328

329 330
    GMG_LoopBody body(frame, fgmask, nfeatures_, colors_, weights_,
                      maxFeatures, learningRate, numInitializationFrames, quantizationLevels, backgroundPrior, decisionThreshold,
331
                      maxVal_, minVal_, frameNum_, updateBackgroundModel);
332
    cv::parallel_for_(cv::Range(0, frame.rows), body);
333

334 335 336 337 338
    if (smoothingRadius > 0)
    {
        cv::medianBlur(fgmask, buf_, smoothingRadius);
        cv::swap(fgmask, buf_);
    }
339

340 341 342
    // keep track of how many frames we have processed
    ++frameNum_;
}
343 344 345 346 347 348 349 350 351 352

void cv::BackgroundSubtractorGMG::release()
{
    frameSize_ = cv::Size();

    nfeatures_.release();
    colors_.release();
    weights_.release();
    buf_.release();
}