diff options
| author | Stanislaw Halik <sthalik@misaki.pl> | 2022-05-11 20:42:25 +0200 | 
|---|---|---|
| committer | Stanislaw Halik <sthalik@misaki.pl> | 2022-05-11 20:45:05 +0200 | 
| commit | 2b19c6c532c37c2eeb2547cf92f719197957ab72 (patch) | |
| tree | 26fb69b756d8ae49fb87a18956eca26dd2c55005 | |
| parent | be42a20f03e3ccb1eaa390c072ac72cbe2d9f13b (diff) | |
tracker/trackhat: new tracker
Sponsored by: TrackHat
| -rw-r--r-- | tracker-trackhat/CMakeLists.txt | 16 | ||||
| -rw-r--r-- | tracker-trackhat/camera.cpp | 126 | ||||
| -rw-r--r-- | tracker-trackhat/dialog.cpp | 144 | ||||
| -rw-r--r-- | tracker-trackhat/dialog.hpp | 36 | ||||
| -rw-r--r-- | tracker-trackhat/dialog.ui | 522 | ||||
| -rw-r--r-- | tracker-trackhat/extractor.cpp | 21 | ||||
| -rw-r--r-- | tracker-trackhat/frame.cpp | 127 | ||||
| -rw-r--r-- | tracker-trackhat/handle.cpp | 72 | ||||
| -rw-r--r-- | tracker-trackhat/images/trackhat-64x64.png | bin | 0 -> 1430 bytes | |||
| -rw-r--r-- | tracker-trackhat/images/trackhat.ico | bin | 0 -> 3758 bytes | |||
| -rw-r--r-- | tracker-trackhat/images/trackhat.png | bin | 0 -> 14581 bytes | |||
| -rw-r--r-- | tracker-trackhat/lang/nl_NL.ts | 134 | ||||
| -rw-r--r-- | tracker-trackhat/lang/ru_RU.ts | 134 | ||||
| -rw-r--r-- | tracker-trackhat/lang/stub.ts | 134 | ||||
| -rw-r--r-- | tracker-trackhat/lang/zh_CN.ts | 134 | ||||
| -rw-r--r-- | tracker-trackhat/metadata.cpp | 39 | ||||
| -rw-r--r-- | tracker-trackhat/metadata.hpp | 21 | ||||
| -rw-r--r-- | tracker-trackhat/settings.cpp | 147 | ||||
| -rw-r--r-- | tracker-trackhat/tracker_trackhat.qrc | 5 | ||||
| -rw-r--r-- | tracker-trackhat/trackhat-res.qrc | 5 | ||||
| -rw-r--r-- | tracker-trackhat/trackhat.hpp | 180 | 
21 files changed, 1997 insertions, 0 deletions
| diff --git a/tracker-trackhat/CMakeLists.txt b/tracker-trackhat/CMakeLists.txt new file mode 100644 index 00000000..483bf4c9 --- /dev/null +++ b/tracker-trackhat/CMakeLists.txt @@ -0,0 +1,16 @@ +if(WIN32) +    include(opentrack-opencv) +    find_package(OpenCV QUIET) +    if(OpenCV_FOUND) +        foreach(k core imgproc) +            otr_install_lib("opencv_${k}" "${opentrack-libexec}") +        endforeach() +        set(SDK_TRACKHAT_SENSOR CACHE PATH "") +        if(SDK_TRACKHAT_SENSOR) +            include_directories("${SDK_TRACKHAT_SENSOR}/include" ${OpenCV_INCLUDE_DIRS}) +            link_directories("${SDK_TRACKHAT_SENSOR}/lib") +            link_libraries(opencv_imgproc opencv_core opentrack-tracker-pt-base track-hat) +            otr_module(tracker-trackhat) +        endif() +    endif() +endif() diff --git a/tracker-trackhat/camera.cpp b/tracker-trackhat/camera.cpp new file mode 100644 index 00000000..dd1f3d3a --- /dev/null +++ b/tracker-trackhat/camera.cpp @@ -0,0 +1,126 @@ +#include "trackhat.hpp" +#include "compat/sleep.hpp" +#include <cstdio> + +namespace trackhat_impl { + +TH_ErrorCode log_error(TH_ErrorCode error, const char* source, +                       const char* file, int line, const char* function) +{ +    if (error == TH_ERROR_DEVICE_ALREADY_OPEN) +        error = TH_SUCCESS; +    if (error) +    { +        auto logger = QMessageLogger(file, line, function).warning(); +        logger << "tracker/trackhat: error" << (void*)-error << "in" << source; +    } +    return error; +} + +} // ns trackhat_impl + +pt_camera::result trackhat_camera::get_info() const +{ +    return {true, get_desired() }; +} + +pt_camera_info trackhat_camera::get_desired() const +{ +    pt_camera_info ret = {}; + +    ret.fov = sensor_fov; +    ret.fps = 250; +    ret.res_x = sensor_size; +    ret.res_y = sensor_size; + +    return ret; +} + +QString trackhat_camera::get_desired_name() const +{ +    return QStringLiteral("TrackHat sensor"); +} + +QString trackhat_camera::get_active_name() const +{ +    return get_desired_name(); +} + +void trackhat_camera::set_fov(pt_camera::f) {} +void trackhat_camera::show_camera_settings() {} + +trackhat_camera::trackhat_camera() +{ +    s.set_raii_dtor_state(false); +    t.set_raii_dtor_state(false); + +    for (auto* slider : { &t.exposure, /*&t.threshold,*/ }) +    { +        QObject::connect(slider, options::value_::value_changed<options::slider_value>(), +                         &sig, &trackhat_impl::setting_receiver::settings_changed, +                         Qt::DirectConnection); +    } +} + +trackhat_camera::~trackhat_camera() +{ +    stop(); +} + +pt_camera::result trackhat_camera::get_frame(pt_frame& frame_) +{ +    if (!device.ensure_connected()) +        goto error; + +    if (sig.test_and_clear() && !init_regs()) +        goto error; + +    set_pt_options(); + +    { +        trackHat_ExtendedPoints_t points; +        if (!!th_check(trackHat_GetDetectedPointsExtended(&*device, &points))) +            goto error; +        auto& frame = *frame_.as<trackhat_frame>(); +        frame.init_points(points, t.min_pt_size, t.max_pt_size); +    } + +    return {true, get_desired()}; + +error: +    stop(); +    return {false, {}}; +} + +static void log_handler(const char* file, int line, const char* function, char level, const char* str, size_t len) +{ +    if (level != 'E') +        return; +    char file_[128]; +    snprintf(file_, std::size(file_), "trackhat/%s", file); +    auto logger = QMessageLogger(file_, line, function).debug(); +    logger << "tracker/trackhat:"; +    logger.noquote() << QLatin1String(str, (int)len); +} + +bool trackhat_camera::start(const pt_settings&) +{ +    trackHat_SetDebugHandler(log_handler); + +    if constexpr(debug_mode) +        trackHat_EnableDebugMode(); +    else +        trackHat_DisableDebugMode(); + +    if (!device.ensure_device_exists()) +        return false; + +    set_pt_options(); + +    return true; +} + +void trackhat_camera::stop() +{ +    device.disconnect(); +} diff --git a/tracker-trackhat/dialog.cpp b/tracker-trackhat/dialog.cpp new file mode 100644 index 00000000..cfff03ba --- /dev/null +++ b/tracker-trackhat/dialog.cpp @@ -0,0 +1,144 @@ +#include "dialog.hpp" + +using namespace options; + +trackhat_dialog::trackhat_dialog() +{ +    ui.setupUi(this); +    poll_tracker_info(); +    poll_timer.setInterval(100); + +    const std::tuple<QString, model_type> model_types[] = { +        { tr("Cap"),                model_cap }, +        { tr("Clip (left)"),        model_clip_left }, +        { tr("Clip (right)"),       model_clip_right }, +        { tr("Mini Clip (left)"),   model_mini_clip_left }, +        { tr("Mini Clip (right)"),  model_mini_clip_right }, +        { tr("Custom"),             model_mystery_meat }, +    }; + +    ui.model_type->clear(); + +    for (const auto& [name, type] : model_types) +        ui.model_type->addItem(QIcon{}, name, (QVariant)(int)type); + +    // model + +    tie_setting(t.model, ui.model_type); +    tie_setting(t.min_pt_size, ui.min_point_size); +    tie_setting(t.max_pt_size, ui.max_point_size); +    tie_setting(t.point_filter_limit, ui.point_filter_limit); + +    // exposure + +    ui.exposure_slider->setMinimum((int)t.exposure->min()); +    ui.exposure_slider->setMaximum((int)t.exposure->max()); + +    tie_setting(t.exposure, ui.exposure_slider); +    ui.exposure_label->setValue((int)*t.exposure); +    ui.point_filter_limit_label->setValue(*t.point_filter_limit); + +    connect(&t.exposure, value_::value_changed<slider_value>(), ui.exposure_label, +        [this] { ui.exposure_label->setValue((int)*t.exposure); }, Qt::QueuedConnection); +    connect(&t.point_filter_limit, value_::value_changed<slider_value>(), ui.point_filter_limit_label, +        [this] { ui.point_filter_limit_label->setValue(*t.point_filter_limit); }, Qt::QueuedConnection); + +    // threshold + +#if 0 +    tie_setting(t.threshold, ui.threshold_slider); + +    ui.threshold_label->setValue((int)*t.threshold); + +    connect(&t.threshold, value_::value_changed<slider_value>(), ui.threshold_label, [=] { +            ui.threshold_label->setValue((int)*t.threshold); +        }, Qt::QueuedConnection); +#endif + +    // point filter + +    tie_setting(t.enable_point_filter, ui.enable_point_filter); +    tie_setting(t.point_filter_coefficient, ui.point_filter_slider); +    ui.point_filter_label->setValue(*t.point_filter_coefficient); + +    connect(&t.point_filter_coefficient, value_::value_changed<slider_value>(), ui.point_filter_label, +        [this] { ui.point_filter_label->setValue(*t.point_filter_coefficient); }, Qt::QueuedConnection); + +    tie_setting(t.point_filter_deadzone, ui.point_filter_deadzone); +    ui.point_filter_deadzone_label->setValue(*t.point_filter_deadzone); + +    connect(&t.point_filter_deadzone, value_::value_changed<slider_value>(), ui.point_filter_deadzone_label, +        [this] { ui.point_filter_deadzone_label->setValue(*t.point_filter_deadzone); }, Qt::QueuedConnection); + +    // stuff + +    connect(&poll_timer, &QTimer::timeout, this, &trackhat_dialog::poll_tracker_info); +    connect(ui.buttonBox, &QDialogButtonBox::accepted, this, &trackhat_dialog::doOK); +    connect(ui.buttonBox, &QDialogButtonBox::rejected, this, &trackhat_dialog::doCancel); +} + +void trackhat_dialog::register_tracker(ITracker* tracker_) +{ +    tracker = static_cast<Tracker_PT*>(tracker_); +    poll_tracker_info(); +    poll_timer.start(); +} + +void trackhat_dialog::unregister_tracker() +{ +    tracker = nullptr; +    poll_tracker_info(); +    poll_timer.stop(); +    update_raw_data(); +} + +void trackhat_dialog::save() +{ +    s.b->save(); +    t.b->save(); +} + +void trackhat_dialog::reload() +{ +    s.b->reload(); +    t.b->reload(); +} + +void trackhat_dialog::doCancel() { reload(); close(); } +void trackhat_dialog::doOK() { save(); close(); } + +trackhat_dialog::~trackhat_dialog() +{ +} + +void trackhat_dialog::poll_tracker_info() +{ +    if (!tracker) +        ui.status_label->setText(tr("Status: Tracking stopped.")); +    else if (tracker->get_n_points() == 3) +        ui.status_label->setText(tr("Status: %1 points detected. Good!").arg(tracker->get_n_points())); +    else +        ui.status_label->setText(tr("Status: %1 points detected. BAD!").arg(tracker->get_n_points())); +    update_raw_data(); +} + +void trackhat_dialog::set_buttons_visible(bool x) +{ +    ui.buttonBox->setVisible(x); +    adjustSize(); +} +void trackhat_dialog::update_raw_data() +{ +    QLabel* labels[] = { ui.label_x, ui.label_y, ui.label_z, ui.label_yaw, ui.label_pitch, ui.label_roll }; +    if (tracker) +    { +        QString str; str.reserve(16); +        double data[6] {}; +        tracker->data(data); +        for (unsigned i = 0; i < std::size(labels); i++) +            labels[i]->setText(str.sprintf("%.2f%s", data[i], i >= 3 ? "°" : " mm")); +    } +    else +        for (QLabel* x : labels) +            x->setText(QStringLiteral("-")); +} diff --git a/tracker-trackhat/dialog.hpp b/tracker-trackhat/dialog.hpp new file mode 100644 index 00000000..35ca866b --- /dev/null +++ b/tracker-trackhat/dialog.hpp @@ -0,0 +1,36 @@ +#pragma once +#include "trackhat.hpp" +#include "ui_dialog.h" +#include "tracker-pt/ftnoir_tracker_pt.h" +#include "api/plugin-api.hpp" +#include <QTimer> + +class trackhat_dialog final : public ITrackerDialog +{ +    Q_OBJECT + +protected: +    Ui_trackhat_dialog ui; +    Tracker_PT* tracker = nullptr; +    QTimer poll_timer{this}; + +    pt_settings s{trackhat_metadata::module_name}; +    trackhat_settings t; + +    void set_buttons_visible(bool x) override; +    void update_raw_data(); + +public: +    trackhat_dialog(); +    ~trackhat_dialog() override; +    void register_tracker(ITracker *tracker) override; +    void unregister_tracker() override; +    bool embeddable() noexcept override { return true; } +    void save() override; +    void reload() override; + +public slots: +    void doOK(); +    void doCancel(); +    void poll_tracker_info(); +}; diff --git a/tracker-trackhat/dialog.ui b/tracker-trackhat/dialog.ui new file mode 100644 index 00000000..30cf78db --- /dev/null +++ b/tracker-trackhat/dialog.ui @@ -0,0 +1,522 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>trackhat_dialog</class> + <widget class="QDialog" name="trackhat_dialog"> +  <property name="geometry"> +   <rect> +    <x>0</x> +    <y>0</y> +    <width>365</width> +    <height>429</height> +   </rect> +  </property> +  <property name="minimumSize"> +   <size> +    <width>365</width> +    <height>0</height> +   </size> +  </property> +  <property name="windowTitle"> +   <string>TrackHat</string> +  </property> +  <property name="windowIcon"> +   <iconset resource="trackhat-res.qrc"> +    <normaloff>:/images/trackhat-64x64.png</normaloff>:/images/trackhat-64x64.png</iconset> +  </property> +  <layout class="QVBoxLayout" name="verticalLayout"> +   <item> +    <widget class="QGroupBox" name="groupBox"> +     <property name="title"> +      <string>Camera</string> +     </property> +     <layout class="QGridLayout" name="gridLayout"> +      <item row="0" column="2"> +       <widget class="QSpinBox" name="exposure_label"> +        <property name="focusPolicy"> +         <enum>Qt::NoFocus</enum> +        </property> +        <property name="alignment"> +         <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> +        </property> +        <property name="readOnly"> +         <bool>true</bool> +        </property> +        <property name="buttonSymbols"> +         <enum>QAbstractSpinBox::NoButtons</enum> +        </property> +        <property name="maximum"> +         <number>999</number> +        </property> +       </widget> +      </item> +      <item row="0" column="1"> +       <widget class="QSlider" name="exposure_slider"> +        <property name="sizePolicy"> +         <sizepolicy hsizetype="Expanding" vsizetype="Maximum"> +          <horstretch>0</horstretch> +          <verstretch>0</verstretch> +         </sizepolicy> +        </property> +        <property name="maximum"> +         <number>239</number> +        </property> +        <property name="pageStep"> +         <number>10</number> +        </property> +        <property name="orientation"> +         <enum>Qt::Horizontal</enum> +        </property> +       </widget> +      </item> +      <item row="0" column="0"> +       <widget class="QLabel" name="label"> +        <property name="text"> +         <string>Exposure</string> +        </property> +       </widget> +      </item> +     </layout> +    </widget> +   </item> +   <item> +    <widget class="QGroupBox" name="groupBox_2"> +     <property name="title"> +      <string>Model</string> +     </property> +     <layout class="QGridLayout" name="gridLayout_2"> +      <item row="0" column="0"> +       <widget class="QLabel" name="label_6"> +        <property name="sizePolicy"> +         <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> +          <horstretch>10</horstretch> +          <verstretch>0</verstretch> +         </sizepolicy> +        </property> +        <property name="text"> +         <string>Type</string> +        </property> +       </widget> +      </item> +      <item row="0" column="1"> +       <widget class="QComboBox" name="model_type"> +        <property name="sizePolicy"> +         <sizepolicy hsizetype="Expanding" vsizetype="Maximum"> +          <horstretch>9</horstretch> +          <verstretch>0</verstretch> +         </sizepolicy> +        </property> +       </widget> +      </item> +      <item row="1" column="0"> +       <widget class="QLabel" name="label_4"> +        <property name="text"> +         <string>Min point size</string> +        </property> +       </widget> +      </item> +      <item row="2" column="0"> +       <widget class="QLabel" name="label_5"> +        <property name="text"> +         <string>Max point size</string> +        </property> +       </widget> +      </item> +      <item row="2" column="1" alignment="Qt::AlignRight"> +       <widget class="QDoubleSpinBox" name="max_point_size"> +        <property name="sizePolicy"> +         <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> +          <horstretch>0</horstretch> +          <verstretch>0</verstretch> +         </sizepolicy> +        </property> +        <property name="alignment"> +         <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> +        </property> +        <property name="suffix"> +         <string> px</string> +        </property> +        <property name="decimals"> +         <number>1</number> +        </property> +        <property name="minimum"> +         <double>1.000000000000000</double> +        </property> +        <property name="maximum"> +         <double>999.000000000000000</double> +        </property> +       </widget> +      </item> +      <item row="1" column="1" alignment="Qt::AlignRight"> +       <widget class="QDoubleSpinBox" name="min_point_size"> +        <property name="sizePolicy"> +         <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> +          <horstretch>0</horstretch> +          <verstretch>0</verstretch> +         </sizepolicy> +        </property> +        <property name="alignment"> +         <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> +        </property> +        <property name="suffix"> +         <string> px</string> +        </property> +        <property name="decimals"> +         <number>1</number> +        </property> +        <property name="minimum"> +         <double>1.000000000000000</double> +        </property> +        <property name="maximum"> +         <double>999.000000000000000</double> +        </property> +        <property name="value"> +         <double>1.000000000000000</double> +        </property> +       </widget> +      </item> +     </layout> +    </widget> +   </item> +   <item> +    <widget class="QGroupBox" name="groupBox_3"> +     <property name="title"> +      <string>Tracking</string> +     </property> +     <layout class="QVBoxLayout" name="verticalLayout_2"> +      <item> +       <widget class="QWidget" name="widget" native="true"> +        <property name="sizePolicy"> +         <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> +          <horstretch>0</horstretch> +          <verstretch>0</verstretch> +         </sizepolicy> +        </property> +        <layout class="QGridLayout" name="gridLayout_3"> +         <property name="leftMargin"> +          <number>0</number> +         </property> +         <property name="topMargin"> +          <number>0</number> +         </property> +         <property name="rightMargin"> +          <number>0</number> +         </property> +         <property name="bottomMargin"> +          <number>0</number> +         </property> +         <item row="0" column="2"> +          <widget class="QDoubleSpinBox" name="point_filter_label"> +           <property name="sizePolicy"> +            <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> +             <horstretch>0</horstretch> +             <verstretch>0</verstretch> +            </sizepolicy> +           </property> +           <property name="focusPolicy"> +            <enum>Qt::NoFocus</enum> +           </property> +           <property name="alignment"> +            <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> +           </property> +           <property name="readOnly"> +            <bool>true</bool> +           </property> +           <property name="buttonSymbols"> +            <enum>QAbstractSpinBox::NoButtons</enum> +           </property> +           <property name="suffix"> +            <string> px</string> +           </property> +           <property name="decimals"> +            <number>2</number> +           </property> +           <property name="maximum"> +            <double>300.000000000000000</double> +           </property> +          </widget> +         </item> +         <item row="1" column="1"> +          <widget class="QSlider" name="point_filter_limit"> +           <property name="sizePolicy"> +            <sizepolicy hsizetype="Expanding" vsizetype="Maximum"> +             <horstretch>0</horstretch> +             <verstretch>0</verstretch> +            </sizepolicy> +           </property> +           <property name="maximum"> +            <number>99</number> +           </property> +           <property name="orientation"> +            <enum>Qt::Horizontal</enum> +           </property> +          </widget> +         </item> +         <item row="1" column="0"> +          <widget class="QLabel" name="label_8"> +           <property name="text"> +            <string>Limit</string> +           </property> +          </widget> +         </item> +         <item row="2" column="0"> +          <widget class="QLabel" name="label_10"> +           <property name="text"> +            <string>Deadzone</string> +           </property> +          </widget> +         </item> +         <item row="0" column="0"> +          <widget class="QCheckBox" name="enable_point_filter"> +           <property name="text"> +            <string>Point filter</string> +           </property> +          </widget> +         </item> +         <item row="1" column="2"> +          <widget class="QDoubleSpinBox" name="point_filter_limit_label"> +           <property name="sizePolicy"> +            <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> +             <horstretch>0</horstretch> +             <verstretch>0</verstretch> +            </sizepolicy> +           </property> +           <property name="focusPolicy"> +            <enum>Qt::NoFocus</enum> +           </property> +           <property name="alignment"> +            <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> +           </property> +           <property name="readOnly"> +            <bool>true</bool> +           </property> +           <property name="buttonSymbols"> +            <enum>QAbstractSpinBox::NoButtons</enum> +           </property> +           <property name="prefix"> +            <string/> +           </property> +           <property name="suffix"> +            <string/> +           </property> +           <property name="decimals"> +            <number>2</number> +           </property> +           <property name="maximum"> +            <double>1.000000000000000</double> +           </property> +          </widget> +         </item> +         <item row="0" column="1"> +          <widget class="QSlider" name="point_filter_slider"> +           <property name="sizePolicy"> +            <sizepolicy hsizetype="Expanding" vsizetype="Maximum"> +             <horstretch>0</horstretch> +             <verstretch>0</verstretch> +            </sizepolicy> +           </property> +           <property name="maximum"> +            <number>300</number> +           </property> +           <property name="orientation"> +            <enum>Qt::Horizontal</enum> +           </property> +          </widget> +         </item> +         <item row="2" column="1"> +          <widget class="QSlider" name="point_filter_deadzone"> +           <property name="sizePolicy"> +            <sizepolicy hsizetype="Expanding" vsizetype="Maximum"> +             <horstretch>0</horstretch> +             <verstretch>0</verstretch> +            </sizepolicy> +           </property> +           <property name="maximum"> +            <number>100</number> +           </property> +           <property name="orientation"> +            <enum>Qt::Horizontal</enum> +           </property> +          </widget> +         </item> +         <item row="2" column="2"> +          <widget class="QDoubleSpinBox" name="point_filter_deadzone_label"> +           <property name="sizePolicy"> +            <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> +             <horstretch>0</horstretch> +             <verstretch>0</verstretch> +            </sizepolicy> +           </property> +           <property name="focusPolicy"> +            <enum>Qt::NoFocus</enum> +           </property> +           <property name="alignment"> +            <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> +           </property> +           <property name="readOnly"> +            <bool>true</bool> +           </property> +           <property name="buttonSymbols"> +            <enum>QAbstractSpinBox::NoButtons</enum> +           </property> +           <property name="suffix"> +            <string> px</string> +           </property> +           <property name="decimals"> +            <number>2</number> +           </property> +           <property name="maximum"> +            <double>300.000000000000000</double> +           </property> +          </widget> +         </item> +        </layout> +       </widget> +      </item> +      <item> +       <widget class="QLabel" name="status_label"> +        <property name="sizePolicy"> +         <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> +          <horstretch>0</horstretch> +          <verstretch>0</verstretch> +         </sizepolicy> +        </property> +        <property name="text"> +         <string>Status</string> +        </property> +       </widget> +      </item> +     </layout> +    </widget> +   </item> +   <item> +    <widget class="QGroupBox" name="groupBox_4"> +     <property name="title"> +      <string>Raw data</string> +     </property> +     <layout class="QGridLayout" name="gridLayout_4"> +      <item row="0" column="0"> +       <widget class="QLabel" name="label_9"> +        <property name="text"> +         <string>X</string> +        </property> +       </widget> +      </item> +      <item row="1" column="1"> +       <widget class="QLabel" name="label_y"> +        <property name="text"> +         <string>TextLabel</string> +        </property> +       </widget> +      </item> +      <item row="2" column="1"> +       <widget class="QLabel" name="label_z"> +        <property name="text"> +         <string>TextLabel</string> +        </property> +       </widget> +      </item> +      <item row="0" column="2"> +       <widget class="QLabel" name="label_13"> +        <property name="text"> +         <string>Yaw</string> +        </property> +       </widget> +      </item> +      <item row="2" column="2"> +       <widget class="QLabel" name="label_15"> +        <property name="text"> +         <string>Roll</string> +        </property> +       </widget> +      </item> +      <item row="1" column="0"> +       <widget class="QLabel" name="label_11"> +        <property name="text"> +         <string>Y</string> +        </property> +       </widget> +      </item> +      <item row="0" column="1"> +       <widget class="QLabel" name="label_x"> +        <property name="text"> +         <string>TextLabel</string> +        </property> +       </widget> +      </item> +      <item row="2" column="0"> +       <widget class="QLabel" name="label_12"> +        <property name="text"> +         <string>Z</string> +        </property> +       </widget> +      </item> +      <item row="1" column="2"> +       <widget class="QLabel" name="label_14"> +        <property name="text"> +         <string>Pitch</string> +        </property> +       </widget> +      </item> +      <item row="0" column="3"> +       <widget class="QLabel" name="label_yaw"> +        <property name="text"> +         <string>TextLabel</string> +        </property> +       </widget> +      </item> +      <item row="1" column="3"> +       <widget class="QLabel" name="label_pitch"> +        <property name="text"> +         <string>TextLabel</string> +        </property> +       </widget> +      </item> +      <item row="2" column="3"> +       <widget class="QLabel" name="label_roll"> +        <property name="text"> +         <string>TextLabel</string> +        </property> +       </widget> +      </item> +     </layout> +    </widget> +   </item> +   <item> +    <spacer name="verticalSpacer"> +     <property name="orientation"> +      <enum>Qt::Vertical</enum> +     </property> +     <property name="sizeType"> +      <enum>QSizePolicy::Expanding</enum> +     </property> +     <property name="sizeHint" stdset="0"> +      <size> +       <width>10</width> +       <height>1</height> +      </size> +     </property> +    </spacer> +   </item> +   <item> +    <widget class="QDialogButtonBox" name="buttonBox"> +     <property name="standardButtons"> +      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> +     </property> +    </widget> +   </item> +  </layout> + </widget> + <tabstops> +  <tabstop>exposure_slider</tabstop> +  <tabstop>model_type</tabstop> +  <tabstop>min_point_size</tabstop> +  <tabstop>max_point_size</tabstop> +  <tabstop>enable_point_filter</tabstop> +  <tabstop>point_filter_slider</tabstop> +  <tabstop>point_filter_limit</tabstop> + </tabstops> + <resources> +  <include location="trackhat-res.qrc"/> + </resources> + <connections/> +</ui> diff --git a/tracker-trackhat/extractor.cpp b/tracker-trackhat/extractor.cpp new file mode 100644 index 00000000..58b9fd05 --- /dev/null +++ b/tracker-trackhat/extractor.cpp @@ -0,0 +1,21 @@ +#include "trackhat.hpp" +#include <algorithm> +#include <iterator> + +void trackhat_extractor::extract_points(const pt_frame& data, +                                        pt_preview&, bool, +                                        std::vector<vec2>& points) +{ +    points.clear(); +    points.reserve(trackhat_camera::point_count); +    const auto& copy = data.as_const<trackhat_frame>()->points; + +    for (const auto& pt : copy) +    { +        if (!pt.ok) +            continue; +        constexpr int sz = trackhat_camera::sensor_size; +        auto [ x, y ] = to_screen_pos(pt.x, pt.y, sz, sz); +        points.push_back({x, y}); +    } +} diff --git a/tracker-trackhat/frame.cpp b/tracker-trackhat/frame.cpp new file mode 100644 index 00000000..d8fae002 --- /dev/null +++ b/tracker-trackhat/frame.cpp @@ -0,0 +1,127 @@ +#include "trackhat.hpp" +#include <opencv2/imgproc.hpp> +#include "compat/math.hpp" + +trackhat_preview::trackhat_preview(int w, int h) +{ +    frame_bgr.create(h, w, CV_8UC3); +    frame_bgra.create(h, w, CV_8UC4); +} + +void trackhat_preview::set_last_frame(const pt_frame& frame_) +{ +    center = {-1, -1}; +    points = frame_.as_const<trackhat_frame>()->points; +} + +void trackhat_preview::draw_head_center(pt_pixel_pos_mixin::f x, pt_pixel_pos_mixin::f y) +{ +    center = {x, y}; +} + +QImage trackhat_preview::get_bitmap() +{ +    frame_bgr.setTo({0}); + +    draw_points(); +    draw_center(); + +    cv::cvtColor(frame_bgr, frame_bgra, cv::COLOR_BGR2BGRA); + +    return QImage((const unsigned char*) frame_bgra.data, +                  frame_bgra.cols, frame_bgra.rows, +                  (int)frame_bgra.step.p[0], +                  QImage::Format_ARGB32); +} + +void trackhat_preview::draw_center() +{ +    if (center == numeric_types::vec2(-1, -1)) +        return; + +    auto [px_, py_] = to_pixel_pos(center[0], center[1], frame_bgr.cols, frame_bgr.rows); +    int px = iround(px_), py = iround(py_); + +    const f dpi = (f)frame_bgr.cols / f(320); +    constexpr int len_ = 9; +    int len = iround(len_ * dpi); + +    static const cv::Scalar color(0, 255, 255); +    cv::line(frame_bgr, +             cv::Point(px - len, py), +             cv::Point(px + len, py), +             color, 1); +    cv::line(frame_bgr, +             cv::Point(px, py - len), +             cv::Point(px, py + len), +             color, 1); +} + +void trackhat_preview::draw_points() +{ +    for (const auto& pt : points) +    { +        if (pt.brightness == 0) +            continue; + +        constexpr int sz = trackhat_camera::sensor_size; +        constexpr f scaling_factor = 10; +        const int x = pt.x * frame_bgr.cols / sz, y = pt.y * frame_bgr.rows / sz; +        const f dpi = (f)frame_bgr.cols / f(320); +        const int W = std::max(1, iround(pt.W * frame_bgr.cols * scaling_factor / sz)), +                  H = std::max(1, iround(pt.H * frame_bgr.rows * scaling_factor / sz)); +        const auto point_color = progn(double c = pt.brightness; return cv::Scalar{c, c, c};); +        const auto outline_color = pt.ok +                                   ? cv::Scalar{255, 255, 0} +                                   : cv::Scalar{192, 192, 192}; + +        cv::ellipse(frame_bgr, {x, y}, {W, H}, +                    0, 0, 360, point_color, -1, cv::LINE_AA); +        cv::ellipse(frame_bgr, {x, y}, {iround(W + 2*dpi), iround(H + 2*dpi)}, +                    0, 0, 360, outline_color, iround(dpi), cv::LINE_AA); + +        char buf[16]; +        std::snprintf(buf, sizeof(buf), "%dpx", pt.area); +        auto text_color = pt.ok +                          ? cv::Scalar(0, 0, 255) +                          : cv::Scalar(160, 160, 160); +        const int offx = iround(W + 9*dpi), offy = H*3/2; + +        cv::putText(frame_bgr, buf, {x+offx, y+offy}, +                    cv::FONT_HERSHEY_PLAIN, iround(dpi), text_color, +                    1); +    } +} + +void trackhat_frame::init_points(const trackHat_ExtendedPoints_t& points_, double min_size, double max_size) +{ +    trackHat_ExtendedPoints_t copy = points_; +    points = {}; + +    std::sort(std::begin(copy.m_point), std::end(copy.m_point), +        [](trackHat_ExtendedPoint_t p1, trackHat_ExtendedPoint_t p2) { +      return p1.m_averageBrightness > p2.m_averageBrightness; +    }); + +    unsigned i = 0; + +    for (const trackHat_ExtendedPoint_t& pt : copy.m_point) +    { +        if (pt.m_averageBrightness == 0) +            continue; + +        point p = {}; + +        if (pt.m_area >= min_size && pt.m_area <= max_size) +            p.ok = true; + +        p.brightness = pt.m_averageBrightness; +        p.area = pt.m_area; +        p.W = std::max(1, pt.m_boundryRigth - pt.m_boundryLeft); +        p.H = std::max(1, pt.m_boundryDown  - pt.m_boundryUp); +        p.x = trackhat_camera::sensor_size-1-pt.m_coordinateX; +        p.y = pt.m_coordinateY; + +        points[i++] = p; +    } +} diff --git a/tracker-trackhat/handle.cpp b/tracker-trackhat/handle.cpp new file mode 100644 index 00000000..42902e76 --- /dev/null +++ b/tracker-trackhat/handle.cpp @@ -0,0 +1,72 @@ +#undef NDEBUG +#include "trackhat.hpp" +#include "compat/sleep.hpp" +#include "compat/timer.hpp" +#include <cassert> + +bool camera_handle::ensure_connected() +{ +    if (state_ >= st_streaming) +        return true; +    else if (state_ == st_stopped) +        return false; + +    Timer t; + +    constexpr int max_attempts = 5; + +    if (!ensure_device_exists()) +        goto error; + +    for (int i = 0; i < max_attempts; i++) +    { +        if (!th_check(trackHat_Connect(&device_, TH_FRAME_EXTENDED))) +        { +            state_ = st_streaming; +            if (int ms = (int)t.elapsed_ms(); ms > 1000) +                qDebug() << "tracker/trackhat: connecting took" << ms << "ms"; +            return true; +        } + +        auto dbg = qDebug(); +        dbg << "tracker/trackhat: connect failed, retry"; +        dbg.space(); dbg.nospace(); +        dbg << (i+1) << "/" << max_attempts; +        portable::sleep(50); +    } + +error: +    disconnect(); +    return false; +} + +bool camera_handle::ensure_device_exists() +{ +    switch (state_) +    { +    case st_streaming: +        return true; +    case st_detected: +        disconnect(); +        [[fallthrough]]; +    case st_stopped: +        assert(!th_check(trackHat_Initialize(&device_)) && device_.m_pInternal); +        if (auto error = th_check(trackHat_DetectDevice(&device_)); error) +        { +            disconnect(); +            return false; +        } +        state_ = st_detected; +        return true; +    } +} + +void camera_handle::disconnect() +{ +    state_ = st_stopped; +    if (device_.m_pInternal) +    { +        (void)!th_check(trackHat_Disconnect(&device_)); +        trackHat_Deinitialize(&device_); +    } +} diff --git a/tracker-trackhat/images/trackhat-64x64.png b/tracker-trackhat/images/trackhat-64x64.pngBinary files differ new file mode 100644 index 00000000..9e856c23 --- /dev/null +++ b/tracker-trackhat/images/trackhat-64x64.png diff --git a/tracker-trackhat/images/trackhat.ico b/tracker-trackhat/images/trackhat.icoBinary files differ new file mode 100644 index 00000000..b5f34db3 --- /dev/null +++ b/tracker-trackhat/images/trackhat.ico diff --git a/tracker-trackhat/images/trackhat.png b/tracker-trackhat/images/trackhat.pngBinary files differ new file mode 100644 index 00000000..4f17de81 --- /dev/null +++ b/tracker-trackhat/images/trackhat.png diff --git a/tracker-trackhat/lang/nl_NL.ts b/tracker-trackhat/lang/nl_NL.ts new file mode 100644 index 00000000..73e375fc --- /dev/null +++ b/tracker-trackhat/lang/nl_NL.ts @@ -0,0 +1,134 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="nl_NL"> +<context> +    <name>trackhat_dialog</name> +    <message> +        <source>Cap</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Clip (left)</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Clip (right)</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Mini Clip (left)</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Mini Clip (right)</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Custom</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Status: Tracking stopped.</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Status: %1 points detected. Good!</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Status: %1 points detected. BAD!</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>TrackHat</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Camera</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Exposure</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Model</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Type</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Min point size</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Max point size</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source> px</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Tracking</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Point filter</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Limit</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Status</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Raw data</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>X</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>TextLabel</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Yaw</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Roll</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Y</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Z</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Pitch</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Deadzone</source> +        <translation type="unfinished"></translation> +    </message> +</context> +<context> +    <name>trackhat_module</name> +    <message> +        <source>TrackHat Point Tracker</source> +        <translation type="unfinished"></translation> +    </message> +</context> +</TS> diff --git a/tracker-trackhat/lang/ru_RU.ts b/tracker-trackhat/lang/ru_RU.ts new file mode 100644 index 00000000..091c2101 --- /dev/null +++ b/tracker-trackhat/lang/ru_RU.ts @@ -0,0 +1,134 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="ru_RU"> +<context> +    <name>trackhat_dialog</name> +    <message> +        <source>Cap</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Clip (left)</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Clip (right)</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Mini Clip (left)</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Mini Clip (right)</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Custom</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Status: Tracking stopped.</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Status: %1 points detected. Good!</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Status: %1 points detected. BAD!</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>TrackHat</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Camera</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Exposure</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Model</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Type</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Min point size</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Max point size</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source> px</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Tracking</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Point filter</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Limit</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Status</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Raw data</source> +        <translation>Исходные данные</translation> +    </message> +    <message> +        <source>X</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>TextLabel</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Yaw</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Roll</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Y</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Z</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Pitch</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Deadzone</source> +        <translation type="unfinished"></translation> +    </message> +</context> +<context> +    <name>trackhat_module</name> +    <message> +        <source>TrackHat Point Tracker</source> +        <translation type="unfinished"></translation> +    </message> +</context> +</TS> diff --git a/tracker-trackhat/lang/stub.ts b/tracker-trackhat/lang/stub.ts new file mode 100644 index 00000000..548138a3 --- /dev/null +++ b/tracker-trackhat/lang/stub.ts @@ -0,0 +1,134 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="stub"> +<context> +    <name>trackhat_dialog</name> +    <message> +        <source>TrackHat</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Camera</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Exposure</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Model</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Type</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Min point size</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Max point size</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source> px</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Tracking</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Limit</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Deadzone</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Point filter</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Status</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Raw data</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>X</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>TextLabel</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Yaw</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Roll</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Y</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Z</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Pitch</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Cap</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Clip (left)</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Clip (right)</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Mini Clip (left)</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Mini Clip (right)</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Custom</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Status: Tracking stopped.</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Status: %1 points detected. Good!</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Status: %1 points detected. BAD!</source> +        <translation type="unfinished"></translation> +    </message> +</context> +<context> +    <name>trackhat_module</name> +    <message> +        <source>TrackHat Point Tracker</source> +        <translation type="unfinished"></translation> +    </message> +</context> +</TS> diff --git a/tracker-trackhat/lang/zh_CN.ts b/tracker-trackhat/lang/zh_CN.ts new file mode 100644 index 00000000..cb881fa8 --- /dev/null +++ b/tracker-trackhat/lang/zh_CN.ts @@ -0,0 +1,134 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="zh_CN"> +<context> +    <name>trackhat_dialog</name> +    <message> +        <source>Cap</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Clip (left)</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Clip (right)</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Mini Clip (left)</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Mini Clip (right)</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Custom</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Status: Tracking stopped.</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Status: %1 points detected. Good!</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Status: %1 points detected. BAD!</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>TrackHat</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Camera</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Exposure</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Model</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Type</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Min point size</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Max point size</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source> px</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Tracking</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Point filter</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Limit</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Status</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Raw data</source> +        <translation>跟踪器原始数据</translation> +    </message> +    <message> +        <source>X</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>TextLabel</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Yaw</source> +        <translation>偏航</translation> +    </message> +    <message> +        <source>Roll</source> +        <translation>横滚</translation> +    </message> +    <message> +        <source>Y</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Z</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <source>Pitch</source> +        <translation>仰俯</translation> +    </message> +    <message> +        <source>Deadzone</source> +        <translation type="unfinished"></translation> +    </message> +</context> +<context> +    <name>trackhat_module</name> +    <message> +        <source>TrackHat Point Tracker</source> +        <translation type="unfinished"></translation> +    </message> +</context> +</TS> diff --git a/tracker-trackhat/metadata.cpp b/tracker-trackhat/metadata.cpp new file mode 100644 index 00000000..8c6f4de0 --- /dev/null +++ b/tracker-trackhat/metadata.cpp @@ -0,0 +1,39 @@ +#include "metadata.hpp" +#include "api/plugin-api.hpp" +#include "trackhat.hpp" +#include "dialog.hpp" + +// XXX TODO +const QString trackhat_metadata::module_name = QStringLiteral("tracker-trackhat/pt"); + +pt_runtime_traits::pointer<pt_camera> trackhat_metadata::make_camera() const +{ +    return std::make_shared<trackhat_camera>(); +} + +pt_runtime_traits::pointer<pt_point_extractor> trackhat_metadata::make_point_extractor() const +{ +    return std::make_shared<trackhat_extractor>(); +} + +pt_runtime_traits::pointer<pt_frame> trackhat_metadata::make_frame() const +{ +    return std::make_shared<trackhat_frame>(); +} + +pt_runtime_traits::pointer<pt_preview> trackhat_metadata::make_preview(int w, int h) const +{ +    return std::make_shared<trackhat_preview>(w, h); +} + +QString trackhat_metadata::get_module_name() const +{ +    return trackhat_metadata::module_name; +} + +trackhat_pt::trackhat_pt() : +      Tracker_PT(pt_runtime_traits::pointer<pt_runtime_traits>(new trackhat_metadata)) +{ +} + +OPENTRACK_DECLARE_TRACKER(trackhat_pt, trackhat_dialog, trackhat_module) diff --git a/tracker-trackhat/metadata.hpp b/tracker-trackhat/metadata.hpp new file mode 100644 index 00000000..c0f7567c --- /dev/null +++ b/tracker-trackhat/metadata.hpp @@ -0,0 +1,21 @@ +#pragma once +#include "trackhat.hpp" +#include "../tracker-pt/ftnoir_tracker_pt.h" + +class trackhat_pt final : public Tracker_PT +{ +    Q_OBJECT + +public: +    trackhat_pt(); +}; + +class trackhat_module final : public Metadata +{ +    Q_OBJECT + +public: +    QString name() override { return tr("TrackHat Point Tracker"); } +    QIcon icon() override { return QIcon(":/images/trackhat-64x64.png"); } +    static const QString module_name; +}; diff --git a/tracker-trackhat/settings.cpp b/tracker-trackhat/settings.cpp new file mode 100644 index 00000000..ec46b1e9 --- /dev/null +++ b/tracker-trackhat/settings.cpp @@ -0,0 +1,147 @@ +#include "trackhat.hpp" +#include "compat/sleep.hpp" +#include "compat/timer.hpp" + +namespace trackhat_impl { + +trackhat_settings::trackhat_settings() : opts{"tracker-trackhat"} +{ +} +int trackhat_settings::effective_exposure() const +{ +    return std::clamp((int)*exposure, min_exposure, max_exposure); +} +int trackhat_settings::effective_gain() const +{ +    return min_gain + std::clamp((int)*exposure - max_exposure, 0, max_gain - min_gain); +} + +void setting_receiver::settings_changed() +{ +    changed = true; +} + +bool setting_receiver::test_and_clear() +{ +    bool x = true; +    return changed.compare_exchange_strong(x, false); +} + +setting_receiver::setting_receiver(bool value) : changed{value} +{ +} + +} // ns trackhat_impl + +void trackhat_camera::set_pt_options() +{ +    s.min_point_size = t.min_pt_size; +    s.max_point_size = t.max_pt_size; + +    switch (t.model) +    { +    default: +    case model_cap: +        s.t_MH_x = 0; s.t_MH_y = 0; s.t_MH_z = 0; +        break; +    case model_mystery_meat: +    case model_clip_left: +    case model_mini_clip_left: +        s.t_MH_x = -135; s.t_MH_y = 0; s.t_MH_z = 0; +        break; +    case model_clip_right: +    case model_mini_clip_right: +        s.t_MH_x = 135; s.t_MH_y = 0; s.t_MH_z = 0; +        break; +    } + +    switch (t.model) +    { +    default: +        eval_once(qDebug() << "tracker/trackhat: unknown model"); +    [[fallthrough]]; +    case model_clip_left: +    case model_clip_right: +        s.clip_tz = 27; s.clip_ty = 43; s.clip_by = 62; s.clip_bz = 74; +        break; +    case model_mini_clip_left: +    case model_mini_clip_right: +        s.clip_tz = 13; s.clip_ty = 42; s.clip_by = 60; s.clip_bz = 38; +        break; +    case model_cap: +        s.cap_x = 60; s.cap_y = 90; s.cap_z = 95; +        break; +    case model_mystery_meat: +        break; +    } + +    s.dynamic_pose = t.model == model_cap; +    s.init_phase_timeout = 500; + +    s.camera_name = QStringLiteral("TrackHat Sensor (WIP)"); + +    s.active_model_panel = t.model == model_cap ? 1 : 0; +    s.enable_point_filter = t.enable_point_filter; +    s.point_filter_coefficient = *t.point_filter_coefficient; +    s.point_filter_limit = *t.point_filter_limit; +    s.point_filter_deadzone = *t.point_filter_deadzone; +} + +bool trackhat_camera::init_regs() +{ +    auto exp    = (uint8_t)t.effective_exposure(); +    auto exp2   = (uint8_t)(exp == 0xff ? 0xf0 : 0xff); +    auto thres  = (uint8_t)0xfe; +    auto thres2 = (uint8_t)3; + +    auto gain   = (uint8_t)t.effective_gain(); +    auto gain_c = (uint8_t)(gain/0x10); +    gain %= 0x10; gain_c %= 4; + +    trackHat_SetRegisterGroup_t regs = { +        { +            { 0x0c, 0x0f, exp2   },  // exposure lo +            { 0x0c, 0x10, exp    },  // exposure hi +            { 0x00, 0x0b, 0xff   },  // blob area max size +            { 0x00, 0x0c, 0x03   },  // blob area min size +            { 0x0c, 0x08, gain   },  // gain +            { 0x0c, 0x0c, gain_c },  // gain multiplier +            { 0x0c, 0x47, thres  },  // min brightness +            { 0x00, 0x0f, thres2 },  // brightness margin, formula is `thres >= px > thres - fuzz' +            { 0x00, 0x01, 0x01   },  // bank0 sync +            { 0x01, 0x01, 0x01   },  // bank1 sync +        }, +        10 +    }; + +    Timer t; + +    constexpr int max = 5; +    int i = 0; +    for (i = 0; i < max; i++) +    { +        TH_ErrorCode status = TH_SUCCESS; +        status = th_check(trackHat_SetRegisterGroupValue(&*device, ®s)); +        if (status == TH_SUCCESS) +            break; +        else if (status != TH_FAILED_TO_SET_REGISTER && +                 status != TH_ERROR_DEVICE_COMMUNICATION_TIMEOUT) +            return false; +        else +        { +            auto dbg = qDebug(); +            dbg << "tracker/trackhat: set register retry attempt"; +            dbg.space(); dbg.nospace(); +            dbg << i << "/" << max; +            portable::sleep(50); +        } +    } + +    if (i == max) +        return false; + +    if (int elapsed = (int)t.elapsed_ms(); elapsed > 100) +        qDebug() << "tracker/trackhat: setting registers took" << elapsed << "ms"; + +    return true; +} diff --git a/tracker-trackhat/tracker_trackhat.qrc b/tracker-trackhat/tracker_trackhat.qrc new file mode 100644 index 00000000..d54010a0 --- /dev/null +++ b/tracker-trackhat/tracker_trackhat.qrc @@ -0,0 +1,5 @@ +<RCC> +    <qresource prefix="/"> +        <file>images/trackhat-64x64.png</file> +    </qresource> +</RCC> diff --git a/tracker-trackhat/trackhat-res.qrc b/tracker-trackhat/trackhat-res.qrc new file mode 100644 index 00000000..9aeb8879 --- /dev/null +++ b/tracker-trackhat/trackhat-res.qrc @@ -0,0 +1,5 @@ +<RCC> +  <qresource> +    <file>images/trackhat-64x64.png</file> +  </qresource> +</RCC> diff --git a/tracker-trackhat/trackhat.hpp b/tracker-trackhat/trackhat.hpp new file mode 100644 index 00000000..d94841d2 --- /dev/null +++ b/tracker-trackhat/trackhat.hpp @@ -0,0 +1,180 @@ +#pragma once + +#include "../tracker-pt/pt-api.hpp" +#include "compat/macros.hpp" +#include "options/options.hpp" + +#include <track_hat_driver.h> + +#include <array> +#include <atomic> +#include <opencv2/core/mat.hpp> + +enum model_type : int +{ +    model_cap = 1, +    model_clip_left, +    model_clip_right, +    model_mini_clip_left, +    model_mini_clip_right, +    model_mystery_meat, +}; + +namespace trackhat_impl +{ +using namespace options; + +TH_ErrorCode log_error(TH_ErrorCode error, const char* source, const char* file, int line, const char* function); +#define th_check_(expr, expr2) ::trackhat_impl::log_error((expr), expr2) +#define th_check(expr) ::trackhat_impl::log_error((expr), #expr, __FILE__, __LINE__, function_name) + +struct trackhat_settings : opts +{ +    static constexpr int min_gain = 16, max_gain = 47, +                         min_exposure = 0x10, max_exposure = 0xff; +    static constexpr int num_exposure_steps = max_gain + max_exposure - min_gain - min_exposure; +    int effective_exposure() const; +    int effective_gain() const; +    trackhat_settings(); +    value<slider_value> exposure{b, "exposure", {min_exposure, min_exposure, max_exposure + max_gain - min_gain}}; +    //value<slider_value> threshold{b, "threshold", {0x97, 64, 0xfe}}; +    value<model_type> model{b, "model", model_mini_clip_left}; +    value<double> min_pt_size{b, "min-point-size", 10}; +    value<double> max_pt_size{b, "max-point-size", 50}; +    value<bool> enable_point_filter{b, "enable-point-filter", true }; +    value<slider_value> point_filter_coefficient{b, "point-filter-coefficient", { 1.5, 1, 4 }}; +    value<slider_value> point_filter_limit { b, "point-filter-limit", { 0.1, 0.01, 1 }}; +    value<slider_value> point_filter_deadzone { b, "point-filter-deadzone", {0, 0, 1}}; +}; + +class setting_receiver : public QObject +{ +    Q_OBJECT + +public: +    explicit setting_receiver(bool value); +    bool test_and_clear(); +public slots: +    void settings_changed(); +private: +    std::atomic<bool> changed{false}; +}; + +} // ns trackhat_impl + +using typename trackhat_impl::trackhat_settings; + +struct trackhat_metadata final : pt_runtime_traits +{ +    pt_runtime_traits::pointer<pt_camera> make_camera() const override; +    pt_runtime_traits::pointer<pt_point_extractor> make_point_extractor() const override; +    pt_runtime_traits::pointer<pt_frame> make_frame() const override; +    pt_runtime_traits::pointer<pt_preview> make_preview(int w, int h) const override; +    QString get_module_name() const override; + +    OTR_DISABLE_MOVE_COPY(trackhat_metadata); + +    trackhat_metadata() = default; +    ~trackhat_metadata() override = default; + +    static const QString module_name; +}; + +struct point +{ +    int brightness = 0, area, x, y, W, H; +    bool ok = false; +}; + +struct camera_handle final +{ +    OTR_DISABLE_MOVE_COPY(camera_handle); +    trackHat_Device_t* operator->() { return &device_; } +    trackHat_Device_t& operator*() { return device_; } + +    camera_handle() = default; +    ~camera_handle() = default; + +    [[nodiscard]] bool ensure_connected(); +    [[nodiscard]] bool ensure_device_exists(); +    void disconnect(); +private: +    trackHat_Device_t device_ = {}; +    enum state { st_stopped, st_detected, st_streaming, }; +    state state_ = st_stopped; +}; + +struct trackhat_camera final : pt_camera +{ +    trackhat_camera(); +    ~trackhat_camera() override; + +    OTR_DISABLE_MOVE_COPY(trackhat_camera); + +    bool start(const pt_settings& s) override; +    void stop() override; + +    pt_camera::result get_frame(pt_frame& frame) override; +    pt_camera::result get_info() const override; +    pt_camera_info get_desired() const override; + +    QString get_desired_name() const override; +    QString get_active_name() const override; + +    void set_fov(f value) override; +    void show_camera_settings() override; + +    f deadzone_amount() const override { return 10; } + +    static constexpr int sensor_size = 2940; +    static constexpr int sensor_fov = 52; +    static constexpr int point_count = TRACK_HAT_NUMBER_OF_POINTS; +    static constexpr bool debug_mode = true; + +private: +    trackhat_impl::setting_receiver sig{true}; + +    [[nodiscard]] bool init_regs(); +    void set_pt_options(); + +    camera_handle device; +    pt_settings s{trackhat_metadata::module_name}; +    trackhat_settings t; +}; + +struct trackhat_frame final : pt_frame +{ +    void init_points(const trackHat_ExtendedPoints_t& points, double min_size, double max_size); +    trackhat_frame() = default; +    ~trackhat_frame() override = default; + +    std::array<point, trackhat_camera::point_count> points; +}; + +struct trackhat_preview final : pt_preview +{ +    QImage get_bitmap() override; +    void draw_head_center(f x, f y) override; +    void set_last_frame(const pt_frame&) override; // NOLINT(misc-unconventional-assign-operator) + +    trackhat_preview(int w, int h); +    ~trackhat_preview() override = default; +    void draw_points(); +    void draw_center(); + +    OTR_DISABLE_MOVE_COPY(trackhat_preview); + +    cv::Mat frame_bgr, frame_bgra; +    numeric_types::vec2 center{-1, -1}; +    std::array<point, trackhat_camera::point_count> points; +}; + +struct trackhat_extractor final : pt_point_extractor +{ +    void extract_points(const pt_frame& data, pt_preview&, bool, std::vector<vec2>& points) override; + +    OTR_DISABLE_MOVE_COPY(trackhat_extractor); + +    trackhat_extractor() = default; +    ~trackhat_extractor() override = default; +}; | 
