parent
79eb46dddc
commit
42b0e13aef
9 changed files with 1699 additions and 1 deletions
@ -1,2 +1,2 @@ |
||||
set(the_description "Background Segmentation Algorithms") |
||||
ocv_define_module(bgsegm opencv_core opencv_imgproc opencv_video WRAP python) |
||||
ocv_define_module(bgsegm opencv_core opencv_imgproc opencv_video opencv_calib3d WRAP python) |
||||
|
@ -0,0 +1,134 @@ |
||||
import argparse |
||||
import cv2 |
||||
import glob |
||||
import numpy as np |
||||
import os |
||||
import time |
||||
|
||||
|
||||
# This tool is intended for evaluation of different background subtraction algorithms presented in OpenCV. |
||||
# Several presets with different settings are available. You can see them below. |
||||
# This tool measures quality metrics as well as speed. |
||||
|
||||
|
||||
ALGORITHMS_TO_EVALUATE = [ |
||||
(cv2.bgsegm.createBackgroundSubtractorMOG, 'MOG', {}), |
||||
(cv2.bgsegm.createBackgroundSubtractorGMG, 'GMG', {}), |
||||
(cv2.bgsegm.createBackgroundSubtractorCNT, 'CNT', {}), |
||||
(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}), |
||||
(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}), |
||||
(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}), |
||||
(cv2.bgsegm.createBackgroundSubtractorLSBP, 'LSBP-camera-motion-compensation', {'mc': 1}), |
||||
(cv2.bgsegm.createBackgroundSubtractorGSOC, 'GSOC', {}), |
||||
(cv2.bgsegm.createBackgroundSubtractorGSOC, 'GSOC-camera-motion-compensation', {'mc': 1}) |
||||
] |
||||
|
||||
|
||||
def contains_relevant_files(root): |
||||
return os.path.isdir(os.path.join(root, 'groundtruth')) and os.path.isdir(os.path.join(root, 'input')) |
||||
|
||||
|
||||
def find_relevant_dirs(root): |
||||
relevant_dirs = [] |
||||
for d in sorted(os.listdir(root)): |
||||
d = os.path.join(root, d) |
||||
if os.path.isdir(d): |
||||
if contains_relevant_files(d): |
||||
relevant_dirs += [d] |
||||
else: |
||||
relevant_dirs += find_relevant_dirs(d) |
||||
return relevant_dirs |
||||
|
||||
|
||||
def load_sequence(root): |
||||
gt_dir, frames_dir = os.path.join(root, 'groundtruth'), os.path.join(root, 'input') |
||||
gt = sorted(glob.glob(os.path.join(gt_dir, '*.png'))) |
||||
f = sorted(glob.glob(os.path.join(frames_dir, '*.jpg'))) |
||||
assert(len(gt) == len(f)) |
||||
return gt, f |
||||
|
||||
|
||||
def evaluate_algorithm(gt, frames, algo, algo_arguments): |
||||
bgs = algo(**algo_arguments) |
||||
mask = [] |
||||
t_start = time.time() |
||||
|
||||
for i in range(len(gt)): |
||||
frame = np.uint8(cv2.imread(frames[i], cv2.IMREAD_COLOR)) |
||||
mask.append(bgs.apply(frame)) |
||||
|
||||
average_duration = (time.time() - t_start) / len(gt) |
||||
average_precision, average_recall, average_f1, average_accuracy = [], [], [], [] |
||||
|
||||
for i in range(len(gt)): |
||||
gt_mask = np.uint8(cv2.imread(gt[i], cv2.IMREAD_GRAYSCALE)) |
||||
roi = ((gt_mask == 255) | (gt_mask == 0)) |
||||
if roi.sum() > 0: |
||||
gt_answer, answer = gt_mask[roi], mask[i][roi] |
||||
|
||||
tp = ((answer == 255) & (gt_answer == 255)).sum() |
||||
tn = ((answer == 0) & (gt_answer == 0)).sum() |
||||
fp = ((answer == 255) & (gt_answer == 0)).sum() |
||||
fn = ((answer == 0) & (gt_answer == 255)).sum() |
||||
|
||||
if tp + fp > 0: |
||||
average_precision.append(float(tp) / (tp + fp)) |
||||
if tp + fn > 0: |
||||
average_recall.append(float(tp) / (tp + fn)) |
||||
if tp + fn + fp > 0: |
||||
average_f1.append(2.0 * tp / (2.0 * tp + fn + fp)) |
||||
average_accuracy.append(float(tp + tn) / (tp + tn + fp + fn)) |
||||
|
||||
return average_duration, np.mean(average_precision), np.mean(average_recall), np.mean(average_f1), np.mean(average_accuracy) |
||||
|
||||
|
||||
def evaluate_on_sequence(seq, summary): |
||||
gt, frames = load_sequence(seq) |
||||
category, video_name = os.path.basename(os.path.dirname(seq)), os.path.basename(seq) |
||||
print('=== %s:%s ===' % (category, video_name)) |
||||
|
||||
for algo, algo_name, algo_arguments in ALGORITHMS_TO_EVALUATE: |
||||
print('Algorithm name: %s' % algo_name) |
||||
sec_per_step, precision, recall, f1, accuracy = evaluate_algorithm(gt, frames, algo, algo_arguments) |
||||
print('Average accuracy: %.3f' % accuracy) |
||||
print('Average precision: %.3f' % precision) |
||||
print('Average recall: %.3f' % recall) |
||||
print('Average F1: %.3f' % f1) |
||||
print('Average sec. per step: %.4f' % sec_per_step) |
||||
print('') |
||||
|
||||
if category not in summary: |
||||
summary[category] = {} |
||||
if algo_name not in summary[category]: |
||||
summary[category][algo_name] = [] |
||||
summary[category][algo_name].append((precision, recall, f1, accuracy)) |
||||
|
||||
|
||||
def main(): |
||||
parser = argparse.ArgumentParser(description='Evaluate all background subtractors using Change Detection 2014 dataset') |
||||
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) |
||||
parser.add_argument('--algorithm', help='Test particular algorithm instead of all.') |
||||
|
||||
args = parser.parse_args() |
||||
dataset_dirs = find_relevant_dirs(args.dataset_path) |
||||
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)) |
||||
if args.algorithm is not None: |
||||
global ALGORITHMS_TO_EVALUATE |
||||
ALGORITHMS_TO_EVALUATE = filter(lambda a: a[1].lower() == args.algorithm.lower(), ALGORITHMS_TO_EVALUATE) |
||||
summary = {} |
||||
|
||||
for seq in dataset_dirs: |
||||
evaluate_on_sequence(seq, summary) |
||||
|
||||
for category in summary: |
||||
for algo_name in summary[category]: |
||||
summary[category][algo_name] = np.mean(summary[category][algo_name], axis=0) |
||||
|
||||
for category in summary: |
||||
print('=== SUMMARY for %s (Precision, Recall, F1, Accuracy) ===' % category) |
||||
for algo_name in summary[category]: |
||||
print('%05s: %.3f %.3f %.3f %.3f' % ((algo_name,) + tuple(summary[category][algo_name]))) |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
main() |
@ -0,0 +1,41 @@ |
||||
import numpy as np |
||||
import cv2 |
||||
import argparse |
||||
import os |
||||
|
||||
|
||||
def main(): |
||||
argparser = argparse.ArgumentParser(description='Vizualization of the LSBP/GSOC background subtraction algorithm.') |
||||
|
||||
argparser.add_argument('-g', '--gt', help='Directory with ground-truth frames', required=True) |
||||
argparser.add_argument('-f', '--frames', help='Directory with input frames', required=True) |
||||
argparser.add_argument('-l', '--lsbp', help='Display LSBP instead of GSOC', default=False) |
||||
args = argparser.parse_args() |
||||
|
||||
gt = map(lambda x: os.path.join(args.gt, x), os.listdir(args.gt)) |
||||
gt.sort() |
||||
f = map(lambda x: os.path.join(args.frames, x), os.listdir(args.frames)) |
||||
f.sort() |
||||
|
||||
gt = np.uint8(map(lambda x: cv2.imread(x, cv2.IMREAD_GRAYSCALE), gt)) |
||||
f = np.uint8(map(lambda x: cv2.imread(x, cv2.IMREAD_COLOR), f)) |
||||
|
||||
if not args.lsbp: |
||||
bgs = cv2.bgsegm.createBackgroundSubtractorGSOC() |
||||
else: |
||||
bgs = cv2.bgsegm.createBackgroundSubtractorLSBP() |
||||
|
||||
for i in xrange(f.shape[0]): |
||||
cv2.imshow('Frame', f[i]) |
||||
cv2.imshow('Ground-truth', gt[i]) |
||||
mask = bgs.apply(f[i]) |
||||
bg = bgs.getBackgroundImage() |
||||
cv2.imshow('BG', bg) |
||||
cv2.imshow('Output mask', mask) |
||||
k = cv2.waitKey(0) |
||||
if k == 27: |
||||
break |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
main() |
@ -0,0 +1,26 @@ |
||||
import cv2 |
||||
import argparse |
||||
|
||||
|
||||
def main(): |
||||
argparser = argparse.ArgumentParser(description='Vizualization of the SyntheticSequenceGenerator.') |
||||
|
||||
argparser.add_argument('-b', '--background', help='Background image.', required=True) |
||||
argparser.add_argument('-o', '--obj', help='Object image. It must be strictly smaller than background.', required=True) |
||||
args = argparser.parse_args() |
||||
|
||||
bg = cv2.imread(args.background) |
||||
obj = cv2.imread(args.obj) |
||||
generator = cv2.bgsegm.createSyntheticSequenceGenerator(bg, obj) |
||||
|
||||
while True: |
||||
frame, mask = generator.getNextFrame() |
||||
cv2.imshow('Generated frame', frame) |
||||
cv2.imshow('Generated mask', mask) |
||||
k = cv2.waitKey(int(1000.0 / 30)) |
||||
if k == 27: |
||||
break |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
main() |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,221 @@ |
||||
/*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.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2013, OpenCV Foundation, 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 the copyright holders 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*/
|
||||
|
||||
/**
|
||||
* @file synthetic_seq.cpp |
||||
* @author Vladislav Samsonov <vvladxx@gmail.com> |
||||
* @brief Synthetic frame sequence generator for testing background subtraction algorithms. |
||||
* |
||||
*/ |
||||
|
||||
#include "precomp.hpp" |
||||
|
||||
namespace cv |
||||
{ |
||||
namespace bgsegm |
||||
{ |
||||
namespace |
||||
{ |
||||
|
||||
inline int clamp(int x, int l, int u) { |
||||
return ((x) < (l)) ? (l) : (((x) > (u)) ? (u) : (x)); |
||||
} |
||||
|
||||
inline int within(int a, int b, int c) { |
||||
return (((a) <= (b)) && ((b) <= (c))) ? 1 : 0; |
||||
} |
||||
|
||||
void bilinearInterp(uint8_t* dest, double x, double y, unsigned bpp, const uint8_t** values) { |
||||
x = std::fmod(x, 1.0); |
||||
y = std::fmod(y, 1.0); |
||||
|
||||
if (x < 0.0) |
||||
x += 1.0; |
||||
if (y < 0.0) |
||||
y += 1.0; |
||||
|
||||
for (unsigned i = 0; i < bpp; i++) { |
||||
double m0 = (1.0 - x) * values[0][i] + x * values[1][i]; |
||||
double m1 = (1.0 - x) * values[2][i] + x * values[3][i]; |
||||
dest[i] = (uint8_t) ((1.0 - y) * m0 + y * m1); |
||||
} |
||||
} |
||||
|
||||
// Static background is a way too easy test. We will add distortion to it.
|
||||
void waveDistortion(const uint8_t* src, uint8_t* dst, int width, int height, int bypp, double amplitude, double wavelength, double phase) { |
||||
const uint8_t zeroes[4] = {0, 0, 0, 0}; |
||||
const long rowsiz = width * bypp; |
||||
const double xhsiz = (double) width / 2.0; |
||||
const double yhsiz = (double) height / 2.0; |
||||
double xscale, yscale; |
||||
|
||||
if (xhsiz < yhsiz) { |
||||
xscale = yhsiz / xhsiz; |
||||
yscale = 1.0; |
||||
} |
||||
else if (xhsiz > yhsiz) { |
||||
xscale = 1.0; |
||||
yscale = xhsiz / yhsiz; |
||||
} |
||||
else { |
||||
xscale = 1.0; |
||||
yscale = 1.0; |
||||
} |
||||
|
||||
wavelength *= 2; |
||||
|
||||
for (int y = 0; y < height; y++) { |
||||
uint8_t* dest = dst; |
||||
|
||||
for (int x = 0; x < width; x++) { |
||||
const double dx = x * xscale; |
||||
const double dy = y * yscale; |
||||
const double d = sqrt (dx * dx + dy * dy); |
||||
const double amnt = amplitude * sin(((d / wavelength) * (2.0 * M_PI) + phase)); |
||||
const double needx = (amnt + dx) / xscale; |
||||
const double needy = (amnt + dy) / yscale; |
||||
const int xi = clamp(int(needx), 0, width - 2); |
||||
const int yi = clamp(int(needy), 0, height - 2); |
||||
|
||||
const uint8_t* p = src + rowsiz * yi + xi * bypp; |
||||
|
||||
const int x1_in = within(0, xi, width - 1); |
||||
const int y1_in = within(0, yi, height - 1); |
||||
const int x2_in = within(0, xi + 1, width - 1); |
||||
const int y2_in = within(0, yi + 1, height - 1); |
||||
const uint8_t* values[4]; |
||||
|
||||
if (x1_in && y1_in) |
||||
values[0] = p; |
||||
else |
||||
values[0] = zeroes; |
||||
|
||||
if (x2_in && y1_in) |
||||
values[1] = p + bypp; |
||||
else |
||||
values[1] = zeroes; |
||||
|
||||
if (x1_in && y2_in) |
||||
values[2] = p + rowsiz; |
||||
else |
||||
values[2] = zeroes; |
||||
|
||||
if (x2_in && y2_in) |
||||
values[3] = p + bypp + rowsiz; |
||||
else |
||||
values[3] = zeroes; |
||||
|
||||
bilinearInterp(dest, needx, needy, bypp, values); |
||||
dest += bypp; |
||||
} |
||||
|
||||
dst += rowsiz; |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
SyntheticSequenceGenerator::SyntheticSequenceGenerator(InputArray _background, InputArray _object, double _amplitude, double _wavelength, double _wavespeed, double _objspeed) |
||||
: amplitude(_amplitude), wavelength(_wavelength), wavespeed(_wavespeed), objspeed(_objspeed), timeStep(0) { |
||||
_background.getMat().copyTo(background); |
||||
_object.getMat().copyTo(object); |
||||
|
||||
if (background.channels() == 1) { |
||||
cvtColor(background, background, COLOR_GRAY2BGR); |
||||
} |
||||
|
||||
if (object.channels() == 1) { |
||||
cvtColor(object, object, COLOR_GRAY2BGR); |
||||
} |
||||
|
||||
CV_Assert(background.channels() == 3); |
||||
CV_Assert(object.channels() == 3); |
||||
CV_Assert(background.size().width > object.size().width); |
||||
CV_Assert(background.size().height > object.size().height); |
||||
|
||||
background.convertTo(background, CV_8U); |
||||
object.convertTo(object, CV_8U); |
||||
|
||||
pos.x = (background.size().width - object.size().width) / 2; |
||||
pos.y = (background.size().height - object.size().height) / 2; |
||||
|
||||
const double phi = rng.uniform(0.0, CV_2PI); |
||||
dir.x = std::cos(phi); |
||||
dir.y = std::sin(phi); |
||||
} |
||||
|
||||
void SyntheticSequenceGenerator::getNextFrame(OutputArray _frame, OutputArray _gtMask) { |
||||
CV_Assert(!background.empty() && !object.empty()); |
||||
const Size sz = background.size(); |
||||
|
||||
_frame.create(sz, CV_8UC3); |
||||
Mat frame = _frame.getMat(); |
||||
|
||||
CV_Assert(background.isContinuous() && frame.isContinuous()); |
||||
|
||||
waveDistortion(background.ptr(), frame.ptr(), sz.width, sz.height, 3, amplitude, wavelength, double(timeStep) * wavespeed); |
||||
|
||||
const Size objSz = object.size(); |
||||
|
||||
object.copyTo(frame(Rect(Point2i(pos), objSz))); |
||||
|
||||
while (pos.x + dir.x * objspeed < 0 || pos.x + dir.x * objspeed >= sz.width - objSz.width || pos.y + dir.y * objspeed < 0 || pos.y + dir.y * objspeed >= sz.height - objSz.height) { |
||||
const double phi = rng.uniform(0.0, CV_2PI); |
||||
dir.x = std::cos(phi); |
||||
dir.y = std::sin(phi); |
||||
} |
||||
|
||||
_gtMask.create(sz, CV_8U); |
||||
Mat gtMask = _gtMask.getMat(); |
||||
gtMask = 0; |
||||
gtMask(Rect(Point2i(pos), objSz)) = 255; |
||||
|
||||
pos += dir * objspeed; |
||||
++timeStep; |
||||
} |
||||
|
||||
Ptr<SyntheticSequenceGenerator> createSyntheticSequenceGenerator(InputArray background, InputArray object, double amplitude, double wavelength, double wavespeed, double objspeed) { |
||||
return makePtr<SyntheticSequenceGenerator>(background, object, amplitude, wavelength, wavespeed, objspeed); |
||||
} |
||||
|
||||
} |
||||
} |
@ -0,0 +1,137 @@ |
||||
#include "test_precomp.hpp" |
||||
#include <set> |
||||
|
||||
using namespace std; |
||||
using namespace cv; |
||||
using namespace cvtest; |
||||
|
||||
static string getDataDir() { return TS::ptr()->get_data_path(); } |
||||
|
||||
static string getLenaImagePath() { return getDataDir() + "shared/lena.png"; } |
||||
|
||||
// Simple synthetic illumination invariance test
|
||||
TEST(BackgroundSubtractor_LSBP, IlluminationInvariance) |
||||
{ |
||||
RNG rng; |
||||
Mat input(100, 100, CV_32FC3); |
||||
|
||||
rng.fill(input, RNG::UNIFORM, 0.0f, 0.1f); |
||||
|
||||
Mat lsv1, lsv2; |
||||
cv::bgsegm::BackgroundSubtractorLSBPDesc::calcLocalSVDValues(lsv1, input); |
||||
input *= 10; |
||||
cv::bgsegm::BackgroundSubtractorLSBPDesc::calcLocalSVDValues(lsv2, input); |
||||
|
||||
ASSERT_LE(cv::norm(lsv1, lsv2), 0.04f); |
||||
} |
||||
|
||||
TEST(BackgroundSubtractor_LSBP, Correctness) |
||||
{ |
||||
Mat input(3, 3, CV_32FC3); |
||||
|
||||
float n = 0; |
||||
for (int i = 0; i < 3; ++i) |
||||
for (int j = 0; j < 3; ++j) { |
||||
input.at<Point3f>(i, j) = Point3f(n, n, n); |
||||
++n; |
||||
} |
||||
|
||||
Mat lsv; |
||||
bgsegm::BackgroundSubtractorLSBPDesc::calcLocalSVDValues(lsv, input); |
||||
|
||||
EXPECT_LE(std::abs(lsv.at<float>(1, 1) - 0.0903614f), 0.001f); |
||||
|
||||
input = 1; |
||||
bgsegm::BackgroundSubtractorLSBPDesc::calcLocalSVDValues(lsv, input); |
||||
|
||||
EXPECT_LE(std::abs(lsv.at<float>(1, 1) - 0.0f), 0.001f); |
||||
} |
||||
|
||||
TEST(BackgroundSubtractor_LSBP, Discrimination) |
||||
{ |
||||
Point2i LSBPSamplePoints[32]; |
||||
for (int i = 0; i < 32; ++i) { |
||||
const double phi = i * CV_2PI / 32.0; |
||||
LSBPSamplePoints[i] = Point2i(int(4 * std::cos(phi)), int(4 * std::sin(phi))); |
||||
} |
||||
|
||||
Mat lena = imread(getLenaImagePath()); |
||||
Mat lsv; |
||||
|
||||
lena.convertTo(lena, CV_32FC3); |
||||
|
||||
bgsegm::BackgroundSubtractorLSBPDesc::calcLocalSVDValues(lsv, lena); |
||||
|
||||
Scalar mean, var; |
||||
meanStdDev(lsv, mean, var); |
||||
|
||||
EXPECT_GE(mean[0], 0.02); |
||||
EXPECT_LE(mean[0], 0.04); |
||||
EXPECT_GE(var[0], 0.03); |
||||
|
||||
Mat desc; |
||||
bgsegm::BackgroundSubtractorLSBPDesc::computeFromLocalSVDValues(desc, lsv, LSBPSamplePoints); |
||||
Size sz = desc.size(); |
||||
std::set<uint32_t> distinctive_elements; |
||||
|
||||
for (int i = 0; i < sz.height; ++i) |
||||
for (int j = 0; j < sz.width; ++j) |
||||
distinctive_elements.insert(desc.at<uint32_t>(i, j)); |
||||
|
||||
EXPECT_GE(distinctive_elements.size(), 35000U); |
||||
} |
||||
|
||||
static double scoreBitwiseReduce(const Mat& mask, const Mat& gtMask, uint8_t v1, uint8_t v2) { |
||||
Mat result; |
||||
cv::bitwise_and(mask == v1, gtMask == v2, result); |
||||
return cv::countNonZero(result); |
||||
} |
||||
|
||||
template<typename T> |
||||
static double evaluateBGSAlgorithm(Ptr<T> bgs) { |
||||
Mat background = imread(getDataDir() + "shared/fruits.png"); |
||||
Mat object = imread(getDataDir() + "shared/baboon.png"); |
||||
cv::resize(object, object, Size(100, 100)); |
||||
Ptr<bgsegm::SyntheticSequenceGenerator> generator = bgsegm::createSyntheticSequenceGenerator(background, object); |
||||
|
||||
double f1_mean = 0; |
||||
unsigned total = 0; |
||||
|
||||
for (int frameNum = 1; frameNum <= 400; ++frameNum) { |
||||
Mat frame, gtMask; |
||||
generator->getNextFrame(frame, gtMask); |
||||
|
||||
Mat mask; |
||||
bgs->apply(frame, mask); |
||||
|
||||
Size sz = frame.size(); |
||||
EXPECT_EQ(sz, gtMask.size()); |
||||
EXPECT_EQ(gtMask.size(), mask.size()); |
||||
EXPECT_EQ(mask.type(), gtMask.type()); |
||||
EXPECT_EQ(mask.type(), CV_8U); |
||||
|
||||
// We will give the algorithm some time for the proper background model inference.
|
||||
// Almost all background subtraction algorithms have a problem with cold start and require some time for background model initialization.
|
||||
// So we will not count first part of the frames in the score.
|
||||
if (frameNum > 300) { |
||||
const double tp = scoreBitwiseReduce(mask, gtMask, 255, 255); |
||||
const double fp = scoreBitwiseReduce(mask, gtMask, 255, 0); |
||||
const double fn = scoreBitwiseReduce(mask, gtMask, 0, 255); |
||||
|
||||
if (tp + fn + fp > 0) { |
||||
const double f1_score = 2.0 * tp / (2.0 * tp + fn + fp); |
||||
f1_mean += f1_score; |
||||
++total; |
||||
} |
||||
} |
||||
} |
||||
|
||||
f1_mean /= total; |
||||
return f1_mean; |
||||
} |
||||
|
||||
TEST(BackgroundSubtractor_LSBP, Accuracy) |
||||
{ |
||||
EXPECT_GE(evaluateBGSAlgorithm(bgsegm::createBackgroundSubtractorGSOC()), 0.9); |
||||
EXPECT_GE(evaluateBGSAlgorithm(bgsegm::createBackgroundSubtractorLSBP()), 0.25); |
||||
} |
Loading…
Reference in new issue