diff options
Diffstat (limited to 'tracker-pt/module')
| -rw-r--r-- | tracker-pt/module/CMakeLists.txt | 6 | ||||
| -rw-r--r-- | tracker-pt/module/Resources/Logo_IR.png | bin | 0 -> 10386 bytes | |||
| -rw-r--r-- | tracker-pt/module/camera.cpp | 171 | ||||
| -rw-r--r-- | tracker-pt/module/camera.h | 69 | ||||
| -rw-r--r-- | tracker-pt/module/frame.cpp | 83 | ||||
| -rw-r--r-- | tracker-pt/module/frame.hpp | 36 | ||||
| -rw-r--r-- | tracker-pt/module/ftnoir_tracker_pt.qrc | 5 | ||||
| -rw-r--r-- | tracker-pt/module/lang/nl_NL.ts | 4 | ||||
| -rw-r--r-- | tracker-pt/module/lang/ru_RU.ts | 4 | ||||
| -rw-r--r-- | tracker-pt/module/lang/stub.ts | 4 | ||||
| -rw-r--r-- | tracker-pt/module/module.cpp | 69 | ||||
| -rw-r--r-- | tracker-pt/module/point_extractor.cpp | 377 | ||||
| -rw-r--r-- | tracker-pt/module/point_extractor.h | 59 | 
13 files changed, 887 insertions, 0 deletions
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 Binary files differnew file mode 100644 index 00000000..95032a25 --- /dev/null +++ b/tracker-pt/module/Resources/Logo_IR.png 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 <opencv2/imgproc.hpp> + +#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<Frame>()->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 <functional> +#include <memory> +#include <tuple> + +#include <opencv2/core.hpp> +#include <opencv2/videoio.hpp> + +#include <QString> + +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<cv::VideoCapture, camera_deleter>; + +    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 <cstring> +#include <tuple> + +#include <opencv2/imgproc.hpp> + +using namespace pt_module; + +Preview& Preview::operator=(const pt_frame& frame_) +{ +    const cv::Mat& frame = frame_.as_const<const Frame>()->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 <opencv2/core.hpp> +#include <QImage> + +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 @@ +<RCC> +    <qresource prefix="/"> +        <file>Resources/Logo_IR.png</file> +    </qresource> +</RCC> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="nl_NL"> +</TS> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="ru_RU"> +</TS> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1"> +</TS> 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 <memory> + +static const QString module_name = "tracker-pt"; + +using namespace pt_module; + +struct pt_module_traits final : pt_runtime_traits +{ +    pointer<pt_camera> make_camera() const override +    { +        return pointer<pt_camera>(new Camera(module_name)); +    } + +    pointer<pt_point_extractor> make_point_extractor() const override +    { +        return pointer<pt_point_extractor>(new PointExtractor(module_name)); +    } + +    QString get_module_name() const override +    { +        return module_name; +    } + +    pointer<pt_frame> make_frame() const override +    { +        return pointer<pt_frame>(new Frame); +    } + +    pointer<pt_preview> make_preview(int w, int h) const override +    { +        return pointer<pt_preview>(new Preview(w, h)); +    } +}; + +struct tracker_pt : Tracker_PT +{ +    tracker_pt() : Tracker_PT(pointer<pt_runtime_traits>(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 <sthalik@misaki.pl> + * + * 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 <opencv2/videoio.hpp> + +#undef PREVIEW +//#define PREVIEW + +#if defined PREVIEW +#   include <opencv2/highgui.hpp> +#endif + +#include <cmath> +#include <algorithm> +#include <cinttypes> +#include <memory> + +#include <QDebug> + +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<int>(); + +    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<vec2>& points) +{ +    const cv::Mat& frame = frame_.as_const<Frame>()->mat; +    cv::Mat& preview_frame = *preview_frame_.as<Preview>(); + +    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 <sthalik@misaki.pl> + * + * 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 <vector> + +#include <opencv2/core.hpp> +#include <opencv2/imgproc.hpp> + +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<vec2>& 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<blob> 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 +  | 
