From da0039221448db93de5411ddcc30dae7fb08aafc Mon Sep 17 00:00:00 2001 From: Tristan Walter <twalter@orn.mpg.de> Date: Wed, 28 Oct 2020 12:24:51 +0100 Subject: [PATCH] * updating, adding background accumulator * using averaging_method_t --- .../src/commons/common/gui/FileChooser.cpp | 1 + .../src/commons/common/video/GenericVideo.cpp | 73 ++----- .../src/commons/common/video/GenericVideo.h | 118 +++++++++++ .../src/commons/common/video/VideoSource.cpp | 43 ++-- .../src/commons/common/video/VideoSource.h | 7 - Application/src/grabber/default_config.cpp | 4 +- Application/src/grabber/default_config.h | 3 - Application/src/grabber/grabber.cpp | 196 ++++++++++-------- Application/src/grabber/grabber.h | 15 +- Application/src/grabber/gui.cpp | 2 +- Application/src/tracker/VideoOpener.cpp | 29 +-- Application/src/tracker/VideoOpener.h | 8 +- docs/parameters_tgrabs.rst | 12 +- 13 files changed, 317 insertions(+), 194 deletions(-) diff --git a/Application/src/commons/common/gui/FileChooser.cpp b/Application/src/commons/common/gui/FileChooser.cpp index 5cf4509..8a769c1 100644 --- a/Application/src/commons/common/gui/FileChooser.cpp +++ b/Application/src/commons/common/gui/FileChooser.cpp @@ -14,6 +14,7 @@ FileChooser::FileChooser(const file::Path& start, const std::string& extension, _overall(std::make_shared<VerticalLayout>()), _base("Choose file", *_graph, [this](){ using namespace gui; + tf::show(); { std::lock_guard<std::mutex> guard(_execute_mutex); diff --git a/Application/src/commons/common/video/GenericVideo.cpp b/Application/src/commons/common/video/GenericVideo.cpp index c3078ad..9eff5e6 100644 --- a/Application/src/commons/common/video/GenericVideo.cpp +++ b/Application/src/commons/common/video/GenericVideo.cpp @@ -2,9 +2,19 @@ #include <misc/Timer.h> #include <misc/GlobalSettings.h> #include <grabber/default_config.h> +#include <misc/Image.h> using namespace cmn; +namespace cmn { +ENUM_CLASS_DOCS(averaging_method_t, + "Sum all samples and divide by N.", + "Calculate a per-pixel median of the samples.", + "Use a per-pixel minimum across samples.", + "Use a per-pixel maximum across samples." +); +} + CropOffsets GenericVideo::crop_offsets() const { return SETTING(crop_offsets); } @@ -98,75 +108,38 @@ void GenericVideo::processImage(const gpuMat& display, gpuMat&out, bool do_mask) } void GenericVideo::generate_average(cv::Mat &av, uint64_t frameIndex) { - gpuMat average; - av.copyTo(average); + if(length() < 10) { + gpuMat average; + av.copyTo(average); + this->processImage(average, average); + return; + } + AveragingAccumulator accumulator; - Debug("Generating average for frame %d...", frameIndex); + Debug("Generating average for frame %d (method='%s')...", frameIndex, accumulator.mode().name()); - //const uint64_t channel = SETTING(color_channel); double count = 0; float samples = GlobalSettings::has("average_samples") ? SETTING(average_samples).value<int>() : (length() * 0.01); const int step = max(1, length() / samples); - gpuMat float_mat; - gpuMat f, ref; - std::vector<gpuMat> vec; - - auto averaging_method = GlobalSettings::has("averaging_method") ? SETTING(averaging_method).value<grab::averaging_method_t::Class>() : grab::averaging_method_t::mean; - bool use_mean = averaging_method != grab::averaging_method_t::max && averaging_method != grab::averaging_method_t::min; - Debug("Use max: %d", !use_mean); - - if(average.empty() || average.cols != size().width || average.rows != size().height) { - average = gpuMat::zeros(size().height, size().width, use_mean ? CV_32FC1 : CV_8UC1); - } else - average = cv::Scalar(0); - - if (length() < 10) { - processImage(average, average); - return; - } - - if(use_mean) - float_mat = gpuMat::zeros(average.rows, average.cols, CV_32FC1); - else - float_mat = gpuMat::zeros(average.rows, average.cols, CV_8UC1); - - cv::Mat local; - //for (uint64_t i=0; i<length(); i+=step) { + cv::Mat f; uint64_t counted = 0; for(long_t i=length() ? length()-1 : 0; i>=0; i-=step) { frame(i, f); assert(f.channels() == 1); - ref = f; - - if(use_mean) { - ref.convertTo(float_mat, CV_32FC1, 1.0/255.0); - //Debug("%d,%d - %d,%d", float_mat.cols, float_mat.rows, average.cols, average.rows); - cv::add(average, float_mat, average); - } else { - ref.copyTo(local); - - if(averaging_method == grab::averaging_method_t::max) - av = cv::max(av, local); - else - av = cv::min(av, local); - } - - ++count; + accumulator.add(f); counted += step; if(counted > float(length()) * 0.1) { Debug("generating average: %d/%d step:%d (frame %d)", (samples - i) / step, int(samples), step, i); counted = 0; } + if(GlobalSettings::has("terminate") && SETTING(terminate)) break; } - if(use_mean) { - cv::divide(average, cv::Scalar(count), average); - average.convertTo(av, CV_8UC1, 255.0); - } else - average.copyTo(av); + auto image = accumulator.finalize(); + image->get().copyTo(av); } diff --git a/Application/src/commons/common/video/GenericVideo.h b/Application/src/commons/common/video/GenericVideo.h index 8fa2958..2c10ddc 100644 --- a/Application/src/commons/common/video/GenericVideo.h +++ b/Application/src/commons/common/video/GenericVideo.h @@ -3,8 +3,126 @@ #include <types.h> #include <misc/CropOffsets.h> +#include <misc/GlobalSettings.h> namespace cmn { class GenericVideo; } +namespace cmn { +ENUM_CLASS(averaging_method_t, mean, mode, max, min); +ENUM_CLASS_HAS_DOCS(averaging_method_t); + +template<typename Mat = cv::Mat, bool threaded = false> +class AveragingAccumulator { +protected: + GETTER(averaging_method_t::Class, mode) + Mat _accumulator; + Mat _float_mat; + Mat _local; + double count = 0; + bool use_mean; + Size2 _size; + std::vector<std::array<uint8_t, 256>> spatial_histogram; + std::vector<std::unique_ptr<std::mutex>> spatial_mutex; + +public: + AveragingAccumulator() { + _mode = GlobalSettings::has("averaging_method") + ? SETTING(averaging_method).template value<averaging_method_t::Class>() + : averaging_method_t::mean; + } + AveragingAccumulator(averaging_method_t::Class mode) + : _mode(mode) + { } + + void add(const Mat &f) { + assert(f.channels() == 1); + assert(f.type() == CV_8UC1); + + // initialization code + if(_accumulator.empty()) { + _size = Size2(f.cols, f.rows); + _accumulator = cv::Mat::zeros(_size.height, _size.width, _mode == averaging_method_t::mean ? CV_32FC1 : CV_8UC1); + if(_mode == averaging_method_t::min) + _accumulator.setTo(255); + + if(_mode == averaging_method_t::mode) { + spatial_histogram.resize(f.cols * f.rows); + for(uint64_t i=0; i<spatial_histogram.size(); ++i) { + std::fill(spatial_histogram.at(i).begin(), spatial_histogram.at(i).end(), 0); + spatial_mutex.push_back(std::make_unique<std::mutex>()); + } + } + } + + if(_mode == averaging_method_t::mean) { + f.convertTo(_float_mat, CV_32FC1); + cv::add(_accumulator, _float_mat, _accumulator); + ++count; + + } else if(_mode == averaging_method_t::mode) { + assert(f.isContinuous()); + + auto ptr = f.data; + const auto end = f.data + f.cols * f.rows; + auto array_ptr = spatial_histogram.data(); + auto mutex_ptr = spatial_mutex.begin(); + + assert(spatial_histogram.size() == (uint64_t)(f.cols * f.rows)); + if constexpr(threaded) { + for (; ptr != end; ++ptr, ++array_ptr, ++mutex_ptr) { + (*mutex_ptr)->lock(); + ++(*array_ptr)[*ptr]; + (*mutex_ptr)->unlock(); + } + + } else { + for (; ptr != end; ++ptr, ++array_ptr) + ++(*array_ptr)[*ptr]; + } + + } else if(_mode == averaging_method_t::max) { + cv::max(_accumulator, f, _accumulator); + } else if(_mode == averaging_method_t::min) { + cv::min(_accumulator, f, _accumulator); + } else + U_EXCEPTION("Unknown averaging_method '%s'.", _mode.name()) + } + + std::unique_ptr<cmn::Image> finalize() { + auto image = std::make_unique<cmn::Image>(_accumulator.rows, _accumulator.cols, 1); + + if(_mode == averaging_method_t::mean) { + cv::divide(_accumulator, cv::Scalar(count), _local); + _local.convertTo(image->get(), CV_8UC1); + + } else if(_mode == averaging_method_t::mode) { + _accumulator.copyTo(image->get()); + + auto ptr = image->data(); + const auto end = image->data() + image->cols * image->rows; + auto array_ptr = spatial_histogram.data(); + + for (; ptr != end; ++ptr, ++array_ptr) { + uchar max_code = 0; + uint8_t max_number = 0; + //for(auto && [code, number] : *array_ptr) { + for(uint64_t code=0; code<array_ptr->size(); ++code) { + const auto& number = (*array_ptr)[code]; + if(number > max_number) { + max_number = number; + max_code = code; + } + } + + *ptr = max_code; + } + } else + _accumulator.copyTo(image->get()); + + return std::move(image); + } +}; + +} //! Interface for things that can load Videos class cmn::GenericVideo { diff --git a/Application/src/commons/common/video/VideoSource.cpp b/Application/src/commons/common/video/VideoSource.cpp index 062da96..05d6d6e 100644 --- a/Application/src/commons/common/video/VideoSource.cpp +++ b/Application/src/commons/common/video/VideoSource.cpp @@ -478,21 +478,14 @@ void VideoSource::generate_average(cv::Mat &av, uint64_t) { gpuMat float_mat, f, ref; std::vector<gpuMat> vec; - AveragingMethod::Class method(AveragingMethod::mean); - if(GlobalSettings::has("averaging_method")) { - auto name = SETTING(averaging_method).value<std::string>(); - if(AveragingMethod::has(name)) - method = AveragingMethod::get(name); - else { - auto str = Meta::toStr(AveragingMethod::names); - Warning("Invalid value for 'averaging_method': '%S'. Known methods are: %S", &name, &str); - } - } + averaging_method_t::Class method(averaging_method_t::mean); + if(GlobalSettings::has("averaging_method")) + method = SETTING(averaging_method).value<averaging_method_t::Class>(); //bool use_mean = GlobalSettings::has("averaging_method") && utils::lowercase(SETTING(averaging_method).value<std::string>()) != "max"; Debug("Use averaging method: '%s'", method.name()); if(average.empty() || average.cols != size().width || average.rows != size().height) { - average = gpuMat::zeros(size().height, size().width, method == AveragingMethod::mean ? CV_32FC1 : CV_8UC1); + average = gpuMat::zeros(size().height, size().width, method == averaging_method_t::mean ? CV_32FC1 : CV_8UC1); } else average = cv::Scalar(0); @@ -501,10 +494,10 @@ void VideoSource::generate_average(cv::Mat &av, uint64_t) { return; } - if(method == AveragingMethod::mean) + if(method == averaging_method_t::mean) float_mat = gpuMat::zeros(average.rows, average.cols, CV_32FC1); else { - if(method == AveragingMethod::min) { + if(method == averaging_method_t::min) { cv::Mat tmp = cv::Mat::ones(average.rows, average.cols, CV_8UC1); tmp = tmp.mul(255); tmp.copyTo(float_mat); @@ -517,14 +510,14 @@ void VideoSource::generate_average(cv::Mat &av, uint64_t) { uint64_t step = max(1, _files_in_seq.size() < samples ? 1 : ceil(_files_in_seq.size() / samples)); uint64_t frames_per_file = max(1, _files_in_seq.size() < samples ? (length() / _files_in_seq.size()) / (length() / samples) : 1); - if(samples > 255 && method == AveragingMethod::mode) + if(samples > 255 && method == averaging_method_t::mode) U_EXCEPTION("Cannot take more than 255 samples with 'averaging_method' = 'mode'. Choose fewer samples or a different averaging method."); std::map<File*, std::set<uint64_t>> file_indexes; //std::vector<std::map<uchar, uint8_t>> spatial_histogram; std::vector<std::array<uint8_t, 256>> spatial_histogram; std::vector<std::unique_ptr<std::mutex>> spatial_mutex; - if(method == AveragingMethod::mode) { + if(method == averaging_method_t::mode) { spatial_histogram.resize(average.cols * average.rows); for(uint64_t i=0; i<spatial_histogram.size(); ++i) { std::fill(spatial_histogram.at(i).begin(), spatial_histogram.at(i).end(), 0); @@ -556,7 +549,7 @@ void VideoSource::generate_average(cv::Mat &av, uint64_t) { cv::Mat float_mat; cv::Mat average; - if(method == AveragingMethod::min) { + if(method == averaging_method_t::min) { average = cv::Mat::ones(gAverage->rows, gAverage->cols, gAverage->type()); cv::multiply(average, cv::Scalar(255), average); } else @@ -569,12 +562,12 @@ void VideoSource::generate_average(cv::Mat &av, uint64_t) { file->frame(index, f, true); assert(f.channels() == 1); - if(method == AveragingMethod::mean) { + if(method == averaging_method_t::mean) { //Debug("%d,%d - %d,%d", float_mat.cols, float_mat.rows, average.cols, average.rows); f.convertTo(float_mat, CV_32FC1, 1.0/255.0); cv::add(average, float_mat, average); - } else if(method == AveragingMethod::mode) { + } else if(method == averaging_method_t::mode) { assert(f.isContinuous()); auto ptr = f.data; @@ -594,9 +587,9 @@ void VideoSource::generate_average(cv::Mat &av, uint64_t) { ++(*array_ptr)[*ptr]; } - } else if(method == AveragingMethod::max) { + } else if(method == averaging_method_t::max) { average = cv::max(average, f); - } else if(method == AveragingMethod::min) { + } else if(method == averaging_method_t::min) { average = cv::min(average, f); } else U_EXCEPTION("Unknown averaging_method '%s'.", method.name()) @@ -618,15 +611,15 @@ void VideoSource::generate_average(cv::Mat &av, uint64_t) { file->close(); std::lock_guard<std::mutex> guard(mutex); - if(method == AveragingMethod::mean) { + if(method == averaging_method_t::mean) { *gCount += count; cv::add(average, *gAverage, *gAverage); - } else if(method == AveragingMethod::mode) { + } else if(method == averaging_method_t::mode) { } else { if(gAv->empty()) *gAv = average; - else if(method == AveragingMethod::max) + else if(method == averaging_method_t::max) *gAv = cv::max(average, *gAv); else *gAv = cv::min(average, *gAv); @@ -645,11 +638,11 @@ void VideoSource::generate_average(cv::Mat &av, uint64_t) { if(SETTING(terminate)) return; - if(method == AveragingMethod::mean) { + if(method == averaging_method_t::mean) { cv::divide(average, cv::Scalar(count), average); average.convertTo(av, CV_8UC1, 255.0); - } else if(method == AveragingMethod::mode) { + } else if(method == averaging_method_t::mode) { Debug("Combining mode image..."); average.copyTo(av); diff --git a/Application/src/commons/common/video/VideoSource.h b/Application/src/commons/common/video/VideoSource.h index bfbd442..e10fd1d 100644 --- a/Application/src/commons/common/video/VideoSource.h +++ b/Application/src/commons/common/video/VideoSource.h @@ -8,13 +8,6 @@ #define VIDEO_SEQUENCE_INVALID_VALUE (-1) #define VIDEO_SEQUENCE_UNSPECIFIED_VALUE (-2) -ENUM_CLASS(AveragingMethod, - mean, - max, - min, - mode -) - namespace cmn { class Video; class VideoSource; diff --git a/Application/src/grabber/default_config.cpp b/Application/src/grabber/default_config.cpp index a52e473..6397cd0 100644 --- a/Application/src/grabber/default_config.cpp +++ b/Application/src/grabber/default_config.cpp @@ -2,6 +2,7 @@ #include <misc/SpriteMap.h> #include <file/Path.h> #include <misc/CropOffsets.h> +#include <video/GenericVideo.h> #ifndef WIN32 #include <unistd.h> @@ -11,9 +12,6 @@ #include <misc/default_settings.h> namespace grab { - - ENUM_CLASS_DOCS(averaging_method_t, "Mean", "Mode", "Min", "Max"); - #ifndef WIN32 struct passwd *pw = getpwuid(getuid()); const char *homedir = pw->pw_dir; diff --git a/Application/src/grabber/default_config.h b/Application/src/grabber/default_config.h index 767f4b7..ac98d9a 100644 --- a/Application/src/grabber/default_config.h +++ b/Application/src/grabber/default_config.h @@ -4,9 +4,6 @@ #include <misc/GlobalSettings.h> namespace grab { - ENUM_CLASS(averaging_method_t, mean, mode, max, min); - ENUM_CLASS_HAS_DOCS(averaging_method_t); - namespace default_config { using namespace cmn; diff --git a/Application/src/grabber/grabber.cpp b/Application/src/grabber/grabber.cpp index b186cf5..39e965c 100644 --- a/Application/src/grabber/grabber.cpp +++ b/Application/src/grabber/grabber.cpp @@ -274,11 +274,22 @@ void FrameGrabber::prepare_average() { Debug("--- done preparing"); } +template<typename F> +auto async_deferred(F&& func) -> std::future<decltype(func())> +{ + auto task = std::packaged_task<decltype(func())()>(std::forward<F>(func)); + auto future = task.get_future(); + + std::thread(std::move(task)).detach(); + + return std::move(future); +} + FrameGrabber::FrameGrabber(std::function<void(FrameGrabber&)> callback_before_starting) : //_current_image(NULL), _current_average_timestamp(0), _average_finished(false), - _average_samples(0), + _average_samples(0), _last_index(0), _video(NULL), _video_mask(NULL), _camera(NULL), _current_fps(0), _fps(0), @@ -367,11 +378,64 @@ FrameGrabber::FrameGrabber(std::function<void(FrameGrabber&)> callback_before_st _current_average_timestamp = 1337; } else { - initialize_video(); + std::vector<file::Path> filenames; + auto video_source = SETTING(video_source).value<std::string>(); + try { + filenames = Meta::fromStr<std::vector<file::Path>>(video_source); + if(filenames.size() > 1) { + Debug("Found an array of filenames (%d).", filenames.size()); + } else if(filenames.size() == 1) { + SETTING(video_source) = filenames.front(); + filenames.clear(); + } else + U_EXCEPTION("Empty input filename '%S'. Please specify an input name.", &video_source); + + } catch(const illegal_syntax& e) { + // ... do nothing + } + + if(filenames.empty()) { + auto filepath = file::Path(SETTING(video_source).value<std::string>()); + if(filepath.remove_filename().empty()) { + auto path = (SETTING(output_dir).value<file::Path>() / filepath); + filenames.push_back(path); + } else + filenames.push_back(filepath); + } + + for(auto &name : filenames) { + name = pv::DataLocation::parse("input", name); + } + + if(filenames.size() == 1) { + _video = new VideoSource(filenames.front().str()); + + } else { + _video = new VideoSource(filenames); + } + + int frame_rate = _video->framerate(); + if(frame_rate == -1) { + frame_rate = 25; + } + + if(SETTING(frame_rate).value<int>() == -1) { + Debug("Setting frame rate to %d (from video).", frame_rate); + SETTING(frame_rate) = frame_rate; + } else if(SETTING(frame_rate).value<int>() != frame_rate) { + Warning("Overwriting default frame rate of %d with %d.", frame_rate, SETTING(frame_rate).value<int>()); + } + + if(!SETTING(mask_path).value<file::Path>().empty()) { + auto path = pv::DataLocation::parse("input", SETTING(mask_path).value<file::Path>()); + if(path.exists()) { + _video_mask = new VideoSource(path.str()); + } + } } // determine recording resolution and set it - _cam_size = determine_resolution(); + _cam_size = determine_resolution(); SETTING(video_size) = Size2(_cam_size) * GRAB_SETTINGS(cam_scale); #if WITH_FFMPEG @@ -403,6 +467,39 @@ FrameGrabber::FrameGrabber(std::function<void(FrameGrabber&)> callback_before_st Output::Library::Init(); } + // determine offsets + CropOffsets roff = SETTING(crop_offsets); + _processed.set_offsets(roff); + + _crop_rect = roff.toPixels(_cam_size); + _cropped_size = cv::Size(_crop_rect.width * GRAB_SETTINGS(cam_scale), _crop_rect.height * GRAB_SETTINGS(cam_scale)); + + { + std::lock_guard<std::mutex> guard(_camera_lock); + if(_camera) { + _processed.set_resolution(_cropped_size); + _camera->set_crop(_crop_rect); + } + } + + // create mask if necessary + if(SETTING(cam_circle_mask)) { + cv::Mat mask = cv::Mat::zeros(_cropped_size.height, _cropped_size.width, CV_8UC1); + cv::circle(mask, cv::Point(mask.cols/2, mask.rows/2), min(mask.cols, mask.rows)/2, cv::Scalar(1), -1); + _processed.set_mask(mask); + } + + _task._complete = false; + _task._future = async_deferred([this, callback = std::move(callback_before_starting)]() mutable { + initialize(std::move(callback)); + _task._complete = true; + }); +} + +void FrameGrabber::initialize(std::function<void(FrameGrabber&)>&& callback_before_starting) { + if(_video) + initialize_video(); + if (GRAB_SETTINGS(enable_closed_loop)) { track::PythonIntegration::set_settings(GlobalSettings::instance()); track::PythonIntegration::set_display_function([](auto& name, auto& mat) { tf::imshow(name, mat); }); @@ -452,21 +549,6 @@ FrameGrabber::FrameGrabber(std::function<void(FrameGrabber&)> callback_before_st _average.copyTo(_original_average); callback_before_starting(*this); - // determine offsets - CropOffsets roff = SETTING(crop_offsets); - _processed.set_offsets(roff); - - _crop_rect = roff.toPixels(_cam_size); - _cropped_size = cv::Size(_crop_rect.width * GRAB_SETTINGS(cam_scale), _crop_rect.height * GRAB_SETTINGS(cam_scale)); - - { - std::lock_guard<std::mutex> guard(_camera_lock); - if(_camera) { - _processed.set_resolution(_cropped_size); - _camera->set_crop(_crop_rect); - } - } - if(_video) { _average.copyTo(_original_average); prepare_average(); @@ -474,17 +556,10 @@ FrameGrabber::FrameGrabber(std::function<void(FrameGrabber&)> callback_before_st _current_average_timestamp = 42; } - // create mask if necessary - if(SETTING(cam_circle_mask)) { - cv::Mat mask = cv::Mat::zeros(_cropped_size.height, _cropped_size.width, CV_8UC1); - cv::circle(mask, cv::Point(mask.cols/2, mask.rows/2), min(mask.cols, mask.rows)/2, cv::Scalar(1), -1); - _processed.set_mask(mask); - } - //auto epoch = std::chrono::time_point<std::chrono::system_clock>(); _start_timing = _video && !_video->has_timestamps() ? 0 : UINT64_MAX;//Image::clock_::now(); _real_timing = std::chrono::system_clock::now(); - + _analysis = new std::decay<decltype(*_analysis)>::type( [&]() -> Image_t* { // create object return new Image_t(_cam_size.height, _cam_size.width); @@ -671,64 +746,9 @@ file::Path FrameGrabber::average_name() const { } void FrameGrabber::initialize_video() { - std::vector<file::Path> filenames; - auto video_source = SETTING(video_source).value<std::string>(); - try { - filenames = Meta::fromStr<std::vector<file::Path>>(video_source); - if(filenames.size() > 1) { - Debug("Found an array of filenames (%d).", filenames.size()); - } else if(filenames.size() == 1) { - SETTING(video_source) = filenames.front(); - filenames.clear(); - } else - U_EXCEPTION("Empty input filename '%S'. Please specify an input name.", &video_source); - - } catch(const illegal_syntax& e) { - // ... do nothing - } - - if(filenames.empty()) { - auto filepath = file::Path(SETTING(video_source).value<std::string>()); - if(filepath.remove_filename().empty()) { - auto path = (SETTING(output_dir).value<file::Path>() / filepath); - filenames.push_back(path); - } else - filenames.push_back(filepath); - } - - for(auto &name : filenames) { - name = pv::DataLocation::parse("input", name); - } - - if(filenames.size() == 1) { - _video = new VideoSource(filenames.front().str()); - - } else { - _video = new VideoSource(filenames); - } - - int frame_rate = _video->framerate(); - if(frame_rate == -1) { - frame_rate = 25; - } - auto path = average_name(); Debug("Saving average at or loading from '%S'.", &path.str()); - if(SETTING(frame_rate).value<int>() == -1) { - Debug("Setting frame rate to %d (from video).", frame_rate); - SETTING(frame_rate) = frame_rate; - } else if(SETTING(frame_rate).value<int>() != frame_rate) { - Warning("Overwriting default frame rate of %d with %d.", frame_rate, SETTING(frame_rate).value<int>()); - } - - if(!SETTING(mask_path).value<file::Path>().empty()) { - auto path = pv::DataLocation::parse("input", SETTING(mask_path).value<file::Path>()); - if(path.exists()) { - _video_mask = new VideoSource(path.str()); - } - } - if(path.exists()) { if(SETTING(reset_average)) { Warning("Average exists, but will not be used because 'reset_average' is set to true."); @@ -791,8 +811,9 @@ bool FrameGrabber::add_image_to_average(const Image_t& current) { _current_image = nullptr; } - static auto averaging_method = GlobalSettings::has("averaging_method") ? utils::lowercase(SETTING(averaging_method).value<grab::averaging_method_t::Class>()) : grab::averaging_method_t::mean; - static bool use_mean = averaging_method != grab::averaging_method_t::max && averaging_method != grab::averaging_method_t::max; + static auto averaging_method = GlobalSettings::has("averaging_method") ? SETTING(averaging_method).value<averaging_method_t::Class>() : averaging_method_t::mean; + static bool use_mean = averaging_method != averaging_method_t::max + && averaging_method != averaging_method_t::max; static gpuMat empty_image; if(empty_image.empty()) empty_image = gpuMat::zeros(_cropped_size.height, _cropped_size.width, CV_8UC1); @@ -815,9 +836,9 @@ bool FrameGrabber::add_image_to_average(const Image_t& current) { _current_average.copyTo(local_av); empty_image.copyTo(local); - if(averaging_method == grab::averaging_method_t::max) + if(averaging_method == averaging_method_t::max) _current_average = cv::max(local, local_av); - else if(averaging_method == grab::averaging_method_t::min) + else if(averaging_method == averaging_method_t::min) _current_average = cv::min(local, local_av); } } @@ -1356,6 +1377,11 @@ Queue::Code FrameGrabber::process_image(Image_t& current) { static Timing timing("process_image", 10); TakeTiming take(timing); + if(_task._valid && _task._complete) { + _task._future.get(); + _task._valid = false; + } + ensure_average_is_ready(); // make timestamp relative to _start_timing diff --git a/Application/src/grabber/grabber.h b/Application/src/grabber/grabber.h index 8e587c9..11faec7 100644 --- a/Application/src/grabber/grabber.h +++ b/Application/src/grabber/grabber.h @@ -108,9 +108,21 @@ public: typedef ImageThreads AnalysisType; static track::Tracker* tracker_instance(); + struct Task { + std::future<void> _future; + std::atomic<bool> _complete = false; + std::atomic<bool> _valid = true; + + Task() = default; + Task(Task&& task) + : _future(std::move(task._future)), _complete(task._complete.load()), _valid(task._valid.load()) + {} + }; protected: - cv::Size _cam_size; + Task _task; + + GETTER(cv::Size, cam_size) GETTER(cv::Size, cropped_size) GETTER(Bounds, crop_rect) @@ -254,6 +266,7 @@ private: void crop_and_scale(gpuMat&); bool add_image_to_average(const Image_t&); + void initialize(std::function<void(FrameGrabber&)>&& callback_before_starting); }; #endif diff --git a/Application/src/grabber/gui.cpp b/Application/src/grabber/gui.cpp index 946bb40..6d55db0 100644 --- a/Application/src/grabber/gui.cpp +++ b/Application/src/grabber/gui.cpp @@ -26,7 +26,7 @@ GUI* GUI::instance() { GUI::GUI(FrameGrabber& grabber) : _grabber(grabber), _crop_offsets(SETTING(crop_offsets).value<CropOffsets>()), - _size(grabber.processed().header().resolution), + _size(grabber.cam_size().width, grabber.cam_size().height), _cropped_size(grabber.cropped_size()), //_window_scale(min((sf::VideoMode::getDesktopMode().height - 250 > _cropped_size.height * 1.8f ? 1.8f : ((sf::VideoMode::getDesktopMode().height - 250) / float(_cropped_size.height))), // (sf::VideoMode::getDesktopMode().width - 250 > _cropped_size.width * 1.8f ? 1.8f : ((sf::VideoMode::getDesktopMode().width - 250) / float(_cropped_size.width))))), diff --git a/Application/src/tracker/VideoOpener.cpp b/Application/src/tracker/VideoOpener.cpp index 0a03eaa..c7cbbd7 100644 --- a/Application/src/tracker/VideoOpener.cpp +++ b/Application/src/tracker/VideoOpener.cpp @@ -74,11 +74,11 @@ VideoOpener::VideoOpener() { }); _text_fields["averaging_method"] = LabeledField("averaging_method"); - _text_fields["averaging_method"]._text_field->set_text(TEMP_SETTING(average_samples).get().valueString()); + _text_fields["averaging_method"]._text_field->set_text(TEMP_SETTING(averaging_method).get().valueString()); _text_fields["averaging_method"]._text_field->on_text_changed([this]() { try { - auto number = Meta::fromStr<grab::averaging_method_t::Class>(_text_fields["averaging_method"]._text_field->text()); - TEMP_SETTING(average_samples) = number; + auto number = Meta::fromStr<averaging_method_t::Class>(_text_fields["averaging_method"]._text_field->text()); + TEMP_SETTING(averaging_method) = number; if (_buffer) { _buffer->restart_background(); @@ -262,9 +262,11 @@ void VideoOpener::BufferedVideo::restart_background() { resize_image(img, 500 / double(max(img.cols, img.rows))); img.convertTo(_background_image, CV_32FC1); - _background_image.copyTo(_accumulator); + _accumulator = std::make_unique<AveragingAccumulator<>>(TEMP_SETTING(averaging_method).value<averaging_method_t::Class>()); + _accumulator->add(img); + //_background_image.copyTo(_accumulator); - _background_samples = 1; + //_background_samples = 1; _background_video_index = 0; //_accumulator = cv::Mat::zeros(img.rows, img.cols, CV_32FC1); @@ -280,17 +282,20 @@ void VideoOpener::BufferedVideo::restart_background() { if(max(img.cols, img.rows) > 500) resize_image(img, 500 / double(max(img.cols, img.rows))); - img.convertTo(flt, CV_32FC1); + /*img.convertTo(flt, CV_32FC1); if(!_accumulator.empty()) cv::add(_accumulator, flt, _accumulator); else flt.copyTo(_accumulator); - ++_background_samples; + ++_background_samples;*/ + + _accumulator->add(img); + Debug("%d/%d (%d)", _background_video_index, _background_video->length(), step); + auto image = _accumulator->finalize(); std::lock_guard guard(_frame_mutex); - cv::divide(_accumulator, cv::Scalar(_background_samples), _background_copy); - _set_copy_background = true; + _background_copy = std::move(image); } Debug("Done calculating background"); @@ -356,9 +361,9 @@ void VideoOpener::BufferedVideo::update_loop() { { std::lock_guard frame_guard(_frame_mutex); - if(_set_copy_background) { - _set_copy_background = false; - _background_copy.copyTo(_background_image); + if(_background_copy) { + _background_copy->get().convertTo(_background_image, CV_32FC1); + _background_copy = nullptr; } cv::absdiff(_background_image, _flt, _diff); } diff --git a/Application/src/tracker/VideoOpener.h b/Application/src/tracker/VideoOpener.h index 70d9572..1f6c2cb 100644 --- a/Application/src/tracker/VideoOpener.h +++ b/Application/src/tracker/VideoOpener.h @@ -30,9 +30,9 @@ public: gpuMat _background_image; cv::Mat _local; gpuMat _flt, _img, _mask, _diff, _alpha, _output; - cv::Mat _accumulator, _background_copy; - bool _set_copy_background = false; - uint64_t _background_samples = 0; + std::unique_ptr<Image> _background_copy; + std::unique_ptr<AveragingAccumulator<>> _accumulator; + //uint64_t _background_samples = 0; uint64_t _background_video_index = 0; std::mutex _frame_mutex; @@ -81,7 +81,7 @@ public: LabeledField(const std::string& name = "") : _text(std::make_shared<gui::Text>(name)), - _text_field(std::make_shared<gui::Textfield>("", Bounds(0, 0, 400, 33))) + _text_field(std::make_shared<gui::Textfield>("", Bounds(0, 0, 300, 33))) //_joint(std::make_shared<gui::HorizontalLayout>(std::vector<Layout::Ptr>{_text, _text_field})) { _text->set_font(Font(0.75, Style::Bold)); diff --git a/docs/parameters_tgrabs.rst b/docs/parameters_tgrabs.rst index 9ad1949..b660fa9 100644 --- a/docs/parameters_tgrabs.rst +++ b/docs/parameters_tgrabs.rst @@ -39,13 +39,19 @@ TGrabs parameters .. seealso:: :func:`average_method`, -.. function:: averaging_method(string) +.. function:: averaging_method(averaging_method_t) :noindex: - **default value:** "mean" + **default value:** mean + **possible values:** + - `mean`: Sum all samples and divide by N. + - `mode`: Calculate a per-pixel median of the samples. + - `max`: Use a per-pixel minimum across samples. + - `min`: Use a per-pixel maximum across samples. + + Determines the way in which the background samples are combined. The background generated in the process will be used to subtract background from foreground objects during conversion. - This can be either 'mean', 'mode', 'min' or 'max'. All accumulated background images (to be used for generating an average background) will be combined using the max or mean function. -- GitLab