diff --git a/modules/gpubgsegm/include/opencv2/gpubgsegm.hpp b/modules/gpubgsegm/include/opencv2/gpubgsegm.hpp index b60d207f7a23acf6e913283e1cb64c7d53df29ba..2b64f6e409164f64e2bb137d523be7843a86eebd 100644 --- a/modules/gpubgsegm/include/opencv2/gpubgsegm.hpp +++ b/modules/gpubgsegm/include/opencv2/gpubgsegm.hpp @@ -73,6 +73,27 @@ CV_EXPORTS Ptr createBackgroundSubtractorMOG(int history = 200, int nmixtures = 5, double backgroundRatio = 0.7, double noiseSigma = 0); +//////////////////////////////////////////////////// +// MOG2 + +class CV_EXPORTS BackgroundSubtractorMOG2 : public cv::BackgroundSubtractorMOG2 +{ +public: + using cv::BackgroundSubtractorMOG2::apply; + using cv::BackgroundSubtractorMOG2::getBackgroundImage; + + virtual void apply(InputArray image, OutputArray fgmask, double learningRate, Stream& stream) = 0; + + virtual void getBackgroundImage(OutputArray backgroundImage, Stream& stream) const = 0; +}; + +CV_EXPORTS Ptr + createBackgroundSubtractorMOG2(int history = 500, double varThreshold = 16, + bool detectShadows = true); + + + + @@ -140,99 +161,6 @@ private: std::auto_ptr impl_; }; -/*! - The class implements the following algorithm: - "Improved adaptive Gausian mixture model for background subtraction" - Z.Zivkovic - International Conference Pattern Recognition, UK, August, 2004. - http://www.zoranz.net/Publications/zivkovic2004ICPR.pdf -*/ -class CV_EXPORTS MOG2_GPU -{ -public: - //! the default constructor - MOG2_GPU(int nmixtures = -1); - - //! re-initiaization method - void initialize(Size frameSize, int frameType); - - //! the update operator - void operator()(const GpuMat& frame, GpuMat& fgmask, float learningRate = -1.0f, Stream& stream = Stream::Null()); - - //! computes a background image which are the mean of all background gaussians - void getBackgroundImage(GpuMat& backgroundImage, Stream& stream = Stream::Null()) const; - - //! releases all inner buffers - void release(); - - // parameters - // you should call initialize after parameters changes - - int history; - - //! here it is the maximum allowed number of mixture components. - //! Actual number is determined dynamically per pixel - float varThreshold; - // threshold on the squared Mahalanobis distance to decide if it is well described - // by the background model or not. Related to Cthr from the paper. - // This does not influence the update of the background. A typical value could be 4 sigma - // and that is varThreshold=4*4=16; Corresponds to Tb in the paper. - - ///////////////////////// - // less important parameters - things you might change but be carefull - //////////////////////// - - float backgroundRatio; - // corresponds to fTB=1-cf from the paper - // TB - threshold when the component becomes significant enough to be included into - // the background model. It is the TB=1-cf from the paper. So I use cf=0.1 => TB=0. - // For alpha=0.001 it means that the mode should exist for approximately 105 frames before - // it is considered foreground - // float noiseSigma; - float varThresholdGen; - - //correspondts to Tg - threshold on the squared Mahalan. dist. to decide - //when a sample is close to the existing components. If it is not close - //to any a new component will be generated. I use 3 sigma => Tg=3*3=9. - //Smaller Tg leads to more generated components and higher Tg might make - //lead to small number of components but they can grow too large - float fVarInit; - float fVarMin; - float fVarMax; - - //initial variance for the newly generated components. - //It will will influence the speed of adaptation. A good guess should be made. - //A simple way is to estimate the typical standard deviation from the images. - //I used here 10 as a reasonable value - // min and max can be used to further control the variance - float fCT; //CT - complexity reduction prior - //this is related to the number of samples needed to accept that a component - //actually exists. We use CT=0.05 of all the samples. By setting CT=0 you get - //the standard Stauffer&Grimson algorithm (maybe not exact but very similar) - - //shadow detection parameters - bool bShadowDetection; //default 1 - do shadow detection - unsigned char nShadowDetection; //do shadow detection - insert this value as the detection result - 127 default value - float fTau; - // Tau - shadow threshold. The shadow is detected if the pixel is darker - //version of the background. Tau is a threshold on how much darker the shadow can be. - //Tau= 0.5 means that if pixel is more than 2 times darker then it is not shadow - //See: Prati,Mikic,Trivedi,Cucchiarra,"Detecting Moving Shadows...",IEEE PAMI,2003. - -private: - int nmixtures_; - - Size frameSize_; - int frameType_; - int nframes_; - - GpuMat weight_; - GpuMat variance_; - GpuMat mean_; - - GpuMat bgmodelUsedModes_; //keep track of number of modes per pixel -}; - /** * Background Subtractor module. Takes a series of images and returns a sequence of mask (8UC1) * images of the same size, where 255 indicates Foreground and 0 represents Background. diff --git a/modules/gpubgsegm/perf/perf_bgsegm.cpp b/modules/gpubgsegm/perf/perf_bgsegm.cpp index a05428887ff0af4581fa497f498725f6ad01f8c0..8ec9c6f026a0be526663414f8a27054c98e66b13 100644 --- a/modules/gpubgsegm/perf/perf_bgsegm.cpp +++ b/modules/gpubgsegm/perf/perf_bgsegm.cpp @@ -274,13 +274,13 @@ PERF_TEST_P(Video_Cn, MOG2, if (PERF_RUN_GPU()) { - cv::gpu::MOG2_GPU d_mog2; - d_mog2.bShadowDetection = false; + cv::Ptr d_mog2 = cv::gpu::createBackgroundSubtractorMOG2(); + d_mog2->setDetectShadows(false); cv::gpu::GpuMat d_frame(frame); cv::gpu::GpuMat foreground; - d_mog2(d_frame, foreground); + d_mog2->apply(d_frame, foreground); for (int i = 0; i < 10; ++i) { @@ -300,7 +300,7 @@ PERF_TEST_P(Video_Cn, MOG2, d_frame.upload(frame); startTimer(); next(); - d_mog2(d_frame, foreground); + d_mog2->apply(d_frame, foreground); stopTimer(); } @@ -308,8 +308,8 @@ PERF_TEST_P(Video_Cn, MOG2, } else { - cv::Ptr mog2 = cv::createBackgroundSubtractorMOG2(); - mog2->set("detectShadows", false); + cv::Ptr mog2 = cv::createBackgroundSubtractorMOG2(); + mog2->setDetectShadows(false); cv::Mat foreground; @@ -360,8 +360,9 @@ PERF_TEST_P(Video_Cn, MOG2GetBackgroundImage, if (PERF_RUN_GPU()) { + cv::Ptr d_mog2 = cv::gpu::createBackgroundSubtractorMOG2(); + cv::gpu::GpuMat d_frame; - cv::gpu::MOG2_GPU d_mog2; cv::gpu::GpuMat d_foreground; for (int i = 0; i < 10; ++i) @@ -381,12 +382,12 @@ PERF_TEST_P(Video_Cn, MOG2GetBackgroundImage, d_frame.upload(frame); - d_mog2(d_frame, d_foreground); + d_mog2->apply(d_frame, d_foreground); } cv::gpu::GpuMat background; - TEST_CYCLE() d_mog2.getBackgroundImage(background); + TEST_CYCLE() d_mog2->getBackgroundImage(background); GPU_SANITY_CHECK(background, 1); } diff --git a/modules/gpubgsegm/src/mog2.cpp b/modules/gpubgsegm/src/mog2.cpp index 7258294c251347560f8ac3c8bdcbabadcdce1bab..5ab731f7e3de536653350755751475b3a21384d1 100644 --- a/modules/gpubgsegm/src/mog2.cpp +++ b/modules/gpubgsegm/src/mog2.cpp @@ -42,13 +42,12 @@ #include "precomp.hpp" +using namespace cv; +using namespace cv::gpu; + #if !defined HAVE_CUDA || defined(CUDA_DISABLER) -cv::gpu::MOG2_GPU::MOG2_GPU(int) { throw_no_cuda(); } -void cv::gpu::MOG2_GPU::initialize(cv::Size, int) { throw_no_cuda(); } -void cv::gpu::MOG2_GPU::operator()(const GpuMat&, GpuMat&, float, Stream&) { throw_no_cuda(); } -void cv::gpu::MOG2_GPU::getBackgroundImage(GpuMat&, Stream&) const { throw_no_cuda(); } -void cv::gpu::MOG2_GPU::release() {} +Ptr cv::gpu::createBackgroundSubtractorMOG2(int, double, bool) { throw_no_cuda(); return Ptr(); } #else @@ -62,7 +61,7 @@ namespace cv { namespace gpu { namespace cudev } }}} -namespace mog2 +namespace { // default parameters of gaussian background detection algorithm const int defaultHistory = 500; // Learning rate; alpha = 1/defaultHistory2 @@ -75,99 +74,180 @@ namespace mog2 const float defaultVarMin = 4.0f; // additional parameters - const float defaultfCT = 0.05f; // complexity reduction prior constant 0 - no reduction of number of components - const unsigned char defaultnShadowDetection = 127; // value to use in the segmentation mask for shadows, set 0 not to do shadow detection - const float defaultfTau = 0.5f; // Tau - shadow threshold, see the paper for explanation -} + const float defaultCT = 0.05f; // complexity reduction prior constant 0 - no reduction of number of components + const unsigned char defaultShadowValue = 127; // value to use in the segmentation mask for shadows, set 0 not to do shadow detection + const float defaultShadowThreshold = 0.5f; // Tau - shadow threshold, see the paper for explanation -cv::gpu::MOG2_GPU::MOG2_GPU(int nmixtures) : - frameSize_(0, 0), frameType_(0), nframes_(0) -{ - nmixtures_ = nmixtures > 0 ? nmixtures : mog2::defaultNMixtures; + class MOG2Impl : public gpu::BackgroundSubtractorMOG2 + { + public: + MOG2Impl(int history, double varThreshold, bool detectShadows); - history = mog2::defaultHistory; - varThreshold = mog2::defaultVarThreshold; - bShadowDetection = true; + void apply(InputArray image, OutputArray fgmask, double learningRate=-1); + void apply(InputArray image, OutputArray fgmask, double learningRate, Stream& stream); - backgroundRatio = mog2::defaultBackgroundRatio; - fVarInit = mog2::defaultVarInit; - fVarMax = mog2::defaultVarMax; - fVarMin = mog2::defaultVarMin; + void getBackgroundImage(OutputArray backgroundImage) const; + void getBackgroundImage(OutputArray backgroundImage, Stream& stream) const; - varThresholdGen = mog2::defaultVarThresholdGen; - fCT = mog2::defaultfCT; - nShadowDetection = mog2::defaultnShadowDetection; - fTau = mog2::defaultfTau; -} + int getHistory() const { return history_; } + void setHistory(int history) { history_ = history; } -void cv::gpu::MOG2_GPU::initialize(cv::Size frameSize, int frameType) -{ - using namespace cv::gpu::cudev::mog2; + int getNMixtures() const { return nmixtures_; } + void setNMixtures(int nmixtures) { nmixtures_ = nmixtures; } - CV_Assert(frameType == CV_8UC1 || frameType == CV_8UC3 || frameType == CV_8UC4); + double getBackgroundRatio() const { return backgroundRatio_; } + void setBackgroundRatio(double ratio) { backgroundRatio_ = (float) ratio; } - frameSize_ = frameSize; - frameType_ = frameType; - nframes_ = 0; + double getVarThreshold() const { return varThreshold_; } + void setVarThreshold(double varThreshold) { varThreshold_ = (float) varThreshold; } - int ch = CV_MAT_CN(frameType); - int work_ch = ch; + double getVarThresholdGen() const { return varThresholdGen_; } + void setVarThresholdGen(double varThresholdGen) { varThresholdGen_ = (float) varThresholdGen; } - // for each gaussian mixture of each pixel bg model we store ... - // the mixture weight (w), - // the mean (nchannels values) and - // the covariance - weight_.create(frameSize.height * nmixtures_, frameSize_.width, CV_32FC1); - variance_.create(frameSize.height * nmixtures_, frameSize_.width, CV_32FC1); - mean_.create(frameSize.height * nmixtures_, frameSize_.width, CV_32FC(work_ch)); + double getVarInit() const { return varInit_; } + void setVarInit(double varInit) { varInit_ = (float) varInit; } - //make the array for keeping track of the used modes per pixel - all zeros at start - bgmodelUsedModes_.create(frameSize_, CV_8UC1); - bgmodelUsedModes_.setTo(cv::Scalar::all(0)); + double getVarMin() const { return varMin_; } + void setVarMin(double varMin) { varMin_ = (float) varMin; } - loadConstants(nmixtures_, varThreshold, backgroundRatio, varThresholdGen, fVarInit, fVarMin, fVarMax, fTau, nShadowDetection); -} + double getVarMax() const { return varMax_; } + void setVarMax(double varMax) { varMax_ = (float) varMax; } -void cv::gpu::MOG2_GPU::operator()(const GpuMat& frame, GpuMat& fgmask, float learningRate, Stream& stream) -{ - using namespace cv::gpu::cudev::mog2; + double getComplexityReductionThreshold() const { return ct_; } + void setComplexityReductionThreshold(double ct) { ct_ = (float) ct; } - int ch = frame.channels(); - int work_ch = ch; + bool getDetectShadows() const { return detectShadows_; } + void setDetectShadows(bool detectShadows) { detectShadows_ = detectShadows; } - if (nframes_ == 0 || learningRate >= 1.0f || frame.size() != frameSize_ || work_ch != mean_.channels()) - initialize(frame.size(), frame.type()); + int getShadowValue() const { return shadowValue_; } + void setShadowValue(int value) { shadowValue_ = (uchar) value; } - fgmask.create(frameSize_, CV_8UC1); - fgmask.setTo(cv::Scalar::all(0)); + double getShadowThreshold() const { return shadowThreshold_; } + void setShadowThreshold(double threshold) { shadowThreshold_ = (float) threshold; } - ++nframes_; - learningRate = learningRate >= 0.0f && nframes_ > 1 ? learningRate : 1.0f / std::min(2 * nframes_, history); - CV_Assert(learningRate >= 0.0f); + private: + void initialize(Size frameSize, int frameType); - mog2_gpu(frame, frame.channels(), fgmask, bgmodelUsedModes_, weight_, variance_, mean_, learningRate, -learningRate * fCT, bShadowDetection, StreamAccessor::getStream(stream)); -} + int history_; + int nmixtures_; + float backgroundRatio_; + float varThreshold_; + float varThresholdGen_; + float varInit_; + float varMin_; + float varMax_; + float ct_; + bool detectShadows_; + uchar shadowValue_; + float shadowThreshold_; -void cv::gpu::MOG2_GPU::getBackgroundImage(GpuMat& backgroundImage, Stream& stream) const -{ - using namespace cv::gpu::cudev::mog2; + Size frameSize_; + int frameType_; + int nframes_; - backgroundImage.create(frameSize_, frameType_); + GpuMat weight_; + GpuMat variance_; + GpuMat mean_; - getBackgroundImage2_gpu(backgroundImage.channels(), bgmodelUsedModes_, weight_, mean_, backgroundImage, StreamAccessor::getStream(stream)); -} + //keep track of number of modes per pixel + GpuMat bgmodelUsedModes_; + }; -void cv::gpu::MOG2_GPU::release() -{ - frameSize_ = Size(0, 0); - frameType_ = 0; - nframes_ = 0; + MOG2Impl::MOG2Impl(int history, double varThreshold, bool detectShadows) : + frameSize_(0, 0), frameType_(0), nframes_(0) + { + history_ = history > 0 ? history : defaultHistory; + varThreshold_ = varThreshold > 0 ? (float) varThreshold : defaultVarThreshold; + detectShadows_ = detectShadows; + + nmixtures_ = defaultNMixtures; + backgroundRatio_ = defaultBackgroundRatio; + varInit_ = defaultVarInit; + varMax_ = defaultVarMax; + varMin_ = defaultVarMin; + varThresholdGen_ = defaultVarThresholdGen; + ct_ = defaultCT; + shadowValue_ = defaultShadowValue; + shadowThreshold_ = defaultShadowThreshold; + } + + void MOG2Impl::apply(InputArray image, OutputArray fgmask, double learningRate) + { + apply(image, fgmask, learningRate, Stream::Null()); + } + + void MOG2Impl::apply(InputArray _frame, OutputArray _fgmask, double learningRate, Stream& stream) + { + using namespace cv::gpu::cudev::mog2; + + GpuMat frame = _frame.getGpuMat(); + + int ch = frame.channels(); + int work_ch = ch; + + if (nframes_ == 0 || learningRate >= 1.0 || frame.size() != frameSize_ || work_ch != mean_.channels()) + initialize(frame.size(), frame.type()); + + _fgmask.create(frameSize_, CV_8UC1); + GpuMat fgmask = _fgmask.getGpuMat(); - weight_.release(); - variance_.release(); - mean_.release(); + fgmask.setTo(Scalar::all(0), stream); - bgmodelUsedModes_.release(); + ++nframes_; + learningRate = learningRate >= 0 && nframes_ > 1 ? learningRate : 1.0 / std::min(2 * nframes_, history_); + CV_Assert( learningRate >= 0 ); + + mog2_gpu(frame, frame.channels(), fgmask, bgmodelUsedModes_, weight_, variance_, mean_, + (float) learningRate, static_cast(-learningRate * ct_), detectShadows_, StreamAccessor::getStream(stream)); + } + + void MOG2Impl::getBackgroundImage(OutputArray backgroundImage) const + { + getBackgroundImage(backgroundImage, Stream::Null()); + } + + void MOG2Impl::getBackgroundImage(OutputArray _backgroundImage, Stream& stream) const + { + using namespace cv::gpu::cudev::mog2; + + _backgroundImage.create(frameSize_, frameType_); + GpuMat backgroundImage = _backgroundImage.getGpuMat(); + + getBackgroundImage2_gpu(backgroundImage.channels(), bgmodelUsedModes_, weight_, mean_, backgroundImage, StreamAccessor::getStream(stream)); + } + + void MOG2Impl::initialize(cv::Size frameSize, int frameType) + { + using namespace cv::gpu::cudev::mog2; + + CV_Assert( frameType == CV_8UC1 || frameType == CV_8UC3 || frameType == CV_8UC4 ); + + frameSize_ = frameSize; + frameType_ = frameType; + nframes_ = 0; + + int ch = CV_MAT_CN(frameType); + int work_ch = ch; + + // for each gaussian mixture of each pixel bg model we store ... + // the mixture weight (w), + // the mean (nchannels values) and + // the covariance + weight_.create(frameSize.height * nmixtures_, frameSize_.width, CV_32FC1); + variance_.create(frameSize.height * nmixtures_, frameSize_.width, CV_32FC1); + mean_.create(frameSize.height * nmixtures_, frameSize_.width, CV_32FC(work_ch)); + + //make the array for keeping track of the used modes per pixel - all zeros at start + bgmodelUsedModes_.create(frameSize_, CV_8UC1); + bgmodelUsedModes_.setTo(Scalar::all(0)); + + loadConstants(nmixtures_, varThreshold_, backgroundRatio_, varThresholdGen_, varInit_, varMin_, varMax_, shadowThreshold_, shadowValue_); + } +} + +Ptr cv::gpu::createBackgroundSubtractorMOG2(int history, double varThreshold, bool detectShadows) +{ + return new MOG2Impl(history, varThreshold, detectShadows); } #endif diff --git a/modules/gpubgsegm/test/test_bgsegm.cpp b/modules/gpubgsegm/test/test_bgsegm.cpp index 588db8badfcdddf6fe61d6ec60ab189755b1a2a5..4ff505f9aa1c31edde30bebc0f7c82e355d4cce1 100644 --- a/modules/gpubgsegm/test/test_bgsegm.cpp +++ b/modules/gpubgsegm/test/test_bgsegm.cpp @@ -267,8 +267,8 @@ GPU_TEST_P(MOG2, Update) cap >> frame; ASSERT_FALSE(frame.empty()); - cv::gpu::MOG2_GPU mog2; - mog2.bShadowDetection = detectShadow; + cv::Ptr mog2 = cv::gpu::createBackgroundSubtractorMOG2(); + mog2->setDetectShadows(detectShadow); cv::gpu::GpuMat foreground = createMat(frame.size(), CV_8UC1, useRoi); cv::Ptr mog2_gold = cv::createBackgroundSubtractorMOG2(); @@ -287,7 +287,7 @@ GPU_TEST_P(MOG2, Update) cv::swap(temp, frame); } - mog2(loadMat(frame, useRoi), foreground); + mog2->apply(loadMat(frame, useRoi), foreground); mog2_gold->apply(frame, foreground_gold); @@ -312,8 +312,8 @@ GPU_TEST_P(MOG2, getBackgroundImage) cv::Mat frame; - cv::gpu::MOG2_GPU mog2; - mog2.bShadowDetection = detectShadow; + cv::Ptr mog2 = cv::gpu::createBackgroundSubtractorMOG2(); + mog2->setDetectShadows(detectShadow); cv::gpu::GpuMat foreground; cv::Ptr mog2_gold = cv::createBackgroundSubtractorMOG2(); @@ -325,13 +325,13 @@ GPU_TEST_P(MOG2, getBackgroundImage) cap >> frame; ASSERT_FALSE(frame.empty()); - mog2(loadMat(frame, useRoi), foreground); + mog2->apply(loadMat(frame, useRoi), foreground); mog2_gold->apply(frame, foreground_gold); } cv::gpu::GpuMat background = createMat(frame.size(), frame.type(), useRoi); - mog2.getBackgroundImage(background); + mog2->getBackgroundImage(background); cv::Mat background_gold; mog2_gold->getBackgroundImage(background_gold); diff --git a/samples/gpu/bgfg_segm.cpp b/samples/gpu/bgfg_segm.cpp index 3fe26a99dae7e088f2c41efaf51b800bf3c9e5c9..01d8680f3615250c7a7d61afdbc8127978fbf56d 100644 --- a/samples/gpu/bgfg_segm.cpp +++ b/samples/gpu/bgfg_segm.cpp @@ -76,8 +76,8 @@ int main(int argc, const char** argv) GpuMat d_frame(frame); FGDStatModel fgd_stat; - cv::Ptr mog = cv::gpu::createBackgroundSubtractorMOG(); - MOG2_GPU mog2; + cv::Ptr mog = cv::gpu::createBackgroundSubtractorMOG(); + cv::Ptr mog2 = cv::gpu::createBackgroundSubtractorMOG2(); GMG_GPU gmg; gmg.numInitializationFrames = 40; @@ -100,7 +100,7 @@ int main(int argc, const char** argv) break; case MOG2: - mog2(d_frame, d_fgmask); + mog2->apply(d_frame, d_fgmask); break; case GMG: @@ -140,8 +140,8 @@ int main(int argc, const char** argv) break; case MOG2: - mog2(d_frame, d_fgmask); - mog2.getBackgroundImage(d_bgimg); + mog2->apply(d_frame, d_fgmask); + mog2->getBackgroundImage(d_bgimg); break; case GMG: diff --git a/samples/gpu/performance/tests.cpp b/samples/gpu/performance/tests.cpp index 3681ff95c2946b3788ab3f76bb2b9df81d90f393..b0217c3c3a48484c2948b1197890dcdd85f2f7ab 100644 --- a/samples/gpu/performance/tests.cpp +++ b/samples/gpu/performance/tests.cpp @@ -1399,13 +1399,13 @@ TEST(MOG2) cap >> frame; + cv::Ptr d_mog2 = cv::gpu::createBackgroundSubtractorMOG2(); cv::gpu::GpuMat d_frame(frame); - cv::gpu::MOG2_GPU d_mog2; cv::gpu::GpuMat d_foreground; cv::gpu::GpuMat d_background; - d_mog2(d_frame, d_foreground); - d_mog2.getBackgroundImage(d_background); + d_mog2->apply(d_frame, d_foreground); + d_mog2->getBackgroundImage(d_background); while (!TestSystem::instance().stop()) { @@ -1414,8 +1414,8 @@ TEST(MOG2) TestSystem::instance().gpuOn(); - d_mog2(d_frame, d_foreground); - d_mog2.getBackgroundImage(d_background); + d_mog2->apply(d_frame, d_foreground); + d_mog2->getBackgroundImage(d_background); TestSystem::instance().gpuOff(); }