In the first step, an initial model of the background is computed, while in the second step that
model is updated in order to adapt to possible changes in the scene.
-- In this tutorial we will learn how to perform BS by using OpenCV. As input, we will use data
- coming from the publicly available data set [Background Models Challenge
- (BMC)](http://bmc.univ-bpclermont.fr/) .
+- In this tutorial we will learn how to perform BS by using OpenCV.
Goals
-----
In this tutorial you will learn how to:
--# Read data from videos by using @ref cv::VideoCapture or image sequences by using @ref
- cv::imread ;
+-# Read data from videos or image sequences by using @ref cv::VideoCapture ;
-# Create and update the background model by using @ref cv::BackgroundSubtractor class;
-# Get and show the foreground mask by using @ref cv::imshow ;
--# Save the output by using @ref cv::imwrite to quantitatively evaluate the results.
Code
----
-In the following you can find the source code. We will let the user chose to process either a video
+In the following you can find the source code. We will let the user choose to process either a video
file or a sequence of images.
We will use @ref cv::BackgroundSubtractorMOG2 in this sample, to generate the foreground mask.
The results as well as the input data are shown on the screen.
-The source file can be downloaded [here ](https://github.com/opencv/opencv/tree/3.4/samples/cpp/tutorial_code/video/bg_sub.cpp).
-@include samples/cpp/tutorial_code/video/bg_sub.cpp
+@add_toggle_cpp
+- **Downloadable code**: Click
+ [here](https://github.com/opencv/opencv/tree/3.4/samples/cpp/tutorial_code/video/bg_sub.cpp)
+
+- **Code at glance:**
+ @include samples/cpp/tutorial_code/video/bg_sub.cpp
+@end_toggle
+
+@add_toggle_java
+- **Downloadable code**: Click
+ [here](https://github.com/opencv/opencv/tree/3.4/samples/java/tutorial_code/video/background_subtraction/BackgroundSubtractionDemo.java)
+
+- **Code at glance:**
+ @include samples/java/tutorial_code/video/background_subtraction/BackgroundSubtractionDemo.java
+@end_toggle
+
+@add_toggle_python
+- **Downloadable code**: Click
+ [here](https://github.com/opencv/opencv/tree/3.4/samples/python/tutorial_code/video/background_subtraction/bg_sub.py)
+
+- **Code at glance:**
+ @include samples/python/tutorial_code/video/background_subtraction/bg_sub.py
+@end_toggle
Explanation
-----------
-We discuss the main parts of the above code:
+We discuss the main parts of the code above:
--# First, two Mat objects are allocated to store the current frame and two foreground masks,
- obtained by using two different BS algorithms.
- @code{.cpp}
- Mat frame; //current frame
- Mat fgMaskMOG2; //fg mask fg mask generated by MOG2 method
- @endcode
--# A @ref cv::BackgroundSubtractor object will be used to generate the foreground mask. In this
+- A @ref cv::BackgroundSubtractor object will be used to generate the foreground mask. In this
example, default parameters are used, but it is also possible to declare specific parameters in
the create function.
- @code{.cpp}
- Ptr<BackgroundSubtractor> pMOG2; //MOG2 Background subtractor
- ...
- //create Background Subtractor object
- pMOG2 = createBackgroundSubtractorMOG2(); //MOG2 approach
- @endcode
--# The command line arguments are analysed. The user can chose between two options:
- - video files (by choosing the option -vid);
- - image sequences (by choosing the option -img).
- @code{.cpp}
- if(strcmp(argv[1], "-vid") == 0) {
- //input data coming from a video
- processVideo(argv[2]);
- }
- else if(strcmp(argv[1], "-img") == 0) {
- //input data coming from a sequence of images
- processImages(argv[2]);
- }
- @endcode
--# Suppose you want to process a video file. The video is read until the end is reached or the user
- presses the button 'q' or the button 'ESC'.
- @code{.cpp}
- while( (char)keyboard != 'q' && (char)keyboard != 27 ){
- //read the current frame
- if(!capture.read(frame)) {
- cerr << "Unable to read next frame." << endl;
- cerr << "Exiting..." << endl;
- exit(EXIT_FAILURE);
- }
- @endcode
--# Every frame is used both for calculating the foreground mask and for updating the background. If
+
+@add_toggle_cpp
+@snippet samples/cpp/tutorial_code/video/bg_sub.cpp create
+@end_toggle
+
+@add_toggle_java
+@snippet samples/java/tutorial_code/video/background_subtraction/BackgroundSubtractionDemo.java create
+@end_toggle
+
+@add_toggle_python
+@snippet samples/python/tutorial_code/video/background_subtraction/bg_sub.py create
+@end_toggle
+
+- A @ref cv::VideoCapture object is used to read the input video or input images sequence.
+
+@add_toggle_cpp
+@snippet samples/cpp/tutorial_code/video/bg_sub.cpp capture
+@end_toggle
+
+@add_toggle_java
+@snippet samples/java/tutorial_code/video/background_subtraction/BackgroundSubtractionDemo.java capture
+@end_toggle
+
+@add_toggle_python
+@snippet samples/python/tutorial_code/video/background_subtraction/bg_sub.py capture
+@end_toggle
+
+- Every frame is used both for calculating the foreground mask and for updating the background. If
you want to change the learning rate used for updating the background model, it is possible to
- set a specific learning rate by passing a third parameter to the 'apply' method.
- @code{.cpp}
- //update the background model
- pMOG2->apply(frame, fgMaskMOG2);
- @endcode
--# The current frame number can be extracted from the @ref cv::VideoCapture object and stamped in
+ set a specific learning rate by passing a parameter to the `apply` method.
+
+@add_toggle_cpp
+@snippet samples/cpp/tutorial_code/video/bg_sub.cpp apply
+@end_toggle
+
+@add_toggle_java
+@snippet samples/java/tutorial_code/video/background_subtraction/BackgroundSubtractionDemo.java apply
+@end_toggle
+
+@add_toggle_python
+@snippet samples/python/tutorial_code/video/background_subtraction/bg_sub.py apply
+@end_toggle
+
+- The current frame number can be extracted from the @ref cv::VideoCapture object and stamped in
the top left corner of the current frame. A white rectangle is used to highlight the black
colored frame number.
- @code{.cpp}
- //get the frame number and write it on the current frame
- stringstream ss;
- rectangle(frame, cv::Point(10, 2), cv::Point(100,20),
- cv::Scalar(255,255,255), -1);
- ss << capture.get(CAP_PROP_POS_FRAMES);
- string frameNumberString = ss.str();
- putText(frame, frameNumberString.c_str(), cv::Point(15, 15),
- FONT_HERSHEY_SIMPLEX, 0.5 , cv::Scalar(0,0,0));
- @endcode
--# We are ready to show the current input frame and the results.
- @code{.cpp}
- //show the current frame and the fg masks
- imshow("Frame", frame);
- imshow("FG Mask MOG 2", fgMaskMOG2);
- @endcode
--# The same operations listed above can be performed using a sequence of images as input. The
- processImage function is called and, instead of using a @ref cv::VideoCapture object, the images
- are read by using @ref cv::imread , after individuating the correct path for the next frame to
- read.
- @code{.cpp}
- //read the first file of the sequence
- frame = imread(fistFrameFilename);
- if(!frame.data){
- //error in opening the first image
- cerr << "Unable to open first image frame: " << fistFrameFilename << endl;
- exit(EXIT_FAILURE);
- }
- ...
- //search for the next image in the sequence
- ostringstream oss;
- oss << (frameNumber + 1);
- string nextFrameNumberString = oss.str();
- string nextFrameFilename = prefix + nextFrameNumberString + suffix;
- //read the next frame
- frame = imread(nextFrameFilename);
- if(!frame.data){
- //error in opening the next image in the sequence
- cerr << "Unable to open image frame: " << nextFrameFilename << endl;
- exit(EXIT_FAILURE);
- }
- //update the path of the current frame
- fn.assign(nextFrameFilename);
- @endcode
- Note that this example works only on image sequences in which the filename format is \<n\>.png,
- where n is the frame number (e.g., 7.png).
+
+@add_toggle_cpp
+@snippet samples/cpp/tutorial_code/video/bg_sub.cpp display_frame_number
+@end_toggle
+
+@add_toggle_java
+@snippet samples/java/tutorial_code/video/background_subtraction/BackgroundSubtractionDemo.java display_frame_number
+@end_toggle
+
+@add_toggle_python
+@snippet samples/python/tutorial_code/video/background_subtraction/bg_sub.py display_frame_number
+@end_toggle
+
+- We are ready to show the current input frame and the results.
+
+@add_toggle_cpp
+@snippet samples/cpp/tutorial_code/video/bg_sub.cpp show
+@end_toggle
+
+@add_toggle_java
+@snippet samples/java/tutorial_code/video/background_subtraction/BackgroundSubtractionDemo.java show
+@end_toggle
+
+@add_toggle_python
+@snippet samples/python/tutorial_code/video/background_subtraction/bg_sub.py show
+@end_toggle
Results
-------
-- Given the following input parameters:
- @code{.cpp}
- -vid Video_001.avi
- @endcode
- The output of the program will look as the following:
-
- ![](images/Background_Subtraction_Tutorial_Result_1.png)
-
-- The video file Video_001.avi is part of the [Background Models Challenge
- (BMC)](http://bmc.univ-bpclermont.fr/) data set and it can be downloaded from the following link
- [Video_001](http://bmc.univ-bpclermont.fr/sites/default/files/videos/evaluation/Video_001.zip)
- (about 32 MB).
-- If you want to process a sequence of images, then the '-img' option has to be chosen:
- @code{.cpp}
- -img 111_png/input/1.png
- @endcode
- The output of the program will look as the following:
-
- ![](images/Background_Subtraction_Tutorial_Result_2.png)
-
-- The sequence of images used in this example is part of the [Background Models Challenge
- (BMC)](http://bmc.univ-bpclermont.fr/) dataset and it can be downloaded from the following link
- [sequence 111](http://bmc.univ-bpclermont.fr/sites/default/files/videos/learning/111_png.zip)
- (about 708 MB). Please, note that this example works only on sequences in which the filename
- format is \<n\>.png, where n is the frame number (e.g., 7.png).
-
-Evaluation
-----------
+- With the `vtest.avi` video, for the following frame:
+
+ ![](images/Background_Subtraction_Tutorial_frame.jpg)
+
+ The output of the program will look as the following for MOG2 method (gray areas are detected shadows):
+
+ ![](images/Background_Subtraction_Tutorial_result_MOG2.jpg)
+
+ The output of the program will look as the following for the KNN method (gray areas are detected shadows):
-To quantitatively evaluate the results obtained, we need to:
-
-- Save the output images;
-- Have the ground truth images for the chosen sequence.
-
-In order to save the output images, we can use @ref cv::imwrite . Adding the following code allows
-for saving the foreground masks.
-@code{.cpp}
-string imageToSave = "output_MOG_" + frameNumberString + ".png";
-bool saved = imwrite(imageToSave, fgMaskMOG);
-if(!saved) {
- cerr << "Unable to save " << imageToSave << endl;
-}
-@endcode
-Once we have collected the result images, we can compare them with the ground truth data. There
-exist several publicly available sequences for background subtraction that come with ground truth
-data. If you decide to use the [Background Models Challenge (BMC)](http://bmc.univ-bpclermont.fr/),
-then the result images can be used as input for the [BMC
-Wizard](http://bmc.univ-bpclermont.fr/?q=node/7). The wizard can compute different measures about
-the accuracy of the results.
+ ![](images/Background_Subtraction_Tutorial_result_KNN.jpg)
References
----------
-- [Background Models Challenge (BMC) website](http://bmc.univ-bpclermont.fr/)
+- [Background Models Challenge (BMC) website](https://web.archive.org/web/20140418093037/http://bmc.univ-bpclermont.fr/)
- A Benchmark Dataset for Foreground/Background Extraction @cite vacavant2013benchmark
- @subpage tutorial_background_subtraction
+ *Languages:* C++, Java, Python
+
*Compatibility:* \> OpenCV 2.4.6
*Author:* Domenico Daniele Bloisi
* @author Domenico D. Bloisi
*/
-//opencv
-#include "opencv2/imgcodecs.hpp"
-#include "opencv2/imgproc.hpp"
-#include "opencv2/videoio.hpp"
-#include <opencv2/highgui.hpp>
-#include <opencv2/video.hpp>
-//C
-#include <stdio.h>
-//C++
#include <iostream>
#include <sstream>
+#include <opencv2/imgcodecs.hpp>
+#include <opencv2/imgproc.hpp>
+#include <opencv2/videoio.hpp>
+#include <opencv2/highgui.hpp>
+#include <opencv2/video.hpp>
using namespace cv;
using namespace std;
-// Global variables
-Mat frame; //current frame
-Mat fgMaskMOG2; //fg mask fg mask generated by MOG2 method
-Ptr<BackgroundSubtractor> pMOG2; //MOG2 Background subtractor
-char keyboard; //input from keyboard
-
-/** Function Headers */
-void help();
-void processVideo(char* videoFilename);
-void processImages(char* firstFrameFilename);
+const char* params
+ = "{ help h | | Print usage }"
+ "{ input | ../data/vtest.avi | Path to a video or a sequence of image }"
+ "{ algo | MOG2 | Background subtraction method (KNN, MOG2) }";
-void help()
-{
- cout
- << "--------------------------------------------------------------------------" << endl
- << "This program shows how to use background subtraction methods provided by " << endl
- << " OpenCV. You can process both videos (-vid) and images (-img)." << endl
- << endl
- << "Usage:" << endl
- << "./bg_sub {-vid <video filename>|-img <image filename>}" << endl
- << "for example: ./bg_sub -vid video.avi" << endl
- << "or: ./bg_sub -img /data/images/1.png" << endl
- << "--------------------------------------------------------------------------" << endl
- << endl;
-}
-
-/**
- * @function main
- */
int main(int argc, char* argv[])
{
- //print help information
- help();
-
- //check for the input parameter correctness
- if(argc != 3) {
- cerr <<"Incorret input list" << endl;
- cerr <<"exiting..." << endl;
- return EXIT_FAILURE;
+ CommandLineParser parser(argc, argv, params);
+ parser.about( "This program shows how to use background subtraction methods provided by "
+ " OpenCV. You can process both videos and images.\n" );
+ if (parser.has("help"))
+ {
+ //print help information
+ parser.printMessage();
}
- //create GUI windows
- namedWindow("Frame");
- namedWindow("FG Mask MOG 2");
-
+ //! [create]
//create Background Subtractor objects
- pMOG2 = createBackgroundSubtractorMOG2(); //MOG2 approach
-
- if(strcmp(argv[1], "-vid") == 0) {
- //input data coming from a video
- processVideo(argv[2]);
- }
- else if(strcmp(argv[1], "-img") == 0) {
- //input data coming from a sequence of images
- processImages(argv[2]);
- }
- else {
- //error in reading input parameters
- cerr <<"Please, check the input parameters." << endl;
- cerr <<"Exiting..." << endl;
- return EXIT_FAILURE;
- }
- //destroy GUI windows
- destroyAllWindows();
- return EXIT_SUCCESS;
-}
+ Ptr<BackgroundSubtractor> pBackSub;
+ if (parser.get<String>("algo") == "MOG2")
+ pBackSub = createBackgroundSubtractorMOG2();
+ else
+ pBackSub = createBackgroundSubtractorKNN();
+ //! [create]
-/**
- * @function processVideo
- */
-void processVideo(char* videoFilename) {
- //create the capture object
- VideoCapture capture(videoFilename);
- if(!capture.isOpened()){
+ //! [capture]
+ VideoCapture capture(parser.get<String>("input"));
+ if (!capture.isOpened()){
//error in opening the video input
- cerr << "Unable to open video file: " << videoFilename << endl;
- exit(EXIT_FAILURE);
+ cerr << "Unable to open: " << parser.get<String>("input") << endl;
+ return 0;
}
- //read input data. ESC or 'q' for quitting
- keyboard = 0;
- while( keyboard != 'q' && keyboard != 27 ){
- //read the current frame
- if(!capture.read(frame)) {
- cerr << "Unable to read next frame." << endl;
- cerr << "Exiting..." << endl;
- exit(EXIT_FAILURE);
- }
+ //! [capture]
+
+ Mat frame, fgMask;
+ while (true) {
+ capture >> frame;
+ if (frame.empty())
+ break;
+
+ //! [apply]
//update the background model
- pMOG2->apply(frame, fgMaskMOG2);
+ pBackSub->apply(frame, fgMask);
+ //! [apply]
+
+ //! [display_frame_number]
//get the frame number and write it on the current frame
- stringstream ss;
rectangle(frame, cv::Point(10, 2), cv::Point(100,20),
cv::Scalar(255,255,255), -1);
+ stringstream ss;
ss << capture.get(CAP_PROP_POS_FRAMES);
string frameNumberString = ss.str();
putText(frame, frameNumberString.c_str(), cv::Point(15, 15),
FONT_HERSHEY_SIMPLEX, 0.5 , cv::Scalar(0,0,0));
- //show the current frame and the fg masks
- imshow("Frame", frame);
- imshow("FG Mask MOG 2", fgMaskMOG2);
- //get the input from the keyboard
- keyboard = (char)waitKey( 30 );
- }
- //delete capture object
- capture.release();
-}
+ //! [display_frame_number]
-/**
- * @function processImages
- */
-void processImages(char* fistFrameFilename) {
- //read the first file of the sequence
- frame = imread(fistFrameFilename);
- if(frame.empty()){
- //error in opening the first image
- cerr << "Unable to open first image frame: " << fistFrameFilename << endl;
- exit(EXIT_FAILURE);
- }
- //current image filename
- string fn(fistFrameFilename);
- //read input data. ESC or 'q' for quitting
- keyboard = 0;
- while( keyboard != 'q' && keyboard != 27 ){
- //update the background model
- pMOG2->apply(frame, fgMaskMOG2);
- //get the frame number and write it on the current frame
- size_t index = fn.find_last_of("/");
- if(index == string::npos) {
- index = fn.find_last_of("\\");
- }
- size_t index2 = fn.find_last_of(".");
- string prefix = fn.substr(0,index+1);
- string suffix = fn.substr(index2);
- string frameNumberString = fn.substr(index+1, index2-index-1);
- istringstream iss(frameNumberString);
- int frameNumber = 0;
- iss >> frameNumber;
- rectangle(frame, cv::Point(10, 2), cv::Point(100,20),
- cv::Scalar(255,255,255), -1);
- putText(frame, frameNumberString.c_str(), cv::Point(15, 15),
- FONT_HERSHEY_SIMPLEX, 0.5 , cv::Scalar(0,0,0));
+ //! [show]
//show the current frame and the fg masks
imshow("Frame", frame);
- imshow("FG Mask MOG 2", fgMaskMOG2);
+ imshow("FG Mask", fgMask);
+ //! [show]
+
//get the input from the keyboard
- keyboard = (char)waitKey( 30 );
- //search for the next image in the sequence
- ostringstream oss;
- oss << (frameNumber + 1);
- string nextFrameNumberString = oss.str();
- string nextFrameFilename = prefix + nextFrameNumberString + suffix;
- //read the next frame
- frame = imread(nextFrameFilename);
- if(frame.empty()){
- //error in opening the next image in the sequence
- cerr << "Unable to open image frame: " << nextFrameFilename << endl;
- exit(EXIT_FAILURE);
- }
- //update the path of the current frame
- fn.assign(nextFrameFilename);
+ int keyboard = waitKey(30);
+ if (keyboard == 'q' || keyboard == 27)
+ break;
}
+
+ return 0;
}
--- /dev/null
+import org.opencv.core.Core;
+import org.opencv.core.Mat;
+import org.opencv.core.Point;
+import org.opencv.core.Scalar;
+import org.opencv.highgui.HighGui;
+import org.opencv.imgproc.Imgproc;
+import org.opencv.video.BackgroundSubtractor;
+import org.opencv.video.Video;
+import org.opencv.videoio.VideoCapture;
+import org.opencv.videoio.Videoio;
+
+class BackgroundSubtraction {
+ public void run(String[] args) {
+ String input = args.length > 0 ? args[0] : "../data/vtest.avi";
+ boolean useMOG2 = args.length > 1 ? args[1] == "MOG2" : true;
+
+ //! [create]
+ BackgroundSubtractor backSub;
+ if (useMOG2) {
+ backSub = Video.createBackgroundSubtractorMOG2();
+ } else {
+ backSub = Video.createBackgroundSubtractorKNN();
+ }
+ //! [create]
+
+ //! [capture]
+ VideoCapture capture = new VideoCapture(input);
+ if (!capture.isOpened()) {
+ System.err.println("Unable to open: " + input);
+ System.exit(0);
+ }
+ //! [capture]
+
+ Mat frame = new Mat(), fgMask = new Mat();
+ while (true) {
+ capture.read(frame);
+ if (frame.empty()) {
+ break;
+ }
+
+ //! [apply]
+ // update the background model
+ backSub.apply(frame, fgMask);
+ //! [apply]
+
+ //! [display_frame_number]
+ // get the frame number and write it on the current frame
+ Imgproc.rectangle(frame, new Point(10, 2), new Point(100, 20), new Scalar(255, 255, 255), -1);
+ String frameNumberString = String.format("%d", (int)capture.get(Videoio.CAP_PROP_POS_FRAMES));
+ Imgproc.putText(frame, frameNumberString, new Point(15, 15), Core.FONT_HERSHEY_SIMPLEX, 0.5,
+ new Scalar(0, 0, 0));
+ //! [display_frame_number]
+
+ //! [show]
+ // show the current frame and the fg masks
+ HighGui.imshow("Frame", frame);
+ HighGui.imshow("FG Mask", fgMask);
+ //! [show]
+
+ // get the input from the keyboard
+ int keyboard = HighGui.waitKey(30);
+ if (keyboard == 'q' || keyboard == 27) {
+ break;
+ }
+ }
+
+ HighGui.waitKey();
+ System.exit(0);
+ }
+}
+
+public class BackgroundSubtractionDemo {
+ public static void main(String[] args) {
+ // Load the native OpenCV library
+ System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
+
+ new BackgroundSubtraction().run(args);
+ }
+}
--- /dev/null
+from __future__ import print_function
+import cv2 as cv
+import argparse
+
+parser = argparse.ArgumentParser(description='This program shows how to use background subtraction methods provided by \
+ OpenCV. You can process both videos and images.')
+parser.add_argument('--input', type=str, help='Path to a video or a sequence of image.', default='../data/vtest.avi')
+parser.add_argument('--algo', type=str, help='Background subtraction method (KNN, MOG2).', default='MOG2')
+args = parser.parse_args()
+
+## [create]
+#create Background Subtractor objects
+if args.algo == 'MOG2':
+ backSub = cv.createBackgroundSubtractorMOG2()
+else:
+ backSub = cv.createBackgroundSubtractorKNN()
+## [create]
+
+## [capture]
+capture = cv.VideoCapture(args.input)
+if not capture.isOpened:
+ print('Unable to open: ' + args.input)
+ exit(0)
+## [capture]
+
+while True:
+ ret, frame = capture.read()
+ if frame is None:
+ break
+
+ ## [apply]
+ #update the background model
+ fgMask = backSub.apply(frame)
+ ## [apply]
+
+ ## [display_frame_number]
+ #get the frame number and write it on the current frame
+ cv.rectangle(frame, (10, 2), (100,20), (255,255,255), -1)
+ cv.putText(frame, str(capture.get(cv.CAP_PROP_POS_FRAMES)), (15, 15),
+ cv.FONT_HERSHEY_SIMPLEX, 0.5 , (0,0,0))
+ ## [display_frame_number]
+
+ ## [show]
+ #show the current frame and the fg masks
+ cv.imshow('Frame', frame)
+ cv.imshow('FG Mask', fgMask)
+ ## [show]
+
+ keyboard = cv.waitKey(30)
+ if keyboard == 'q' or keyboard == 27:
+ break