From 0657dc14e07ee705a8ab0bba67dfbe04c603db49 Mon Sep 17 00:00:00 2001 From: Stanislaw Halik Date: Thu, 18 Jan 2018 23:45:42 +0100 Subject: tracker/pt: move impl bits away from pt-base --- tracker-pt/module/CMakeLists.txt | 6 + tracker-pt/module/Resources/Logo_IR.png | Bin 0 -> 10386 bytes tracker-pt/module/camera.cpp | 171 +++++++++++++++ tracker-pt/module/camera.h | 69 ++++++ tracker-pt/module/frame.cpp | 83 +++++++ tracker-pt/module/frame.hpp | 36 +++ tracker-pt/module/ftnoir_tracker_pt.qrc | 5 + tracker-pt/module/lang/nl_NL.ts | 4 + tracker-pt/module/lang/ru_RU.ts | 4 + tracker-pt/module/lang/stub.ts | 4 + tracker-pt/module/module.cpp | 69 ++++++ tracker-pt/module/point_extractor.cpp | 377 ++++++++++++++++++++++++++++++++ tracker-pt/module/point_extractor.h | 59 +++++ 13 files changed, 887 insertions(+) create mode 100644 tracker-pt/module/CMakeLists.txt create mode 100644 tracker-pt/module/Resources/Logo_IR.png create mode 100644 tracker-pt/module/camera.cpp create mode 100644 tracker-pt/module/camera.h create mode 100644 tracker-pt/module/frame.cpp create mode 100644 tracker-pt/module/frame.hpp create mode 100644 tracker-pt/module/ftnoir_tracker_pt.qrc create mode 100644 tracker-pt/module/lang/nl_NL.ts create mode 100644 tracker-pt/module/lang/ru_RU.ts create mode 100644 tracker-pt/module/lang/stub.ts create mode 100644 tracker-pt/module/module.cpp create mode 100644 tracker-pt/module/point_extractor.cpp create mode 100644 tracker-pt/module/point_extractor.h (limited to 'tracker-pt/module') diff --git a/tracker-pt/module/CMakeLists.txt b/tracker-pt/module/CMakeLists.txt new file mode 100644 index 00000000..1d1b4458 --- /dev/null +++ b/tracker-pt/module/CMakeLists.txt @@ -0,0 +1,6 @@ +find_package(OpenCV 3.0 QUIET) +if(OpenCV_FOUND) + otr_module(tracker-pt) + target_link_libraries(opentrack-tracker-pt opentrack-tracker-pt-base) + target_include_directories(opentrack-tracker-pt PRIVATE "${CMAKE_SOURCE_DIR}/tracker-pt") +endif() diff --git a/tracker-pt/module/Resources/Logo_IR.png b/tracker-pt/module/Resources/Logo_IR.png new file mode 100644 index 00000000..95032a25 Binary files /dev/null and b/tracker-pt/module/Resources/Logo_IR.png differ diff --git a/tracker-pt/module/camera.cpp b/tracker-pt/module/camera.cpp new file mode 100644 index 00000000..ba4583da --- /dev/null +++ b/tracker-pt/module/camera.cpp @@ -0,0 +1,171 @@ +/* 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/sleep.hpp" +#include "compat/camera-names.hpp" +#include "compat/math-imports.hpp" + +#include + +#include "cv/video-property-page.hpp" + +using namespace pt_module; + +Camera::Camera(const QString& module_name) : s { module_name } +{ +} + +QString Camera::get_desired_name() const +{ + return desired_name; +} + +QString Camera::get_active_name() const +{ + return active_name; +} + +void Camera::show_camera_settings() +{ + const int idx = camera_name_to_index(s.camera_name); + + if (bool(*this)) + video_property_page::show_from_capture(*cap, idx); + else + { + video_property_page::show(idx); + } +} + +Camera::result Camera::get_info() const +{ + if (cam_info.res_x == 0 || cam_info.res_y == 0) + return result(false, pt_camera_info()); + return result(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 double dt = t.elapsed_seconds(); + t.start(); + + // measure fps of valid frames + constexpr double RC = .1; // seconds + const double 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 result(true, cam_info); + } + else + return result(false, pt_camera_info()); +} + +pt_camera_open_status Camera::start(int idx, int fps, int res_x, int res_y) +{ + if (idx >= 0 && fps >= 0 && res_x >= 0 && res_y >= 0) + { + if (cam_desired.idx != idx || + cam_desired.fps != fps || + cam_desired.res_x != res_x || + cam_desired.res_y != res_y || + !cap || !cap->isOpened() || !cap->grab()) + { + stop(); + + desired_name = get_camera_names().value(idx); + cam_desired.idx = idx; + cam_desired.fps = fps; + cam_desired.res_x = res_x; + cam_desired.res_y = res_y; + cam_desired.fov = fov; + + cap = camera_ptr(new cv::VideoCapture(cam_desired.idx)); + + if (cam_desired.res_x) + cap->set(cv::CAP_PROP_FRAME_WIDTH, cam_desired.res_x); + if (cam_desired.res_y) + cap->set(cv::CAP_PROP_FRAME_HEIGHT, cam_desired.res_y); + if (cam_desired.fps) + cap->set(cv::CAP_PROP_FPS, cam_desired.fps); + + if (cap->isOpened() && cap->grab()) + { + cam_info = pt_camera_info(); + active_name = QString(); + cam_info.idx = idx; + dt_mean = 0; + active_name = desired_name; + + t.start(); + + return cam_open_ok_change; + } + else + { + stop(); + return cam_open_error; + } + } + + return cam_open_ok_no_change; + } + + stop(); + return cam_open_error; +} + +void Camera::stop() +{ + cap = nullptr; + desired_name = QString(); + active_name = QString(); + cam_info = pt_camera_info(); + cam_desired = pt_camera_info(); +} + +bool Camera::_get_frame(cv::Mat& frame) +{ + if (cap && cap->isOpened()) + { + for (int i = 0; i < 5; i++) + { + if (cap->read(frame)) + return true; + portable::sleep(1); + } + } + return false; +} + +void Camera::camera_deleter::operator()(cv::VideoCapture* cap) +{ + if (cap) + { + if (cap->isOpened()) + cap->release(); + delete cap; + } +} + diff --git a/tracker-pt/module/camera.h b/tracker-pt/module/camera.h new file mode 100644 index 00000000..96234840 --- /dev/null +++ b/tracker-pt/module/camera.h @@ -0,0 +1,69 @@ +/* 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 +#include +#include + +#include +#include + +#include + +namespace pt_module { + +struct Camera final : pt_camera +{ + Camera(const QString& module_name); + + pt_camera_open_status start(int idx, 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; + + operator bool() const override { return cap && cap->isOpened(); } + + void set_fov(double value) override { fov = value; } + void show_camera_settings() override; + +private: + warn_result_unused bool _get_frame(cv::Mat& Frame); + + double dt_mean = 0, fov = 30; + + Timer t; + + pt_camera_info cam_info; + pt_camera_info cam_desired; + QString desired_name, active_name; + + struct camera_deleter final + { + void operator()(cv::VideoCapture* cap); + }; + + using camera_ptr = std::unique_ptr; + + camera_ptr cap; + + pt_settings s; + + static constexpr inline double dt_eps = 1./384; +}; + +} // ns pt_module diff --git a/tracker-pt/module/frame.cpp b/tracker-pt/module/frame.cpp new file mode 100644 index 00000000..e403af07 --- /dev/null +++ b/tracker-pt/module/frame.cpp @@ -0,0 +1,83 @@ +#include "frame.hpp" + +#include "compat/math.hpp" + +#include +#include + +#include + +using namespace pt_module; + +Preview& Preview::operator=(const pt_frame& frame_) +{ + const cv::Mat& frame = frame_.as_const()->mat; + ensure_size(frame_copy, frame_out.cols, frame_out.rows, CV_8UC3); + + if (frame.channels() != 3) + { + once_only(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); + + frame_out.setTo(cv::Scalar(0, 0, 0, 0)); +} + +QImage Preview::get_bitmap() +{ + int stride = frame_out.step.p[0]; + + if (stride < 64 || stride < frame_out.cols * 4) + { + once_only(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(double x, double y) +{ + double px_, py_; + + std::tie(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); +} diff --git a/tracker-pt/module/frame.hpp b/tracker-pt/module/frame.hpp new file mode 100644 index 00000000..9e4f809a --- /dev/null +++ b/tracker-pt/module/frame.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include "pt-api.hpp" + +#include +#include + +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(double x, double 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); + + bool fresh = true; + cv::Mat frame_copy, frame_color, frame_out; +}; + +} // ns pt_module diff --git a/tracker-pt/module/ftnoir_tracker_pt.qrc b/tracker-pt/module/ftnoir_tracker_pt.qrc new file mode 100644 index 00000000..dfeb7369 --- /dev/null +++ b/tracker-pt/module/ftnoir_tracker_pt.qrc @@ -0,0 +1,5 @@ + + + Resources/Logo_IR.png + + diff --git a/tracker-pt/module/lang/nl_NL.ts b/tracker-pt/module/lang/nl_NL.ts new file mode 100644 index 00000000..9e739505 --- /dev/null +++ b/tracker-pt/module/lang/nl_NL.ts @@ -0,0 +1,4 @@ + + + + diff --git a/tracker-pt/module/lang/ru_RU.ts b/tracker-pt/module/lang/ru_RU.ts new file mode 100644 index 00000000..f62cf2e1 --- /dev/null +++ b/tracker-pt/module/lang/ru_RU.ts @@ -0,0 +1,4 @@ + + + + diff --git a/tracker-pt/module/lang/stub.ts b/tracker-pt/module/lang/stub.ts new file mode 100644 index 00000000..6401616d --- /dev/null +++ b/tracker-pt/module/lang/stub.ts @@ -0,0 +1,4 @@ + + + + diff --git a/tracker-pt/module/module.cpp b/tracker-pt/module/module.cpp new file mode 100644 index 00000000..5c298ca5 --- /dev/null +++ b/tracker-pt/module/module.cpp @@ -0,0 +1,69 @@ +#include "ftnoir_tracker_pt.h" +#include "api/plugin-api.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"; + +using 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() : Tracker_PT(pointer(new pt_module_traits)) + { + } +}; + +struct dialog_pt : TrackerDialog_PT +{ + dialog_pt(); +}; + +class metadata_pt : public Metadata +{ + QString name() { return _("PointTracker 1.1"); } + QIcon icon() { return QIcon(":/Resources/Logo_IR.png"); } +}; + +// ns pt_module + +using namespace pt_module; + +dialog_pt::dialog_pt() : TrackerDialog_PT(module_name) {} + +OPENTRACK_DECLARE_TRACKER(tracker_pt, dialog_pt, metadata_pt) diff --git a/tracker-pt/module/point_extractor.cpp b/tracker-pt/module/point_extractor.cpp new file mode 100644 index 00000000..b67c4036 --- /dev/null +++ b/tracker-pt/module/point_extractor.cpp @@ -0,0 +1,377 @@ +/* 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 "point_tracker.h" +#include "frame.hpp" + +#include "cv/numeric.hpp" +#include "compat/math.hpp" + +#include + +#undef PREVIEW +//#define PREVIEW + +#if defined PREVIEW +# include +#endif + +#include +#include +#include +#include + +#include + +using namespace types; +using namespace pt_module; + +/* +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 cv::Vec2d MeanShiftIteration(const cv::Mat &frame_gray, const vec2 ¤t_center, f filter_width) +{ + // Most amazingly this function runs faster with doubles than with floats. + const f s = 1.0 / filter_width; + + f m = 0; + vec2 com { 0, 0 }; + for (int i = 0; i < frame_gray.rows; i++) + { + auto frame_ptr = (uint8_t const* restrict_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 wights brighter parts of the image stronger. + { + f dx = (j - current_center[0])*s; + f dy = (i - current_center[1])*s; + f f = std::fmax(0, 1 - dx*dx - dy*dy); + val *= f; + } + m += val; + com[0] += j * val; + com[1] += i * val; + } + } + if (m > f(.1)) + { + com *= f(1) / m; + return com; + } + else + return current_center; +} + +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 (unsigned k = 0; k < 3; k++) + ch[k] = 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_blobs = cv::Mat1b(H, W); + } +} + +void PointExtractor::extract_single_channel(const cv::Mat& orig_frame, int idx, cv::Mat& dest) +{ + ensure_channel_buffers(orig_frame); + + const int from_to[] = { + idx, 0, + }; + + cv::mixChannels(&orig_frame, 1, &dest, 1, from_to, 1); +} + +void PointExtractor::extract_channels(const cv::Mat& orig_frame, const int* order, int order_npairs) +{ + ensure_channel_buffers(orig_frame); + + cv::mixChannels(&orig_frame, 1, (cv::Mat*) ch, order_npairs, order, order_npairs); +} + +void PointExtractor::color_to_grayscale(const cv::Mat& frame, cv::Mat1b& output) +{ + switch (s.blob_color) + { + 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; + const cv::Mat tmp = frame.reshape(1, W * H); + cv::Mat output_ = output.reshape(1, W * H); + cv::reduce(tmp, output_, 1, cv::REDUCE_AVG); + break; + } + default: + once_only(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, + (int const*) &hist_size, + &ranges); + + const f radius = (f) threshold_radius_value(frame_gray.cols, frame_gray.rows, threshold_slider_value); + + auto ptr = (float const* const restrict_ptr) hist.ptr(0); + const unsigned area = uround(3 * M_PI * radius*radius); + const unsigned sz = unsigned(hist.cols * hist.rows); + unsigned thres = 32; + for (unsigned i = sz-1, cnt = 0; i > 32; i--) + { + cnt += ptr[i]; + if (cnt >= area) + break; + thres = i; + } + + cv::threshold(frame_gray, output, thres, 255, CV_THRESH_BINARY); + } +} + +void PointExtractor::extract_points(const pt_frame& frame_, pt_preview& preview_frame_, std::vector& points) +{ + const cv::Mat& frame = frame_.as_const()->mat; + cv::Mat& preview_frame = *preview_frame_.as(); + + ensure_buffers(frame); + color_to_grayscale(frame, frame_gray); + +#if defined PREVIEW + cv::imshow("capture", frame_gray); + cv::waitKey(1); +#endif + + threshold_image(frame_gray, frame_bin); + + blobs.clear(); + frame_bin.copyTo(frame_blobs); + + const f region_size_min = s.min_point_size; + const f region_size_max = s.max_point_size; + + unsigned idx = 0; + for (int y=0; y < frame_blobs.rows; y++) + { + const unsigned char* ptr_bin = frame_blobs.ptr(y); + for (int x=0; x < frame_blobs.cols; x++) + { + if (ptr_bin[x] != 255) + continue; + idx = blobs.size() + 1; + + cv::Rect rect; + cv::floodFill(frame_blobs, + 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 ptr_blobs = frame_blobs.ptr(i); + unsigned char const* const restrict_ptr 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 double radius = std::sqrt(cnt / M_PI); + if (radius > region_size_max || radius < region_size_min) + continue; + + blobs.emplace_back(radius, + vec2(rect.width/2., rect.height/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 +#if BROKEN && 0 + break; +#endif + } + } +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/2., rect.height/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) < 1e-2) + break; + } + + b.pos[0] = pos[0] + rect.x; + b.pos[1] = pos[1] + rect.y; + } + + for (unsigned k = 0; k < blobs.size(); k++) + { + blob& b = blobs[k]; + + const f dpi = preview_frame.cols / f(320); + const f offx = 10 * dpi, offy = 7.5 * dpi; + + const f cx = preview_frame.cols / f(frame.cols), + cy = preview_frame.rows / f(frame.rows), + c_ = (cx+cy)/2; + + static constexpr unsigned fract_bits = 16; + static constexpr double 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 >= PointModel::N_POINTS + ? cv::Scalar(192, 192, 192) + : cv::Scalar(255, 255, 0); + + const f overlay_size = dpi > 1.5 ? 2 : 1; + + cv::circle(preview_frame, p, iround((b.radius + 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", b.radius); + + auto text_color = k >= PointModel::N_POINTS + ? 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); + } + + // 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); + } +} + +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]; +} diff --git a/tracker-pt/module/point_extractor.h b/tracker-pt/module/point_extractor.h new file mode 100644 index 00000000..1b6f55a2 --- /dev/null +++ b/tracker-pt/module/point_extractor.h @@ -0,0 +1,59 @@ +/* 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 types; + +struct blob +{ + 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) override; + PointExtractor(const QString& module_name); +private: + static constexpr int max_blobs = 16; + + pt_settings s; + + cv::Mat1b frame_gray, frame_bin, frame_blobs; + cv::Mat1f hist; + std::vector blobs; + 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::Mat& dest); + void extract_channels(const cv::Mat& orig_frame, const int* order, int order_npairs); + + void color_to_grayscale(const cv::Mat& frame, cv::Mat1b& output); + void threshold_image(const cv::Mat& frame_gray, cv::Mat1b& output); +}; + +} // ns impl + -- cgit v1.2.3