Skip to content

Commit 708215d

Browse files
D00EGitOps Bot
authored andcommitted
Merge pull request opencv#27810 from D00E:known-foreground-mask
2025-10-14T05:53:31.5387050Z C:\GHA-OCV-1\_work\ci-gha-workflow\ci-gha-workflow\opencv\modules\imgcodecs\src\bitstrm.cpp(156,57): warning C4244: 'argument': conversion from 'int64_t' to 'ptrdiff_t', possible loss of data [C:\GHA-OCV-1\_work\ci-gha-workflow\ci-gha-workflow\build\modules\imgcodecs\opencv_imgcodecs.vcxproj] ### Pull Request Readiness Checklist Optional Known Foreground Mask for Background Subtractors opencv#27810 See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake ### Description This adds an optional foreground input mask parameter to the MOG2 and KNN background subtractors, in line with issue opencv#26476 4 tests are added under test_bgfg2.cpp: 2 for each subtractor type (1 with shadow detection and 1 without) A demo shows the feature with only 3 parameters and with a 4th optional foreground mask for both core subtractor types. Note: To patch contrib inheritance of the background subtraction class, empty apply method which throws a not implemented error is added to contrib subclasses. This is done to keep the overloaded apply function as pure virtual. Contrib PR to be made and linked shortly. Contrib Repo Paired Pull Request: opencv/opencv_contrib#4017
1 parent e33141d commit 708215d

File tree

5 files changed

+268
-6
lines changed

5 files changed

+268
-6
lines changed

modules/video/include/opencv2/video/background_segm.hpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,21 @@ class CV_EXPORTS_W BackgroundSubtractor : public Algorithm
7171
*/
7272
CV_WRAP virtual void apply(InputArray image, OutputArray fgmask, double learningRate=-1) = 0;
7373

74+
/** @brief Computes a foreground mask with known foreground mask input.
75+
76+
@param image Next video frame. Floating point frame will be used without scaling and should be in range \f$[0,255]\f$.
77+
@param fgmask The output foreground mask as an 8-bit binary image.
78+
@param knownForegroundMask The mask for inputting already known foreground, allows model to ignore pixels.
79+
@param learningRate The value between 0 and 1 that indicates how fast the background model is
80+
learnt. Negative parameter value makes the algorithm to use some automatically chosen learning
81+
rate. 0 means that the background model is not updated at all, 1 means that the background model
82+
is completely reinitialized from the last frame.
83+
84+
@note This method has a default virtual implementation that throws a "not impemented" error.
85+
Foreground masking may not be supported by all background subtractors.
86+
*/
87+
CV_WRAP virtual void apply(InputArray image, InputArray knownForegroundMask, OutputArray fgmask, double learningRate=-1) = 0;
88+
7489
/** @brief Computes a background image.
7590
7691
@param backgroundImage The output background image.
@@ -206,6 +221,18 @@ class CV_EXPORTS_W BackgroundSubtractorMOG2 : public BackgroundSubtractor
206221
is completely reinitialized from the last frame.
207222
*/
208223
CV_WRAP virtual void apply(InputArray image, OutputArray fgmask, double learningRate=-1) CV_OVERRIDE = 0;
224+
225+
/** @brief Computes a foreground mask and skips known foreground in evaluation.
226+
227+
@param image Next video frame. Floating point frame will be used without scaling and should be in range \f$[0,255]\f$.
228+
@param fgmask The output foreground mask as an 8-bit binary image.
229+
@param knownForegroundMask The mask for inputting already known foreground, allows model to ignore pixels.
230+
@param learningRate The value between 0 and 1 that indicates how fast the background model is
231+
learnt. Negative parameter value makes the algorithm to use some automatically chosen learning
232+
rate. 0 means that the background model is not updated at all, 1 means that the background model
233+
is completely reinitialized from the last frame.
234+
*/
235+
CV_WRAP virtual void apply(InputArray image, InputArray knownForegroundMask, OutputArray fgmask, double learningRate=-1) CV_OVERRIDE = 0;
209236
};
210237

211238
/** @brief Creates MOG2 Background Subtractor

modules/video/src/bgfg_KNN.cpp

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ class BackgroundSubtractorKNNImpl CV_FINAL : public BackgroundSubtractorKNN
132132
//! the update operator
133133
void apply(InputArray image, OutputArray fgmask, double learningRate) CV_OVERRIDE;
134134

135+
void apply(InputArray image, InputArray knownForegroundMask, OutputArray fgmask, double learningRate) CV_OVERRIDE;
136+
135137
//! computes a background image which are the mean of all background gaussians
136138
virtual void getBackgroundImage(OutputArray backgroundImage) const CV_OVERRIDE;
137139

@@ -526,7 +528,9 @@ class KNNInvoker : public ParallelLoopBody
526528
int _nkNN,
527529
float _fTau,
528530
bool _bShadowDetection,
529-
uchar _nShadowDetection)
531+
uchar _nShadowDetection,
532+
const Mat& _knownForegroundMask)
533+
: knownForegroundMask(_knownForegroundMask)
530534
{
531535
src = &_src;
532536
dst = &_dst;
@@ -587,6 +591,17 @@ class KNNInvoker : public ParallelLoopBody
587591
m_nShortCounter,
588592
include
589593
);
594+
// Check that foreground mask exists
595+
if (!knownForegroundMask.empty()) {
596+
// If input mask states pixel is foreground
597+
if (knownForegroundMask.at<uchar>(y, x) > 0)
598+
{
599+
mask[x] = 255; // ensure output mask marks this pixel as FG
600+
data += nchannels;
601+
m_aModel += m_nN*3*ndata;
602+
continue;
603+
}
604+
}
590605
switch (result)
591606
{
592607
case 0:
@@ -626,6 +641,7 @@ class KNNInvoker : public ParallelLoopBody
626641
int m_nkNN;
627642
bool m_bShadowDetection;
628643
uchar m_nShadowDetection;
644+
const Mat& knownForegroundMask;
629645
};
630646

631647
#ifdef HAVE_OPENCL
@@ -728,7 +744,12 @@ void BackgroundSubtractorKNNImpl::create_ocl_apply_kernel()
728744

729745
#endif
730746

731-
void BackgroundSubtractorKNNImpl::apply(InputArray _image, OutputArray _fgmask, double learningRate)
747+
// Base 3 version class
748+
void BackgroundSubtractorKNNImpl::apply(InputArray _image, OutputArray _fgmask, double learningRate) {
749+
apply(_image, noArray(), _fgmask, learningRate);
750+
}
751+
752+
void BackgroundSubtractorKNNImpl::apply(InputArray _image, InputArray _knownForegroundMask, OutputArray _fgmask, double learningRate)
732753
{
733754
CV_INSTRUMENT_REGION();
734755

@@ -757,6 +778,14 @@ void BackgroundSubtractorKNNImpl::apply(InputArray _image, OutputArray _fgmask,
757778
_fgmask.create( image.size(), CV_8U );
758779
Mat fgmask = _fgmask.getMat();
759780

781+
Mat knownForegroundMask = _knownForegroundMask.getMat();
782+
783+
if(!knownForegroundMask.empty())
784+
{
785+
CV_Assert(knownForegroundMask.type() == CV_8UC1);
786+
CV_Assert(knownForegroundMask.size() == image.size());
787+
}
788+
760789
++nframes;
761790
learningRate = learningRate >= 0 && nframes > 1 ? learningRate : 1./std::min( 2*nframes, history );
762791
CV_Assert(learningRate >= 0);
@@ -791,7 +820,8 @@ void BackgroundSubtractorKNNImpl::apply(InputArray _image, OutputArray _fgmask,
791820
nkNN,
792821
fTau,
793822
bShadowDetection,
794-
nShadowDetection),
823+
nShadowDetection,
824+
knownForegroundMask),
795825
image.total()/(double)(1 << 16));
796826

797827
nShortCounter++;//0,1,...,nShortUpdate-1

modules/video/src/bgfg_gaussmix2.cpp

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ class BackgroundSubtractorMOG2Impl CV_FINAL : public BackgroundSubtractorMOG2
178178
//! the update operator
179179
void apply(InputArray image, OutputArray fgmask, double learningRate) CV_OVERRIDE;
180180

181+
void apply(InputArray image, InputArray knownForegroundMask, OutputArray fgmask, double learningRate) CV_OVERRIDE;
182+
181183
//! computes a background image which are the mean of all background gaussians
182184
virtual void getBackgroundImage(OutputArray backgroundImage) const CV_OVERRIDE;
183185

@@ -546,7 +548,8 @@ class MOG2Invoker : public ParallelLoopBody
546548
float _Tb, float _TB, float _Tg,
547549
float _varInit, float _varMin, float _varMax,
548550
float _prune, float _tau, bool _detectShadows,
549-
uchar _shadowVal)
551+
uchar _shadowVal, const Mat& _knownForegroundMask)
552+
: knownForegroundMask(_knownForegroundMask)
550553
{
551554
src = &_src;
552555
dst = &_dst;
@@ -590,6 +593,18 @@ class MOG2Invoker : public ParallelLoopBody
590593

591594
for( int x = 0; x < ncols; x++, data += nchannels, gmm += nmixtures, mean += nmixtures*nchannels )
592595
{
596+
597+
// Check that foreground mask exists
598+
if (!knownForegroundMask.empty())
599+
{
600+
// If input mask states pixel is foreground
601+
if (knownForegroundMask.at<uchar>(y, x) > 0)
602+
{
603+
mask[x] = 255; // ensure output mask marks this pixel as FG
604+
continue;
605+
}
606+
}
607+
593608
//calculate distances to the modes (+ sort)
594609
//here we need to go in descending order!!!
595610
bool background = false;//return value -> true - the pixel classified as background
@@ -766,6 +781,7 @@ class MOG2Invoker : public ParallelLoopBody
766781

767782
bool detectShadows;
768783
uchar shadowVal;
784+
const Mat& knownForegroundMask;
769785
};
770786

771787
#ifdef HAVE_OPENCL
@@ -844,7 +860,12 @@ void BackgroundSubtractorMOG2Impl::create_ocl_apply_kernel()
844860

845861
#endif
846862

847-
void BackgroundSubtractorMOG2Impl::apply(InputArray _image, OutputArray _fgmask, double learningRate)
863+
// Base 3 version class
864+
void BackgroundSubtractorMOG2Impl::apply(InputArray _image, OutputArray _fgmask, double learningRate) {
865+
apply(_image, noArray(), _fgmask, learningRate);
866+
}
867+
868+
void BackgroundSubtractorMOG2Impl::apply(InputArray _image, InputArray _knownForegroundMask, OutputArray _fgmask, double learningRate)
848869
{
849870
CV_INSTRUMENT_REGION();
850871

@@ -867,6 +888,14 @@ void BackgroundSubtractorMOG2Impl::apply(InputArray _image, OutputArray _fgmask,
867888
_fgmask.create( image.size(), CV_8U );
868889
Mat fgmask = _fgmask.getMat();
869890

891+
Mat knownForegroundMask = _knownForegroundMask.getMat();
892+
893+
if(!knownForegroundMask.empty())
894+
{
895+
CV_Assert(knownForegroundMask.type() == CV_8UC1);
896+
CV_Assert(knownForegroundMask.size() == image.size());
897+
}
898+
870899
++nframes;
871900
learningRate = learningRate >= 0 && nframes > 1 ? learningRate : 1./std::min( 2*nframes, history );
872901
CV_Assert(learningRate >= 0);
@@ -879,7 +908,7 @@ void BackgroundSubtractorMOG2Impl::apply(InputArray _image, OutputArray _fgmask,
879908
(float)varThreshold,
880909
backgroundRatio, varThresholdGen,
881910
fVarInit, fVarMin, fVarMax, float(-learningRate*fCT), fTau,
882-
bShadowDetection, nShadowDetection),
911+
bShadowDetection, nShadowDetection, knownForegroundMask),
883912
image.total()/(double)(1 << 16));
884913
}
885914

modules/video/test/test_bgfg2.cpp

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// This file is part of OpenCV project.
2+
// It is subject to the license terms in the LICENSE file found in the top-level directory
3+
// of this distribution and at http://opencv.org/license.html.
4+
5+
#include "test_precomp.hpp"
6+
#include "opencv2/video/background_segm.hpp"
7+
8+
namespace opencv_test { namespace {
9+
10+
using namespace cv;
11+
12+
///////////////////////// MOG2 //////////////////////////////
13+
TEST(BackgroundSubtractorMOG2, KnownForegroundMaskShadowsTrue)
14+
{
15+
Ptr<BackgroundSubtractorMOG2> mog2 = createBackgroundSubtractorMOG2(500, 16, true);
16+
17+
//Black Frame
18+
Mat input = Mat::zeros(480,640 , CV_8UC3);
19+
20+
//White Rectangle
21+
Mat knownFG = Mat::zeros(input.size(), CV_8U);
22+
23+
rectangle(knownFG, Rect(3,3,5,5), Scalar(255,255,255), -1);
24+
25+
Mat output;
26+
mog2->apply(input, knownFG, output);
27+
28+
for(int y = 3; y < 8; y++)
29+
{
30+
for (int x = 3; x < 8; x++){
31+
EXPECT_EQ(255,output.at<uchar>(y,x)) << "Expected foreground at (" << x << "," << y << ")";
32+
}
33+
}
34+
}
35+
36+
TEST(BackgroundSubtractorMOG2, KnownForegroundMaskShadowsFalse)
37+
{
38+
Ptr<BackgroundSubtractorMOG2> mog2 = createBackgroundSubtractorMOG2(500, 16, false);
39+
40+
//Black Frame
41+
Mat input = Mat::zeros(480,640 , CV_8UC3);
42+
43+
//White Rectangle
44+
Mat knownFG = Mat::zeros(input.size(), CV_8U);
45+
46+
rectangle(knownFG, Rect(3,3,5,5), Scalar(255,255,255), FILLED);
47+
48+
Mat output;
49+
mog2->apply(input, knownFG, output);
50+
51+
for(int y = 3; y < 8; y++)
52+
{
53+
for (int x = 3; x < 8; x++){
54+
EXPECT_EQ(255,output.at<uchar>(y,x)) << "Expected foreground at (" << x << "," << y << ")";
55+
}
56+
}
57+
}
58+
59+
///////////////////////// KNN //////////////////////////////
60+
61+
TEST(BackgroundSubtractorKNN, KnownForegroundMaskShadowsTrue)
62+
{
63+
Ptr<BackgroundSubtractorKNN> knn = createBackgroundSubtractorKNN(500, 400.0, true);
64+
65+
//Black Frame
66+
Mat input = Mat::zeros(480,640 , CV_8UC3);
67+
68+
//White Rectangle
69+
Mat knownFG = Mat::zeros(input.size(), CV_8U);
70+
71+
rectangle(knownFG, Rect(3,3,5,5), Scalar(255,255,255), FILLED);
72+
73+
Mat output;
74+
knn->apply(input, knownFG, output);
75+
76+
for(int y = 3; y < 8; y++)
77+
{
78+
for (int x = 3; x < 8; x++){
79+
EXPECT_EQ(255,output.at<uchar>(y,x)) << "Expected foreground at (" << x << "," << y << ")";
80+
}
81+
}
82+
}
83+
84+
TEST(BackgroundSubtractorKNN, KnownForegroundMaskShadowsFalse)
85+
{
86+
Ptr<BackgroundSubtractorKNN> knn = createBackgroundSubtractorKNN(500, 400.0, false);
87+
88+
//Black Frame
89+
Mat input = Mat::zeros(480,640 , CV_8UC3);
90+
91+
//White Rectangle
92+
Mat knownFG = Mat::zeros(input.size(), CV_8U);
93+
94+
rectangle(knownFG, Rect(3,3,5,5), Scalar(255,255,255), FILLED);
95+
96+
Mat output;
97+
knn->apply(input, knownFG, output);
98+
99+
for(int y = 3; y < 8; y++)
100+
{
101+
for (int x = 3; x < 8; x++){
102+
EXPECT_EQ(255,output.at<uchar>(y,x)) << "Expected foreground at (" << x << "," << y << ")";
103+
}
104+
}
105+
}
106+
107+
}} // namespace
108+
/* End of file. */
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
2+
'''
3+
Showcases the use of background subtraction from a live video feed,
4+
aswell as pass through of a known foreground parameter
5+
'''
6+
7+
# Python 2/3 compatibility
8+
from __future__ import print_function
9+
10+
import numpy as np
11+
import cv2 as cv
12+
13+
def main():
14+
cap = cv.VideoCapture(0)
15+
if not cap.isOpened:
16+
print("Capture source avaialable.")
17+
exit()
18+
19+
# Create background subtractor
20+
mog2_bg_subtractor = cv.createBackgroundSubtractorMOG2(history=300, varThreshold=50, detectShadows=False)
21+
knn_bg_subtractor = cv.createBackgroundSubtractorKNN(history=300, detectShadows=False)
22+
23+
frame_count = 0
24+
# Allows for a frame buffer for the mask to learn pre known foreground
25+
show_count = 10
26+
27+
while True:
28+
ret, frame = cap.read()
29+
if not ret:
30+
break
31+
32+
x = 100 + (frame_count % 10) * 3
33+
34+
frame = cv.resize(frame, (640, 480))
35+
aKnownForegroundMask = np.zeros(frame.shape[:2], dtype=np.uint8)
36+
37+
# Allow for models to "settle"/learn
38+
if frame_count > show_count:
39+
cv.rectangle(aKnownForegroundMask, (x,200), (x+50,300), 255, -1)
40+
cv.rectangle(aKnownForegroundMask, (540,180), (640,480), 255, -1)
41+
42+
#MOG2 Subtraction
43+
mog2_with_mask = mog2_bg_subtractor.apply(frame,knownForegroundMask=aKnownForegroundMask)
44+
mog2_without_mask = mog2_bg_subtractor.apply(frame)
45+
46+
#KNN Subtraction
47+
knn_with_mask = knn_bg_subtractor.apply(frame,knownForegroundMask=aKnownForegroundMask)
48+
knn_without_mask = knn_bg_subtractor.apply(frame)
49+
50+
# Display the 3 parameter apply and the 4 parameter apply for both subtractors
51+
cv.imshow("MOG2 With a Foreground Mask", mog2_with_mask)
52+
cv.imshow("MOG2 Without a Foreground Mask", mog2_without_mask)
53+
cv.imshow("KNN With a Foreground Mask", knn_with_mask)
54+
cv.imshow("KNN Without a Foreground Mask", knn_without_mask)
55+
56+
key = cv.waitKey(30)
57+
if key == 27: # ESC
58+
break
59+
60+
frame_count += 1
61+
62+
cap.release()
63+
cv.destroyAllWindows()
64+
65+
if __name__ == '__main__':
66+
print(__doc__)
67+
main()
68+
cv.destroyAllWindows()

0 commit comments

Comments
 (0)