Skip to content

Commit 6520dba

Browse files
committed
Merge pull request #1211 from VladX:lsbp
2 parents 79eb46d + 311feb2 commit 6520dba

File tree

9 files changed

+1696
-1
lines changed

9 files changed

+1696
-1
lines changed

modules/bgsegm/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
set(the_description "Background Segmentation Algorithms")
2-
ocv_define_module(bgsegm opencv_core opencv_imgproc opencv_video WRAP python)
2+
ocv_define_module(bgsegm opencv_core opencv_imgproc opencv_video opencv_calib3d WRAP python)

modules/bgsegm/doc/bgsegm.bib

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,13 @@ @inproceedings{Gold2012
1515
year={2012},
1616
organization={IEEE}
1717
}
18+
19+
@inproceedings{LGuo2016,
20+
author={L. Guo and D. Xu and Z. Qiang},
21+
booktitle={2016 IEEE Conference on Computer Vision and Pattern Recognition Workshops (CVPRW)},
22+
title={Background Subtraction Using Local SVD Binary Pattern},
23+
year={2016},
24+
pages={1159-1167},
25+
doi={10.1109/CVPRW.2016.148},
26+
month={June}
27+
}

modules/bgsegm/include/opencv2/bgsegm.hpp

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,135 @@ createBackgroundSubtractorCNT(int minPixelStability = 15,
242242
int maxPixelStability = 15*60,
243243
bool isParallel = true);
244244

245+
enum LSBPCameraMotionCompensation {
246+
LSBP_CAMERA_MOTION_COMPENSATION_NONE = 0,
247+
LSBP_CAMERA_MOTION_COMPENSATION_LK
248+
};
249+
250+
/** @brief Implementation of the different yet better algorithm which is called GSOC, as it was implemented during GSOC and was not originated from any paper.
251+
252+
This algorithm demonstrates better performance on CDNET 2014 dataset compared to other algorithms in OpenCV.
253+
*/
254+
class CV_EXPORTS_W BackgroundSubtractorGSOC : public BackgroundSubtractor
255+
{
256+
public:
257+
// BackgroundSubtractor interface
258+
CV_WRAP virtual void apply(InputArray image, OutputArray fgmask, double learningRate=-1) = 0;
259+
260+
CV_WRAP virtual void getBackgroundImage(OutputArray backgroundImage) const = 0;
261+
};
262+
263+
/** @brief Background Subtraction using Local SVD Binary Pattern. More details about the algorithm can be found at @cite LGuo2016
264+
*/
265+
class CV_EXPORTS_W BackgroundSubtractorLSBP : public BackgroundSubtractor
266+
{
267+
public:
268+
// BackgroundSubtractor interface
269+
CV_WRAP virtual void apply(InputArray image, OutputArray fgmask, double learningRate=-1) = 0;
270+
271+
CV_WRAP virtual void getBackgroundImage(OutputArray backgroundImage) const = 0;
272+
};
273+
274+
/** @brief This is for calculation of the LSBP descriptors.
275+
*/
276+
class CV_EXPORTS_W BackgroundSubtractorLSBPDesc
277+
{
278+
public:
279+
static void calcLocalSVDValues(OutputArray localSVDValues, const Mat& frame);
280+
281+
static void computeFromLocalSVDValues(OutputArray desc, const Mat& localSVDValues, const Point2i* LSBPSamplePoints);
282+
283+
static void compute(OutputArray desc, const Mat& frame, const Point2i* LSBPSamplePoints);
284+
};
285+
286+
/** @brief Creates an instance of BackgroundSubtractorGSOC algorithm.
287+
288+
Implementation of the different yet better algorithm which is called GSOC, as it was implemented during GSOC and was not originated from any paper.
289+
290+
@param mc Whether to use camera motion compensation.
291+
@param nSamples Number of samples to maintain at each point of the frame.
292+
@param replaceRate Probability of replacing the old sample - how fast the model will update itself.
293+
@param propagationRate Probability of propagating to neighbors.
294+
@param hitsThreshold How many positives the sample must get before it will be considered as a possible replacement.
295+
@param alpha Scale coefficient for threshold.
296+
@param beta Bias coefficient for threshold.
297+
@param blinkingSupressionDecay Blinking supression decay factor.
298+
@param blinkingSupressionMultiplier Blinking supression multiplier.
299+
@param noiseRemovalThresholdFacBG Strength of the noise removal for background points.
300+
@param noiseRemovalThresholdFacFG Strength of the noise removal for foreground points.
301+
*/
302+
CV_EXPORTS_W Ptr<BackgroundSubtractorGSOC> createBackgroundSubtractorGSOC(int mc = LSBP_CAMERA_MOTION_COMPENSATION_NONE, int nSamples = 20, float replaceRate = 0.003f, float propagationRate = 0.01f, int hitsThreshold = 32, float alpha = 0.01f, float beta = 0.0022f, float blinkingSupressionDecay = 0.1f, float blinkingSupressionMultiplier = 0.1f, float noiseRemovalThresholdFacBG = 0.0004f, float noiseRemovalThresholdFacFG = 0.0008f);
303+
304+
/** @brief Creates an instance of BackgroundSubtractorLSBP algorithm.
305+
306+
Background Subtraction using Local SVD Binary Pattern. More details about the algorithm can be found at @cite LGuo2016
307+
308+
@param mc Whether to use camera motion compensation.
309+
@param nSamples Number of samples to maintain at each point of the frame.
310+
@param LSBPRadius LSBP descriptor radius.
311+
@param Tlower Lower bound for T-values. See @cite LGuo2016 for details.
312+
@param Tupper Upper bound for T-values. See @cite LGuo2016 for details.
313+
@param Tinc Increase step for T-values. See @cite LGuo2016 for details.
314+
@param Tdec Decrease step for T-values. See @cite LGuo2016 for details.
315+
@param Rscale Scale coefficient for threshold values.
316+
@param Rincdec Increase/Decrease step for threshold values.
317+
@param noiseRemovalThresholdFacBG Strength of the noise removal for background points.
318+
@param noiseRemovalThresholdFacFG Strength of the noise removal for foreground points.
319+
@param LSBPthreshold Threshold for LSBP binary string.
320+
@param minCount Minimal number of matches for sample to be considered as foreground.
321+
*/
322+
CV_EXPORTS_W Ptr<BackgroundSubtractorLSBP> createBackgroundSubtractorLSBP(int mc = LSBP_CAMERA_MOTION_COMPENSATION_NONE, int nSamples = 20, int LSBPRadius = 16, float Tlower = 2.0f, float Tupper = 32.0f, float Tinc = 1.0f, float Tdec = 0.05f, float Rscale = 10.0f, float Rincdec = 0.005f, float noiseRemovalThresholdFacBG = 0.0004f, float noiseRemovalThresholdFacFG = 0.0008f, int LSBPthreshold = 8, int minCount = 2);
323+
324+
/** @brief Synthetic frame sequence generator for testing background subtraction algorithms.
325+
326+
It will generate the moving object on top of the background.
327+
It will apply some distortion to the background to make the test more complex.
328+
*/
329+
class CV_EXPORTS_W SyntheticSequenceGenerator : public Algorithm
330+
{
331+
private:
332+
const double amplitude;
333+
const double wavelength;
334+
const double wavespeed;
335+
const double objspeed;
336+
unsigned timeStep;
337+
Point2d pos;
338+
Point2d dir;
339+
Mat background;
340+
Mat object;
341+
RNG rng;
342+
343+
public:
344+
/** @brief Creates an instance of SyntheticSequenceGenerator.
345+
346+
@param background Background image for object.
347+
@param object Object image which will move slowly over the background.
348+
@param amplitude Amplitude of wave distortion applied to background.
349+
@param wavelength Length of waves in distortion applied to background.
350+
@param wavespeed How fast waves will move.
351+
@param objspeed How fast object will fly over background.
352+
*/
353+
CV_WRAP SyntheticSequenceGenerator(InputArray background, InputArray object, double amplitude, double wavelength, double wavespeed, double objspeed);
354+
355+
/** @brief Obtain the next frame in the sequence.
356+
357+
@param frame Output frame.
358+
@param gtMask Output ground-truth (reference) segmentation mask object/background.
359+
*/
360+
CV_WRAP void getNextFrame(OutputArray frame, OutputArray gtMask);
361+
};
362+
363+
/** @brief Creates an instance of SyntheticSequenceGenerator.
364+
365+
@param background Background image for object.
366+
@param object Object image which will move slowly over the background.
367+
@param amplitude Amplitude of wave distortion applied to background.
368+
@param wavelength Length of waves in distortion applied to background.
369+
@param wavespeed How fast waves will move.
370+
@param objspeed How fast object will fly over background.
371+
*/
372+
CV_EXPORTS_W Ptr<SyntheticSequenceGenerator> createSyntheticSequenceGenerator(InputArray background, InputArray object, double amplitude = 2.0, double wavelength = 20.0, double wavespeed = 0.2, double objspeed = 6.0);
373+
245374
//! @}
246375

247376
}

modules/bgsegm/samples/evaluation.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import argparse
2+
import cv2
3+
import glob
4+
import numpy as np
5+
import os
6+
import time
7+
8+
9+
# This tool is intended for evaluation of different background subtraction algorithms presented in OpenCV.
10+
# Several presets with different settings are available. You can see them below.
11+
# This tool measures quality metrics as well as speed.
12+
13+
14+
ALGORITHMS_TO_EVALUATE = [
15+
(cv2.bgsegm.createBackgroundSubtractorMOG, 'MOG', {}),
16+
(cv2.bgsegm.createBackgroundSubtractorGMG, 'GMG', {}),
17+
(cv2.bgsegm.createBackgroundSubtractorCNT, 'CNT', {}),
18+
(cv2.bgsegm.createBackgroundSubtractorLSBP, 'LSBP-vanilla', {'nSamples': 20, 'LSBPRadius': 4, 'Tlower': 2.0, 'Tupper': 200.0, 'Tinc': 1.0, 'Tdec': 0.05, 'Rscale': 5.0, 'Rincdec': 0.05, 'LSBPthreshold': 8}),
19+
(cv2.bgsegm.createBackgroundSubtractorLSBP, 'LSBP-speed', {'nSamples': 10, 'LSBPRadius': 16, 'Tlower': 2.0, 'Tupper': 32.0, 'Tinc': 1.0, 'Tdec': 0.05, 'Rscale': 10.0, 'Rincdec': 0.005, 'LSBPthreshold': 8}),
20+
(cv2.bgsegm.createBackgroundSubtractorLSBP, 'LSBP-quality', {'nSamples': 20, 'LSBPRadius': 16, 'Tlower': 2.0, 'Tupper': 32.0, 'Tinc': 1.0, 'Tdec': 0.05, 'Rscale': 10.0, 'Rincdec': 0.005, 'LSBPthreshold': 8}),
21+
(cv2.bgsegm.createBackgroundSubtractorLSBP, 'LSBP-camera-motion-compensation', {'mc': 1}),
22+
(cv2.bgsegm.createBackgroundSubtractorGSOC, 'GSOC', {}),
23+
(cv2.bgsegm.createBackgroundSubtractorGSOC, 'GSOC-camera-motion-compensation', {'mc': 1})
24+
]
25+
26+
27+
def contains_relevant_files(root):
28+
return os.path.isdir(os.path.join(root, 'groundtruth')) and os.path.isdir(os.path.join(root, 'input'))
29+
30+
31+
def find_relevant_dirs(root):
32+
relevant_dirs = []
33+
for d in sorted(os.listdir(root)):
34+
d = os.path.join(root, d)
35+
if os.path.isdir(d):
36+
if contains_relevant_files(d):
37+
relevant_dirs += [d]
38+
else:
39+
relevant_dirs += find_relevant_dirs(d)
40+
return relevant_dirs
41+
42+
43+
def load_sequence(root):
44+
gt_dir, frames_dir = os.path.join(root, 'groundtruth'), os.path.join(root, 'input')
45+
gt = sorted(glob.glob(os.path.join(gt_dir, '*.png')))
46+
f = sorted(glob.glob(os.path.join(frames_dir, '*.jpg')))
47+
assert(len(gt) == len(f))
48+
return gt, f
49+
50+
51+
def evaluate_algorithm(gt, frames, algo, algo_arguments):
52+
bgs = algo(**algo_arguments)
53+
mask = []
54+
t_start = time.time()
55+
56+
for i in range(len(gt)):
57+
frame = np.uint8(cv2.imread(frames[i], cv2.IMREAD_COLOR))
58+
mask.append(bgs.apply(frame))
59+
60+
average_duration = (time.time() - t_start) / len(gt)
61+
average_precision, average_recall, average_f1, average_accuracy = [], [], [], []
62+
63+
for i in range(len(gt)):
64+
gt_mask = np.uint8(cv2.imread(gt[i], cv2.IMREAD_GRAYSCALE))
65+
roi = ((gt_mask == 255) | (gt_mask == 0))
66+
if roi.sum() > 0:
67+
gt_answer, answer = gt_mask[roi], mask[i][roi]
68+
69+
tp = ((answer == 255) & (gt_answer == 255)).sum()
70+
tn = ((answer == 0) & (gt_answer == 0)).sum()
71+
fp = ((answer == 255) & (gt_answer == 0)).sum()
72+
fn = ((answer == 0) & (gt_answer == 255)).sum()
73+
74+
if tp + fp > 0:
75+
average_precision.append(float(tp) / (tp + fp))
76+
if tp + fn > 0:
77+
average_recall.append(float(tp) / (tp + fn))
78+
if tp + fn + fp > 0:
79+
average_f1.append(2.0 * tp / (2.0 * tp + fn + fp))
80+
average_accuracy.append(float(tp + tn) / (tp + tn + fp + fn))
81+
82+
return average_duration, np.mean(average_precision), np.mean(average_recall), np.mean(average_f1), np.mean(average_accuracy)
83+
84+
85+
def evaluate_on_sequence(seq, summary):
86+
gt, frames = load_sequence(seq)
87+
category, video_name = os.path.basename(os.path.dirname(seq)), os.path.basename(seq)
88+
print('=== %s:%s ===' % (category, video_name))
89+
90+
for algo, algo_name, algo_arguments in ALGORITHMS_TO_EVALUATE:
91+
print('Algorithm name: %s' % algo_name)
92+
sec_per_step, precision, recall, f1, accuracy = evaluate_algorithm(gt, frames, algo, algo_arguments)
93+
print('Average accuracy: %.3f' % accuracy)
94+
print('Average precision: %.3f' % precision)
95+
print('Average recall: %.3f' % recall)
96+
print('Average F1: %.3f' % f1)
97+
print('Average sec. per step: %.4f' % sec_per_step)
98+
print('')
99+
100+
if category not in summary:
101+
summary[category] = {}
102+
if algo_name not in summary[category]:
103+
summary[category][algo_name] = []
104+
summary[category][algo_name].append((precision, recall, f1, accuracy))
105+
106+
107+
def main():
108+
parser = argparse.ArgumentParser(description='Evaluate all background subtractors using Change Detection 2014 dataset')
109+
parser.add_argument('--dataset_path', help='Path to the directory with dataset. It may contain multiple inner directories. It will be scanned recursively.', required=True)
110+
parser.add_argument('--algorithm', help='Test particular algorithm instead of all.')
111+
112+
args = parser.parse_args()
113+
dataset_dirs = find_relevant_dirs(args.dataset_path)
114+
assert len(dataset_dirs) > 0, ("Passed directory must contain at least one sequence from the Change Detection dataset. There is no relevant directories in %s. Check that this directory is correct." % (args.dataset_path))
115+
if args.algorithm is not None:
116+
global ALGORITHMS_TO_EVALUATE
117+
ALGORITHMS_TO_EVALUATE = filter(lambda a: a[1].lower() == args.algorithm.lower(), ALGORITHMS_TO_EVALUATE)
118+
summary = {}
119+
120+
for seq in dataset_dirs:
121+
evaluate_on_sequence(seq, summary)
122+
123+
for category in summary:
124+
for algo_name in summary[category]:
125+
summary[category][algo_name] = np.mean(summary[category][algo_name], axis=0)
126+
127+
for category in summary:
128+
print('=== SUMMARY for %s (Precision, Recall, F1, Accuracy) ===' % category)
129+
for algo_name in summary[category]:
130+
print('%05s: %.3f %.3f %.3f %.3f' % ((algo_name,) + tuple(summary[category][algo_name])))
131+
132+
133+
if __name__ == '__main__':
134+
main()

modules/bgsegm/samples/viz.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import numpy as np
2+
import cv2
3+
import argparse
4+
import os
5+
6+
7+
def main():
8+
argparser = argparse.ArgumentParser(description='Vizualization of the LSBP/GSOC background subtraction algorithm.')
9+
10+
argparser.add_argument('-g', '--gt', help='Directory with ground-truth frames', required=True)
11+
argparser.add_argument('-f', '--frames', help='Directory with input frames', required=True)
12+
argparser.add_argument('-l', '--lsbp', help='Display LSBP instead of GSOC', default=False)
13+
args = argparser.parse_args()
14+
15+
gt = map(lambda x: os.path.join(args.gt, x), os.listdir(args.gt))
16+
gt.sort()
17+
f = map(lambda x: os.path.join(args.frames, x), os.listdir(args.frames))
18+
f.sort()
19+
20+
gt = np.uint8(map(lambda x: cv2.imread(x, cv2.IMREAD_GRAYSCALE), gt))
21+
f = np.uint8(map(lambda x: cv2.imread(x, cv2.IMREAD_COLOR), f))
22+
23+
if not args.lsbp:
24+
bgs = cv2.bgsegm.createBackgroundSubtractorGSOC()
25+
else:
26+
bgs = cv2.bgsegm.createBackgroundSubtractorLSBP()
27+
28+
for i in xrange(f.shape[0]):
29+
cv2.imshow('Frame', f[i])
30+
cv2.imshow('Ground-truth', gt[i])
31+
mask = bgs.apply(f[i])
32+
bg = bgs.getBackgroundImage()
33+
cv2.imshow('BG', bg)
34+
cv2.imshow('Output mask', mask)
35+
k = cv2.waitKey(0)
36+
if k == 27:
37+
break
38+
39+
40+
if __name__ == '__main__':
41+
main()
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import cv2
2+
import argparse
3+
4+
5+
def main():
6+
argparser = argparse.ArgumentParser(description='Vizualization of the SyntheticSequenceGenerator.')
7+
8+
argparser.add_argument('-b', '--background', help='Background image.', required=True)
9+
argparser.add_argument('-o', '--obj', help='Object image. It must be strictly smaller than background.', required=True)
10+
args = argparser.parse_args()
11+
12+
bg = cv2.imread(args.background)
13+
obj = cv2.imread(args.obj)
14+
generator = cv2.bgsegm.createSyntheticSequenceGenerator(bg, obj)
15+
16+
while True:
17+
frame, mask = generator.getNextFrame()
18+
cv2.imshow('Generated frame', frame)
19+
cv2.imshow('Generated mask', mask)
20+
k = cv2.waitKey(int(1000.0 / 30))
21+
if k == 27:
22+
break
23+
24+
25+
if __name__ == '__main__':
26+
main()

0 commit comments

Comments
 (0)