From d136059fb8f11ae72ff12946159ab8032b820d2d Mon Sep 17 00:00:00 2001 From: Stéphane Lenclud Date: Mon, 1 Apr 2019 20:50:41 +0200 Subject: Easy Tracker: Removing base static library. --- tracker-easy/CMakeLists.txt | 4 +- tracker-easy/camera.cpp | 151 ++++++++++++ tracker-easy/camera.h | 57 +++++ tracker-easy/export.hpp | 11 + tracker-easy/frame.cpp | 80 ++++++ tracker-easy/frame.hpp | 44 ++++ tracker-easy/ftnoir_tracker_pt_dialog.cpp | 2 +- tracker-easy/module.cpp | 72 ++++++ tracker-easy/module.hpp | 20 ++ tracker-easy/module/CMakeLists.txt | 6 - tracker-easy/module/Resources/Logo_IR.png | Bin 10386 -> 0 bytes tracker-easy/module/camera.cpp | 151 ------------ tracker-easy/module/camera.h | 57 ----- tracker-easy/module/export.hpp | 11 - tracker-easy/module/frame.cpp | 80 ------ tracker-easy/module/frame.hpp | 44 ---- tracker-easy/module/lang/nl_NL.ts | 11 - tracker-easy/module/lang/ru_RU.ts | 11 - tracker-easy/module/lang/stub.ts | 11 - tracker-easy/module/lang/zh_CN.ts | 11 - tracker-easy/module/module.cpp | 72 ------ tracker-easy/module/module.hpp | 20 -- tracker-easy/module/point_extractor.cpp | 388 ------------------------------ tracker-easy/module/point_extractor.h | 61 ----- tracker-easy/module/tracker_pt.qrc | 5 - tracker-easy/point_extractor.cpp | 388 ++++++++++++++++++++++++++++++ tracker-easy/point_extractor.h | 61 +++++ tracker-easy/tracker_easy.qrc | 9 + tracker-easy/tracker_pt_base.qrc | 8 - 29 files changed, 895 insertions(+), 951 deletions(-) create mode 100644 tracker-easy/camera.cpp create mode 100644 tracker-easy/camera.h create mode 100644 tracker-easy/export.hpp create mode 100644 tracker-easy/frame.cpp create mode 100644 tracker-easy/frame.hpp create mode 100644 tracker-easy/module.cpp create mode 100644 tracker-easy/module.hpp delete mode 100644 tracker-easy/module/CMakeLists.txt delete mode 100644 tracker-easy/module/Resources/Logo_IR.png delete mode 100644 tracker-easy/module/camera.cpp delete mode 100644 tracker-easy/module/camera.h delete mode 100644 tracker-easy/module/export.hpp delete mode 100644 tracker-easy/module/frame.cpp delete mode 100644 tracker-easy/module/frame.hpp delete mode 100644 tracker-easy/module/lang/nl_NL.ts delete mode 100644 tracker-easy/module/lang/ru_RU.ts delete mode 100644 tracker-easy/module/lang/stub.ts delete mode 100644 tracker-easy/module/lang/zh_CN.ts delete mode 100644 tracker-easy/module/module.cpp delete mode 100644 tracker-easy/module/module.hpp delete mode 100644 tracker-easy/module/point_extractor.cpp delete mode 100644 tracker-easy/module/point_extractor.h delete mode 100644 tracker-easy/module/tracker_pt.qrc create mode 100644 tracker-easy/point_extractor.cpp create mode 100644 tracker-easy/point_extractor.h create mode 100644 tracker-easy/tracker_easy.qrc delete mode 100644 tracker-easy/tracker_pt_base.qrc (limited to 'tracker-easy') diff --git a/tracker-easy/CMakeLists.txt b/tracker-easy/CMakeLists.txt index faf9d2f3..229a90ae 100644 --- a/tracker-easy/CMakeLists.txt +++ b/tracker-easy/CMakeLists.txt @@ -1,8 +1,6 @@ find_package(OpenCV QUIET) if(OpenCV_FOUND) - otr_module(tracker-easy-base STATIC) + otr_module(tracker-easy) target_include_directories(${self} SYSTEM PUBLIC ${OpenCV_INCLUDE_DIRS}) target_link_libraries(${self} opencv_imgproc opencv_calib3d opentrack-cv opencv_core opentrack-video) - #set_property(TARGET ${self} PROPERTY OUTPUT_NAME "points-base") endif() -add_subdirectory(module) diff --git a/tracker-easy/camera.cpp b/tracker-easy/camera.cpp new file mode 100644 index 00000000..25f1f8d5 --- /dev/null +++ b/tracker-easy/camera.cpp @@ -0,0 +1,151 @@ +/* Copyright (c) 2012 Patrick Ruoff + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + */ + +#include "camera.h" +#include "frame.hpp" + +#include "compat/math-imports.hpp" + +#include + +namespace pt_module { + +Camera::Camera(const QString& module_name) : s { module_name } +{ +} + +QString Camera::get_desired_name() const +{ + return cam_desired.name; +} + +QString Camera::get_active_name() const +{ + return cam_info.name; +} + +void Camera::show_camera_settings() +{ + if (cap) + (void)cap->show_dialog(); +} + +Camera::result Camera::get_info() const +{ + if (cam_info.res_x == 0 || cam_info.res_y == 0) + return { false, pt_camera_info() }; + else + return { true, cam_info }; +} + +Camera::result Camera::get_frame(pt_frame& frame_) +{ + cv::Mat& frame = frame_.as()->mat; + + const bool new_frame = get_frame_(frame); + + if (new_frame) + { + const f dt = (f)t.elapsed_seconds(); + t.start(); + + // measure fps of valid frames + constexpr f RC = f{1}/10; // seconds + const f alpha = dt/(dt + RC); + + if (dt_mean < dt_eps) + dt_mean = dt; + else + dt_mean = (1-alpha) * dt_mean + alpha * dt; + + cam_info.fps = dt_mean > dt_eps ? 1 / dt_mean : 0; + cam_info.res_x = frame.cols; + cam_info.res_y = frame.rows; + cam_info.fov = fov; + + return { true, cam_info }; + } + else + return { false, {} }; +} + +bool Camera::start(const QString& name, int fps, int res_x, int res_y) +{ + if (fps >= 0 && res_x >= 0 && res_y >= 0) + { + if (cam_desired.name != name || + (int)cam_desired.fps != fps || + cam_desired.res_x != res_x || + cam_desired.res_y != res_y || + !cap || !cap->is_open()) + { + stop(); + + cam_desired.name = name; + cam_desired.fps = fps; + cam_desired.res_x = res_x; + cam_desired.res_y = res_y; + cam_desired.fov = fov; + + cap = video::make_camera(name); + + if (!cap) + goto fail; + + info.fps = fps; + info.width = res_x; + info.height = res_y; + + if (!cap->start(info)) + goto fail; + + cam_info = pt_camera_info(); + cam_info.name = name; + dt_mean = 0; + + cv::Mat tmp; + + if (!get_frame_(tmp)) + goto fail; + + t.start(); + } + } + + return true; + +fail: + stop(); + return false; +} + +void Camera::stop() +{ + cap = nullptr; + cam_info = {}; + cam_desired = {}; +} + +bool Camera::get_frame_(cv::Mat& img) +{ + if (cap && cap->is_open()) + { + auto [ frame, ret ] = cap->get_frame(); + if (ret) + { + int stride = frame.stride; + if (stride == 0) + stride = cv::Mat::AUTO_STEP; + img = cv::Mat(frame.height, frame.width, CV_8UC(frame.channels), (void*)frame.data, stride); + return true; + } + } + + return false; +} + +} // ns pt_module diff --git a/tracker-easy/camera.h b/tracker-easy/camera.h new file mode 100644 index 00000000..65b0e552 --- /dev/null +++ b/tracker-easy/camera.h @@ -0,0 +1,57 @@ +/* Copyright (c) 2012 Patrick Ruoff + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + */ + +#pragma once + +#include "pt-api.hpp" +#include "compat/timer.hpp" +#include "video/camera.hpp" + +#include + +#include + +#include + +namespace pt_module { + +struct Camera final : pt_camera +{ + Camera(const QString& module_name); + + bool start(const QString& name, int fps, int res_x, int res_y) override; + void stop() override; + + result get_frame(pt_frame& Frame) override; + result get_info() const override; + + pt_camera_info get_desired() const override { return cam_desired; } + QString get_desired_name() const override; + QString get_active_name() const override; + + void set_fov(f value) override { fov = value; } + void show_camera_settings() override; + + +private: + using camera = typename video::impl::camera; + + [[nodiscard]] bool get_frame_(cv::Mat& frame); + + f dt_mean = 0, fov = 30; + Timer t; + pt_camera_info cam_info; + pt_camera_info cam_desired; + + std::unique_ptr cap; + + pt_settings s; + + static constexpr f dt_eps = f{1}/256; +}; + +} // ns pt_module diff --git a/tracker-easy/export.hpp b/tracker-easy/export.hpp new file mode 100644 index 00000000..a733c9fe --- /dev/null +++ b/tracker-easy/export.hpp @@ -0,0 +1,11 @@ +// generates export.hpp for each module from compat/linkage.hpp + +#pragma once + +#include "compat/linkage-macros.hpp" + +#ifdef BUILD_TRACKER_PT +# define OTR_PT_EXPORT OTR_GENERIC_EXPORT +#else +# define OTR_PT_EXPORT OTR_GENERIC_IMPORT +#endif diff --git a/tracker-easy/frame.cpp b/tracker-easy/frame.cpp new file mode 100644 index 00000000..a045b783 --- /dev/null +++ b/tracker-easy/frame.cpp @@ -0,0 +1,80 @@ +#include "frame.hpp" + +#include "compat/math.hpp" + +#include + +namespace pt_module { + +Preview& Preview::operator=(const pt_frame& frame_) +{ + const cv::Mat& frame = frame_.as_const()->mat; + + if (frame.channels() != 3) + { + eval_once(qDebug() << "tracker/pt: camera frame depth: 3 !=" << frame.channels()); + return *this; + } + + const bool need_resize = frame.cols != frame_out.cols || frame.rows != frame_out.rows; + if (need_resize) + cv::resize(frame, frame_copy, cv::Size(frame_out.cols, frame_out.rows), 0, 0, cv::INTER_NEAREST); + else + frame.copyTo(frame_copy); + + return *this; +} + +Preview::Preview(int w, int h) +{ + ensure_size(frame_out, w, h, CV_8UC4); + ensure_size(frame_copy, w, h, CV_8UC3); + + frame_copy.setTo(cv::Scalar(0, 0, 0)); +} + +QImage Preview::get_bitmap() +{ + int stride = frame_out.step.p[0]; + + if (stride < 64 || stride < frame_out.cols * 4) + { + eval_once(qDebug() << "bad stride" << stride + << "for bitmap size" << frame_copy.cols << frame_copy.rows); + return QImage(); + } + + cv::cvtColor(frame_copy, frame_out, cv::COLOR_BGR2BGRA); + + return QImage((const unsigned char*) frame_out.data, + frame_out.cols, frame_out.rows, + stride, + QImage::Format_ARGB32); +} + +void Preview::draw_head_center(f x, f y) +{ + auto [px_, py_] = to_pixel_pos(x, y, frame_copy.cols, frame_copy.rows); + + int px = iround(px_), py = iround(py_); + + constexpr int len = 9; + + static const cv::Scalar color(0, 255, 255); + cv::line(frame_copy, + cv::Point(px - len, py), + cv::Point(px + len, py), + color, 1); + cv::line(frame_copy, + cv::Point(px, py - len), + cv::Point(px, py + len), + color, 1); +} + +void Preview::ensure_size(cv::Mat& frame, int w, int h, int type) +{ + if (frame.cols != w || frame.rows != h) + frame = cv::Mat(h, w, type); +} + +} // ns pt_module diff --git a/tracker-easy/frame.hpp b/tracker-easy/frame.hpp new file mode 100644 index 00000000..89334599 --- /dev/null +++ b/tracker-easy/frame.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include "pt-api.hpp" + +#include +#include + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wweak-vtables" +#endif + +namespace pt_module { + +struct Frame final : pt_frame +{ + cv::Mat mat; + + operator const cv::Mat&() const& { return mat; } + operator cv::Mat&() & { return mat; } +}; + +struct Preview final : pt_preview +{ + Preview(int w, int h); + + Preview& operator=(const pt_frame& frame) override; + QImage get_bitmap() override; + void draw_head_center(f x, f y) override; + + operator cv::Mat&() { return frame_copy; } + operator cv::Mat const&() const { return frame_copy; } + +private: + static void ensure_size(cv::Mat& frame, int w, int h, int type); + + cv::Mat frame_copy, frame_out; +}; + +} // ns pt_module + +#ifdef __clang__ +# pragma clang diagnostic pop +#endif diff --git a/tracker-easy/ftnoir_tracker_pt_dialog.cpp b/tracker-easy/ftnoir_tracker_pt_dialog.cpp index f56d3014..a3fa9470 100644 --- a/tracker-easy/ftnoir_tracker_pt_dialog.cpp +++ b/tracker-easy/ftnoir_tracker_pt_dialog.cpp @@ -18,7 +18,7 @@ using namespace options; -static void init_resources() { Q_INIT_RESOURCE(tracker_pt_base); } +static void init_resources() { Q_INIT_RESOURCE(tracker_easy); } namespace pt_impl { diff --git a/tracker-easy/module.cpp b/tracker-easy/module.cpp new file mode 100644 index 00000000..9aa71385 --- /dev/null +++ b/tracker-easy/module.cpp @@ -0,0 +1,72 @@ +#include "ftnoir_tracker_pt.h" + +#include "module.hpp" +#include "camera.h" +#include "frame.hpp" +#include "point_extractor.h" +#include "ftnoir_tracker_pt_dialog.h" + +#include "pt-api.hpp" + +#include + +static const QString module_name = "tracker-pt"; + +#ifdef __clang__ +# pragma clang diagnostic ignored "-Wweak-vtables" +#endif + +namespace pt_module { + +struct pt_module_traits final : pt_runtime_traits +{ + pointer make_camera() const override + { + return pointer(new Camera(module_name)); + } + + pointer make_point_extractor() const override + { + return pointer(new PointExtractor(module_name)); + } + + QString get_module_name() const override + { + return module_name; + } + + pointer make_frame() const override + { + return pointer(new Frame); + } + + pointer make_preview(int w, int h) const override + { + return pointer(new Preview(w, h)); + } +}; + +struct tracker_pt : Tracker_PT +{ + tracker_pt() : EasyTracker(pointer(new pt_module_traits)) + { + } +}; + +struct dialog_pt : TrackerDialog_PT +{ + dialog_pt(); +}; + +dialog_pt::dialog_pt() : EasyTrackerDialog(module_name) {} + +QString metadata_pt::name() { return tr("Easy Tracker 0.1"); } +QIcon metadata_pt::icon() { return QIcon(":/Resources/Logo_IR.png"); } + +} + +// ns pt_module + +using namespace pt_module; + +OPENTRACK_DECLARE_TRACKER(tracker_pt, dialog_pt, metadata_pt) diff --git a/tracker-easy/module.hpp b/tracker-easy/module.hpp new file mode 100644 index 00000000..0b3f12cf --- /dev/null +++ b/tracker-easy/module.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "api/plugin-api.hpp" +#include +#include + +#include "compat/linkage-macros.hpp" + +namespace pt_module +{ + +class OTR_GENERIC_EXPORT metadata_pt : public Metadata +{ + Q_OBJECT + + QString name() override; + QIcon icon() override; +}; + +} // ns pt_module diff --git a/tracker-easy/module/CMakeLists.txt b/tracker-easy/module/CMakeLists.txt deleted file mode 100644 index e0c22f3c..00000000 --- a/tracker-easy/module/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -find_package(OpenCV QUIET) -if(OpenCV_FOUND) - otr_module(tracker-easy) - target_link_libraries(${self} opentrack-tracker-easy-base) - target_include_directories(${self} PUBLIC "${CMAKE_SOURCE_DIR}/tracker-easy") -endif() diff --git a/tracker-easy/module/Resources/Logo_IR.png b/tracker-easy/module/Resources/Logo_IR.png deleted file mode 100644 index 95032a25..00000000 Binary files a/tracker-easy/module/Resources/Logo_IR.png and /dev/null differ diff --git a/tracker-easy/module/camera.cpp b/tracker-easy/module/camera.cpp deleted file mode 100644 index 25f1f8d5..00000000 --- a/tracker-easy/module/camera.cpp +++ /dev/null @@ -1,151 +0,0 @@ -/* Copyright (c) 2012 Patrick Ruoff - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - */ - -#include "camera.h" -#include "frame.hpp" - -#include "compat/math-imports.hpp" - -#include - -namespace pt_module { - -Camera::Camera(const QString& module_name) : s { module_name } -{ -} - -QString Camera::get_desired_name() const -{ - return cam_desired.name; -} - -QString Camera::get_active_name() const -{ - return cam_info.name; -} - -void Camera::show_camera_settings() -{ - if (cap) - (void)cap->show_dialog(); -} - -Camera::result Camera::get_info() const -{ - if (cam_info.res_x == 0 || cam_info.res_y == 0) - return { false, pt_camera_info() }; - else - return { true, cam_info }; -} - -Camera::result Camera::get_frame(pt_frame& frame_) -{ - cv::Mat& frame = frame_.as()->mat; - - const bool new_frame = get_frame_(frame); - - if (new_frame) - { - const f dt = (f)t.elapsed_seconds(); - t.start(); - - // measure fps of valid frames - constexpr f RC = f{1}/10; // seconds - const f alpha = dt/(dt + RC); - - if (dt_mean < dt_eps) - dt_mean = dt; - else - dt_mean = (1-alpha) * dt_mean + alpha * dt; - - cam_info.fps = dt_mean > dt_eps ? 1 / dt_mean : 0; - cam_info.res_x = frame.cols; - cam_info.res_y = frame.rows; - cam_info.fov = fov; - - return { true, cam_info }; - } - else - return { false, {} }; -} - -bool Camera::start(const QString& name, int fps, int res_x, int res_y) -{ - if (fps >= 0 && res_x >= 0 && res_y >= 0) - { - if (cam_desired.name != name || - (int)cam_desired.fps != fps || - cam_desired.res_x != res_x || - cam_desired.res_y != res_y || - !cap || !cap->is_open()) - { - stop(); - - cam_desired.name = name; - cam_desired.fps = fps; - cam_desired.res_x = res_x; - cam_desired.res_y = res_y; - cam_desired.fov = fov; - - cap = video::make_camera(name); - - if (!cap) - goto fail; - - info.fps = fps; - info.width = res_x; - info.height = res_y; - - if (!cap->start(info)) - goto fail; - - cam_info = pt_camera_info(); - cam_info.name = name; - dt_mean = 0; - - cv::Mat tmp; - - if (!get_frame_(tmp)) - goto fail; - - t.start(); - } - } - - return true; - -fail: - stop(); - return false; -} - -void Camera::stop() -{ - cap = nullptr; - cam_info = {}; - cam_desired = {}; -} - -bool Camera::get_frame_(cv::Mat& img) -{ - if (cap && cap->is_open()) - { - auto [ frame, ret ] = cap->get_frame(); - if (ret) - { - int stride = frame.stride; - if (stride == 0) - stride = cv::Mat::AUTO_STEP; - img = cv::Mat(frame.height, frame.width, CV_8UC(frame.channels), (void*)frame.data, stride); - return true; - } - } - - return false; -} - -} // ns pt_module diff --git a/tracker-easy/module/camera.h b/tracker-easy/module/camera.h deleted file mode 100644 index 65b0e552..00000000 --- a/tracker-easy/module/camera.h +++ /dev/null @@ -1,57 +0,0 @@ -/* Copyright (c) 2012 Patrick Ruoff - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - */ - -#pragma once - -#include "pt-api.hpp" -#include "compat/timer.hpp" -#include "video/camera.hpp" - -#include - -#include - -#include - -namespace pt_module { - -struct Camera final : pt_camera -{ - Camera(const QString& module_name); - - bool start(const QString& name, int fps, int res_x, int res_y) override; - void stop() override; - - result get_frame(pt_frame& Frame) override; - result get_info() const override; - - pt_camera_info get_desired() const override { return cam_desired; } - QString get_desired_name() const override; - QString get_active_name() const override; - - void set_fov(f value) override { fov = value; } - void show_camera_settings() override; - - -private: - using camera = typename video::impl::camera; - - [[nodiscard]] bool get_frame_(cv::Mat& frame); - - f dt_mean = 0, fov = 30; - Timer t; - pt_camera_info cam_info; - pt_camera_info cam_desired; - - std::unique_ptr cap; - - pt_settings s; - - static constexpr f dt_eps = f{1}/256; -}; - -} // ns pt_module diff --git a/tracker-easy/module/export.hpp b/tracker-easy/module/export.hpp deleted file mode 100644 index a733c9fe..00000000 --- a/tracker-easy/module/export.hpp +++ /dev/null @@ -1,11 +0,0 @@ -// generates export.hpp for each module from compat/linkage.hpp - -#pragma once - -#include "compat/linkage-macros.hpp" - -#ifdef BUILD_TRACKER_PT -# define OTR_PT_EXPORT OTR_GENERIC_EXPORT -#else -# define OTR_PT_EXPORT OTR_GENERIC_IMPORT -#endif diff --git a/tracker-easy/module/frame.cpp b/tracker-easy/module/frame.cpp deleted file mode 100644 index a045b783..00000000 --- a/tracker-easy/module/frame.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include "frame.hpp" - -#include "compat/math.hpp" - -#include - -namespace pt_module { - -Preview& Preview::operator=(const pt_frame& frame_) -{ - const cv::Mat& frame = frame_.as_const()->mat; - - if (frame.channels() != 3) - { - eval_once(qDebug() << "tracker/pt: camera frame depth: 3 !=" << frame.channels()); - return *this; - } - - const bool need_resize = frame.cols != frame_out.cols || frame.rows != frame_out.rows; - if (need_resize) - cv::resize(frame, frame_copy, cv::Size(frame_out.cols, frame_out.rows), 0, 0, cv::INTER_NEAREST); - else - frame.copyTo(frame_copy); - - return *this; -} - -Preview::Preview(int w, int h) -{ - ensure_size(frame_out, w, h, CV_8UC4); - ensure_size(frame_copy, w, h, CV_8UC3); - - frame_copy.setTo(cv::Scalar(0, 0, 0)); -} - -QImage Preview::get_bitmap() -{ - int stride = frame_out.step.p[0]; - - if (stride < 64 || stride < frame_out.cols * 4) - { - eval_once(qDebug() << "bad stride" << stride - << "for bitmap size" << frame_copy.cols << frame_copy.rows); - return QImage(); - } - - cv::cvtColor(frame_copy, frame_out, cv::COLOR_BGR2BGRA); - - return QImage((const unsigned char*) frame_out.data, - frame_out.cols, frame_out.rows, - stride, - QImage::Format_ARGB32); -} - -void Preview::draw_head_center(f x, f y) -{ - auto [px_, py_] = to_pixel_pos(x, y, frame_copy.cols, frame_copy.rows); - - int px = iround(px_), py = iround(py_); - - constexpr int len = 9; - - static const cv::Scalar color(0, 255, 255); - cv::line(frame_copy, - cv::Point(px - len, py), - cv::Point(px + len, py), - color, 1); - cv::line(frame_copy, - cv::Point(px, py - len), - cv::Point(px, py + len), - color, 1); -} - -void Preview::ensure_size(cv::Mat& frame, int w, int h, int type) -{ - if (frame.cols != w || frame.rows != h) - frame = cv::Mat(h, w, type); -} - -} // ns pt_module diff --git a/tracker-easy/module/frame.hpp b/tracker-easy/module/frame.hpp deleted file mode 100644 index 89334599..00000000 --- a/tracker-easy/module/frame.hpp +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include "pt-api.hpp" - -#include -#include - -#ifdef __clang__ -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wweak-vtables" -#endif - -namespace pt_module { - -struct Frame final : pt_frame -{ - cv::Mat mat; - - operator const cv::Mat&() const& { return mat; } - operator cv::Mat&() & { return mat; } -}; - -struct Preview final : pt_preview -{ - Preview(int w, int h); - - Preview& operator=(const pt_frame& frame) override; - QImage get_bitmap() override; - void draw_head_center(f x, f y) override; - - operator cv::Mat&() { return frame_copy; } - operator cv::Mat const&() const { return frame_copy; } - -private: - static void ensure_size(cv::Mat& frame, int w, int h, int type); - - cv::Mat frame_copy, frame_out; -}; - -} // ns pt_module - -#ifdef __clang__ -# pragma clang diagnostic pop -#endif diff --git a/tracker-easy/module/lang/nl_NL.ts b/tracker-easy/module/lang/nl_NL.ts deleted file mode 100644 index e0f4dbb8..00000000 --- a/tracker-easy/module/lang/nl_NL.ts +++ /dev/null @@ -1,11 +0,0 @@ - - - - - pt_module::metadata_pt - - Easy Tracker 0.1 - - - - diff --git a/tracker-easy/module/lang/ru_RU.ts b/tracker-easy/module/lang/ru_RU.ts deleted file mode 100644 index c349c60a..00000000 --- a/tracker-easy/module/lang/ru_RU.ts +++ /dev/null @@ -1,11 +0,0 @@ - - - - - pt_module::metadata_pt - - Easy Tracker 0.1 - - - - diff --git a/tracker-easy/module/lang/stub.ts b/tracker-easy/module/lang/stub.ts deleted file mode 100644 index 678740cd..00000000 --- a/tracker-easy/module/lang/stub.ts +++ /dev/null @@ -1,11 +0,0 @@ - - - - - pt_module::metadata_pt - - Easy Tracker 0.1 - - - - diff --git a/tracker-easy/module/lang/zh_CN.ts b/tracker-easy/module/lang/zh_CN.ts deleted file mode 100644 index 678740cd..00000000 --- a/tracker-easy/module/lang/zh_CN.ts +++ /dev/null @@ -1,11 +0,0 @@ - - - - - pt_module::metadata_pt - - Easy Tracker 0.1 - - - - diff --git a/tracker-easy/module/module.cpp b/tracker-easy/module/module.cpp deleted file mode 100644 index 9aa71385..00000000 --- a/tracker-easy/module/module.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#include "ftnoir_tracker_pt.h" - -#include "module.hpp" -#include "camera.h" -#include "frame.hpp" -#include "point_extractor.h" -#include "ftnoir_tracker_pt_dialog.h" - -#include "pt-api.hpp" - -#include - -static const QString module_name = "tracker-pt"; - -#ifdef __clang__ -# pragma clang diagnostic ignored "-Wweak-vtables" -#endif - -namespace pt_module { - -struct pt_module_traits final : pt_runtime_traits -{ - pointer make_camera() const override - { - return pointer(new Camera(module_name)); - } - - pointer make_point_extractor() const override - { - return pointer(new PointExtractor(module_name)); - } - - QString get_module_name() const override - { - return module_name; - } - - pointer make_frame() const override - { - return pointer(new Frame); - } - - pointer make_preview(int w, int h) const override - { - return pointer(new Preview(w, h)); - } -}; - -struct tracker_pt : Tracker_PT -{ - tracker_pt() : EasyTracker(pointer(new pt_module_traits)) - { - } -}; - -struct dialog_pt : TrackerDialog_PT -{ - dialog_pt(); -}; - -dialog_pt::dialog_pt() : EasyTrackerDialog(module_name) {} - -QString metadata_pt::name() { return tr("Easy Tracker 0.1"); } -QIcon metadata_pt::icon() { return QIcon(":/Resources/Logo_IR.png"); } - -} - -// ns pt_module - -using namespace pt_module; - -OPENTRACK_DECLARE_TRACKER(tracker_pt, dialog_pt, metadata_pt) diff --git a/tracker-easy/module/module.hpp b/tracker-easy/module/module.hpp deleted file mode 100644 index 0b3f12cf..00000000 --- a/tracker-easy/module/module.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include "api/plugin-api.hpp" -#include -#include - -#include "compat/linkage-macros.hpp" - -namespace pt_module -{ - -class OTR_GENERIC_EXPORT metadata_pt : public Metadata -{ - Q_OBJECT - - QString name() override; - QIcon icon() override; -}; - -} // ns pt_module diff --git a/tracker-easy/module/point_extractor.cpp b/tracker-easy/module/point_extractor.cpp deleted file mode 100644 index 0d54a66b..00000000 --- a/tracker-easy/module/point_extractor.cpp +++ /dev/null @@ -1,388 +0,0 @@ -/* Copyright (c) 2012 Patrick Ruoff - * Copyright (c) 2015-2017 Stanislaw Halik - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - */ - -#include "point_extractor.h" -#include "frame.hpp" - -#include "cv/numeric.hpp" -#include "compat/math.hpp" - -#undef PREVIEW -//#define PREVIEW - -#if defined PREVIEW -# include -#endif - -#include -#include -#include -#include - -#include - -using namespace numeric_types; - -// meanshift code written by Michael Welter - -/* -http://en.wikipedia.org/wiki/Mean-shift -In this application the idea, is to eliminate any bias of the point estimate -which is introduced by the rather arbitrary thresholded area. One must recognize -that the thresholded area can only move in one pixel increments since it is -binary. Thus, its center of mass might make "jumps" as pixels are added/removed -from the thresholded area. -With mean-shift, a moving "window" or kernel is multiplied with the gray-scale -image, and the COM is calculated of the result. This is iterated where the -kernel center is set the previously computed COM. Thus, peaks in the image intensity -distribution "pull" the kernel towards themselves. Eventually it stops moving, i.e. -then the computed COM coincides with the kernel center. We hope that the -corresponding location is a good candidate for the extracted point. -The idea similar to the window scaling suggested in Berglund et al. "Fast, bias-free -algorithm for tracking single particles with variable size and shape." (2008). -*/ -static vec2 MeanShiftIteration(const cv::Mat1b &frame_gray, const vec2 ¤t_center, f filter_width) -{ - const f s = 1 / filter_width; - - f m = 0; - vec2 com { 0, 0 }; - for (int i = 0; i < frame_gray.rows; i++) - { - uint8_t const* const __restrict frame_ptr = frame_gray.ptr(i); - for (int j = 0; j < frame_gray.cols; j++) - { - f val = frame_ptr[j]; - val = val * val; // taking the square weighs brighter parts of the image stronger. - f dx = (j - current_center[0])*s; - f dy = (i - current_center[1])*s; - f max = std::fmax(f(0), 1 - dx*dx - dy*dy); - val *= max; - m += val; - com[0] += j * val; - com[1] += i * val; - } - } - if (m > f(.1)) - { - com *= 1 / m; - return com; - } - else - return current_center; -} - -namespace pt_module { - -PointExtractor::PointExtractor(const QString& module_name) : s(module_name) -{ - blobs.reserve(max_blobs); -} - -void PointExtractor::ensure_channel_buffers(const cv::Mat& orig_frame) -{ - if (ch[0].rows != orig_frame.rows || ch[0].cols != orig_frame.cols) - for (cv::Mat1b& x : ch) - x = cv::Mat1b(orig_frame.rows, orig_frame.cols); -} - -void PointExtractor::ensure_buffers(const cv::Mat& frame) -{ - const int W = frame.cols, H = frame.rows; - - if (frame_gray.rows != W || frame_gray.cols != H) - { - frame_gray = cv::Mat1b(H, W); - frame_bin = cv::Mat1b(H, W); - frame_gray_unmasked = cv::Mat1b(H, W); - } -} - -void PointExtractor::extract_single_channel(const cv::Mat& orig_frame, int idx, cv::Mat1b& dest) -{ - ensure_channel_buffers(orig_frame); - - const int from_to[] = { - idx, 0, - }; - - cv::mixChannels(&orig_frame, 1, &dest, 1, from_to, 1); -} - -void PointExtractor::color_to_grayscale(const cv::Mat& frame, cv::Mat1b& output) -{ - switch (s.blob_color) - { - case pt_color_green_only: - { - extract_single_channel(frame, 1, output); - break; - } - case pt_color_blue_only: - { - extract_single_channel(frame, 0, output); - break; - } - case pt_color_red_only: - { - extract_single_channel(frame, 2, output); - break; - } - case pt_color_average: - { - const int W = frame.cols, H = frame.rows, sz = W*H; - cv::reduce(frame.reshape(1, sz), - output.reshape(1, sz), - 1, cv::REDUCE_AVG); - break; - } - default: - eval_once(qDebug() << "wrong pt_color_type enum value" << int(s.blob_color)); - [[fallthrough]]; - case pt_color_natural: - cv::cvtColor(frame, output, cv::COLOR_BGR2GRAY); - break; - } -} - -void PointExtractor::threshold_image(const cv::Mat& frame_gray, cv::Mat1b& output) -{ - const int threshold_slider_value = s.threshold_slider.to(); - - if (!s.auto_threshold) - { - cv::threshold(frame_gray, output, threshold_slider_value, 255, cv::THRESH_BINARY); - } - else - { - const int hist_size = 256; - const float ranges_[] = { 0, 256 }; - float const* ranges = (const float*) ranges_; - - cv::calcHist(&frame_gray, - 1, - nullptr, - cv::noArray(), - hist, - 1, - &hist_size, - &ranges); - - const f radius = threshold_radius_value(frame_gray.cols, frame_gray.rows, threshold_slider_value); - - float const* const __restrict ptr = hist.ptr(0); - const unsigned area = uround(3 * pi * radius*radius); - const unsigned sz = unsigned(hist.cols * hist.rows); - constexpr unsigned min_thres = 64; - unsigned thres = min_thres; - for (unsigned i = sz-1, cnt = 0; i > 32; i--) - { - cnt += (unsigned)ptr[i]; - if (cnt >= area) - break; - thres = i; - } - - cv::threshold(frame_gray, output, thres, 255, cv::THRESH_BINARY); - } -} - -static void draw_blobs(cv::Mat& preview_frame, const blob* blobs, unsigned nblobs, const cv::Size& size) -{ - for (unsigned k = 0; k < nblobs; k++) - { - const blob& b = blobs[k]; - - if (b.radius < 0) - continue; - - const f dpi = preview_frame.cols / f(320); - const f offx = 10 * dpi, offy = f(7.5) * dpi; - - const f cx = preview_frame.cols / f(size.width), - cy = preview_frame.rows / f(size.height), - c = std::fmax(f(1), cx+cy)/2; - - constexpr unsigned fract_bits = 8; - constexpr int c_fract(1 << fract_bits); - - cv::Point p(iround(b.pos[0] * cx * c_fract), iround(b.pos[1] * cy * c_fract)); - - auto circle_color = k >= KPointCount - ? cv::Scalar(192, 192, 192) - : cv::Scalar(255, 255, 0); - - const int overlay_size = iround(dpi); - - cv::circle(preview_frame, p, iround((b.radius + f(3.3) * c) * c_fract), - circle_color, overlay_size, - cv::LINE_AA, fract_bits); - - char buf[16]; - buf[sizeof(buf)-1] = '\0'; - std::snprintf(buf, sizeof(buf) - 1, "%.2fpx", (double)b.radius); - - auto text_color = k >= KPointCount - ? cv::Scalar(160, 160, 160) - : cv::Scalar(0, 0, 255); - - cv::Point pos(iround(b.pos[0]*cx+offx), iround(b.pos[1]*cy+offy)); - cv::putText(preview_frame, buf, pos, - cv::FONT_HERSHEY_PLAIN, overlay_size, text_color, - 1); - } -} - -void PointExtractor::extract_points(const pt_frame& frame_, pt_preview& preview_frame_, std::vector& points, std::vector& imagePoints) -{ - const cv::Mat& frame = frame_.as_const()->mat; - - ensure_buffers(frame); - color_to_grayscale(frame, frame_gray_unmasked); - -#if defined PREVIEW - cv::imshow("capture", frame_gray); - cv::waitKey(1); -#endif - - threshold_image(frame_gray_unmasked, frame_bin); - frame_gray_unmasked.copyTo(frame_gray, frame_bin); - - const f region_size_min = (f)s.min_point_size; - const f region_size_max = (f)s.max_point_size; - - unsigned idx = 0; - - blobs.clear(); - - for (int y=0; y < frame_bin.rows; y++) - { - const unsigned char* __restrict ptr_bin = frame_bin.ptr(y); - for (int x=0; x < frame_bin.cols; x++) - { - if (ptr_bin[x] != 255) - continue; - idx = blobs.size() + 1; - - cv::Rect rect; - cv::floodFill(frame_bin, - cv::Point(x,y), - cv::Scalar(idx), - &rect, - cv::Scalar(0), - cv::Scalar(0), - 4 | cv::FLOODFILL_FIXED_RANGE); - - unsigned cnt = 0; - unsigned norm = 0; - - const int ymax = rect.y+rect.height, - xmax = rect.x+rect.width; - - for (int i=rect.y; i < ymax; i++) - { - unsigned char const* const __restrict ptr_blobs = frame_bin.ptr(i); - unsigned char const* const __restrict ptr_gray = frame_gray.ptr(i); - for (int j=rect.x; j < xmax; j++) - { - if (ptr_blobs[j] != idx) - continue; - - //ptr_blobs[j] = 0; - norm += ptr_gray[j]; - cnt++; - } - } - - const f radius = std::sqrt(cnt / pi); - if (radius > region_size_max || radius < region_size_min) - continue; - - blobs.emplace_back(radius, - vec2(rect.width/f(2), rect.height/f(2)), - std::pow(f(norm), f(1.1))/cnt, - rect); - - if (idx >= max_blobs) - goto end; - - // XXX we could go to the next scanline unless the points are really small. - // i'd expect each point being present on at least one unique scanline - // but it turns out some people are using 2px points -sh 20180110 - //break; - } - } -end: - - const int W = frame_gray.cols; - const int H = frame_gray.rows; - - const unsigned sz = blobs.size(); - - std::sort(blobs.begin(), blobs.end(), [](const blob& b1, const blob& b2) { return b2.brightness < b1.brightness; }); - - for (idx = 0; idx < sz; ++idx) - { - blob& b = blobs[idx]; - cv::Rect rect = b.rect; - - rect.x -= rect.width / 2; - rect.y -= rect.height / 2; - rect.width *= 2; - rect.height *= 2; - rect &= cv::Rect(0, 0, W, H); // crop at frame boundaries - - cv::Mat frame_roi = frame_gray(rect); - - // smaller values mean more changes. 1 makes too many changes while 1.5 makes about .1 - static constexpr f radius_c = f(1.75); - - const f kernel_radius = b.radius * radius_c; - vec2 pos(rect.width/f(2), rect.height/f(2)); // position relative to ROI. - - for (int iter = 0; iter < 10; ++iter) - { - vec2 com_new = MeanShiftIteration(frame_roi, pos, kernel_radius); - vec2 delta = com_new - pos; - pos = com_new; - if (delta.dot(delta) < f(1e-3)) - break; - } - - b.pos[0] = pos[0] + rect.x; - b.pos[1] = pos[1] + rect.y; - } - - // TODO: Do not do that if no preview. Delay blob drawing until we know where are the points? - draw_blobs(preview_frame_.as()->mat, - blobs.data(), blobs.size(), - frame_gray.size()); - - - // End of mean shift code. At this point, blob positions are updated with hopefully less noisy less biased values. - points.reserve(max_blobs); - points.clear(); - - for (const auto& b : blobs) - { - // note: H/W is equal to fx/fy - - vec2 p; - std::tie(p[0], p[1]) = to_screen_pos(b.pos[0], b.pos[1], W, H); - points.push_back(p); - imagePoints.push_back(vec2(b.pos[0], b.pos[1])); - } -} - -blob::blob(f radius, const vec2& pos, f brightness, const cv::Rect& rect) : - radius(radius), brightness(brightness), pos(pos), rect(rect) -{ - //qDebug() << "radius" << radius << "pos" << pos[0] << pos[1]; -} - -} // ns pt_module diff --git a/tracker-easy/module/point_extractor.h b/tracker-easy/module/point_extractor.h deleted file mode 100644 index 2af5c131..00000000 --- a/tracker-easy/module/point_extractor.h +++ /dev/null @@ -1,61 +0,0 @@ -/* Copyright (c) 2012 Patrick Ruoff - * Copyright (c) 2015-2016 Stanislaw Halik - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - */ - -#pragma once - -#include "pt-api.hpp" - -#include - -#include -#include - -namespace pt_module { - -using namespace numeric_types; - -struct blob final -{ - f radius, brightness; - vec2 pos; - cv::Rect rect; - - blob(f radius, const vec2& pos, f brightness, const cv::Rect& rect); -}; - -class PointExtractor final : public pt_point_extractor -{ -public: - // extracts points from frame and draws some processing info into frame, if draw_output is set - // dt: time since last call in seconds - void extract_points(const pt_frame& frame, pt_preview& preview_frame, std::vector& points, std::vector& imagePoints) override; - PointExtractor(const QString& module_name); - -public: - std::vector blobs; - -private: - static constexpr int max_blobs = 16; - - pt_settings s; - - cv::Mat1b frame_gray_unmasked, frame_bin, frame_gray; - cv::Mat1f hist; - cv::Mat1b ch[3]; - - void ensure_channel_buffers(const cv::Mat& orig_frame); - void ensure_buffers(const cv::Mat& frame); - - void extract_single_channel(const cv::Mat& orig_frame, int idx, cv::Mat1b& dest); - - void color_to_grayscale(const cv::Mat& frame, cv::Mat1b& output); - void threshold_image(const cv::Mat& frame_gray, cv::Mat1b& output); -}; - -} // ns impl - diff --git a/tracker-easy/module/tracker_pt.qrc b/tracker-easy/module/tracker_pt.qrc deleted file mode 100644 index dfeb7369..00000000 --- a/tracker-easy/module/tracker_pt.qrc +++ /dev/null @@ -1,5 +0,0 @@ - - - Resources/Logo_IR.png - - diff --git a/tracker-easy/point_extractor.cpp b/tracker-easy/point_extractor.cpp new file mode 100644 index 00000000..0d54a66b --- /dev/null +++ b/tracker-easy/point_extractor.cpp @@ -0,0 +1,388 @@ +/* Copyright (c) 2012 Patrick Ruoff + * Copyright (c) 2015-2017 Stanislaw Halik + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + */ + +#include "point_extractor.h" +#include "frame.hpp" + +#include "cv/numeric.hpp" +#include "compat/math.hpp" + +#undef PREVIEW +//#define PREVIEW + +#if defined PREVIEW +# include +#endif + +#include +#include +#include +#include + +#include + +using namespace numeric_types; + +// meanshift code written by Michael Welter + +/* +http://en.wikipedia.org/wiki/Mean-shift +In this application the idea, is to eliminate any bias of the point estimate +which is introduced by the rather arbitrary thresholded area. One must recognize +that the thresholded area can only move in one pixel increments since it is +binary. Thus, its center of mass might make "jumps" as pixels are added/removed +from the thresholded area. +With mean-shift, a moving "window" or kernel is multiplied with the gray-scale +image, and the COM is calculated of the result. This is iterated where the +kernel center is set the previously computed COM. Thus, peaks in the image intensity +distribution "pull" the kernel towards themselves. Eventually it stops moving, i.e. +then the computed COM coincides with the kernel center. We hope that the +corresponding location is a good candidate for the extracted point. +The idea similar to the window scaling suggested in Berglund et al. "Fast, bias-free +algorithm for tracking single particles with variable size and shape." (2008). +*/ +static vec2 MeanShiftIteration(const cv::Mat1b &frame_gray, const vec2 ¤t_center, f filter_width) +{ + const f s = 1 / filter_width; + + f m = 0; + vec2 com { 0, 0 }; + for (int i = 0; i < frame_gray.rows; i++) + { + uint8_t const* const __restrict frame_ptr = frame_gray.ptr(i); + for (int j = 0; j < frame_gray.cols; j++) + { + f val = frame_ptr[j]; + val = val * val; // taking the square weighs brighter parts of the image stronger. + f dx = (j - current_center[0])*s; + f dy = (i - current_center[1])*s; + f max = std::fmax(f(0), 1 - dx*dx - dy*dy); + val *= max; + m += val; + com[0] += j * val; + com[1] += i * val; + } + } + if (m > f(.1)) + { + com *= 1 / m; + return com; + } + else + return current_center; +} + +namespace pt_module { + +PointExtractor::PointExtractor(const QString& module_name) : s(module_name) +{ + blobs.reserve(max_blobs); +} + +void PointExtractor::ensure_channel_buffers(const cv::Mat& orig_frame) +{ + if (ch[0].rows != orig_frame.rows || ch[0].cols != orig_frame.cols) + for (cv::Mat1b& x : ch) + x = cv::Mat1b(orig_frame.rows, orig_frame.cols); +} + +void PointExtractor::ensure_buffers(const cv::Mat& frame) +{ + const int W = frame.cols, H = frame.rows; + + if (frame_gray.rows != W || frame_gray.cols != H) + { + frame_gray = cv::Mat1b(H, W); + frame_bin = cv::Mat1b(H, W); + frame_gray_unmasked = cv::Mat1b(H, W); + } +} + +void PointExtractor::extract_single_channel(const cv::Mat& orig_frame, int idx, cv::Mat1b& dest) +{ + ensure_channel_buffers(orig_frame); + + const int from_to[] = { + idx, 0, + }; + + cv::mixChannels(&orig_frame, 1, &dest, 1, from_to, 1); +} + +void PointExtractor::color_to_grayscale(const cv::Mat& frame, cv::Mat1b& output) +{ + switch (s.blob_color) + { + case pt_color_green_only: + { + extract_single_channel(frame, 1, output); + break; + } + case pt_color_blue_only: + { + extract_single_channel(frame, 0, output); + break; + } + case pt_color_red_only: + { + extract_single_channel(frame, 2, output); + break; + } + case pt_color_average: + { + const int W = frame.cols, H = frame.rows, sz = W*H; + cv::reduce(frame.reshape(1, sz), + output.reshape(1, sz), + 1, cv::REDUCE_AVG); + break; + } + default: + eval_once(qDebug() << "wrong pt_color_type enum value" << int(s.blob_color)); + [[fallthrough]]; + case pt_color_natural: + cv::cvtColor(frame, output, cv::COLOR_BGR2GRAY); + break; + } +} + +void PointExtractor::threshold_image(const cv::Mat& frame_gray, cv::Mat1b& output) +{ + const int threshold_slider_value = s.threshold_slider.to(); + + if (!s.auto_threshold) + { + cv::threshold(frame_gray, output, threshold_slider_value, 255, cv::THRESH_BINARY); + } + else + { + const int hist_size = 256; + const float ranges_[] = { 0, 256 }; + float const* ranges = (const float*) ranges_; + + cv::calcHist(&frame_gray, + 1, + nullptr, + cv::noArray(), + hist, + 1, + &hist_size, + &ranges); + + const f radius = threshold_radius_value(frame_gray.cols, frame_gray.rows, threshold_slider_value); + + float const* const __restrict ptr = hist.ptr(0); + const unsigned area = uround(3 * pi * radius*radius); + const unsigned sz = unsigned(hist.cols * hist.rows); + constexpr unsigned min_thres = 64; + unsigned thres = min_thres; + for (unsigned i = sz-1, cnt = 0; i > 32; i--) + { + cnt += (unsigned)ptr[i]; + if (cnt >= area) + break; + thres = i; + } + + cv::threshold(frame_gray, output, thres, 255, cv::THRESH_BINARY); + } +} + +static void draw_blobs(cv::Mat& preview_frame, const blob* blobs, unsigned nblobs, const cv::Size& size) +{ + for (unsigned k = 0; k < nblobs; k++) + { + const blob& b = blobs[k]; + + if (b.radius < 0) + continue; + + const f dpi = preview_frame.cols / f(320); + const f offx = 10 * dpi, offy = f(7.5) * dpi; + + const f cx = preview_frame.cols / f(size.width), + cy = preview_frame.rows / f(size.height), + c = std::fmax(f(1), cx+cy)/2; + + constexpr unsigned fract_bits = 8; + constexpr int c_fract(1 << fract_bits); + + cv::Point p(iround(b.pos[0] * cx * c_fract), iround(b.pos[1] * cy * c_fract)); + + auto circle_color = k >= KPointCount + ? cv::Scalar(192, 192, 192) + : cv::Scalar(255, 255, 0); + + const int overlay_size = iround(dpi); + + cv::circle(preview_frame, p, iround((b.radius + f(3.3) * c) * c_fract), + circle_color, overlay_size, + cv::LINE_AA, fract_bits); + + char buf[16]; + buf[sizeof(buf)-1] = '\0'; + std::snprintf(buf, sizeof(buf) - 1, "%.2fpx", (double)b.radius); + + auto text_color = k >= KPointCount + ? cv::Scalar(160, 160, 160) + : cv::Scalar(0, 0, 255); + + cv::Point pos(iround(b.pos[0]*cx+offx), iround(b.pos[1]*cy+offy)); + cv::putText(preview_frame, buf, pos, + cv::FONT_HERSHEY_PLAIN, overlay_size, text_color, + 1); + } +} + +void PointExtractor::extract_points(const pt_frame& frame_, pt_preview& preview_frame_, std::vector& points, std::vector& imagePoints) +{ + const cv::Mat& frame = frame_.as_const()->mat; + + ensure_buffers(frame); + color_to_grayscale(frame, frame_gray_unmasked); + +#if defined PREVIEW + cv::imshow("capture", frame_gray); + cv::waitKey(1); +#endif + + threshold_image(frame_gray_unmasked, frame_bin); + frame_gray_unmasked.copyTo(frame_gray, frame_bin); + + const f region_size_min = (f)s.min_point_size; + const f region_size_max = (f)s.max_point_size; + + unsigned idx = 0; + + blobs.clear(); + + for (int y=0; y < frame_bin.rows; y++) + { + const unsigned char* __restrict ptr_bin = frame_bin.ptr(y); + for (int x=0; x < frame_bin.cols; x++) + { + if (ptr_bin[x] != 255) + continue; + idx = blobs.size() + 1; + + cv::Rect rect; + cv::floodFill(frame_bin, + cv::Point(x,y), + cv::Scalar(idx), + &rect, + cv::Scalar(0), + cv::Scalar(0), + 4 | cv::FLOODFILL_FIXED_RANGE); + + unsigned cnt = 0; + unsigned norm = 0; + + const int ymax = rect.y+rect.height, + xmax = rect.x+rect.width; + + for (int i=rect.y; i < ymax; i++) + { + unsigned char const* const __restrict ptr_blobs = frame_bin.ptr(i); + unsigned char const* const __restrict ptr_gray = frame_gray.ptr(i); + for (int j=rect.x; j < xmax; j++) + { + if (ptr_blobs[j] != idx) + continue; + + //ptr_blobs[j] = 0; + norm += ptr_gray[j]; + cnt++; + } + } + + const f radius = std::sqrt(cnt / pi); + if (radius > region_size_max || radius < region_size_min) + continue; + + blobs.emplace_back(radius, + vec2(rect.width/f(2), rect.height/f(2)), + std::pow(f(norm), f(1.1))/cnt, + rect); + + if (idx >= max_blobs) + goto end; + + // XXX we could go to the next scanline unless the points are really small. + // i'd expect each point being present on at least one unique scanline + // but it turns out some people are using 2px points -sh 20180110 + //break; + } + } +end: + + const int W = frame_gray.cols; + const int H = frame_gray.rows; + + const unsigned sz = blobs.size(); + + std::sort(blobs.begin(), blobs.end(), [](const blob& b1, const blob& b2) { return b2.brightness < b1.brightness; }); + + for (idx = 0; idx < sz; ++idx) + { + blob& b = blobs[idx]; + cv::Rect rect = b.rect; + + rect.x -= rect.width / 2; + rect.y -= rect.height / 2; + rect.width *= 2; + rect.height *= 2; + rect &= cv::Rect(0, 0, W, H); // crop at frame boundaries + + cv::Mat frame_roi = frame_gray(rect); + + // smaller values mean more changes. 1 makes too many changes while 1.5 makes about .1 + static constexpr f radius_c = f(1.75); + + const f kernel_radius = b.radius * radius_c; + vec2 pos(rect.width/f(2), rect.height/f(2)); // position relative to ROI. + + for (int iter = 0; iter < 10; ++iter) + { + vec2 com_new = MeanShiftIteration(frame_roi, pos, kernel_radius); + vec2 delta = com_new - pos; + pos = com_new; + if (delta.dot(delta) < f(1e-3)) + break; + } + + b.pos[0] = pos[0] + rect.x; + b.pos[1] = pos[1] + rect.y; + } + + // TODO: Do not do that if no preview. Delay blob drawing until we know where are the points? + draw_blobs(preview_frame_.as()->mat, + blobs.data(), blobs.size(), + frame_gray.size()); + + + // End of mean shift code. At this point, blob positions are updated with hopefully less noisy less biased values. + points.reserve(max_blobs); + points.clear(); + + for (const auto& b : blobs) + { + // note: H/W is equal to fx/fy + + vec2 p; + std::tie(p[0], p[1]) = to_screen_pos(b.pos[0], b.pos[1], W, H); + points.push_back(p); + imagePoints.push_back(vec2(b.pos[0], b.pos[1])); + } +} + +blob::blob(f radius, const vec2& pos, f brightness, const cv::Rect& rect) : + radius(radius), brightness(brightness), pos(pos), rect(rect) +{ + //qDebug() << "radius" << radius << "pos" << pos[0] << pos[1]; +} + +} // ns pt_module diff --git a/tracker-easy/point_extractor.h b/tracker-easy/point_extractor.h new file mode 100644 index 00000000..2af5c131 --- /dev/null +++ b/tracker-easy/point_extractor.h @@ -0,0 +1,61 @@ +/* Copyright (c) 2012 Patrick Ruoff + * Copyright (c) 2015-2016 Stanislaw Halik + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + */ + +#pragma once + +#include "pt-api.hpp" + +#include + +#include +#include + +namespace pt_module { + +using namespace numeric_types; + +struct blob final +{ + f radius, brightness; + vec2 pos; + cv::Rect rect; + + blob(f radius, const vec2& pos, f brightness, const cv::Rect& rect); +}; + +class PointExtractor final : public pt_point_extractor +{ +public: + // extracts points from frame and draws some processing info into frame, if draw_output is set + // dt: time since last call in seconds + void extract_points(const pt_frame& frame, pt_preview& preview_frame, std::vector& points, std::vector& imagePoints) override; + PointExtractor(const QString& module_name); + +public: + std::vector blobs; + +private: + static constexpr int max_blobs = 16; + + pt_settings s; + + cv::Mat1b frame_gray_unmasked, frame_bin, frame_gray; + cv::Mat1f hist; + cv::Mat1b ch[3]; + + void ensure_channel_buffers(const cv::Mat& orig_frame); + void ensure_buffers(const cv::Mat& frame); + + void extract_single_channel(const cv::Mat& orig_frame, int idx, cv::Mat1b& dest); + + void color_to_grayscale(const cv::Mat& frame, cv::Mat1b& output); + void threshold_image(const cv::Mat& frame_gray, cv::Mat1b& output); +}; + +} // ns impl + diff --git a/tracker-easy/tracker_easy.qrc b/tracker-easy/tracker_easy.qrc new file mode 100644 index 00000000..8be79de9 --- /dev/null +++ b/tracker-easy/tracker_easy.qrc @@ -0,0 +1,9 @@ + + + Resources/Logo_IR.png + Resources/cap_front.png + Resources/cap_side.png + Resources/clip_front.png + Resources/clip_side.png + + diff --git a/tracker-easy/tracker_pt_base.qrc b/tracker-easy/tracker_pt_base.qrc deleted file mode 100644 index 8c270540..00000000 --- a/tracker-easy/tracker_pt_base.qrc +++ /dev/null @@ -1,8 +0,0 @@ - - - Resources/cap_front.png - Resources/cap_side.png - Resources/clip_front.png - Resources/clip_side.png - - -- cgit v1.2.3