diff options
Diffstat (limited to 'tracker-pt')
45 files changed, 3913 insertions, 1576 deletions
diff --git a/tracker-pt/CMakeLists.txt b/tracker-pt/CMakeLists.txt index abc7fb33..0c2e9ce3 100644 --- a/tracker-pt/CMakeLists.txt +++ b/tracker-pt/CMakeLists.txt @@ -1,6 +1,12 @@ -find_package(OpenCV 3.0 QUIET COMPONENTS ${opencv-modules}) +include(opentrack-opencv) +find_package(OpenCV QUIET) if(OpenCV_FOUND) - otr_module(tracker-pt) - target_link_libraries(opentrack-tracker-pt opentrack-cv ${OpenCV_LIBS}) - target_include_directories(opentrack-tracker-pt SYSTEM PUBLIC ${OpenCV_INCLUDE_DIRS}) + foreach(k core) + otr_install_lib("opencv_${k}" "${opentrack-libexec}") + endforeach() + otr_module(tracker-pt-base STATIC) + target_include_directories(${self} SYSTEM PUBLIC ${OpenCV_INCLUDE_DIRS}) + target_link_libraries(${self} opentrack-cv opencv_core) + set_property(TARGET ${self} PROPERTY OUTPUT_NAME "pt-base") endif() +add_subdirectory(module) diff --git a/tracker-pt/FTNoIR_PT_Controls.ui b/tracker-pt/FTNoIR_PT_Controls.ui index 718c7df9..53a29c1a 100644 --- a/tracker-pt/FTNoIR_PT_Controls.ui +++ b/tracker-pt/FTNoIR_PT_Controls.ui @@ -9,12 +9,12 @@ <rect> <x>0</x> <y>0</y> - <width>424</width> - <height>584</height> + <width>413</width> + <height>630</height> </rect> </property> <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> @@ -23,7 +23,7 @@ <string>PointTracker Settings</string> </property> <property name="windowIcon"> - <iconset resource="ftnoir_tracker_pt.qrc"> + <iconset resource="module/tracker_pt.qrc"> <normaloff>:/Resources/Logo_IR.png</normaloff>:/Resources/Logo_IR.png</iconset> </property> <property name="layoutDirection"> @@ -32,24 +32,24 @@ <property name="autoFillBackground"> <bool>false</bool> </property> - <layout class="QGridLayout" name="gridLayout_9"> - <property name="sizeConstraint"> - <enum>QLayout::SetFixedSize</enum> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <property name="spacing"> + <number>0</number> </property> - <item row="0" column="0"> + <property name="leftMargin"> + <number>4</number> + </property> + <property name="topMargin"> + <number>4</number> + </property> + <property name="rightMargin"> + <number>4</number> + </property> + <property name="bottomMargin"> + <number>4</number> + </property> + <item> <widget class="QTabWidget" name="tabWidget"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="minimumSize"> - <size> - <width>0</width> - <height>0</height> - </size> - </property> <property name="locale"> <locale language="English" country="UnitedStates"/> </property> @@ -57,75 +57,129 @@ <number>0</number> </property> <widget class="QWidget" name="tab_2"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> <attribute name="title"> <string>Camera</string> </attribute> <layout class="QVBoxLayout" name="verticalLayout"> <item> - <widget class="QGroupBox" name="groupBox"> + <widget class="QGroupBox" name="camera_settings_groupbox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> <property name="title"> <string>Camera settings</string> </property> <layout class="QGridLayout" name="gridLayout_2"> - <item row="0" column="1"> - <widget class="QComboBox" name="camdevice_combo"> + <item row="11" column="1"> + <widget class="QCheckBox" name="chroma_key_overexposed"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="5" column="0"> + <widget class="QLabel" name="label_4"> <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> - <property name="minimumContentsLength"> - <number>10</number> + <property name="cursor"> + <cursorShape>WhatsThisCursor</cursorShape> + </property> + <property name="toolTip"> + <string>This should be 56° or 76° for the PS3 Eye, dependent upon the physical lens setting. It's only neccessary to get position correspond to real-world values.</string> + </property> + <property name="text"> + <string>Diagonal field of view</string> </property> </widget> </item> - <item row="4" column="1"> - <widget class="QSpinBox" name="fov"> - <property name="suffix"> - <string>°</string> + <item row="6" column="0"> + <widget class="QLabel" name="label_5"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> </property> - <property name="prefix"> - <string/> + <property name="text"> + <string>Dynamic pose (for caps only, never clips)</string> </property> - <property name="minimum"> - <number>10</number> + </widget> + </item> + <item row="9" column="0"> + <widget class="QLabel" name="label_12"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> </property> - <property name="maximum"> - <number>90</number> + <property name="cursor"> + <cursorShape>WhatsThisCursor</cursorShape> + </property> + <property name="toolTip"> + <string><html><head/><body><p>For LEDs, 'Natural' is the fastest grayscale mode thanks to optimized SIMD code. Color key allows to track regular pieces of colored paper.</p></body></html></string> + </property> + <property name="text"> + <string>Color channels used</string> </property> </widget> </item> - <item row="4" column="0"> - <widget class="QLabel" name="label_4"> + <item row="8" column="1"> + <widget class="QPushButton" name="camera_settings"> <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> <property name="text"> - <string>Diagonal field of view</string> + <string>Open</string> </property> </widget> </item> - <item row="1" column="0"> - <widget class="QLabel" name="label_36"> + <item row="4" column="1"> + <widget class="QCheckBox" name="use_mjpeg"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_13"> <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> + <property name="cursor"> + <cursorShape>WhatsThisCursor</cursorShape> + </property> + <property name="toolTip"> + <string>Enable MJPEG compression for high-speed cameras other than the PS3 Eye. Windows only.</string> + </property> <property name="text"> - <string>Width</string> + <string>MJPEG compression</string> </property> </widget> </item> <item row="3" column="0"> <widget class="QLabel" name="label_37"> <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> @@ -138,56 +192,66 @@ </property> </widget> </item> - <item row="2" column="1"> - <widget class="QSpinBox" name="res_y_spin"> + <item row="1" column="0"> + <widget class="QLabel" name="label_36"> <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> - <property name="toolTip"> - <string>Desired capture height</string> - </property> - <property name="suffix"> - <string> px</string> - </property> - <property name="maximum"> - <number>2000</number> - </property> - <property name="singleStep"> - <number>10</number> + <property name="text"> + <string>Width</string> </property> </widget> </item> - <item row="6" column="0"> - <widget class="QLabel" name="label_6"> + <item row="11" column="0"> + <widget class="QLabel" name="label_16"> <property name="text"> - <string>Dynamic pose timeout</string> + <string>Chroma key includes overexposed pixels</string> </property> </widget> </item> - <item row="3" column="1"> - <widget class="QSpinBox" name="fps_spin"> + <item row="8" column="0"> + <widget class="QLabel" name="label_9"> <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> - <property name="toolTip"> - <string>Desired capture framerate</string> + <property name="text"> + <string>Camera settings (when available)</string> + </property> + </widget> + </item> + <item row="7" column="1"> + <widget class="QSpinBox" name="init_phase_timeout"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> </property> <property name="suffix"> - <string> Hz</string> + <string> ms</string> + </property> + <property name="minimum"> + <number>50</number> </property> <property name="maximum"> - <number>2000</number> + <number>5000</number> </property> </widget> </item> - <item row="5" column="1"> + <item row="6" column="1"> <widget class="QCheckBox" name="dynamic_pose"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> <property name="text"> <string/> </property> @@ -196,7 +260,7 @@ <item row="1" column="1"> <widget class="QSpinBox" name="res_x_spin"> <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> @@ -215,10 +279,52 @@ </property> </widget> </item> + <item row="7" column="0"> + <widget class="QLabel" name="label_6"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Dynamic pose timeout</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="camdevice_combo"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumContentsLength"> + <number>10</number> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label_2"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Device</string> + </property> + <property name="buddy"> + <cstring>camdevice_combo</cstring> + </property> + </widget> + </item> <item row="2" column="0"> <widget class="QLabel" name="label_41"> <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> @@ -228,89 +334,200 @@ </property> </widget> </item> - <item row="6" column="1"> - <widget class="QSpinBox" name="init_phase_timeout"> - <property name="suffix"> - <string> ms</string> + <item row="3" column="1"> + <widget class="QSpinBox" name="fps_spin"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> </property> - <property name="minimum"> - <number>50</number> + <property name="toolTip"> + <string>Desired capture framerate</string> + </property> + <property name="suffix"> + <string> Hz</string> </property> <property name="maximum"> - <number>5000</number> + <number>2000</number> </property> </widget> </item> - <item row="5" column="0"> - <widget class="QLabel" name="label_5"> + <item row="9" column="1"> + <widget class="QComboBox" name="blob_color"> <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> - <property name="text"> - <string>Dynamic pose resolution</string> - </property> + <item> + <property name="text"> + <string>Grayscale BT.709</string> + </property> + </item> + <item> + <property name="text"> + <string>Grayscale (from hardware)</string> + </property> + </item> + <item> + <property name="text"> + <string>Red only</string> + </property> + </item> + <item> + <property name="text"> + <string>Green only</string> + </property> + </item> + <item> + <property name="text"> + <string>Blue only</string> + </property> + </item> + <item> + <property name="text"> + <string>Red chroma key</string> + </property> + </item> + <item> + <property name="text"> + <string>Green chroma key</string> + </property> + </item> + <item> + <property name="text"> + <string>Blue chroma key</string> + </property> + </item> + <item> + <property name="text"> + <string>Cyan chroma key</string> + </property> + </item> + <item> + <property name="text"> + <string>Yellow chroma key</string> + </property> + </item> + <item> + <property name="text"> + <string>Magenta chroma key</string> + </property> + </item> </widget> </item> - <item row="0" column="0"> - <widget class="QLabel" name="label_2"> + <item row="5" column="1"> + <widget class="QSpinBox" name="fov"> <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> - <property name="text"> - <string>Device</string> + <property name="suffix"> + <string>°</string> </property> - <property name="buddy"> - <cstring>camdevice_combo</cstring> + <property name="prefix"> + <string/> + </property> + <property name="minimum"> + <number>10</number> + </property> + <property name="maximum"> + <number>90</number> </property> </widget> </item> - <item row="7" column="1"> - <widget class="QPushButton" name="camera_settings"> + <item row="2" column="1"> + <widget class="QSpinBox" name="res_y_spin"> <property name="sizePolicy"> <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> - <property name="text"> - <string>Open</string> + <property name="toolTip"> + <string>Desired capture height</string> + </property> + <property name="suffix"> + <string> px</string> + </property> + <property name="maximum"> + <number>2000</number> + </property> + <property name="singleStep"> + <number>10</number> </property> </widget> </item> - <item row="7" column="0"> - <widget class="QLabel" name="label_9"> + <item row="10" column="0"> + <widget class="QLabel" name="label_17"> <property name="text"> - <string>Camera settings (when available)</string> + <string>Chroma key strength</string> </property> </widget> </item> + <item row="10" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QSlider" name="chroma_key_strength_slider"> + <property name="minimum"> + <number>5</number> + </property> + <property name="maximum"> + <number>40</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QDoubleSpinBox" name="chroma_key_strength_label"> + <property name="focusPolicy"> + <enum>Qt::NoFocus</enum> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + <property name="buttonSymbols"> + <enum>QAbstractSpinBox::NoButtons</enum> + </property> + <property name="minimum"> + <double>0.500000000000000</double> + </property> + <property name="maximum"> + <double>4.000000000000000</double> + </property> + </widget> + </item> + </layout> + </item> </layout> </widget> </item> <item> <widget class="QGroupBox" name="groupBox_2"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> <property name="title"> <string>Point extraction</string> </property> <layout class="QGridLayout" name="gridLayout_7"> - <item row="3" column="0"> - <widget class="QLabel" name="label_8"> - <property name="text"> - <string>Max size</string> - </property> - <property name="buddy"> - <cstring>maxdiam_spin</cstring> - </property> - </widget> - </item> <item row="1" column="0"> <widget class="QLabel" name="label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> <property name="text"> <string>Threshold</string> </property> @@ -319,8 +536,20 @@ </property> </widget> </item> - <item row="2" column="0"> + <item row="3" column="0"> <widget class="QLabel" name="label_7"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="cursor"> + <cursorShape>WhatsThisCursor</cursorShape> + </property> + <property name="toolTip"> + <string>Set minimum size to avoid small stray lights from being treated as points.</string> + </property> <property name="text"> <string>Min size</string> </property> @@ -329,10 +558,26 @@ </property> </widget> </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_8"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Max size</string> + </property> + <property name="buddy"> + <cstring>maxdiam_spin</cstring> + </property> + </widget> + </item> <item row="1" column="1" colspan="2"> <widget class="QSlider" name="threshold_slider"> <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Maximum"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> @@ -360,22 +605,46 @@ </property> </widget> </item> - <item row="0" column="0"> - <widget class="QLabel" name="label_11"> + <item row="0" column="1"> + <widget class="QCheckBox" name="auto_threshold"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> <property name="text"> - <string>Automatic threshold</string> + <string>Enable, slider sets point size</string> </property> </widget> </item> - <item row="0" column="1"> - <widget class="QCheckBox" name="auto_threshold"> + <item row="0" column="0"> + <widget class="QLabel" name="label_11"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="cursor"> + <cursorShape>WhatsThisCursor</cursorShape> + </property> + <property name="toolTip"> + <string>Track dependent on point size and not absolute brightness. This may allow more stable tracking.</string> + </property> <property name="text"> - <string>Enable, slider sets point size</string> + <string>Automatic threshold</string> </property> </widget> </item> - <item row="3" column="1"> + <item row="4" column="1"> <widget class="QDoubleSpinBox" name="maxdiam_spin"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> <property name="toolTip"> <string>Maximum point diameter</string> </property> @@ -391,7 +660,33 @@ </widget> </item> <item row="2" column="1"> + <widget class="QLabel" name="threshold_value_display"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_14"> + <property name="text"> + <string>Value</string> + </property> + </widget> + </item> + <item row="3" column="1"> <widget class="QDoubleSpinBox" name="mindiam_spin"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> <property name="toolTip"> <string>Minimum point diameter</string> </property> @@ -409,6 +704,19 @@ </layout> </widget> </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>1</height> + </size> + </property> + </spacer> + </item> </layout> </widget> <widget class="QWidget" name="tab_4"> @@ -512,7 +820,7 @@ <string/> </property> <property name="pixmap"> - <pixmap resource="ftnoir_tracker_pt.qrc">:/Resources/clip_side.png</pixmap> + <pixmap resource="tracker_pt_base.qrc">:/Resources/clip_side.png</pixmap> </property> </widget> <widget class="QLabel" name="label_50"> @@ -592,7 +900,7 @@ <string/> </property> <property name="pixmap"> - <pixmap resource="ftnoir_tracker_pt.qrc">:/Resources/clip_front.png</pixmap> + <pixmap resource="tracker_pt_base.qrc">:/Resources/clip_front.png</pixmap> </property> </widget> </widget> @@ -628,7 +936,7 @@ <string/> </property> <property name="pixmap"> - <pixmap resource="ftnoir_tracker_pt.qrc">:/Resources/cap_side.png</pixmap> + <pixmap resource="tracker_pt_base.qrc">:/Resources/cap_side.png</pixmap> </property> </widget> <widget class="QLabel" name="label_48"> @@ -676,7 +984,7 @@ <string/> </property> <property name="pixmap"> - <pixmap resource="ftnoir_tracker_pt.qrc">:/Resources/cap_front.png</pixmap> + <pixmap resource="tracker_pt_base.qrc">:/Resources/cap_front.png</pixmap> </property> </widget> <widget class="QSpinBox" name="cap_width_spin"> @@ -1110,6 +1418,231 @@ Don't roll or change position.</string> </layout> </widget> </item> + <item row="2" column="0"> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>1</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab"> + <attribute name="title"> + <string>Filter</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_9"> + <item row="0" column="0"> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Point filter</string> + </property> + <layout class="QGridLayout" name="gridLayout_12"> + <item row="1" column="1"> + <widget class="QSlider" name="point_filter_limit_slider"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimum"> + <number>0</number> + </property> + <property name="maximum"> + <number>99</number> + </property> + <property name="singleStep"> + <number>1</number> + </property> + <property name="pageStep"> + <number>10</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QDoubleSpinBox" name="point_filter_label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>2</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="maximum"> + <double>999.990000000000009</double> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_15"> + <property name="text"> + <string>Limit</string> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QDoubleSpinBox" name="point_filter_deadzone_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="suffix"> + <string> px</string> + </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="Minimum" vsizetype="Maximum"> + <horstretch>10</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimum"> + <number>0</number> + </property> + <property name="maximum"> + <number>400</number> + </property> + <property name="singleStep"> + <number>1</number> + </property> + <property name="pageStep"> + <number>10</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QSlider" name="point_filter_deadzone_slider"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimum"> + <number>0</number> + </property> + <property name="maximum"> + <number>100</number> + </property> + <property name="singleStep"> + <number>1</number> + </property> + <property name="pageStep"> + <number>3</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_30"> + <property name="text"> + <string>Deadzone</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="maximum"> + <double>1.000000000000000</double> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QCheckBox" name="enable_point_filter"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Filter point centers prior to pose estimation.</string> + </property> + <property name="text"> + <string>Enable</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="1" column="0"> + <spacer name="verticalSpacer_3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> </layout> </widget> <widget class="QWidget" name="tab_3"> @@ -1136,7 +1669,7 @@ Don't roll or change position.</string> <string/> </property> <property name="pixmap"> - <pixmap resource="ftnoir_tracker_pt.qrc">:/Resources/Logo_IR.png</pixmap> + <pixmap resource="module/tracker_pt.qrc">:/Resources/Logo_IR.png</pixmap> </property> <property name="alignment"> <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> @@ -1147,7 +1680,7 @@ Don't roll or change position.</string> </widget> </widget> </item> - <item row="2" column="0"> + <item> <widget class="QGroupBox" name="groupBox_5"> <property name="sizePolicy"> <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> @@ -1159,73 +1692,69 @@ Don't roll or change position.</string> <string>Status</string> </property> <layout class="QGridLayout" name="gridLayout_10"> - <item row="1" column="0"> - <widget class="QLabel" name="label_3"> + <item row="1" column="1"> + <widget class="QLabel" name="pointinfo_label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> <property name="text"> - <string>Extracted Points:</string> + <string/> </property> </widget> </item> - <item row="0" column="0"> - <widget class="QLabel" name="label_38"> + <item row="0" column="1"> + <widget class="QLabel" name="caminfo_label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> <property name="text"> - <string>Camera Info:</string> + <string/> </property> </widget> </item> - <item row="1" column="1"> - <widget class="QLabel" name="pointinfo_label"> - <property name="minimumSize"> - <size> - <width>50</width> - <height>0</height> - </size> + <item row="1" column="0"> + <widget class="QLabel" name="label_3"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> </property> <property name="text"> - <string/> + <string>Extracted Points:</string> </property> </widget> </item> - <item row="0" column="1"> - <widget class="QLabel" name="caminfo_label"> + <item row="0" column="0"> + <widget class="QLabel" name="label_38"> <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> - <property name="minimumSize"> - <size> - <width>120</width> - <height>0</height> - </size> - </property> <property name="text"> - <string/> + <string>Camera Info:</string> </property> </widget> </item> </layout> </widget> </item> - <item row="1" column="0"> - <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>20</width> - <height>0</height> - </size> - </property> - </spacer> - </item> - <item row="3" column="0"> + <item> <widget class="QDialogButtonBox" name="buttonBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> <property name="standardButtons"> <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> </property> @@ -1239,8 +1768,18 @@ Don't roll or change position.</string> <tabstop>res_x_spin</tabstop> <tabstop>res_y_spin</tabstop> <tabstop>fps_spin</tabstop> + <tabstop>use_mjpeg</tabstop> <tabstop>fov</tabstop> + <tabstop>dynamic_pose</tabstop> + <tabstop>init_phase_timeout</tabstop> + <tabstop>camera_settings</tabstop> + <tabstop>blob_color</tabstop> + <tabstop>chroma_key_strength_slider</tabstop> + <tabstop>chroma_key_overexposed</tabstop> + <tabstop>auto_threshold</tabstop> <tabstop>threshold_slider</tabstop> + <tabstop>mindiam_spin</tabstop> + <tabstop>maxdiam_spin</tabstop> <tabstop>model_tabs</tabstop> <tabstop>clip_tlength_spin</tabstop> <tabstop>clip_theight_spin</tabstop> @@ -1258,9 +1797,27 @@ Don't roll or change position.</string> <tabstop>tx_spin</tabstop> <tabstop>ty_spin</tabstop> <tabstop>tz_spin</tabstop> + <tabstop>tcalib_button</tabstop> + <tabstop>enable_point_filter</tabstop> + <tabstop>point_filter_slider</tabstop> + <tabstop>point_filter_limit_slider</tabstop> + <tabstop>point_filter_deadzone_slider</tabstop> + <tabstop>maxdiam_spin</tabstop> + <tabstop>mindiam_spin</tabstop> + <tabstop>auto_threshold</tabstop> + <tabstop>threshold_slider</tabstop> </tabstops> <resources> - <include location="ftnoir_tracker_pt.qrc"/> + <include location="module/tracker_pt.qrc"/> + <include location="module/tracker_pt.qrc"/> + <include location="module/tracker_pt.qrc"/> + <include location="module/tracker_pt.qrc"/> + <include location="module/tracker_pt.qrc"/> + <include location="tracker_pt_base.qrc"/> + <include location="tracker_pt_base.qrc"/> + <include location="tracker_pt_base.qrc"/> + <include location="tracker_pt_base.qrc"/> + <include location="tracker_pt_base.qrc"/> </resources> <connections/> <slots> diff --git a/tracker-pt/affine.cpp b/tracker-pt/affine.cpp deleted file mode 100644 index 9018e107..00000000 --- a/tracker-pt/affine.cpp +++ /dev/null @@ -1,32 +0,0 @@ -/* Copyright (c) 2012 Patrick Ruoff - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - */ - -#include "affine.hpp" - -Affine::Affine() : R(mat33::eye()), t(0,0,0) {} - -Affine::Affine(const mat33& R, const vec3& t) : R(R),t(t) {} - -Affine operator*(const Affine& X, const Affine& Y) -{ - return Affine(X.R*Y.R, X.R*Y.t + X.t); -} - -Affine operator*(const mat33& X, const Affine& Y) -{ - return Affine(X*Y.R, X*Y.t); -} - -Affine operator*(const Affine& X, const mat33& Y) -{ - return Affine(X.R*Y, X.t); -} - -vec3 operator*(const Affine& X, const vec3& v) -{ - return X.R*v + X.t; -} diff --git a/tracker-pt/affine.hpp b/tracker-pt/affine.hpp deleted file mode 100644 index aedb0bc8..00000000 --- a/tracker-pt/affine.hpp +++ /dev/null @@ -1,29 +0,0 @@ -/* Copyright (c) 2012 Patrick Ruoff - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - */ - -#pragma once - -#include <opencv2/core.hpp> -#include "numeric.hpp" -using namespace types; - - - -class Affine final -{ -public: - Affine(); - Affine(const mat33& R, const vec3& t); - - mat33 R; - vec3 t; -}; - -Affine operator*(const Affine& X, const Affine& Y); -Affine operator*(const mat33& X, const Affine& Y); -Affine operator*(const Affine& X, const mat33& Y); -vec3 operator*(const Affine& X, const vec3& v); diff --git a/tracker-pt/camera.cpp b/tracker-pt/camera.cpp deleted file mode 100644 index 6a724fc7..00000000 --- a/tracker-pt/camera.cpp +++ /dev/null @@ -1,161 +0,0 @@ -/* Copyright (c) 2012 Patrick Ruoff - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - */ - -#include "camera.h" -#include "compat/sleep.hpp" - -constexpr double Camera::dt_eps; - -QString Camera::get_desired_name() const -{ - return desired_name; -} - -QString Camera::get_active_name() const -{ - return active_name; -} - -void CamInfo::get_focal_length(double& fx) const -{ - using std::tan; - using std::atan; - using std::sqrt; - - const double diag_len = sqrt(double(res_x*res_x + res_y*res_y)); - const double aspect_x = res_x / diag_len; - //const double aspect_y = res_y / diag_len; - const double diag_fov = fov * M_PI/180; - const double fov_x = 2*atan(tan(diag_fov*.5) * aspect_x); - //const double fov_y = 2*atan(tan(diag_fov*.5) * aspect_y); - fx = .5 / tan(fov_x * .5); - //fy = .5 / tan(fov_y * .5); - //static bool once = false; if (!once) { once = true; qDebug() << "f" << ret << "fov" << (fov * 180/M_PI); } -} - -DEFUN_WARN_UNUSED Camera::result Camera::get_info() const -{ - if (cam_info.res_x == 0 || cam_info.res_y == 0) - return result(false, CamInfo()); - return result(true, cam_info); -} - -DEFUN_WARN_UNUSED Camera::result Camera::get_frame(cv::Mat& frame) -{ - bool new_frame = _get_frame(frame); - - if (new_frame) - { - const double dt = t.elapsed_seconds(); - t.start(); - - // measure fps of valid frames - static 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; - } - else - qDebug() << "pt camera: can't get frame"; - - return result(new_frame, cam_info); -} - -DEFUN_WARN_UNUSED 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 (!cap || !cap->isOpened() || - cam_desired.idx != idx || - cam_desired.fps != fps || - cam_desired.res_x != res_x || - cam_desired.res_y != res_y) - { - 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()) - { - qDebug() << "pt: opening camera"; - - cam_info = CamInfo(); - active_name = QString(); - cam_info.idx = -1; - dt_mean = 0; - active_name = desired_name; - t.start(); - - return open_ok_change; - } - else - { - stop(); - return open_error; - } - } - } - else - { - stop(); - return open_error; - } - - return open_ok_no_change; -} - -void Camera::stop() -{ - cap = nullptr; - desired_name = QString(); - active_name = QString(); - cam_info = CamInfo(); - cam_desired = CamInfo(); -} - -DEFUN_WARN_UNUSED 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(14); - } - } - return false; -} - -void Camera::camera_deleter::operator()(cv::VideoCapture* cap) -{ - if (cap) - { - if (cap->isOpened()) - cap->release(); - delete cap; - } -} diff --git a/tracker-pt/camera.h b/tracker-pt/camera.h deleted file mode 100644 index 449a2d3f..00000000 --- a/tracker-pt/camera.h +++ /dev/null @@ -1,86 +0,0 @@ -/* Copyright (c) 2012 Patrick Ruoff - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - */ - -#pragma once - -#include "compat/ndebug-guard.hpp" - -#undef NDEBUG -#include <cassert> - -#include "compat/util.hpp" -#include "compat/timer.hpp" - -#include <opencv2/core/core.hpp> -#include <opencv2/videoio.hpp> - -#include <memory> -#include <tuple> -#include <QString> - -struct CamInfo final -{ - CamInfo() : fov(0), fps(0), res_x(0), res_y(0), idx(-1) {} - void get_focal_length(double& fx) const; - - double fov; - double fps; - - int res_x; - int res_y; - int idx; -}; - -struct Camera final -{ - enum open_status : unsigned { open_error, open_ok_no_change, open_ok_change }; - - using result = std::tuple<bool, CamInfo>; - - Camera() : dt_mean(0), fov(0) {} - - DEFUN_WARN_UNUSED open_status start(int idx, int fps, int res_x, int res_y); - void stop(); - - DEFUN_WARN_UNUSED result get_frame(cv::Mat& frame); - DEFUN_WARN_UNUSED result get_info() const; - - CamInfo get_desired() const { return cam_desired; } - QString get_desired_name() const; - QString get_active_name() const; - - cv::VideoCapture& operator*() { assert(cap); return *cap; } - const cv::VideoCapture& operator*() const { assert(cap); return *cap; } - cv::VideoCapture* operator->() { assert(cap); return cap.get(); } - const cv::VideoCapture* operator->() const { return cap.get(); } - operator bool() const { return cap && cap->isOpened(); } - - void set_fov(double value) { fov = value; } - -private: - DEFUN_WARN_UNUSED bool _get_frame(cv::Mat& frame); - - double dt_mean; - double fov; - - Timer t; - - CamInfo cam_info; - CamInfo 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; - - static constexpr double dt_eps = 1./384; -}; diff --git a/tracker-pt/ftnoir_tracker_pt.cpp b/tracker-pt/ftnoir_tracker_pt.cpp index e35acc0e..0f8495d9 100644 --- a/tracker-pt/ftnoir_tracker_pt.cpp +++ b/tracker-pt/ftnoir_tracker_pt.cpp @@ -6,244 +6,221 @@ * copyright notice and this permission notice appear in all copies. */ +#undef NDEBUG #include "ftnoir_tracker_pt.h" -#include "compat/camera-names.hpp" +#include "pt-api.hpp" +#include "cv/init.hpp" +#include "video/video-widget.hpp" +#include "compat/math-imports.hpp" +#include "compat/check-visible.hpp" +#include "compat/thread-name.hpp" +#include "compat/qt-dpi.hpp" + +#include <cassert> #include <QHBoxLayout> -#include <cmath> #include <QDebug> #include <QFile> #include <QCoreApplication> -#include <functional> -//#define PT_PERF_LOG //log performance +using namespace options; -//----------------------------------------------------------------------------- -Tracker_PT::Tracker_PT() : - point_count(0), - commands(0), - ever_success(false) +namespace pt_impl { + +Tracker_PT::Tracker_PT(pointer<pt_runtime_traits> const& traits) : + traits { traits }, + s { traits->get_module_name() }, + point_extractor { traits->make_point_extractor() }, + frame { traits->make_frame() } { - connect(s.b.get(), SIGNAL(saving()), this, SLOT(maybe_reopen_camera()), Qt::DirectConnection); - connect(&s.fov, SIGNAL(valueChanged(int)), this, SLOT(set_fov(int)), Qt::DirectConnection); - set_fov(s.fov); + opencv_init(); + + connect(&*s.b, &bundle_::saving, this, [this]{ reopen_camera_flag = true; }, Qt::DirectConnection); } Tracker_PT::~Tracker_PT() { - set_command(ABORT); + requestInterruption(); wait(); - QMutexLocker l(&camera_mtx); - camera.stop(); + if (camera) + camera->stop(); } -void Tracker_PT::set_command(Command command) +bool Tracker_PT::check_camera() { - //QMutexLocker lock(&mutex); - commands |= command; -} + if (reopen_camera_flag) + { + reopen_camera_flag = false; -void Tracker_PT::reset_command(Command command) -{ - //QMutexLocker lock(&mutex); - commands &= ~command; + camera = nullptr; + camera = traits->make_camera(); + if (!camera || !camera->start(s)) + return false; + } + assert(camera); + if (progn(bool x = true; return open_camera_dialog_flag.compare_exchange_strong(x, false);)) + run_in_thread_sync(qApp->thread(), [this] { camera->show_camera_settings(); }); + return true; } void Tracker_PT::run() { - cv::setNumThreads(0); + portable::set_curthread_name("tracker/pt"); -#ifdef PT_PERF_LOG - QFile log_file(QCoreApplication::applicationDirPath() + "/PointTrackerPerformance.txt"); - if (!log_file.open(QIODevice::WriteOnly | QIODevice::Text)) return; - QTextStream log_stream(&log_file); -#endif - - maybe_reopen_camera(); - - while((commands & ABORT) == 0) + while(!isInterruptionRequested()) { - CamInfo cam_info; - bool new_frame; + if (!check_camera()) + break; + + pt_camera_info info; + bool new_frame = false; { - QMutexLocker l(&camera_mtx); - std::tie(new_frame, cam_info) = camera.get_frame(frame); + camera->set_fov(s.fov); + std::tie(new_frame, info) = camera->get_frame(*frame); } if (new_frame) { - cv::resize(frame, preview_frame, cv::Size(preview_size.width(), preview_size.height()), 0, 0, cv::INTER_NEAREST); + const bool preview_visible = check_is_visible(); - point_extractor.extract_points(frame, preview_frame, points); - point_count = points.size(); + if (preview_visible && !widget->fresh()) + preview_frame->set_last_frame(*frame); - f fx; - cam_info.get_focal_length(fx); + point_extractor->extract_points(*frame, *preview_frame, preview_visible && !widget->fresh(), points); + point_count.store(points.size(), std::memory_order_relaxed); const bool success = points.size() >= PointModel::N_POINTS; - if (success) - { - point_tracker.track(points, - PointModel(s), - cam_info, - s.dynamic_pose ? s.init_phase_timeout : 0); - ever_success = true; - } + Affine X_CM; { - Affine X_CM; + QMutexLocker l(¢er_lock); + + if (success) { - QMutexLocker l(&data_mtx); - X_CM = point_tracker.pose(); + int dynamic_pose_ms = s.dynamic_pose ? s.init_phase_timeout : 0; + + point_tracker.track(points, PointModel(s), info, dynamic_pose_ms, filter, camera->deadzone_amount()); + ever_success.store(true, std::memory_order_relaxed); } - Affine X_MH(mat33::eye(), vec3(s.t_MH_x, s.t_MH_y, s.t_MH_z)); // just copy pasted these lines from below + QMutexLocker l2(&data_lock); + X_CM = point_tracker.pose(); + } + + if (preview_visible && !widget->fresh()) + { + const f fx = pt_camera_info::get_focal_length(info.fov, info.res_x, info.res_y); + Affine X_MH(mat33::eye(), vec3(s.t_MH_x, s.t_MH_y, s.t_MH_z)); Affine X_GH = X_CM * X_MH; vec3 p = X_GH.t; // head (center?) position in global space - vec2 p_(p[0] / p[2] * fx, p[1] / p[2] * fx); // projected to screen - - static constexpr int len = 9; - - cv::Point p2(iround(p_[0] * preview_frame.cols + preview_frame.cols/2), - iround(-p_[1] * preview_frame.cols + preview_frame.rows/2)); - static const cv::Scalar color(0, 0, 255); - cv::line(preview_frame, - cv::Point(p2.x - len, p2.y), - cv::Point(p2.x + len, p2.y), - color, - 1); - cv::line(preview_frame, - cv::Point(p2.x, p2.y - len), - cv::Point(p2.x, p2.y + len), - color, - 1); - } - video_widget->update_image(preview_frame); + if (p[2] > f(.1)) + preview_frame->draw_head_center((p[0] * fx) / p[2], (p[1] * fx) / p[2]); + + widget->update_image(preview_frame->get_bitmap()); + } } } - qDebug() << "pt: thread stopped"; } -void Tracker_PT::maybe_reopen_camera() +module_status Tracker_PT::start_tracker(QFrame* video_frame) { - QMutexLocker l(&camera_mtx); - - Camera::open_status status = camera.start(camera_name_to_index(s.camera_name), s.cam_fps, s.cam_res_x, s.cam_res_y); - - switch (status) { - case Camera::open_error: - qDebug() << "can't start camera" << s.camera_name; - break; - case Camera::open_ok_change: - frame = cv::Mat(); - break; - case Camera::open_ok_no_change: - break; + auto camera = traits->make_camera(); + if (!camera || !camera->start(s)) + return error(tr("Failed to open camera '%1'").arg(s.camera_name)); } -} -void Tracker_PT::set_fov(int value) -{ - QMutexLocker l(&camera_mtx); - camera.set_fov(value); -} - -void Tracker_PT::start_tracker(QFrame* video_frame) -{ - //video_frame->setAttribute(Qt::WA_NativeWindow); - preview_size = video_frame->size(); - - preview_frame = cv::Mat(video_frame->height(), video_frame->width(), CV_8UC3); - preview_frame.setTo(cv::Scalar(0, 0, 0)); - - video_widget = qptr<cv_video_widget>(video_frame); - layout = qptr<QHBoxLayout>(video_frame); + widget = std::make_unique<video_widget>(video_frame); + layout = std::make_unique<QHBoxLayout>(video_frame); layout->setContentsMargins(0, 0, 0, 0); - layout->addWidget(video_widget.data()); - video_frame->setLayout(layout.data()); + layout->addWidget(&*widget); + video_frame->setLayout(&*layout); //video_widget->resize(video_frame->width(), video_frame->height()); video_frame->show(); - start(); + + double dpi = screen_dpi(video_frame); + preview_frame = traits->make_preview(iround(preview_width * dpi), + iround(preview_height * dpi)); + + start(QThread::HighPriority); + + return {}; } void Tracker_PT::data(double *data) { - if (ever_success) + if (ever_success.load(std::memory_order_relaxed)) { - Affine X_CM = pose(); + Affine X_CM; + { + QMutexLocker l(&data_lock); + X_CM = point_tracker.pose(); + } Affine X_MH(mat33::eye(), vec3(s.t_MH_x, s.t_MH_y, s.t_MH_z)); - Affine X_GH = X_CM * X_MH; + Affine X_GH(X_CM * X_MH); // translate rotation matrix from opengl (G) to roll-pitch-yaw (E) frame // -z -> x, y -> z, x -> -y mat33 R_EG(0, 0,-1, -1, 0, 0, 0, 1, 0); - mat33 R = R_EG * X_GH.R * R_EG.t(); - - using std::atan2; - using std::sqrt; - using std::atan; - using std::fabs; - using std::copysign; + mat33 R(R_EG * X_GH.R * R_EG.t()); // get translation(s) const vec3& t = X_GH.t; // extract rotation angles - { - f alpha, beta, gamma; - beta = atan2( -R(2,0), sqrt(R(2,1)*R(2,1) + R(2,2)*R(2,2)) ); - alpha = atan2( R(1,0), R(0,0)); - gamma = atan2( R(2,1), R(2,2)); + auto r00 = (double)R(0, 0); + auto r10 = (double)R(1,0), r20 = (double)R(2,0); + auto r21 = (double)R(2,1), r22 = (double)R(2,2); -#if 0 - if (t[2] > 1e-4) - { - alpha += copysign(atan(t[0] / t[2]), t[0]); - // pitch is skewed anyway due to only one focal length value - //beta -= copysign(atan(t[1] / t[2]), t[1]); - } -#endif + double beta = atan2(-r20, sqrt(r21*r21 + r22*r22)); + double alpha = atan2(r10, r00); + double gamma = atan2(r21, r22); - data[Yaw] = rad2deg * alpha; - data[Pitch] = -rad2deg * beta; - data[Roll] = rad2deg * gamma; - } + constexpr double rad2deg = 180/M_PI; + + data[Yaw] = rad2deg * alpha; + data[Pitch] = -rad2deg * beta; + data[Roll] = rad2deg * gamma; // convert to cm - data[TX] = t[0] / 10; - data[TY] = t[1] / 10; - data[TZ] = t[2] / 10; + data[TX] = (double)t[0] / 10; + data[TY] = (double)t[1] / 10; + data[TZ] = (double)t[2] / 10; } } -Affine Tracker_PT::pose() +bool Tracker_PT::center() { - QMutexLocker l(&data_mtx); + QMutexLocker l(¢er_lock); - return point_tracker.pose(); + point_tracker.reset_state(); + return false; } int Tracker_PT::get_n_points() { - return int(point_count); + return (int)point_count.load(std::memory_order_relaxed); } -bool Tracker_PT::get_cam_info(CamInfo* info) +bool Tracker_PT::get_cam_info(pt_camera_info& info) { - QMutexLocker lock(&camera_mtx); - bool ret; + bool ret = false; - std::tie(ret, *info) = camera.get_info(); + if (camera) + std::tie(ret, info) = camera->get_info(); return ret; } -#include "ftnoir_tracker_pt_dialog.h" -OPENTRACK_DECLARE_TRACKER(Tracker_PT, TrackerDialog_PT, PT_metadata) +Affine Tracker_PT::pose() const +{ + QMutexLocker l(&data_lock); + return point_tracker.pose(); +} +} // ns pt_impl diff --git a/tracker-pt/ftnoir_tracker_pt.h b/tracker-pt/ftnoir_tracker_pt.h index b7b0019a..a793f94b 100644 --- a/tracker-pt/ftnoir_tracker_pt.h +++ b/tracker-pt/ftnoir_tracker_pt.h @@ -9,90 +9,71 @@ #pragma once #include "api/plugin-api.hpp" -#include "ftnoir_tracker_pt_settings.h" - -#include "numeric.hpp" - -#include "camera.h" -#include "point_extractor.h" +#include "pt-api.hpp" #include "point_tracker.h" -#include "cv/video-widget.hpp" -#include "compat/util.hpp" +#include "cv/numeric.hpp" +#include "video/video-widget.hpp" +#include "point-filter.hpp" -#include <QCoreApplication> -#include <QThread> -#include <QMutex> -#include <QMutexLocker> -#include <QTime> -#include <QLayout> -#include <QSize> #include <atomic> #include <memory> #include <vector> -class TrackerDialog_PT; +#include <QThread> +#include <QMutex> +#include <QLayout> -namespace impl { +namespace pt_impl { -using namespace types; +class TrackerDialog_PT; + +using namespace numeric_types; -class Tracker_PT : public QThread, public ITracker +struct Tracker_PT : QThread, ITracker { - Q_OBJECT - friend class camera_dialog; - friend class ::TrackerDialog_PT; -public: - Tracker_PT(); + friend class TrackerDialog_PT; + + template<typename t> using pointer = pt_pointer<t>; + + explicit Tracker_PT(pointer<pt_runtime_traits> const& pt_runtime_traits); ~Tracker_PT() override; - void start_tracker(QFrame* parent_window) override; + module_status start_tracker(QFrame* parent_window) override; void data(double* data) override; + bool center() override; - Affine pose(); int get_n_points(); - bool get_cam_info(CamInfo* info); -public slots: - void maybe_reopen_camera(); - void set_fov(int value); -protected: - void run() override; + [[nodiscard]] bool get_cam_info(pt_camera_info& info); + Affine pose() const; + private: - // thread commands - enum Command : unsigned char - { - ABORT = 1<<0 - }; - void set_command(Command command); - void reset_command(Command command); - - QMutex camera_mtx; - QMutex data_mtx; - Camera camera; - PointExtractor point_extractor; - PointTracker point_tracker; - - qshared<cv_video_widget> video_widget; - qshared<QLayout> layout; - - settings_pt s; - cv::Mat frame, preview_frame; - std::vector<vec2> points; + void run() override; + [[nodiscard]] bool check_camera(); - QSize preview_size; + pointer<pt_runtime_traits> traits; - volatile unsigned point_count; - volatile unsigned char commands; - volatile bool ever_success; + PointTracker point_tracker; - static constexpr f rad2deg = f(180/M_PI); - //static constexpr float deg2rad = float(M_PI/180); -}; + pt_settings s; -} // ns impl + std::unique_ptr<QLayout> layout; + std::vector<vec2> points; -class PT_metadata : public Metadata -{ - QString name() { return QString(QCoreApplication::translate("PT_metadata", "PointTracker 1.1")); } - QIcon icon() { return QIcon(":/Resources/Logo_IR.png"); } + int preview_width = 320, preview_height = 240; + + pointer<pt_point_extractor> point_extractor; + pointer<pt_camera> camera; + pointer<video_widget> widget; + pointer<pt_frame> frame; + pointer<pt_preview> preview_frame; + + std::atomic<unsigned> point_count { 0 }; + std::atomic<bool> ever_success = false; + std::atomic<bool> reopen_camera_flag = true; + std::atomic<bool> open_camera_dialog_flag = false; + mutable QMutex center_lock, data_lock; + point_filter filter{s}; }; -using impl::Tracker_PT; +} // ns pt_impl + +using Tracker_PT = pt_impl::Tracker_PT; diff --git a/tracker-pt/ftnoir_tracker_pt_dialog.cpp b/tracker-pt/ftnoir_tracker_pt_dialog.cpp index 98e8e424..d67f79a7 100644 --- a/tracker-pt/ftnoir_tracker_pt_dialog.cpp +++ b/tracker-pt/ftnoir_tracker_pt_dialog.cpp @@ -7,28 +7,39 @@ */ #include "ftnoir_tracker_pt_dialog.h" -#include "cv/video-property-page.hpp" +#include "compat/math.hpp" +#include "video/camera.hpp" -#include "compat/camera-names.hpp" -#include <opencv2/core.hpp> #include <QString> +#include <QtGlobal> #include <QDebug> -TrackerDialog_PT::TrackerDialog_PT() - : tracker(nullptr), - timer(this), - trans_calib(1, 2, 0) +using namespace options; + +static void init_resources() { Q_INIT_RESOURCE(tracker_pt_base); } + +namespace pt_impl { + +TrackerDialog_PT::TrackerDialog_PT(const QString& module_name) : + s(module_name), + tracker(nullptr), + timer(this), + trans_calib(1, 2) { + init_resources(); + ui.setupUi(this); - ui.camdevice_combo->addItems(get_camera_names()); + for (const QString& str : video::camera_names()) + ui.camdevice_combo->addItem(str); tie_setting(s.camera_name, ui.camdevice_combo); tie_setting(s.cam_res_x, ui.res_x_spin); tie_setting(s.cam_res_y, ui.res_y_spin); tie_setting(s.cam_fps, ui.fps_spin); + tie_setting(s.use_mjpeg, ui.use_mjpeg); - tie_setting(s.threshold, ui.threshold_slider); + tie_setting(s.threshold_slider, ui.threshold_slider); tie_setting(s.min_point_size, ui.mindiam_spin); tie_setting(s.max_point_size, ui.maxdiam_spin); @@ -63,7 +74,7 @@ TrackerDialog_PT::TrackerDialog_PT() tie_setting(s.auto_threshold, ui.auto_threshold); - connect( ui.tcalib_button,SIGNAL(toggled(bool)), this,SLOT(startstop_trans_calib(bool))); + connect(ui.tcalib_button,SIGNAL(toggled(bool)), this, SLOT(startstop_trans_calib(bool))); connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(doOK())); connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(doCancel())); @@ -76,15 +87,94 @@ TrackerDialog_PT::TrackerDialog_PT() timer.setInterval(250); connect(&calib_timer, &QTimer::timeout, this, &TrackerDialog_PT::trans_calib_step); - calib_timer.setInterval(100); + calib_timer.setInterval(35); poll_tracker_info_impl(); - connect(this, &TrackerDialog_PT::poll_tracker_info, this, &TrackerDialog_PT::poll_tracker_info_impl, Qt::QueuedConnection); + constexpr pt_color_type color_types[] = { + pt_color_bt709, + pt_color_hardware, + pt_color_red_only, + pt_color_green_only, + pt_color_blue_only, + pt_color_red_chromakey, + pt_color_green_chromakey, + pt_color_blue_chromakey, + pt_color_cyan_chromakey, + pt_color_yellow_chromakey, + pt_color_magenta_chromakey, + }; + + for (unsigned k = 0; k < std::size(color_types); k++) + ui.blob_color->setItemData(k, int(color_types[k])); + + tie_setting(s.blob_color, ui.blob_color); + + tie_setting(s.chroma_key_strength, ui.chroma_key_strength_slider); + connect(&s.chroma_key_strength, value_::value_changed<slider_value>(), ui.chroma_key_strength_label, + [this] { ui.chroma_key_strength_label->setValue(*s.chroma_key_strength); }); + ui.chroma_key_strength_label->setValue(*s.chroma_key_strength); + + tie_setting(s.chroma_key_overexposed, ui.chroma_key_overexposed); + connect(ui.blob_color, &QComboBox::currentTextChanged, this, &TrackerDialog_PT::chroma_key_controls_enable); + + chroma_key_controls_enable(""); + + tie_setting(s.threshold_slider, ui.threshold_value_display, [this](const slider_value& val) { + return threshold_display_text(int(val)); + }); + + // refresh threshold display on auto-threshold checkbox state change + tie_setting(s.auto_threshold, this, [this](bool) { s.threshold_slider.notify_(); }); + + tie_setting(s.enable_point_filter, ui.enable_point_filter); + tie_setting(s.point_filter_coefficient, ui.point_filter_slider); + tie_setting(s.point_filter_limit, ui.point_filter_limit_slider); + connect(&s.point_filter_coefficient, value_::value_changed<slider_value>(), + ui.point_filter_label, [this] { ui.point_filter_label->setValue(*s.point_filter_coefficient); } ); + connect(&s.point_filter_limit, value_::value_changed<slider_value>(), ui.point_filter_limit_label, + [this] { ui.point_filter_limit_label->setValue(*s.point_filter_limit); }, Qt::QueuedConnection); + ui.point_filter_label->setValue(*s.point_filter_coefficient); + ui.point_filter_limit_label->setValue(*s.point_filter_limit); + + tie_setting(s.point_filter_deadzone, ui.point_filter_deadzone_slider); + ui.point_filter_deadzone_label->setValue(*s.point_filter_deadzone); + + connect(&s.point_filter_deadzone, value_::value_changed<slider_value>(), ui.point_filter_deadzone_label, + [this] { ui.point_filter_deadzone_label->setValue(*s.point_filter_deadzone); }, Qt::QueuedConnection); +} + +QString TrackerDialog_PT::threshold_display_text(int threshold_value) +{ + if (!s.auto_threshold) + return tr("Brightness %1/255").arg(threshold_value); + else + { + pt_camera_info info; + int w = s.cam_res_x, h = s.cam_res_y; + + if (w * h <= 0) + { + w = 640; + h = 480; + } + + if (tracker && tracker->get_cam_info(info) && info.res_x * info.res_y != 0) + { + w = info.res_x; + h = info.res_y; + } + + double value = (double)pt_point_extractor::threshold_radius_value(w, h, threshold_value); + + return tr("LED radius %1 pixels").arg(value, 0, 'f', 2); + } } void TrackerDialog_PT::startstop_trans_calib(bool start) { + QMutexLocker l(&calibrator_mutex); + if (start) { qDebug() << "pt: starting translation calibration"; @@ -101,17 +191,28 @@ void TrackerDialog_PT::startstop_trans_calib(bool start) calib_timer.stop(); qDebug() << "pt: stopping translation calibration"; { - cv::Vec3f tmp; - unsigned nsamples; - std::tie(tmp, nsamples) = trans_calib.get_estimate(); + auto [tmp, nsamples] = trans_calib.get_estimate(); s.t_MH_x = int(tmp[0]); s.t_MH_y = int(tmp[1]); s.t_MH_z = int(tmp[2]); - static constexpr unsigned min_samples = 80; - const QString sample_feedback = nsamples >= min_samples - ? tr("%1 samples. Over %2, good!").arg(nsamples).arg(min_samples) - : tr("%1 samples. Try for at least %2 for a precise calibration.").arg(nsamples).arg(min_samples); + constexpr int min_yaw_samples = 15; + constexpr int min_pitch_samples = 15; + constexpr int min_samples = min_yaw_samples+min_pitch_samples; + + // Don't bother counting roll samples. Roll calibration is hard enough + // that it's a hidden unsupported feature anyway. + + QString sample_feedback; + if (nsamples[0] < min_yaw_samples) + sample_feedback = tr("%1 yaw samples. Yaw more to %2 samples for stable calibration.").arg(nsamples[0]).arg(min_yaw_samples); + else if (nsamples[1] < min_pitch_samples) + sample_feedback = tr("%1 pitch samples. Pitch more to %2 samples for stable calibration.").arg(nsamples[1]).arg(min_pitch_samples); + else + { + const int nsamples_total = nsamples[0] + nsamples[1]; + sample_feedback = tr("%1 samples. Over %2, good!").arg(nsamples_total).arg(min_samples); + } ui.sample_count_display->setText(sample_feedback); } @@ -119,18 +220,17 @@ void TrackerDialog_PT::startstop_trans_calib(bool start) ui.tx_spin->setEnabled(!start); ui.ty_spin->setEnabled(!start); ui.tz_spin->setEnabled(!start); - ui.tcalib_button->setText(progn( - if (start) - return tr("Stop calibration"); - else - return tr("Start calibration"); - )); + + if (start) + ui.tcalib_button->setText(tr("Stop calibration")); + else + ui.tcalib_button->setText(tr("Start calibration")); } void TrackerDialog_PT::poll_tracker_info_impl() { - CamInfo info; - if (tracker && tracker->get_cam_info(&info)) + pt_camera_info info; + if (tracker && tracker->get_cam_info(info)) { ui.caminfo_label->setText(tr("%1x%2 @ %3 FPS").arg(info.res_x).arg(info.res_y).arg(iround(info.fps))); @@ -145,35 +245,37 @@ void TrackerDialog_PT::poll_tracker_info_impl() } } -void TrackerDialog_PT::set_camera_settings_available(const QString& camera_name) +void TrackerDialog_PT::set_camera_settings_available(const QString& /* camera_name */) { - const bool avail = video_property_page::should_show_dialog(camera_name); - ui.camera_settings->setEnabled(avail); + ui.camera_settings->setEnabled(true); } void TrackerDialog_PT::show_camera_settings() { - const int idx = ui.camdevice_combo->currentIndex(); - if (tracker) - { - if (tracker->camera) - { - cv::VideoCapture& cap = *tracker->camera; + tracker->open_camera_dialog_flag = true; + else + (void)video::show_dialog(s.camera_name); +} - CamInfo info; - bool status; - std::tie(status, info) = tracker->camera.get_info(); - if (status) - video_property_page::show_from_capture(cap, info.idx); - } +void TrackerDialog_PT::chroma_key_controls_enable(const QString&) +{ + bool enabled = false; + QVariant data = ui.blob_color->currentData(); + if (data.isValid()) + { + pt_color_type blob_color = pt_color_type(data.toInt()); + enabled = blob_color >= pt_color_red_chromakey && blob_color <= pt_color_magenta_chromakey; } - else - video_property_page::show(idx); + ui.chroma_key_strength_slider->setEnabled(enabled); + ui.chroma_key_strength_label->setEnabled(enabled); + ui.chroma_key_overexposed->setEnabled(enabled); } void TrackerDialog_PT::trans_calib_step() { + QMutexLocker l(&calibrator_mutex); + if (tracker) { Affine X_CM = tracker->pose(); @@ -203,14 +305,26 @@ void TrackerDialog_PT::register_tracker(ITracker *t) { tracker = static_cast<Tracker_PT*>(t); ui.tcalib_button->setEnabled(true); - poll_tracker_info(); + poll_tracker_info_impl(); timer.start(); } void TrackerDialog_PT::unregister_tracker() { - tracker = NULL; + tracker = nullptr; ui.tcalib_button->setEnabled(false); - poll_tracker_info(); + poll_tracker_info_impl(); timer.stop(); } + +void TrackerDialog_PT::set_buttons_visible(bool x) +{ + ui.buttonBox->setVisible(x); +} + +void TrackerDialog_PT::reload() +{ + s.b->reload(); +} + +} // ns pt_impl diff --git a/tracker-pt/ftnoir_tracker_pt_dialog.h b/tracker-pt/ftnoir_tracker_pt_dialog.h index 2932252c..79cd91bd 100644 --- a/tracker-pt/ftnoir_tracker_pt_dialog.h +++ b/tracker-pt/ftnoir_tracker_pt_dialog.h @@ -5,26 +5,31 @@ * copyright notice and this permission notice appear in all copies. */ -#ifndef FTNOIR_TRACKER_PT_DIALOG_H -#define FTNOIR_TRACKER_PT_DIALOG_H +#pragma once + +#include "pt-api.hpp" -#include "api/plugin-api.hpp" -#include "ftnoir_tracker_pt_settings.h" #include "ftnoir_tracker_pt.h" -#include "ui_FTNoIR_PT_Controls.h" +#include "tracker-pt/ui_FTNoIR_PT_Controls.h" #include "cv/translation-calibrator.hpp" -#include "cv/video-widget.hpp" +#include "video/video-widget.hpp" #include <QTimer> +#include <QMutex> + +namespace pt_impl { class TrackerDialog_PT : public ITrackerDialog { Q_OBJECT public: - TrackerDialog_PT(); + TrackerDialog_PT(const QString& module_name); void register_tracker(ITracker *tracker) override; void unregister_tracker() override; - void save(); + bool embeddable() noexcept override { return true; } + void set_buttons_visible(bool x) override; + void save() override; + void reload() override; public slots: void doOK(); void doCancel(); @@ -34,15 +39,20 @@ public slots: void poll_tracker_info_impl(); void set_camera_settings_available(const QString& camera_name); void show_camera_settings(); -signals: - void poll_tracker_info(); -private: - settings_pt s; + void chroma_key_controls_enable(const QString&); + +protected: + QString threshold_display_text(int threshold_value); + + pt_settings s; Tracker_PT* tracker; QTimer timer, calib_timer; TranslationCalibrator trans_calib; + QMutex calibrator_mutex; Ui::UICPTClientControls ui; }; -#endif //FTNOIR_TRACKER_PT_DIALOG_H +} // ns pt_impl + +using TrackerDialog_PT = pt_impl::TrackerDialog_PT; diff --git a/tracker-pt/ftnoir_tracker_pt_settings.cpp b/tracker-pt/ftnoir_tracker_pt_settings.cpp deleted file mode 100644 index 8a5257d3..00000000 --- a/tracker-pt/ftnoir_tracker_pt_settings.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include "numeric.hpp" - -namespace types { - -constexpr f constants::eps; - -} diff --git a/tracker-pt/ftnoir_tracker_pt_settings.h b/tracker-pt/ftnoir_tracker_pt_settings.h deleted file mode 100644 index d890bbd2..00000000 --- a/tracker-pt/ftnoir_tracker_pt_settings.h +++ /dev/null @@ -1,68 +0,0 @@ -/* Copyright (c) 2012 Patrick Ruoff - * Copyright (c) 2014-2015 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 "options/options.hpp" -using namespace options; - -struct settings_pt : opts -{ - value<QString> camera_name; - value<int> cam_res_x, - cam_res_y, - cam_fps, - threshold; - value<double> min_point_size, max_point_size; - - value<int> m01_x, m01_y, m01_z; - value<int> m02_x, m02_y, m02_z; - - value<int> t_MH_x, t_MH_y, t_MH_z; - - value<int> clip_ty, clip_tz, clip_by, clip_bz; - value<int> active_model_panel, cap_x, cap_y, cap_z; - - value<int> fov; - - value<bool> dynamic_pose; - value<int> init_phase_timeout; - value<bool> auto_threshold; - - settings_pt() : - opts("tracker-pt"), - camera_name(b, "camera-name", ""), - cam_res_x(b, "camera-res-width", 640), - cam_res_y(b, "camera-res-height", 480), - cam_fps(b, "camera-fps", 30), - threshold(b, "threshold-primary", 128), - min_point_size(b, "min-point-size", 1), - max_point_size(b, "max-point-size", 50), - m01_x(b, "m_01-x", 0), - m01_y(b, "m_01-y", 0), - m01_z(b, "m_01-z", 0), - m02_x(b, "m_02-x", 0), - m02_y(b, "m_02-y", 0), - m02_z(b, "m_02-z", 0), - t_MH_x(b, "model-centroid-x", 0), - t_MH_y(b, "model-centroid-y", 0), - t_MH_z(b, "model-centroid-z", 0), - clip_ty(b, "clip-ty", 40), - clip_tz(b, "clip-tz", 30), - clip_by(b, "clip-by", 70), - clip_bz(b, "clip-bz", 80), - active_model_panel(b, "active-model-panel", 0), - cap_x(b, "cap-x", 40), - cap_y(b, "cap-y", 60), - cap_z(b, "cap-z", 100), - fov(b, "camera-fov", 56), - dynamic_pose(b, "dynamic-pose-resolution", true), - init_phase_timeout(b, "init-phase-timeout", 500), - auto_threshold(b, "automatic-threshold", true) - {} -}; diff --git a/tracker-pt/lang/de_DE.ts b/tracker-pt/lang/de_DE.ts new file mode 100644 index 00000000..30ab42c2 --- /dev/null +++ b/tracker-pt/lang/de_DE.ts @@ -0,0 +1,378 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="de_DE"> +<context> + <name>UICPTClientControls</name> + <message> + <source>PointTracker Settings</source> + <translation>PointTracker-Einstellungen</translation> + </message> + <message> + <source>Camera</source> + <translation>Kamera</translation> + </message> + <message> + <source>Camera settings</source> + <translation>Kamera-Einstellungen</translation> + </message> + <message> + <source>This should be 56° or 76° for the PS3 Eye, dependent upon the physical lens setting. It's only neccessary to get position correspond to real-world values.</source> + <translation>Für die PS3 Eye sollte dies 56° oder 76° sein, abhängig von der Einstellung der physikalischen Linse. Dies wird nur benutzt, um die Position aus den Welt-Koordinaten zur ermitteln.</translation> + </message> + <message> + <source>Diagonal field of view</source> + <translation>Diagonales Sichtfeld</translation> + </message> + <message> + <source>Dynamic pose (for caps only, never clips)</source> + <translation>Dynamische Pose (nur für Hüte, niemals Sticker)</translation> + </message> + <message> + <source><html><head/><body><p>For LEDs, 'Natural' is the fastest grayscale mode thanks to optimized SIMD code. Color key allows to track regular pieces of colored paper.</p></body></html></source> + <translation><html><head/><body><p>‚Natürlich‘ ist, dank des optimierten SIMD-Codes, die schnellste Methode für LEDs. Mit dem Farbschlüssel kann man normale farbige Papierstücke tracken.</p></body></html></translation> + </message> + <message> + <source>Color channels used</source> + <translation>Benutzte Farbkanäle</translation> + </message> + <message> + <source>Open</source> + <translation>Öffnen</translation> + </message> + <message> + <source>Enable MJPEG compression for high-speed cameras other than the PS3 Eye. Windows only.</source> + <translation>Aktiviert MJPEG-Kompression für Hochgeschwindigkeitskameras, PS3 Eye ausgenommen. Nur für Windows.</translation> + </message> + <message> + <source>MJPEG compression</source> + <translation>MJPEG-Kompression</translation> + </message> + <message> + <source>FPS</source> + <translation>FPS</translation> + </message> + <message> + <source>Width</source> + <translation>Breite</translation> + </message> + <message> + <source>Chroma key includes overexposed pixels</source> + <translation>Chroma-Key enthält überbelichtete Pixel</translation> + </message> + <message> + <source>Camera settings (when available)</source> + <translation>Kamera-Einstellungen (falls verfügbar)</translation> + </message> + <message> + <source> ms</source> + <translation> ms</translation> + </message> + <message> + <source>Desired capture width</source> + <translation>Angestrebte Aufnahmebreite</translation> + </message> + <message> + <source> px</source> + <translation> px</translation> + </message> + <message> + <source>Dynamic pose timeout</source> + <translation>Dynamisches Pose-Timeout</translation> + </message> + <message> + <source>Device</source> + <translation>Gerät</translation> + </message> + <message> + <source>Height</source> + <translation>Höhe</translation> + </message> + <message> + <source>Desired capture framerate</source> + <translation>Angestrebte Aufnahme-Bildrate</translation> + </message> + <message> + <source> Hz</source> + <translation> Hz</translation> + </message> + <message> + <source>Grayscale BT.709</source> + <translation>Graustufen BT.709</translation> + </message> + <message> + <source>Grayscale (from hardware)</source> + <translation>Graustufen (gemäß Hardware)</translation> + </message> + <message> + <source>Red only</source> + <translation>nur Rot</translation> + </message> + <message> + <source>Green only</source> + <translation>nur Grün</translation> + </message> + <message> + <source>Blue only</source> + <translation>nur Blau</translation> + </message> + <message> + <source>Red chroma key</source> + <translation>Roter Chroma-Key</translation> + </message> + <message> + <source>Green chroma key</source> + <translation>Grüner Chroma-Key</translation> + </message> + <message> + <source>Blue chroma key</source> + <translation>Blauer Chroma-Key</translation> + </message> + <message> + <source>Cyan chroma key</source> + <translation>Cyan-Chroma-Key</translation> + </message> + <message> + <source>Yellow chroma key</source> + <translation>Gelber Chroma-Key</translation> + </message> + <message> + <source>Magenta chroma key</source> + <translation>Magenta-Chroma-Key</translation> + </message> + <message> + <source>°</source> + <translation>°</translation> + </message> + <message> + <source>Desired capture height</source> + <translation>Angestrebte Aufnahmehöhe</translation> + </message> + <message> + <source>Chroma key strength</source> + <translation>Chroma-Key-Stärke</translation> + </message> + <message> + <source>Point extraction</source> + <translation>Punktextraktion</translation> + </message> + <message> + <source>Threshold</source> + <translation>Schwelle</translation> + </message> + <message> + <source>Set minimum size to avoid small stray lights from being treated as points.</source> + <translation>Setzt die Mindestgröße, um zu verhindern, dass kleine Streulichter als Punkte erkannt werden.</translation> + </message> + <message> + <source>Min size</source> + <translation>Mindestgröße</translation> + </message> + <message> + <source>Max size</source> + <translation>Maximale Größe</translation> + </message> + <message> + <source>Intensity threshold for point extraction</source> + <translation>Intensitätsschwelle für die Punktextraktion</translation> + </message> + <message> + <source>Enable, slider sets point size</source> + <translation>Aktivieren, Regler setzt die Punktgröße</translation> + </message> + <message> + <source>Track dependent on point size and not absolute brightness. This may allow more stable tracking.</source> + <translation>Tracking abhängig von der Punktgröße anstelle der absoluten Helligkeit durchführen. Dies kann stabileres Tracking ermöglichen.</translation> + </message> + <message> + <source>Automatic threshold</source> + <translation>Automatische Schwelle</translation> + </message> + <message> + <source>Maximum point diameter</source> + <translation>Maximaler Punktdurchmesser</translation> + </message> + <message> + <source>Value</source> + <translation>Wert</translation> + </message> + <message> + <source>Minimum point diameter</source> + <translation>Minimaler Punktdurchmesser</translation> + </message> + <message> + <source>Model</source> + <translation>Modell</translation> + </message> + <message> + <source>Clip</source> + <translation>Sticker</translation> + </message> + <message> + <source>Model Dimensions</source> + <translation>Modellabmessungen</translation> + </message> + <message> + <source> mm</source> + <translation> mm</translation> + </message> + <message> + <source>Side</source> + <translation>Seitlich</translation> + </message> + <message> + <source>Front</source> + <translation>Vorne</translation> + </message> + <message> + <source>Cap</source> + <translation>Hut</translation> + </message> + <message> + <source>Custom</source> + <translation>Benutzerdefiniert</translation> + </message> + <message> + <source>z:</source> + <translation>z:</translation> + </message> + <message> + <source>x:</source> + <translation>x:</translation> + </message> + <message> + <source><html><head/><body><p>Location of the two remaining model points<br/>with respect to the reference point in default pose</p><p>Use any units you want, not necessarily centimeters.</p></body></html></source> + <translation><html><head/><body><p>Ort der verbleibenden zwei Modellpunkte<br/>unter Berücksichtigung der Referenzpunkte der Standard-Pose</p><p>Dies können beliebige Einheiten sein, nicht zwingend Zentimeter.</p></body></html></translation> + </message> + <message> + <source>y:</source> + <translation>y:</translation> + </message> + <message> + <source><html><head/><body><p><span style=" font-size:16pt;">P</span><span style=" font-size:16pt; vertical-align:sub;">3</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-size:16pt;">P</span><span style=" font-size:16pt; vertical-align:sub;">3</span></p></body></html></translation> + </message> + <message> + <source><html><head/><body><p><span style=" font-size:16pt;">P</span><span style=" font-size:16pt; vertical-align:sub;">2</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-size:16pt;">P</span><span style=" font-size:16pt; vertical-align:sub;">2</span></p></body></html></translation> + </message> + <message> + <source>Model position</source> + <translation>Modell-Position</translation> + </message> + <message> + <source>Use only yaw and pitch while calibrating. +Don't roll or change position.</source> + <translation>Bitte nur gieren oder nicken während der Kalibrierung. +Bitte nicht rollen oder die Position ändern.</translation> + </message> + <message> + <source>Start calibration</source> + <translation>Kalibrierung starten</translation> + </message> + <message> + <source>Filter</source> + <translation>Filter</translation> + </message> + <message> + <source>Point filter</source> + <translation>Punktfilter</translation> + </message> + <message> + <source>Limit</source> + <translation>Grenze</translation> + </message> + <message> + <source>Deadzone</source> + <translation>Totbereich</translation> + </message> + <message> + <source>Filter point centers prior to pose estimation.</source> + <translation>Punktmitten vor der Posen-Abschätzung filtern.</translation> + </message> + <message> + <source>Enable</source> + <translation>Einschalten</translation> + </message> + <message> + <source>About</source> + <translation>Über</translation> + </message> + <message> + <source><html><head/><body><p><span style=" font-weight:600;">FTNoIR PointTracker Plugin<br/>Version 1.1</span></p><p><span style=" font-weight:600;">by Patrick Ruoff</span></p><p><a href="http://ftnoirpt.sourceforge.net/"><span style=" font-weight:600; text-decoration: underline; color:#0000ff;">Manual (external)</span></a></p></body></html></source> + <translation><html><head/><body><p><span style=" font-weight:600;">FTNoIR PointTracker Plugin<br/>Version 1.1</span></p><p><span style=" font-weight:600;">von Patrick Ruoff</span></p><p><a href="http://ftnoirpt.sourceforge.net/"><span style=" font-weight:600; text-decoration: underline; color:#0000ff;">Anleitung (extern)</span></a></p></body></html></translation> + </message> + <message> + <source>Status</source> + <translation>Status</translation> + </message> + <message> + <source>Extracted Points:</source> + <translation>Extrahierte Punkte:</translation> + </message> + <message> + <source>Camera Info:</source> + <translation>Kamera-Info:</translation> + </message> +</context> +<context> + <name>pt_impl::TrackerDialog_PT</name> + <message> + <source>Brightness %1/255</source> + <translation>Helligkeit %1/255</translation> + </message> + <message> + <source>LED radius %1 pixels</source> + <translation>LED-Radius %1 Pixel</translation> + </message> + <message> + <source>%1 yaw samples. Yaw more to %2 samples for stable calibration.</source> + <translation>%1 Gieren-Proben. Weiterhin gieren bis %2 Proben für eine stabile Kalibrierung.</translation> + </message> + <message> + <source>%1 pitch samples. Pitch more to %2 samples for stable calibration.</source> + <translation>%1 Nicken-Proben. Weiterhin nicken bis %2 Proben für eine stabile Kalibrierung.</translation> + </message> + <message> + <source>%1 samples. Over %2, good!</source> + <translation>%1 Proben. Mehr als %2, gut!</translation> + </message> + <message> + <source>Stop calibration</source> + <translation>Kalibrierung stoppen</translation> + </message> + <message> + <source>Start calibration</source> + <translation>Kalibrierung starten</translation> + </message> + <message> + <source>%1x%2 @ %3 FPS</source> + <translation>%1x%2 @ %3 FPS</translation> + </message> + <message> + <source>%1 OK!</source> + <translation>%1 OKAY!</translation> + </message> + <message> + <source>%1 BAD!</source> + <translation>%1 SCHLECHT!</translation> + </message> + <message> + <source>Tracker offline</source> + <translation>Tracker offline</translation> + </message> +</context> +<context> + <name>pt_impl::Tracker_PT</name> + <message> + <source>Failed to open camera '%1'</source> + <translation>Öffnen der Kamera ‚%1‘ fehlgeschlagen</translation> + </message> +</context> +<context> + <name>pt_module::metadata_pt</name> + <message> + <source>PointTracker 1.1</source> + <translation>PointTracker 1.1</translation> + </message> +</context> +</TS> diff --git a/tracker-pt/lang/nl_NL.ts b/tracker-pt/lang/nl_NL.ts index a81f94e2..fc44b0f1 100644 --- a/tracker-pt/lang/nl_NL.ts +++ b/tracker-pt/lang/nl_NL.ts @@ -2,292 +2,376 @@ <!DOCTYPE TS> <TS version="2.1" language="nl_NL"> <context> - <name>PT_metadata</name> - <message> - <location filename="../ftnoir_tracker_pt.h" line="+95"/> - <source>PointTracker 1.1</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> <name>UICPTClientControls</name> <message> - <location filename="../FTNoIR_PT_Controls.ui" line="+23"/> <source>PointTracker Settings</source> <translation type="unfinished"></translation> </message> <message> - <location line="+38"/> <source>Camera</source> <translation type="unfinished"></translation> </message> <message> - <location line="+6"/> <source>Camera settings</source> <translation type="unfinished"></translation> </message> <message> - <location line="+19"/> <source>°</source> <translation type="unfinished"></translation> </message> <message> - <location line="+22"/> <source>Diagonal field of view</source> <translation type="unfinished"></translation> </message> <message> - <location line="+13"/> <source>Width</source> <translation type="unfinished"></translation> </message> <message> - <location line="+13"/> <source>FPS</source> <translation type="unfinished"></translation> </message> <message> - <location line="+16"/> <source>Desired capture height</source> <translation type="unfinished"></translation> </message> <message> - <location line="+3"/> - <location line="+55"/> - <location line="+175"/> - <location line="+16"/> <source> px</source> <translation type="unfinished"></translation> </message> <message> - <location line="-233"/> <source>Dynamic pose timeout</source> <translation type="unfinished"></translation> </message> <message> - <location line="+13"/> <source>Desired capture framerate</source> <translation type="unfinished"></translation> </message> <message> - <location line="+3"/> <source> Hz</source> <translation type="unfinished"></translation> </message> <message> - <location line="+23"/> <source>Desired capture width</source> <translation type="unfinished"></translation> </message> <message> - <location line="+22"/> <source>Height</source> <translation type="unfinished"></translation> </message> <message> - <location line="+7"/> <source> ms</source> <translation type="unfinished"></translation> </message> <message> - <location line="+19"/> - <source>Dynamic pose resolution</source> - <translation type="unfinished"></translation> - </message> - <message> - <location line="+13"/> <source>Device</source> <translation type="unfinished"></translation> </message> <message> - <location line="+16"/> <source>Open</source> <translation type="unfinished"></translation> </message> <message> - <location line="+7"/> <source>Camera settings (when available)</source> <translation type="unfinished"></translation> </message> <message> - <location line="+10"/> <source>Point extraction</source> <translation type="unfinished"></translation> </message> <message> - <location line="+6"/> <source>Max size</source> <translation type="unfinished"></translation> </message> <message> - <location line="+10"/> <source>Threshold</source> <translation type="unfinished"></translation> </message> <message> - <location line="+10"/> <source>Min size</source> <translation type="unfinished"></translation> </message> <message> - <location line="+16"/> <source>Intensity threshold for point extraction</source> <translation type="unfinished"></translation> </message> <message> - <location line="+25"/> <source>Automatic threshold</source> <translation type="unfinished"></translation> </message> <message> - <location line="+7"/> <source>Enable, slider sets point size</source> <translation type="unfinished"></translation> </message> <message> - <location line="+7"/> + <source>Color channels used</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Red only</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Blue only</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Dynamic pose (for caps only, never clips)</source> + <translation type="unfinished"></translation> + </message> + <message> <source>Maximum point diameter</source> <translation type="unfinished"></translation> </message> <message> - <location line="+16"/> <source>Minimum point diameter</source> <translation type="unfinished"></translation> </message> <message> - <location line="+20"/> + <source>Value</source> + <translation type="unfinished"></translation> + </message> + <message> <source>Model</source> <translation type="unfinished"></translation> </message> <message> - <location line="+28"/> <source>Clip</source> <translation type="unfinished"></translation> </message> <message> - <location line="+18"/> - <location line="+154"/> - <location line="+129"/> <source>Model Dimensions</source> <translation type="unfinished"></translation> </message> <message> - <location line="-271"/> - <location line="+19"/> - <location line="+48"/> - <location line="+19"/> - <location line="+97"/> - <location line="+35"/> - <location line="+32"/> - <location line="+40"/> - <location line="+26"/> - <location line="+13"/> - <location line="+13"/> - <location line="+13"/> - <location line="+26"/> - <location line="+132"/> - <location line="+26"/> - <location line="+26"/> <source> mm</source> <translation type="unfinished"></translation> </message> <message> - <location line="-511"/> - <location line="+116"/> <source>Side</source> <translation type="unfinished"></translation> </message> <message> - <location line="-65"/> - <location line="+132"/> <source>Front</source> <translation type="unfinished"></translation> </message> <message> - <location line="-107"/> <source>Cap</source> <translation type="unfinished"></translation> </message> <message> - <location line="+135"/> <source>Custom</source> <translation type="unfinished"></translation> </message> <message> - <location line="+18"/> - <location line="+169"/> - <location line="+106"/> <source>z:</source> <translation type="unfinished"></translation> </message> <message> - <location line="-249"/> - <location line="+104"/> - <location line="+93"/> <source>x:</source> <translation type="unfinished"></translation> </message> <message> - <location line="-132"/> <source><html><head/><body><p>Location of the two remaining model points<br/>with respect to the reference point in default pose</p><p>Use any units you want, not necessarily centimeters.</p></body></html></source> <translation type="unfinished"></translation> </message> <message> - <location line="+26"/> - <location line="+65"/> - <location line="+67"/> <source>y:</source> <translation type="unfinished"></translation> </message> <message> - <location line="-106"/> <source><html><head/><body><p><span style=" font-size:16pt;">P</span><span style=" font-size:16pt; vertical-align:sub;">3</span></p></body></html></source> <translation type="unfinished"></translation> </message> <message> - <location line="+13"/> <source><html><head/><body><p><span style=" font-size:16pt;">P</span><span style=" font-size:16pt; vertical-align:sub;">2</span></p></body></html></source> <translation type="unfinished"></translation> </message> <message> - <location line="+46"/> <source>Model position</source> <translation type="unfinished"></translation> </message> <message> - <location line="+105"/> - <source><html><head/><body><p><a href="https://github.com/opentrack/opentrack/wiki/model-calibration-for-PT-and-Aruco-trackers"><span style=" text-decoration: underline; color:#0000ff;">Instructions on the opentrack wiki</span></a></p></body></html></source> + <source>Use only yaw and pitch while calibrating. +Don't roll or change position.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+13"/> <source>Start calibration</source> <translation type="unfinished"></translation> </message> <message> - <location line="+17"/> <source>About</source> <translation type="unfinished"></translation> </message> <message> - <location line="+6"/> <source><html><head/><body><p><span style=" font-weight:600;">FTNoIR PointTracker Plugin<br/>Version 1.1</span></p><p><span style=" font-weight:600;">by Patrick Ruoff</span></p><p><a href="http://ftnoirpt.sourceforge.net/"><span style=" font-weight:600; text-decoration: underline; color:#0000ff;">Manual (external)</span></a></p></body></html></source> <translation type="unfinished"></translation> </message> <message> - <location line="+36"/> <source>Status</source> <translation type="unfinished"></translation> </message> <message> - <location line="+6"/> <source>Extracted Points:</source> <translation type="unfinished"></translation> </message> <message> - <location line="+7"/> <source>Camera Info:</source> <translation type="unfinished"></translation> </message> + <message> + <source>Green only</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Red chroma key</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Green chroma key</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Blue chroma key</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Cyan chroma key</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Yellow chroma key</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Magenta chroma key</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>This should be 56° or 76° for the PS3 Eye, dependent upon the physical lens setting. It's only neccessary to get position correspond to real-world values.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Enable MJPEG compression for high-speed cameras other than the PS3 Eye. Windows only.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Set minimum size to avoid small stray lights from being treated as points.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Track dependent on point size and not absolute brightness. This may allow more stable tracking.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source><html><head/><body><p>For LEDs, 'Natural' is the fastest grayscale mode thanks to optimized SIMD code. Color key allows to track regular pieces of colored paper.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <source>MJPEG compression</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Point filter</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Filter point centers prior to pose estimation.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Enable</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Limit</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Grayscale BT.709</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Grayscale (from hardware)</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Deadzone</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Filter</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Chroma key includes overexposed pixels</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Chroma key strength</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>pt_impl::TrackerDialog_PT</name> + <message> + <source>Brightness %1/255</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>LED radius %1 pixels</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>%1 yaw samples. Yaw more to %2 samples for stable calibration.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>%1 pitch samples. Pitch more to %2 samples for stable calibration.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>%1 samples. Over %2, good!</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Stop calibration</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Start calibration</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>%1x%2 @ %3 FPS</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>%1 OK!</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>%1 BAD!</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Tracker offline</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>pt_impl::Tracker_PT</name> + <message> + <source>Failed to open camera '%1'</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>pt_module::metadata_pt</name> + <message> + <source>PointTracker 1.1</source> + <translation type="unfinished"></translation> + </message> </context> </TS> diff --git a/tracker-pt/lang/ru_RU.ts b/tracker-pt/lang/ru_RU.ts index f6094d27..7ff4657e 100644 --- a/tracker-pt/lang/ru_RU.ts +++ b/tracker-pt/lang/ru_RU.ts @@ -2,293 +2,381 @@ <!DOCTYPE TS> <TS version="2.1" language="ru_RU"> <context> - <name>PT_metadata</name> - <message> - <location filename="../ftnoir_tracker_pt.h" line="+92"/> - <source>PointTracker 1.1</source> - <translation></translation> - </message> -</context> -<context> <name>UICPTClientControls</name> <message> - <location filename="../FTNoIR_PT_Controls.ui" line="+23"/> <source>PointTracker Settings</source> <translation>Настройки PointTracker</translation> </message> <message> - <location line="+38"/> <source>Camera</source> <translation>Камера</translation> </message> <message> - <location line="+6"/> <source>Camera settings</source> <translation>Настройка камеры</translation> </message> <message> - <location line="+19"/> <source>°</source> <translation></translation> </message> <message> - <location line="+22"/> <source>Diagonal field of view</source> <translation>Угол обзора камеры</translation> </message> <message> - <location line="+13"/> <source>Width</source> <translation>Ширина</translation> </message> <message> - <location line="+13"/> <source>FPS</source> <translation>FPS (Кадров в секунду)</translation> </message> <message> - <location line="+16"/> <source>Desired capture height</source> <translation></translation> </message> <message> - <location line="+3"/> - <location line="+55"/> - <location line="+175"/> - <location line="+16"/> <source> px</source> <translation></translation> </message> <message> - <location line="-233"/> <source>Dynamic pose timeout</source> <translation>Динамическая поза (время ожидания)</translation> </message> <message> - <location line="+13"/> <source>Desired capture framerate</source> <translation>Желаемая частота кадров</translation> </message> <message> - <location line="+3"/> <source> Hz</source> <translation> Гц</translation> </message> <message> - <location line="+23"/> <source>Desired capture width</source> <translation>Желаемая ширина захвата</translation> </message> <message> - <location line="+22"/> <source>Height</source> <translation>Высота</translation> </message> <message> - <location line="+7"/> <source> ms</source> <translation> мс</translation> </message> <message> - <location line="+19"/> - <source>Dynamic pose resolution</source> - <translation>Динамическая поза (активация) </translation> + <source>Dynamic pose (for caps only, never clips)</source> + <translation>Динамическая поза (Только для модели "Кепка")</translation> </message> <message> - <location line="+13"/> <source>Device</source> <translation>Устройство</translation> </message> <message> - <location line="+16"/> <source>Open</source> <translation>Открыть</translation> </message> <message> - <location line="+7"/> <source>Camera settings (when available)</source> <translation>Параметры камеры (если доступно)</translation> </message> <message> - <location line="+10"/> <source>Point extraction</source> <translation>Извлечение точек</translation> </message> <message> - <location line="+6"/> <source>Max size</source> <translation>Макс.размер</translation> </message> <message> - <location line="+10"/> <source>Threshold</source> <translation>Порог</translation> </message> <message> - <location line="+10"/> <source>Min size</source> <translation>Мин.размер</translation> </message> <message> - <location line="+16"/> <source>Intensity threshold for point extraction</source> <translation>Порог интенсивности для извлечения точки</translation> </message> <message> - <location line="+25"/> <source>Automatic threshold</source> <translation>Автоматич. порог</translation> </message> <message> - <location line="+7"/> <source>Enable, slider sets point size</source> <translation>Полузнок устанавливает размер точек</translation> </message> <message> - <location line="+7"/> + <source>Color channels used</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Red only</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Blue only</source> + <translation type="unfinished"></translation> + </message> + <message> <source>Maximum point diameter</source> <translation></translation> </message> <message> - <location line="+16"/> <source>Minimum point diameter</source> <translation></translation> </message> <message> - <location line="+20"/> + <source>Value</source> + <translation type="unfinished"></translation> + </message> + <message> <source>Model</source> <translation>Модель</translation> </message> <message> - <location line="+28"/> <source>Clip</source> <translation>Клипса</translation> </message> <message> - <location line="+18"/> - <location line="+154"/> - <location line="+129"/> <source>Model Dimensions</source> <translation>Размеры модели</translation> </message> <message> - <location line="-271"/> - <location line="+19"/> - <location line="+48"/> - <location line="+19"/> - <location line="+97"/> - <location line="+35"/> - <location line="+32"/> - <location line="+40"/> - <location line="+26"/> - <location line="+13"/> - <location line="+13"/> - <location line="+13"/> - <location line="+26"/> - <location line="+132"/> - <location line="+26"/> - <location line="+26"/> <source> mm</source> <translation> мм</translation> </message> <message> - <location line="-511"/> - <location line="+116"/> <source>Side</source> <translation>Сбоку</translation> </message> <message> - <location line="-65"/> - <location line="+132"/> <source>Front</source> <translation>Спереди</translation> </message> <message> - <location line="-107"/> <source>Cap</source> <translation>Кепка</translation> </message> <message> - <location line="+135"/> <source>Custom</source> <translation>Свой</translation> </message> <message> - <location line="+18"/> - <location line="+169"/> - <location line="+106"/> <source>z:</source> <translation></translation> </message> <message> - <location line="-249"/> - <location line="+104"/> - <location line="+93"/> <source>x:</source> <translation></translation> </message> <message> - <location line="-132"/> <source><html><head/><body><p>Location of the two remaining model points<br/>with respect to the reference point in default pose</p><p>Use any units you want, not necessarily centimeters.</p></body></html></source> <translatorcomment>Расположение двух оставшихся точек модели относительно опорной точки в стандартной позе. Возможно исп-ть любые единицы измерения, не обязательно сантиметры.</translatorcomment> <translation><html><head/><body><p> Расположение двух оставшихся точек модели<br/>относительно опорной точки в стандартной позе. </p><p>Возможно использовать любые единицы измерения.</p></body></html</translation> </message> <message> - <location line="+26"/> - <location line="+65"/> - <location line="+67"/> <source>y:</source> <translation></translation> </message> <message> - <location line="-106"/> <source><html><head/><body><p><span style=" font-size:16pt;">P</span><span style=" font-size:16pt; vertical-align:sub;">3</span></p></body></html></source> <translation></translation> </message> <message> - <location line="+13"/> <source><html><head/><body><p><span style=" font-size:16pt;">P</span><span style=" font-size:16pt; vertical-align:sub;">2</span></p></body></html></source> <translation></translation> </message> <message> - <location line="+46"/> <source>Model position</source> <translation>Положение модели</translation> </message> <message> - <location line="+105"/> - <source><html><head/><body><p><a href="https://github.com/opentrack/opentrack/wiki/model-calibration-for-PT-and-Aruco-trackers"><span style=" text-decoration: underline; color:#0000ff;">Instructions on the opentrack wiki</span></a></p></body></html></source> - <translation><html><head/><body><p><a href="https://github.com/opentrack/opentrack/wiki/model-calibration-for-PT-and-Aruco-trackers"><span style=" text-decoration: underline; color:#0000ff;">Инструкция на opentrack-вики</span></a></p></body></html></translation> + <source>Use only yaw and pitch while calibrating. +Don't roll or change position.</source> + <translation>Во время калибровки +используйте только оси +YAW и PITCH. +Не используйте оси +ROLL или X/Y-смещения.</translation> </message> <message> - <location line="+13"/> <source>Start calibration</source> <translation>Начать калибровку</translation> </message> <message> - <location line="+17"/> <source>About</source> <translation>О программе</translation> </message> <message> - <location line="+6"/> <source><html><head/><body><p><span style=" font-weight:600;">FTNoIR PointTracker Plugin<br/>Version 1.1</span></p><p><span style=" font-weight:600;">by Patrick Ruoff</span></p><p><a href="http://ftnoirpt.sourceforge.net/"><span style=" font-weight:600; text-decoration: underline; color:#0000ff;">Manual (external)</span></a></p></body></html></source> <translation><html><head/><body><p><span style=" font-weight:600;">FTNoIR PointTracker Plugin<br/>Version 1.1</span></p><p><span style=" font-weight:600;">by Patrick Ruoff</span></p><p><a href="http://ftnoirpt.sourceforge.net/"><span style=" font-weight:600; text-decoration: underline; color:#0000ff;">Руководство (PointTracker)</span></a></p></body></html></translation> </message> <message> - <location line="+36"/> <source>Status</source> <translation>Статус</translation> </message> <message> - <location line="+6"/> <source>Extracted Points:</source> <translation>Извлечено точек:</translation> </message> <message> - <location line="+7"/> <source>Camera Info:</source> <translation>Параметры камеры:</translation> </message> + <message> + <source>Green only</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Red chroma key</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Green chroma key</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Blue chroma key</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Cyan chroma key</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Yellow chroma key</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Magenta chroma key</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>This should be 56° or 76° for the PS3 Eye, dependent upon the physical lens setting. It's only neccessary to get position correspond to real-world values.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Enable MJPEG compression for high-speed cameras other than the PS3 Eye. Windows only.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Set minimum size to avoid small stray lights from being treated as points.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Track dependent on point size and not absolute brightness. This may allow more stable tracking.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source><html><head/><body><p>For LEDs, 'Natural' is the fastest grayscale mode thanks to optimized SIMD code. Color key allows to track regular pieces of colored paper.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <source>MJPEG compression</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Point filter</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Filter point centers prior to pose estimation.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Enable</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Limit</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Grayscale BT.709</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Grayscale (from hardware)</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Deadzone</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Filter</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Chroma key includes overexposed pixels</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Chroma key strength</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>pt_impl::TrackerDialog_PT</name> + <message> + <source>Brightness %1/255</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>LED radius %1 pixels</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>%1 yaw samples. Yaw more to %2 samples for stable calibration.</source> + <translation type="unfinished">По оси YAW выполнено: %1 замер(а/ов). Для стабильного результата необходимо не меньше %2</translation> + </message> + <message> + <source>%1 pitch samples. Pitch more to %2 samples for stable calibration.</source> + <translation type="unfinished">По оси Pitch выполнено: %1 замер(а/ов). Для стабильного результата необходимо не меньше %2</translation> + </message> + <message> + <source>%1 samples. Over %2, good!</source> + <translation type="unfinished">Получено %1 образца(-ов). Больше %2, отлично!!</translation> + </message> + <message> + <source>Stop calibration</source> + <translation type="unfinished">Остановить калибровку</translation> + </message> + <message> + <source>Start calibration</source> + <translation type="unfinished">Начать калибровку</translation> + </message> + <message> + <source>%1x%2 @ %3 FPS</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>%1 OK!</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>%1 BAD!</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Tracker offline</source> + <translation type="unfinished">Отслеживание отключено</translation> + </message> +</context> +<context> + <name>pt_impl::Tracker_PT</name> + <message> + <source>Failed to open camera '%1'</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>pt_module::metadata_pt</name> + <message> + <source>PointTracker 1.1</source> + <translation type="unfinished"></translation> + </message> </context> </TS> diff --git a/tracker-pt/lang/stub.ts b/tracker-pt/lang/stub.ts index c81750dc..3dbe208d 100644 --- a/tracker-pt/lang/stub.ts +++ b/tracker-pt/lang/stub.ts @@ -2,292 +2,376 @@ <!DOCTYPE TS> <TS version="2.1"> <context> - <name>PT_metadata</name> - <message> - <location filename="../ftnoir_tracker_pt.h" line="+95"/> - <source>PointTracker 1.1</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> <name>UICPTClientControls</name> <message> - <location filename="../FTNoIR_PT_Controls.ui" line="+23"/> <source>PointTracker Settings</source> <translation type="unfinished"></translation> </message> <message> - <location line="+38"/> <source>Camera</source> <translation type="unfinished"></translation> </message> <message> - <location line="+6"/> <source>Camera settings</source> <translation type="unfinished"></translation> </message> <message> - <location line="+19"/> <source>°</source> <translation type="unfinished"></translation> </message> <message> - <location line="+22"/> <source>Diagonal field of view</source> <translation type="unfinished"></translation> </message> <message> - <location line="+13"/> <source>Width</source> <translation type="unfinished"></translation> </message> <message> - <location line="+13"/> <source>FPS</source> <translation type="unfinished"></translation> </message> <message> - <location line="+16"/> <source>Desired capture height</source> <translation type="unfinished"></translation> </message> <message> - <location line="+3"/> - <location line="+55"/> - <location line="+175"/> - <location line="+16"/> <source> px</source> <translation type="unfinished"></translation> </message> <message> - <location line="-233"/> <source>Dynamic pose timeout</source> <translation type="unfinished"></translation> </message> <message> - <location line="+13"/> <source>Desired capture framerate</source> <translation type="unfinished"></translation> </message> <message> - <location line="+3"/> <source> Hz</source> <translation type="unfinished"></translation> </message> <message> - <location line="+23"/> <source>Desired capture width</source> <translation type="unfinished"></translation> </message> <message> - <location line="+22"/> <source>Height</source> <translation type="unfinished"></translation> </message> <message> - <location line="+7"/> <source> ms</source> <translation type="unfinished"></translation> </message> <message> - <location line="+19"/> - <source>Dynamic pose resolution</source> - <translation type="unfinished"></translation> - </message> - <message> - <location line="+13"/> <source>Device</source> <translation type="unfinished"></translation> </message> <message> - <location line="+16"/> <source>Open</source> <translation type="unfinished"></translation> </message> <message> - <location line="+7"/> <source>Camera settings (when available)</source> <translation type="unfinished"></translation> </message> <message> - <location line="+10"/> <source>Point extraction</source> <translation type="unfinished"></translation> </message> <message> - <location line="+6"/> <source>Max size</source> <translation type="unfinished"></translation> </message> <message> - <location line="+10"/> <source>Threshold</source> <translation type="unfinished"></translation> </message> <message> - <location line="+10"/> <source>Min size</source> <translation type="unfinished"></translation> </message> <message> - <location line="+16"/> <source>Intensity threshold for point extraction</source> <translation type="unfinished"></translation> </message> <message> - <location line="+25"/> <source>Automatic threshold</source> <translation type="unfinished"></translation> </message> <message> - <location line="+7"/> <source>Enable, slider sets point size</source> <translation type="unfinished"></translation> </message> <message> - <location line="+7"/> + <source>Color channels used</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Red only</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Blue only</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Dynamic pose (for caps only, never clips)</source> + <translation type="unfinished"></translation> + </message> + <message> <source>Maximum point diameter</source> <translation type="unfinished"></translation> </message> <message> - <location line="+16"/> <source>Minimum point diameter</source> <translation type="unfinished"></translation> </message> <message> - <location line="+20"/> + <source>Value</source> + <translation type="unfinished"></translation> + </message> + <message> <source>Model</source> <translation type="unfinished"></translation> </message> <message> - <location line="+28"/> <source>Clip</source> <translation type="unfinished"></translation> </message> <message> - <location line="+18"/> - <location line="+154"/> - <location line="+129"/> <source>Model Dimensions</source> <translation type="unfinished"></translation> </message> <message> - <location line="-271"/> - <location line="+19"/> - <location line="+48"/> - <location line="+19"/> - <location line="+97"/> - <location line="+35"/> - <location line="+32"/> - <location line="+40"/> - <location line="+26"/> - <location line="+13"/> - <location line="+13"/> - <location line="+13"/> - <location line="+26"/> - <location line="+132"/> - <location line="+26"/> - <location line="+26"/> <source> mm</source> <translation type="unfinished"></translation> </message> <message> - <location line="-511"/> - <location line="+116"/> <source>Side</source> <translation type="unfinished"></translation> </message> <message> - <location line="-65"/> - <location line="+132"/> <source>Front</source> <translation type="unfinished"></translation> </message> <message> - <location line="-107"/> <source>Cap</source> <translation type="unfinished"></translation> </message> <message> - <location line="+135"/> <source>Custom</source> <translation type="unfinished"></translation> </message> <message> - <location line="+18"/> - <location line="+169"/> - <location line="+106"/> <source>z:</source> <translation type="unfinished"></translation> </message> <message> - <location line="-249"/> - <location line="+104"/> - <location line="+93"/> <source>x:</source> <translation type="unfinished"></translation> </message> <message> - <location line="-132"/> <source><html><head/><body><p>Location of the two remaining model points<br/>with respect to the reference point in default pose</p><p>Use any units you want, not necessarily centimeters.</p></body></html></source> <translation type="unfinished"></translation> </message> <message> - <location line="+26"/> - <location line="+65"/> - <location line="+67"/> <source>y:</source> <translation type="unfinished"></translation> </message> <message> - <location line="-106"/> <source><html><head/><body><p><span style=" font-size:16pt;">P</span><span style=" font-size:16pt; vertical-align:sub;">3</span></p></body></html></source> <translation type="unfinished"></translation> </message> <message> - <location line="+13"/> <source><html><head/><body><p><span style=" font-size:16pt;">P</span><span style=" font-size:16pt; vertical-align:sub;">2</span></p></body></html></source> <translation type="unfinished"></translation> </message> <message> - <location line="+46"/> <source>Model position</source> <translation type="unfinished"></translation> </message> <message> - <location line="+105"/> - <source><html><head/><body><p><a href="https://github.com/opentrack/opentrack/wiki/model-calibration-for-PT-and-Aruco-trackers"><span style=" text-decoration: underline; color:#0000ff;">Instructions on the opentrack wiki</span></a></p></body></html></source> + <source>Use only yaw and pitch while calibrating. +Don't roll or change position.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+13"/> <source>Start calibration</source> <translation type="unfinished"></translation> </message> <message> - <location line="+17"/> <source>About</source> <translation type="unfinished"></translation> </message> <message> - <location line="+6"/> <source><html><head/><body><p><span style=" font-weight:600;">FTNoIR PointTracker Plugin<br/>Version 1.1</span></p><p><span style=" font-weight:600;">by Patrick Ruoff</span></p><p><a href="http://ftnoirpt.sourceforge.net/"><span style=" font-weight:600; text-decoration: underline; color:#0000ff;">Manual (external)</span></a></p></body></html></source> <translation type="unfinished"></translation> </message> <message> - <location line="+36"/> <source>Status</source> <translation type="unfinished"></translation> </message> <message> - <location line="+6"/> <source>Extracted Points:</source> <translation type="unfinished"></translation> </message> <message> - <location line="+7"/> <source>Camera Info:</source> <translation type="unfinished"></translation> </message> + <message> + <source>Green only</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Red chroma key</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Green chroma key</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Blue chroma key</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Cyan chroma key</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Yellow chroma key</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Magenta chroma key</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>This should be 56° or 76° for the PS3 Eye, dependent upon the physical lens setting. It's only neccessary to get position correspond to real-world values.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Enable MJPEG compression for high-speed cameras other than the PS3 Eye. Windows only.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Set minimum size to avoid small stray lights from being treated as points.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Track dependent on point size and not absolute brightness. This may allow more stable tracking.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source><html><head/><body><p>For LEDs, 'Natural' is the fastest grayscale mode thanks to optimized SIMD code. Color key allows to track regular pieces of colored paper.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <source>MJPEG compression</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Point filter</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Filter point centers prior to pose estimation.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Enable</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Limit</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Grayscale BT.709</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Grayscale (from hardware)</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Deadzone</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Filter</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Chroma key includes overexposed pixels</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Chroma key strength</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>pt_impl::TrackerDialog_PT</name> + <message> + <source>Brightness %1/255</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>LED radius %1 pixels</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>%1 yaw samples. Yaw more to %2 samples for stable calibration.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>%1 pitch samples. Pitch more to %2 samples for stable calibration.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>%1 samples. Over %2, good!</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Stop calibration</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Start calibration</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>%1x%2 @ %3 FPS</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>%1 OK!</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>%1 BAD!</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Tracker offline</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>pt_impl::Tracker_PT</name> + <message> + <source>Failed to open camera '%1'</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>pt_module::metadata_pt</name> + <message> + <source>PointTracker 1.1</source> + <translation type="unfinished"></translation> + </message> </context> </TS> diff --git a/tracker-pt/lang/zh_CN.ts b/tracker-pt/lang/zh_CN.ts new file mode 100644 index 00000000..3519d719 --- /dev/null +++ b/tracker-pt/lang/zh_CN.ts @@ -0,0 +1,377 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="zh_CN"> +<context> + <name>UICPTClientControls</name> + <message> + <source>PointTracker Settings</source> + <translation>PointTracker设置</translation> + </message> + <message> + <source>Camera</source> + <translation>摄像头</translation> + </message> + <message> + <source>Camera settings</source> + <translation>摄像头设置</translation> + </message> + <message> + <source>°</source> + <translation>度</translation> + </message> + <message> + <source>Diagonal field of view</source> + <translation>对角线</translation> + </message> + <message> + <source>Width</source> + <translation>宽度</translation> + </message> + <message> + <source>FPS</source> + <translation>帧数</translation> + </message> + <message> + <source>Desired capture height</source> + <translation>期望高度</translation> + </message> + <message> + <source> px</source> + <translation> 像素点</translation> + </message> + <message> + <source>Dynamic pose timeout</source> + <translation>动态姿态超时时间</translation> + </message> + <message> + <source>Desired capture framerate</source> + <translation>期望帧数</translation> + </message> + <message> + <source> Hz</source> + <translation> 赫兹</translation> + </message> + <message> + <source>Desired capture width</source> + <translation>期望宽度</translation> + </message> + <message> + <source>Height</source> + <translation>高度</translation> + </message> + <message> + <source> ms</source> + <translation> 毫秒</translation> + </message> + <message> + <source>Device</source> + <translation>设备名称</translation> + </message> + <message> + <source>Open</source> + <translation>打开</translation> + </message> + <message> + <source>Camera settings (when available)</source> + <translation>摄像头设置 (连接时)</translation> + </message> + <message> + <source>Point extraction</source> + <translation>跟踪点解析</translation> + </message> + <message> + <source>Max size</source> + <translation>最大</translation> + </message> + <message> + <source>Threshold</source> + <translation>大小门限值</translation> + </message> + <message> + <source>Min size</source> + <translation>最小</translation> + </message> + <message> + <source>Intensity threshold for point extraction</source> + <translation>点密度</translation> + </message> + <message> + <source>Automatic threshold</source> + <translation>自动门限值</translation> + </message> + <message> + <source>Enable, slider sets point size</source> + <translation>激活,滑动,设置跟踪点大小</translation> + </message> + <message> + <source>Maximum point diameter</source> + <translation>最大点直径</translation> + </message> + <message> + <source>Minimum point diameter</source> + <translation>最小点直径</translation> + </message> + <message> + <source>Model</source> + <translation>点模式</translation> + </message> + <message> + <source>Clip</source> + <translation>夹子式</translation> + </message> + <message> + <source>Model Dimensions</source> + <translation>尺寸</translation> + </message> + <message> + <source> mm</source> + <translation> 毫米</translation> + </message> + <message> + <source>Side</source> + <translation>侧面</translation> + </message> + <message> + <source>Front</source> + <translation>正面</translation> + </message> + <message> + <source>Cap</source> + <translation>帽子式</translation> + </message> + <message> + <source>Custom</source> + <translation>自定义模式</translation> + </message> + <message> + <source>z:</source> + <translation>Z:</translation> + </message> + <message> + <source>x:</source> + <translation>X:</translation> + </message> + <message> + <source><html><head/><body><p>Location of the two remaining model points<br/>with respect to the reference point in default pose</p><p>Use any units you want, not necessarily centimeters.</p></body></html></source> + <translation><html><head/><body><p>三点中的两点位置是相对第一个点的</p><p>单位不一定要用厘米</p></body></html></translation> + </message> + <message> + <source>y:</source> + <translation>Y:</translation> + </message> + <message> + <source><html><head/><body><p><span style=" font-size:16pt;">P</span><span style=" font-size:16pt; vertical-align:sub;">3</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-size:16pt;">P</span><span style=" font-size:16pt; vertical-align:sub;">3</span></p></body></html></translation> + </message> + <message> + <source><html><head/><body><p><span style=" font-size:16pt;">P</span><span style=" font-size:16pt; vertical-align:sub;">2</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-size:16pt;">P</span><span style=" font-size:16pt; vertical-align:sub;">2</span></p></body></html></translation> + </message> + <message> + <source>Model position</source> + <translation>姿态空间位置</translation> + </message> + <message> + <source>Start calibration</source> + <translation>开始校准</translation> + </message> + <message> + <source>About</source> + <translation>关于</translation> + </message> + <message> + <source><html><head/><body><p><span style=" font-weight:600;">FTNoIR PointTracker Plugin<br/>Version 1.1</span></p><p><span style=" font-weight:600;">by Patrick Ruoff</span></p><p><a href="http://ftnoirpt.sourceforge.net/"><span style=" font-weight:600; text-decoration: underline; color:#0000ff;">Manual (external)</span></a></p></body></html></source> + <translation><html><head/><body><p><span style=" font-weight:600;">FTNoIR PointTracker Plugin<br/>Version 1.1</span></p><p><span style=" font-weight:600;">Patrick Ruoff</span></p><p><a href="http://ftnoirpt.sourceforge.net/"><span style=" font-weight:600; text-decoration: underline; color:#0000ff;">参考手册 (外部链接)</span></a></p></body></html></translation> + </message> + <message> + <source>Status</source> + <translation>状态</translation> + </message> + <message> + <source>Extracted Points:</source> + <translation>解析出的点:</translation> + </message> + <message> + <source>Camera Info:</source> + <translation>设备信息:</translation> + </message> + <message> + <source>Color channels used</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Red only</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Blue only</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Dynamic pose (for caps only, never clips)</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Value</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Use only yaw and pitch while calibrating. +Don't roll or change position.</source> + <translation>用pitch和yaw校准。不要roll或者变换位置</translation> + </message> + <message> + <source>Green only</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Red chroma key</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Green chroma key</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Blue chroma key</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Cyan chroma key</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Yellow chroma key</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Magenta chroma key</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>This should be 56° or 76° for the PS3 Eye, dependent upon the physical lens setting. It's only neccessary to get position correspond to real-world values.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Enable MJPEG compression for high-speed cameras other than the PS3 Eye. Windows only.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Set minimum size to avoid small stray lights from being treated as points.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Track dependent on point size and not absolute brightness. This may allow more stable tracking.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source><html><head/><body><p>For LEDs, 'Natural' is the fastest grayscale mode thanks to optimized SIMD code. Color key allows to track regular pieces of colored paper.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <source>MJPEG compression</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Point filter</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Filter point centers prior to pose estimation.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Enable</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Limit</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Grayscale BT.709</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Grayscale (from hardware)</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Deadzone</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Filter</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Chroma key includes overexposed pixels</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Chroma key strength</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>pt_impl::TrackerDialog_PT</name> + <message> + <source>Brightness %1/255</source> + <translation type="unfinished">亮度 %1/255</translation> + </message> + <message> + <source>LED radius %1 pixels</source> + <translation type="unfinished">光源半径 %1 像素</translation> + </message> + <message> + <source>%1 yaw samples. Yaw more to %2 samples for stable calibration.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>%1 pitch samples. Pitch more to %2 samples for stable calibration.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>%1 samples. Over %2, good!</source> + <translation type="unfinished">%1 样本。%2 正常</translation> + </message> + <message> + <source>Stop calibration</source> + <translation type="unfinished">停止校准</translation> + </message> + <message> + <source>Start calibration</source> + <translation type="unfinished">开始校准</translation> + </message> + <message> + <source>%1x%2 @ %3 FPS</source> + <translation type="unfinished">%1x%2 @ %3 帧</translation> + </message> + <message> + <source>%1 OK!</source> + <translation type="unfinished">%1 正常</translation> + </message> + <message> + <source>%1 BAD!</source> + <translation type="unfinished">%1 异常</translation> + </message> + <message> + <source>Tracker offline</source> + <translation type="unfinished">跟踪器脱机</translation> + </message> +</context> +<context> + <name>pt_impl::Tracker_PT</name> + <message> + <source>Failed to open camera '%1'</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>pt_module::metadata_pt</name> + <message> + <source>PointTracker 1.1</source> + <translation type="unfinished"></translation> + </message> +</context> +</TS> diff --git a/tracker-pt/module/CMakeLists.txt b/tracker-pt/module/CMakeLists.txt new file mode 100644 index 00000000..b7fc974f --- /dev/null +++ b/tracker-pt/module/CMakeLists.txt @@ -0,0 +1,10 @@ +include(opentrack-opencv) +find_package(OpenCV QUIET) +if(OpenCV_FOUND) + foreach(k core imgproc) + otr_install_lib("opencv_${k}" "${opentrack-libexec}") + endforeach() + otr_module(tracker-pt) + target_link_libraries(${self} opentrack-video opencv_imgproc opentrack-tracker-pt-base) + target_include_directories(${self} PUBLIC "${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..1beba474 --- /dev/null +++ b/tracker-pt/module/camera.cpp @@ -0,0 +1,159 @@ +/* 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 <opencv2/core/mat.hpp> + +namespace pt_module { + +Camera::Camera(const QString& module_name) : s { module_name } +{ +} + +QString Camera::get_desired_name() const +{ + return cam_desired.name; +} + +QString Camera::get_active_name() const +{ + return cam_info.name; +} + +void Camera::show_camera_settings() +{ + if (cap) + (void)cap->show_dialog(); +} + +Camera::result Camera::get_info() const +{ + if (cam_info.res_x == 0 || cam_info.res_y == 0) + return { false, pt_camera_info() }; + else + return { true, cam_info }; +} + +Camera::result Camera::get_frame(pt_frame& frame_) +{ + cv::Mat& frame = frame_.as<Frame>()->mat; + + const bool new_frame = get_frame_(frame); + + if (new_frame) + { + const f dt = (f)t.elapsed_seconds(); + t.start(); + + // measure fps of valid frames + constexpr f RC = f{1}/10; // seconds + const f alpha = dt/(dt + RC); + + if (dt_mean < dt_eps) + dt_mean = dt; + else + dt_mean = (1-alpha) * dt_mean + alpha * dt; + + cam_info.fps = dt_mean > dt_eps ? 1 / dt_mean : 0; + cam_info.res_x = frame.cols; + cam_info.res_y = frame.rows; + cam_info.fov = fov; + + return { true, cam_info }; + } + else + return { false, {} }; +} + +bool Camera::start(const pt_settings& s) +{ + int fps = s.cam_fps, res_x = s.cam_res_x, res_y = s.cam_res_y; + QString name = s.camera_name; + bool use_mjpeg = s.use_mjpeg; + + if (fps >= 0 && res_x >= 0 && res_y >= 0) + { + if (cam_desired.name != name || + (int)cam_desired.fps != fps || + cam_desired.res_x != res_x || + cam_desired.res_y != res_y || + cam_desired.use_mjpeg != use_mjpeg || + !cap || !cap->is_open()) + { + stop(); + + cam_desired.name = name; + cam_desired.fps = (f)fps; + cam_desired.res_x = res_x; + cam_desired.res_y = res_y; + cam_desired.fov = fov; + cam_desired.use_mjpeg = use_mjpeg; + + cap = video::make_camera(name); + + if (!cap) + goto fail; + + camera::info info {}; + info.fps = fps; + info.width = res_x; + info.height = res_y; + info.use_mjpeg = use_mjpeg; + info.num_channels = s.blob_color == pt_color_hardware ? 1 : 3; + + if (!cap->start(info)) + goto fail; + + cam_info = pt_camera_info(); + cam_info.name = name; + cam_info.use_mjpeg = use_mjpeg; + cam_info.fov = (f)s.fov; + dt_mean = 0; + + cv::Mat tmp; + + if (!get_frame_(tmp)) + goto fail; + + t.start(); + } + } + + return true; + +fail: + stop(); + return false; +} + +void Camera::stop() +{ + cap = nullptr; + cam_info = {}; + cam_desired = {}; +} + +bool Camera::get_frame_(cv::Mat& img) +{ + if (cap && cap->is_open()) + { + auto [ frame, ret ] = cap->get_frame(); + if (ret) + { + int stride = frame.stride; + if (stride == 0) + stride = cv::Mat::AUTO_STEP; + img = cv::Mat(frame.height, frame.width, CV_8UC(frame.channels), (void*)frame.data, (size_t)stride); + return true; + } + } + + return false; +} + +} // ns pt_module diff --git a/tracker-pt/module/camera.h b/tracker-pt/module/camera.h new file mode 100644 index 00000000..e4772178 --- /dev/null +++ b/tracker-pt/module/camera.h @@ -0,0 +1,53 @@ +/* Copyright (c) 2012 Patrick Ruoff + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + */ + +#pragma once + +#include "pt-api.hpp" +#include "compat/timer.hpp" +#include "video/camera.hpp" + +#include <memory> + +#include <QString> + +namespace pt_module { + +struct Camera final : pt_camera +{ + Camera(const QString& module_name); + + bool start(const pt_settings& s) override; + void stop() override; + + result get_frame(pt_frame& Frame) override; + result get_info() const override; + + pt_camera_info get_desired() const override { return cam_desired; } + QString get_desired_name() const override; + QString get_active_name() const override; + + void set_fov(f value) override { fov = value; } + void show_camera_settings() override; + +private: + using camera = video::impl::camera; + + [[nodiscard]] bool get_frame_(cv::Mat& frame); + + f dt_mean = 0, fov = 30; + Timer t; + pt_camera_info cam_info; + pt_camera_info cam_desired; + + std::unique_ptr<camera> cap; + pt_settings s; + + static constexpr f dt_eps = f{1}/256; +}; + +} // ns pt_module diff --git a/tracker-pt/module/export.hpp b/tracker-pt/module/export.hpp new file mode 100644 index 00000000..a733c9fe --- /dev/null +++ b/tracker-pt/module/export.hpp @@ -0,0 +1,11 @@ +// generates export.hpp for each module from compat/linkage.hpp + +#pragma once + +#include "compat/linkage-macros.hpp" + +#ifdef BUILD_TRACKER_PT +# define OTR_PT_EXPORT OTR_GENERIC_EXPORT +#else +# define OTR_PT_EXPORT OTR_GENERIC_IMPORT +#endif diff --git a/tracker-pt/module/frame.cpp b/tracker-pt/module/frame.cpp new file mode 100644 index 00000000..1a276f16 --- /dev/null +++ b/tracker-pt/module/frame.cpp @@ -0,0 +1,83 @@ +#include "frame.hpp" +#include "compat/math.hpp" +#include <opencv2/imgproc.hpp> + +namespace pt_module { + +void Preview::set_last_frame(const pt_frame& frame_) +{ + const cv::Mat& frame = frame_.as_const<const Frame>()->mat; + const bool need_resize = frame.size != frame_copy.size; + + if (frame.channels() == 1) + { + if (need_resize) + { + frame_tmp.create(frame.size(), CV_8UC3); + cv::cvtColor(frame, frame_tmp, cv::COLOR_GRAY2BGR); + cv::resize(frame_tmp, frame_copy, frame_copy.size(), 0, 0, cv::INTER_NEAREST); + } + else + cv::cvtColor(frame, frame_copy, cv::COLOR_GRAY2BGR); + } + else if (frame.channels() == 3) + { + if (need_resize) + cv::resize(frame, frame_copy, frame_copy.size(), 0, 0, cv::INTER_NEAREST); + else + frame.copyTo(frame_copy); + } + else + { + eval_once(qDebug() << "tracker/pt: camera frame depth" << frame.channels() << "!= 3"); + frame_copy.create(frame_copy.size(), CV_8UC3); + frame_copy.setTo({0}); + } +} + +Preview::Preview(int w, int h) +{ + frame_out.create(h, w, CV_8UC4); + frame_copy.create(h, w, CV_8UC3); + frame_copy.setTo({0}); +} + +QImage Preview::get_bitmap() +{ + int stride = (int)frame_out.step.p[0]; + + if (stride < frame_out.cols * 4) + { + eval_once(qDebug() << "bad stride" << stride + << "for bitmap size" << frame_copy.cols << frame_copy.rows); + return QImage(); + } + + cv::cvtColor(frame_copy, frame_out, cv::COLOR_BGR2BGRA); + + return QImage((const unsigned char*) frame_out.data, + frame_out.cols, frame_out.rows, + stride, + QImage::Format_ARGB32); +} + +void Preview::draw_head_center(f x, f y) +{ + auto [px_, py_] = to_pixel_pos(x, y, frame_copy.cols, frame_copy.rows); + + int px = iround(px_), py = iround(py_); + + constexpr int len = 9; + + static const cv::Scalar color(0, 255, 255); + cv::line(frame_copy, + cv::Point(px - len, py), + cv::Point(px + len, py), + color, 1); + cv::line(frame_copy, + cv::Point(px, py - len), + cv::Point(px, py + len), + color, 1); +} + +} // ns pt_module diff --git a/tracker-pt/module/frame.hpp b/tracker-pt/module/frame.hpp new file mode 100644 index 00000000..0569a323 --- /dev/null +++ b/tracker-pt/module/frame.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "pt-api.hpp" + +#include <opencv2/core/mat.hpp> +#include <QImage> + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wweak-vtables" +#endif + +namespace pt_module { + +struct Frame final : pt_frame +{ + cv::Mat mat; + + operator const cv::Mat&() const& { return mat; } + operator cv::Mat&() & { return mat; } +}; + +struct Preview final : pt_preview +{ + Preview(int w, int h); + + void set_last_frame(const pt_frame& frame) override; + QImage get_bitmap() override; + void draw_head_center(f x, f y) override; + + operator cv::Mat&() { return frame_copy; } + operator cv::Mat const&() const { return frame_copy; } + +private: + cv::Mat frame_copy, frame_out, frame_tmp; +}; + +} // ns pt_module + +#ifdef __clang__ +# pragma clang diagnostic pop +#endif diff --git a/tracker-pt/module/lang/de_DE.ts b/tracker-pt/module/lang/de_DE.ts new file mode 100644 index 00000000..6c548aba --- /dev/null +++ b/tracker-pt/module/lang/de_DE.ts @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="de_DE"> +<context> + <name>pt_module::metadata_pt</name> + <message> + <source>PointTracker 1.1</source> + <translation>PointTracker 1.1</translation> + </message> +</context> +</TS> diff --git a/tracker-pt/module/lang/nl_NL.ts b/tracker-pt/module/lang/nl_NL.ts new file mode 100644 index 00000000..4679971e --- /dev/null +++ b/tracker-pt/module/lang/nl_NL.ts @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="nl_NL"> +<context> + <name>pt_module::metadata_pt</name> + <message> + <source>PointTracker 1.1</source> + <translation type="unfinished"></translation> + </message> +</context> +</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..c3611ef0 --- /dev/null +++ b/tracker-pt/module/lang/ru_RU.ts @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="ru_RU"> +<context> + <name>pt_module::metadata_pt</name> + <message> + <source>PointTracker 1.1</source> + <translation type="unfinished"></translation> + </message> +</context> +</TS> diff --git a/tracker-pt/module/lang/stub.ts b/tracker-pt/module/lang/stub.ts new file mode 100644 index 00000000..03d19f4e --- /dev/null +++ b/tracker-pt/module/lang/stub.ts @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1"> +<context> + <name>pt_module::metadata_pt</name> + <message> + <source>PointTracker 1.1</source> + <translation type="unfinished"></translation> + </message> +</context> +</TS> diff --git a/tracker-pt/module/lang/zh_CN.ts b/tracker-pt/module/lang/zh_CN.ts new file mode 100644 index 00000000..c39728a1 --- /dev/null +++ b/tracker-pt/module/lang/zh_CN.ts @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="zh_CN"> +<context> + <name>pt_module::metadata_pt</name> + <message> + <source>PointTracker 1.1</source> + <translation type="unfinished"></translation> + </message> +</context> +</TS> diff --git a/tracker-pt/module/module.cpp b/tracker-pt/module/module.cpp new file mode 100644 index 00000000..f665face --- /dev/null +++ b/tracker-pt/module/module.cpp @@ -0,0 +1,72 @@ +#include "ftnoir_tracker_pt.h" + +#include "module.hpp" +#include "camera.h" +#include "frame.hpp" +#include "point_extractor.h" +#include "ftnoir_tracker_pt_dialog.h" + +#include "pt-api.hpp" + +#include <memory> + +static const QString module_name = "tracker-pt"; + +#ifdef __clang__ +# pragma clang diagnostic ignored "-Wweak-vtables" +#endif + +namespace pt_module { + +struct pt_module_traits final : pt_runtime_traits +{ + pointer<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(); +}; + +dialog_pt::dialog_pt() : TrackerDialog_PT(module_name) {} + +QString metadata_pt::name() { return tr("PointTracker 1.1"); } +QIcon metadata_pt::icon() { return QIcon(":/Resources/Logo_IR.png"); } + +} + +// ns pt_module + +using namespace pt_module; + +OPENTRACK_DECLARE_TRACKER(tracker_pt, dialog_pt, metadata_pt) diff --git a/tracker-pt/module/module.hpp b/tracker-pt/module/module.hpp new file mode 100644 index 00000000..0b3f12cf --- /dev/null +++ b/tracker-pt/module/module.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "api/plugin-api.hpp" +#include <QIcon> +#include <QString> + +#include "compat/linkage-macros.hpp" + +namespace pt_module +{ + +class OTR_GENERIC_EXPORT metadata_pt : public Metadata +{ + Q_OBJECT + + QString name() override; + QIcon icon() override; +}; + +} // ns pt_module diff --git a/tracker-pt/module/point_extractor.cpp b/tracker-pt/module/point_extractor.cpp new file mode 100644 index 00000000..3329fafc --- /dev/null +++ b/tracker-pt/module/point_extractor.cpp @@ -0,0 +1,458 @@ +/* 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 "compat/math-imports.hpp" + +#include <opencv2/imgproc.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 numeric_types; + +// meanshift code written by Michael Welter + +/* +http://en.wikipedia.org/wiki/Mean-shift +In this application the idea, is to eliminate any bias of the point estimate +which is introduced by the rather arbitrary thresholded area. One must recognize +that the thresholded area can only move in one pixel increments since it is +binary. Thus, its center of mass might make "jumps" as pixels are added/removed +from the thresholded area. +With mean-shift, a moving "window" or kernel is multiplied with the gray-scale +image, and the COM is calculated of the result. This is iterated where the +kernel center is set the previously computed COM. Thus, peaks in the image intensity +distribution "pull" the kernel towards themselves. Eventually it stops moving, i.e. +then the computed COM coincides with the kernel center. We hope that the +corresponding location is a good candidate for the extracted point. +The idea similar to the window scaling suggested in Berglund et al. "Fast, bias-free +algorithm for tracking single particles with variable size and shape." (2008). +*/ +static vec2 MeanShiftIteration(const cv::Mat1b &frame_gray, const vec2 ¤t_center, f filter_width) +{ + const f s = 1 / filter_width; + + f m = 0; + vec2 com { 0, 0 }; + for (int i = 0; i < frame_gray.rows; i++) + { + uint8_t const* const __restrict frame_ptr = frame_gray.ptr(i); + for (int j = 0; j < frame_gray.cols; j++) + { + f val = frame_ptr[j]; + val = val * val; // taking the square weighs brighter parts of the image stronger. + f dx = (j - current_center[0])*s; + f dy = (i - current_center[1])*s; + f max = std::fmax(f(0), 1 - dx*dx - dy*dy); + val *= max; + m += val; + com[0] += j * val; + com[1] += i * val; + } + } + if (m > f(.1)) + { + com *= 1 / m; + return com; + } + else + return current_center; +} + +namespace pt_module { + +PointExtractor::PointExtractor(const QString& module_name) : s(module_name) +{ + blobs.reserve(max_blobs); +} + +void PointExtractor::ensure_channel_buffers(const cv::Mat& orig_frame) +{ + for (cv::Mat1b& x : ch) + x.create(orig_frame.rows, orig_frame.cols); +} + +void PointExtractor::ensure_buffers(const cv::Mat& frame) +{ + const int W = frame.cols, H = frame.rows; + + frame_gray.create(H, W); + frame_bin.create(H, W); +} + +void PointExtractor::extract_single_channel(const cv::Mat& orig_frame, int idx, cv::Mat1b& dest) +{ + ensure_channel_buffers(orig_frame); + + const int from_to[] = { + idx, 0, + }; + + cv::mixChannels(&orig_frame, 1, &dest, 1, from_to, 1); +} + +void PointExtractor::filter_single_channel(const cv::Mat& orig_frame, float r, float g, float b, bool overexp, cv::Mat1b& dest) +{ + ensure_channel_buffers(orig_frame); + + // just filter for colour or also include overexposed regions? + if (!overexp) + cv::transform(orig_frame, dest, cv::Mat(cv::Matx13f(b, g, r))); + else + { + for (int i = 0; i < orig_frame.rows; i++) + { + cv::Vec3b const* const __restrict orig_ptr = orig_frame.ptr<cv::Vec3b>(i); + uint8_t* const __restrict dest_ptr = dest.ptr(i); + for (int j = 0; j < orig_frame.cols; j++) + { + // get the intensity of the key color (i.e. +ve coefficients) + uchar blue = orig_ptr[j][0], green = orig_ptr[j][1], red = orig_ptr[j][2]; + float key = std::max(b, 0.0f) * blue + std::max(g, 0.0f) * green + std::max(r, 0.0f) * red; + // get the intensity of the non-key color (i.e. -ve coefficients) + float nonkey = std::max(-b, 0.0f) * blue + std::max(-g, 0.0f) * green + std::max(-r, 0.0f) * red; + // the result is key color minus non-key color inversely weighted by key colour intensity + dest_ptr[j] = std::max(0.0f, std::min(255.0f, key - (255.0f - key) / 255.0f * nonkey)); + } + } + } +} + +void PointExtractor::color_to_grayscale(const cv::Mat& frame, cv::Mat1b& output) +{ + if (frame.channels() == 1) + { + output.create(frame.rows, frame.cols); + frame.copyTo(output); + return; + } + + const float half_chr_key_str = *s.chroma_key_strength * 0.5; + switch (s.blob_color) + { + case pt_color_green_only: + { + extract_single_channel(frame, 1, output); + break; + } + case pt_color_blue_only: + { + extract_single_channel(frame, 0, output); + break; + } + case pt_color_red_only: + { + extract_single_channel(frame, 2, output); + break; + } + case pt_color_red_chromakey: + { + filter_single_channel(frame, 1, -half_chr_key_str, -half_chr_key_str, s.chroma_key_overexposed, output); + break; + } + case pt_color_green_chromakey: + { + filter_single_channel(frame, -half_chr_key_str, 1, -half_chr_key_str, s.chroma_key_overexposed, output); + break; + } + case pt_color_blue_chromakey: + { + filter_single_channel(frame, -half_chr_key_str, -half_chr_key_str, 1, s.chroma_key_overexposed, output); + break; + } + case pt_color_cyan_chromakey: + { + filter_single_channel(frame, -*s.chroma_key_strength, 0.5, 0.5, s.chroma_key_overexposed, output); + break; + } + case pt_color_yellow_chromakey: + { + filter_single_channel(frame, 0.5, 0.5, -*s.chroma_key_strength, s.chroma_key_overexposed, output); + break; + } + case pt_color_magenta_chromakey: + { + filter_single_channel(frame, 0.5, -*s.chroma_key_strength, 0.5, s.chroma_key_overexposed, output); + break; + } + case pt_color_hardware: + eval_once(qDebug() << "camera driver doesn't support grayscale"); + goto do_grayscale; + default: + eval_once(qDebug() << "wrong pt_color_type enum value" << int(s.blob_color)); + [[fallthrough]]; + case pt_color_bt709: +do_grayscale: + 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, + &hist_size, + &ranges); + + const f radius = threshold_radius_value(frame_gray.cols, frame_gray.rows, threshold_slider_value); + + float const* const __restrict ptr = hist.ptr<float>(0); + const unsigned area = unsigned(iround(3 * pi * radius*radius)); + const unsigned sz = unsigned(hist.cols * hist.rows); + unsigned thres = 1; + for (unsigned i = sz-1, cnt = 0; i > 1; i--) + { + cnt += (unsigned)ptr[i]; + if (cnt >= area) + break; + thres = i; + } + + cv::threshold(frame_gray, output, thres, 255, cv::THRESH_BINARY); + } +} + +static void draw_blobs(cv::Mat& preview_frame, const blob* blobs, unsigned nblobs, const cv::Size& size) +{ + for (unsigned k = 0; k < nblobs; k++) + { + const blob& b = blobs[k]; + + if (b.radius < 0) + continue; + + const f dpi = preview_frame.cols / f(320); + const f offx = 10 * dpi, offy = f(7.5) * dpi; + + const f cx = preview_frame.cols / f(size.width), + cy = preview_frame.rows / f(size.height), + c = std::fmax(f(1), cx+cy)/2; + + cv::Point p(iround(b.pos[0] * cx), iround(b.pos[1] * cy)); + + auto outline_color = k >= PointModel::N_POINTS + ? cv::Scalar(192, 192, 192) + : cv::Scalar(255, 255, 0); + + cv::ellipse(preview_frame, p, + {iround(b.rect.width/(f)2+2*c), iround(b.rect.height/(f)2+2*c)}, + 0, 0, 360, outline_color, iround(dpi), cv::LINE_AA); + + char buf[16]; + std::snprintf(buf, sizeof(buf), "%.2fpx", (double)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, iround(dpi), text_color, + 1); + } +} + +static vec2 meanshift_initial_guess(const cv::Rect rect, cv::Mat& frame_roi) +{ + vec2 ret = {rect.width/(f)2, rect.height/(f)2}; + + // compute center initial guess + double ynorm = 0, xnorm = 0, y = 0, x = 0; + for (int j = 0; j < rect.height; j++) + { + const unsigned char* __restrict ptr = frame_roi.ptr<unsigned char>(j); + for (int i = 0; i < rect.width; i++) + { + double val = ptr[i] * 1./255; + x += i * val; + y += j * val; + xnorm += val; + ynorm += val; + } + } + constexpr double eps = 1e-4; + if (xnorm > eps && ynorm > eps) + ret = { (f)(x / xnorm), (f)(y / ynorm) }; + return ret; +} + +void PointExtractor::extract_points(const pt_frame& frame_, + pt_preview& preview_frame_, + bool preview_visible, + std::vector<vec2>& points) +{ + const cv::Mat& frame = frame_.as_const<Frame>()->mat; + + 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); + + const f region_size_min = (f)s.min_point_size; + const f region_size_max = (f)s.max_point_size; + + unsigned idx = 0; + + blobs.clear(); + + for (int y=0; y < frame_bin.rows; y++) + { + const unsigned char* __restrict ptr_bin = frame_bin.ptr(y); + for (int x=0; x < frame_bin.cols; x++) + { + if (ptr_bin[x] != 255) + continue; + idx = blobs.size() + 1; + + cv::Rect rect; + cv::floodFill(frame_bin, + cv::Point(x,y), + cv::Scalar(idx), + &rect, + cv::Scalar(0), + cv::Scalar(0), + 4 | cv::FLOODFILL_FIXED_RANGE); + + unsigned cnt = 0; + unsigned norm = 0; + + const int ymax = rect.y+rect.height, + xmax = rect.x+rect.width; + + for (int i=rect.y; i < ymax; i++) + { + unsigned char const* const __restrict ptr_blobs = frame_bin.ptr(i); + unsigned char const* const __restrict ptr_gray = frame_gray.ptr(i); + for (int j=rect.x; j < xmax; j++) + { + if (ptr_blobs[j] != idx) + continue; + + //ptr_blobs[j] = 0; + norm += ptr_gray[j]; + cnt++; + } + } + + const f radius = std::sqrt((f)cnt) / std::sqrt(pi); + if (radius > region_size_max || radius < region_size_min) + continue; + + blobs.emplace_back(radius, + vec2(rect.width/f(2), rect.height/f(2)), + std::pow(f(norm), f(1.1))/cnt, + rect); + + if (idx >= max_blobs) + goto end; + + // XXX we could go to the next scanline unless the points are really small. + // i'd expect each point being present on at least one unique scanline + // but it turns out some people are using 2px points -sh 20180110 + //break; + } + } +end: + + const int W = frame_gray.cols; + const int H = frame_gray.rows; + + const unsigned sz = blobs.size(); + + std::sort(blobs.begin(), blobs.end(), [](const blob& b1, const blob& b2) { return b2.brightness < b1.brightness; }); + + for (idx = 0; idx < sz; ++idx) + { + blob& b = blobs[idx]; + cv::Rect rect = b.rect & 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 = meanshift_initial_guess(rect, frame_roi); // position relative to ROI. + + for (int iter = 0; iter < 10; ++iter) + { + vec2 com_new = MeanShiftIteration(frame_roi, pos, kernel_radius); + vec2 delta = com_new - pos; + pos = com_new; + if (delta.dot(delta) < f(1e-3)) + break; + } + + b.pos[0] = pos[0] + rect.x; + b.pos[1] = pos[1] + rect.y; + } + + if (preview_visible) + draw_blobs(preview_frame_.as<Frame>()->mat, + blobs.data(), blobs.size(), + frame_gray.size()); + + + // End of mean shift code. At this point, blob positions are updated with hopefully less noisy less biased values. + points.reserve(max_blobs); + points.clear(); + + for (const auto& b : blobs) + { + // note: H/W is equal to fx/fy + + vec2 p; + std::tie(p[0], p[1]) = to_screen_pos(b.pos[0], b.pos[1], W, H); + points.push_back(p); + } +} + +blob::blob(f radius, const vec2& pos, f brightness, const cv::Rect& rect) : + radius(radius), brightness(brightness), pos(pos), rect(rect) +{ + //qDebug() << "radius" << radius << "pos" << pos[0] << pos[1]; +} + +} // ns pt_module diff --git a/tracker-pt/module/point_extractor.h b/tracker-pt/module/point_extractor.h new file mode 100644 index 00000000..fbfdbb0b --- /dev/null +++ b/tracker-pt/module/point_extractor.h @@ -0,0 +1,61 @@ +/* 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 <opencv2/core/mat.hpp> +#include <opencv2/core/types.hpp> +#include <vector> + +namespace pt_module { + +using namespace numeric_types; + +struct blob final +{ + f radius, brightness; + vec2 pos; + cv::Rect rect; + + blob(f radius, const vec2& pos, f brightness, const cv::Rect& rect); +}; + +class PointExtractor final : public pt_point_extractor +{ +public: + // extracts points from frame and draws some processing info into frame, if draw_output is set + // dt: time since last call in seconds + void extract_points(const pt_frame& frame, + pt_preview& preview_frame, bool preview_visible, + std::vector<vec2>& points) override; + + explicit PointExtractor(const QString& module_name); + +private: + static constexpr int max_blobs = 16; + + pt_settings s; + + cv::Mat1b frame_bin, frame_gray; + 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::Mat1b& dest); + void filter_single_channel(const cv::Mat& orig_frame, float r, float g, float b, bool overexp, cv::Mat1b& dest); + + void color_to_grayscale(const cv::Mat& frame, cv::Mat1b& output); + void threshold_image(const cv::Mat& frame_gray, cv::Mat1b& output); +}; + +} // ns impl + diff --git a/tracker-pt/module/tracker_pt.qrc b/tracker-pt/module/tracker_pt.qrc new file mode 100644 index 00000000..dfeb7369 --- /dev/null +++ b/tracker-pt/module/tracker_pt.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/"> + <file>Resources/Logo_IR.png</file> + </qresource> +</RCC> diff --git a/tracker-pt/numeric.hpp b/tracker-pt/numeric.hpp deleted file mode 100644 index 9d37086d..00000000 --- a/tracker-pt/numeric.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include <opencv2/core.hpp> -#include <limits> - -namespace types { - using f = double; - - struct constants final - { - constants() = delete; - static constexpr f eps = std::numeric_limits<f>::epsilon(); - }; - - template<int n> using vec = cv::Vec<f, n>; - using vec2 = vec<2>; - using vec3 = vec<3>; - - template<int y, int x> using mat = cv::Matx<f, y, x>; - using mat33 = mat<3, 3>; - using mat22 = mat<2, 2>; -} diff --git a/tracker-pt/point-filter.cpp b/tracker-pt/point-filter.cpp new file mode 100644 index 00000000..10448fe2 --- /dev/null +++ b/tracker-pt/point-filter.cpp @@ -0,0 +1,72 @@ +#include "point-filter.hpp" +#include <algorithm> +#include <cmath> +#include <QDebug> + +namespace pt_point_filter_impl { + +void point_filter::reset() +{ + t = std::nullopt; +} + +const PointOrder& point_filter::operator()(const PointOrder& input, f deadzone_amount) +{ + using std::fmod; + using std::sqrt; + using std::pow; + using std::clamp; + + if (!s.enable_point_filter) + { + t = std::nullopt; + state_ = input; + return state_; + } + + if (!t) + { + t.emplace(); + state_ = input; + return state_; + } + + constexpr auto E = (f)1.75; + const f limit = (f)*s.point_filter_limit; + const f C = progn( + constexpr int A = 1'000'000; + double K = *s.point_filter_coefficient; + f log10_pos = -2 + (int)K, rest = (f)(.999-fmod(K, 1.)*.9); + return A * pow((f)10, (f)-log10_pos) * rest; + ); + + f dist = 0, dz = deadzone_amount * (f)s.point_filter_deadzone / 800; // sqrt(640^2 + 480^2) + + for (unsigned i = 0; i < 3; i++) + { + vec2 tmp = input[i] - state_[i]; + f x = sqrt(tmp.dot(tmp)); + x = std::max((f)0, x - dz); + dist = std::max(dist, x); + } + + if (dist < (f)1e-6) + return state_; + + f dt = (f)t->elapsed_seconds(); t->start(); + f delta = pow(dist, E) * C * dt; // gain + + //qDebug() << "gain" << std::min((f)1, delta); + + for (unsigned i = 0; i < 3; i++) + { + f x = clamp(delta, (f)0, limit); + state_[i] += x*(input[i] - state_[i]); + } + + return state_; +} + +point_filter::point_filter(const pt_settings& s) : s{s} {} + +} // ns pt_point_filter_impl diff --git a/tracker-pt/point-filter.hpp b/tracker-pt/point-filter.hpp new file mode 100644 index 00000000..c3c045dd --- /dev/null +++ b/tracker-pt/point-filter.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "cv/numeric.hpp" +#include "compat/timer.hpp" +#include "pt-settings.hpp" +#include <optional> +#include <array> + +namespace pt_point_filter_impl { + +using namespace numeric_types; +using PointOrder = std::array<numeric_types::vec2, 3>; + +class point_filter final +{ + PointOrder state_; + std::optional<Timer> t; + const pt_settings& s; + +public: + void reset(); + const PointOrder& operator()(const PointOrder& input, f deadzone_amount); + + explicit point_filter(const pt_settings& s); + ~point_filter() = default; + + OTR_DISABLE_MOVE_COPY(point_filter); +}; + +} // ns pt_point_filter_impl + +using point_filter = pt_point_filter_impl::point_filter; diff --git a/tracker-pt/point_extractor.cpp b/tracker-pt/point_extractor.cpp deleted file mode 100644 index 4bd92d44..00000000 --- a/tracker-pt/point_extractor.cpp +++ /dev/null @@ -1,291 +0,0 @@ -/* 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. - */ - -#include "point_extractor.h" -#include "compat/util.hpp" -#include "point_tracker.h" -#include <QDebug> - -#include <opencv2/videoio.hpp> - -#include <cmath> -#include <algorithm> -#include <cinttypes> - -#include <QDebug> - -using namespace types; - -/* -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 cv::Vec2d ¤t_center, double filter_width) -{ - // Most amazingling this function runs faster with doubles than with floats. - const double s = 1.0 / filter_width; - - double m = 0; - cv::Vec2d com(0.0, 0.0); - for (int i = 0; i < frame_gray.rows; i++) - { - auto frame_ptr = (uint8_t *)frame_gray.ptr(i); - for (int j = 0; j < frame_gray.cols; j++) - { - double val = frame_ptr[j]; - val = val * val; // taking the square wights brighter parts of the image stronger. - { - double dx = (j - current_center[0])*s; - double dy = (i - current_center[1])*s; - double f = std::max(0.0, 1.0 - dx*dx - dy*dy); - val *= f; - } - m += val; - com[0] += j * val; - com[1] += i * val; - } - } - if (m > 0.1) - { - com *= 1.0 / m; - return com; - } - else - return current_center; -} - -PointExtractor::PointExtractor() -{ - blobs.reserve(max_blobs); -} - -void PointExtractor::extract_points(const cv::Mat& frame, cv::Mat& preview_frame, std::vector<vec2>& points) -{ - using std::sqrt; - using std::max; - using std::round; - using std::sort; - - if (frame_gray.rows != frame.rows || frame_gray.cols != frame.cols) - { - frame_gray = cv::Mat(frame.rows, frame.cols, CV_8U); - frame_bin = cv::Mat(frame.rows, frame.cols, CV_8U); - frame_blobs = cv::Mat(frame.rows, frame.cols, CV_8U); - } - - // convert to grayscale - cv::cvtColor(frame, frame_gray, cv::COLOR_RGB2GRAY); - - const double region_size_min = s.min_point_size; - const double region_size_max = s.max_point_size; - - if (!s.auto_threshold) - { - const int thres = s.threshold; - cv::threshold(frame_gray, frame_bin, thres, 255, cv::THRESH_BINARY); - } - else - { - cv::calcHist(std::vector<cv::Mat> { frame_gray }, - std::vector<int> { 0 }, - cv::Mat(), - hist, - std::vector<int> { 256 }, - std::vector<float> { 0, 256 }, - false); - - static constexpr double min_radius = 2.5; - static constexpr double max_radius = 15; - - const double radius = max(0., (max_radius-min_radius) * s.threshold / 255 + min_radius); - const float* ptr = reinterpret_cast<const float*>(hist.ptr(0)); - const unsigned area = unsigned(round(3 * M_PI * radius*radius)); - const unsigned sz = unsigned(hist.cols * hist.rows); - unsigned thres = 1; - for (unsigned i = sz-1, cnt = 0; i > 1; i--) - { - cnt += ptr[i]; - if (cnt >= area) - { - thres = i; - break; - } - } - //val *= 240./256.; - //qDebug() << "thres" << thres; - - cv::threshold(frame_gray, frame_bin, thres, 255, CV_THRESH_BINARY); - } - - blobs.clear(); - frame_bin.copyTo(frame_blobs); - - 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), - 8); - - // these are doubles since m10 and m01 could overflow theoretically - // log2(255^2 * 640^2 * pi) > 36 - double m10 = 0; - double m01 = 0; - // norm can't overflow since there's no 640^2 component - int norm = 0; - int cnt = 0; - - for (int i=rect.y; i < (rect.y+rect.height); i++) - { - unsigned char* ptr_blobs = frame_blobs.ptr(i); - const unsigned char* ptr_gray = frame_gray.ptr(i); - for (int j=rect.x; j < (rect.x+rect.width); j++) - { - if (ptr_blobs[j] != idx) - continue; - - ptr_blobs[j] = 0; - - // square as a weight gives better results - const int val(int(ptr_gray[j]) * int(ptr_gray[j])); - - norm += val; - m01 += i * val; - m10 += j * val; - cnt++; - } - } - if (norm > 0) - { - const double radius = sqrt(cnt / M_PI), N = double(norm); - if (radius > region_size_max || radius < region_size_min) - continue; - - blob b(radius, cv::Vec2d(m10 / N, m01 / N), N/sqrt(double(cnt)), rect); - blobs.push_back(b); - - { - static const f offx = 10, offy = 7.5; - const f cx = preview_frame.cols / f(frame.cols), - cy = preview_frame.rows / f(frame.rows); - cv::Point p(iround(b.pos[0] * cx), iround(b.pos[1] * cy)); - - static const cv::Scalar color(0, 255, 0); - - cv::circle(preview_frame, p, b.radius, color); - - { - const int len = iround(b.radius * 1.25) + 1; - cv::line(preview_frame, - cv::Point(p.x - len, p.y), - cv::Point(p.x + len, p.y), - color); - cv::line(preview_frame, - cv::Point(p.x, p.y - len), - cv::Point(p.x, p.y + len), - color); - } - - char buf[64]; - sprintf(buf, "%.2fpx", radius); - - cv::putText(preview_frame, - buf, - cv::Point(iround(b.pos[0]*cx+offx), iround(b.pos[1]*cy+offy)), - cv::FONT_HERSHEY_PLAIN, - 1, - cv::Scalar(0, 0, 255), - 1); - } - - if (idx >= max_blobs) goto end; - } - } - } -end: - - sort(blobs.begin(), blobs.end(), [](const blob& b1, const blob& b2) -> bool { return b2.brightness < b1.brightness; }); - - const int W = frame.cols; - const int H = frame.rows; - - for (idx = 0; idx < std::min(PointModel::N_POINTS, unsigned(blobs.size())); ++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 - // seems values close to 1.3 reduce noise best with about .15->.2 changes - static constexpr double radius_c = 1.3; - - const double kernel_radius = b.radius * radius_c; - cv::Vec2d pos(b.pos[0] - rect.x, b.pos[1] - rect.y); // position relative to ROI. - - for (int iter = 0; iter < 10; ++iter) - { - cv::Vec2d com_new = MeanShiftIteration(frame_roi, pos, kernel_radius); - cv::Vec2d delta = com_new - pos; - pos = com_new; - if (delta.dot(delta) < 1e-3) - break; - } - - b.pos[0] = pos[0] + rect.x; - b.pos[1] = pos[1] + rect.y; - } - - // End of mean shift code. At this point, blob positions are updated which 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((b.pos[0] - W/2)/W, -(b.pos[1] - H/2)/W); - points.push_back(p); - } -} - -PointExtractor::blob::blob(double radius, const cv::Vec2d& pos, double brightness, cv::Rect& rect) : - radius(radius), brightness(brightness), pos(pos), rect(rect) -{ - //qDebug() << "radius" << radius << "pos" << pos[0] << pos[1]; -} diff --git a/tracker-pt/point_extractor.h b/tracker-pt/point_extractor.h deleted file mode 100644 index f931edd5..00000000 --- a/tracker-pt/point_extractor.h +++ /dev/null @@ -1,55 +0,0 @@ -/* 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 "ftnoir_tracker_pt_settings.h" -#include "camera.h" -#include "numeric.hpp" - -#include <opencv2/core.hpp> -#include <opencv2/imgproc.hpp> - -#include <vector> - -namespace impl { - -using namespace types; - -class PointExtractor final -{ -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 cv::Mat& frame, cv::Mat& preview_frame, std::vector<vec2>& points); - PointExtractor(); - - settings_pt s; -private: - static constexpr int max_blobs = 16; - - cv::Mat frame_gray; - cv::Mat frame_bin; - cv::Mat hist; - cv::Mat frame_blobs; - - struct blob - { - double radius, brightness; - vec2 pos; - cv::Rect rect; - - blob(double radius, const cv::Vec2d& pos, double brightness, cv::Rect &rect); - }; - - std::vector<blob> blobs; -}; - -} // ns impl - -using impl::PointExtractor; diff --git a/tracker-pt/point_tracker.cpp b/tracker-pt/point_tracker.cpp index cae68bf3..39e96038 100644 --- a/tracker-pt/point_tracker.cpp +++ b/tracker-pt/point_tracker.cpp @@ -6,9 +6,7 @@ */ #include "point_tracker.h" -#include "compat/nan.hpp" - -using namespace types; +#include "compat/math-imports.hpp" #include <vector> #include <algorithm> @@ -16,7 +14,9 @@ using namespace types; #include <QDebug> -constexpr unsigned PointModel::N_POINTS; +namespace pt_impl { + +using namespace numeric_types; static void get_row(const mat33& m, int i, vec3& v) { @@ -32,12 +32,12 @@ static void set_row(mat33& m, int i, const vec3& v) m(i,2) = v[2]; } -PointModel::PointModel(settings_pt& s) +PointModel::PointModel(const pt_settings& s) { set_model(s); // calculate u u = M01.cross(M02); - u /= norm(u); + u = cv::normalize(u); // calculate projection matrix on M01,M02 plane f s11 = M01.dot(M01); @@ -46,17 +46,20 @@ PointModel::PointModel(settings_pt& s) P = 1/(s11*s22-s12*s12) * mat22(s22, -s12, -s12, s11); } -void PointModel::set_model(settings_pt& s) +void PointModel::set_model(const pt_settings& s) { switch (s.active_model_panel) { + default: + eval_once(qDebug() << "pt: wrong model type selected"); + [[fallthrough]]; case Clip: - M01 = vec3(0, static_cast<f>(s.clip_ty), -static_cast<f>(s.clip_tz)); - M02 = vec3(0, -static_cast<f>(s.clip_by), -static_cast<f>(s.clip_bz)); + M01 = vec3(0, s.clip_ty, -s.clip_tz); + M02 = vec3(0, -s.clip_by, -s.clip_bz); break; case Cap: - M01 = vec3(-static_cast<f>(s.cap_x), -static_cast<f>(s.cap_y), -static_cast<f>(s.cap_z)); - M02 = vec3(static_cast<f>(s.cap_x), -static_cast<f>(s.cap_y), -static_cast<f>(s.cap_z)); + M01 = vec3(-s.cap_x, -s.cap_y, -s.cap_z); + M02 = vec3(s.cap_x, -s.cap_y, -s.cap_z); break; case Custom: M01 = vec3(s.m01_x, s.m01_y, s.m01_z); @@ -65,69 +68,62 @@ void PointModel::set_model(settings_pt& s) } } -void PointModel::get_d_order(const vec2* points, unsigned* d_order, const vec2& d) const +void PointModel::get_d_order(const vec2* points, unsigned* d_order, const vec2& d) { + constexpr unsigned cnt = PointModel::N_POINTS; // fit line to orthographically projected points using t = std::pair<f,unsigned>; - t d_vals[3]; + t d_vals[cnt]; // get sort indices with respect to d scalar product - for (unsigned i = 0; i < PointModel::N_POINTS; ++i) - d_vals[i] = t(d.dot(points[i]), i); + for (unsigned i = 0; i < cnt; ++i) + d_vals[i] = {d.dot(points[i]), i}; - std::sort(d_vals, - d_vals + 3u, - [](const t& a, const t& b) { return a.first < b.first; }); + std::sort(std::begin(d_vals), std::end(d_vals), + [](t a, t b) { return a.first < b.first; }); - for (unsigned i = 0; i < PointModel::N_POINTS; ++i) + for (unsigned i = 0; i < cnt; ++i) d_order[i] = d_vals[i].second; } -PointTracker::PointTracker() : init_phase(true) -{ -} +PointTracker::PointTracker() = default; PointTracker::PointOrder PointTracker::find_correspondences_previous(const vec2* points, const PointModel& model, - const CamInfo& info) + const pt_camera_info& info) { - f fx; info.get_focal_length(fx); - PointTracker::PointOrder p; - p[0] = project(vec3(0,0,0), fx); - p[1] = project(model.M01, fx); - p[2] = project(model.M02, fx); + const f fx = pt_camera_info::get_focal_length(info.fov, info.res_x, info.res_y); + PointTracker::PointOrder p { + project(vec3(0,0,0), fx), + project(model.M01, fx), + project(model.M02, fx) + }; - const int diagonal = int(std::sqrt(double(info.res_x*info.res_x + info.res_y*info.res_y))); - static constexpr int div = 100; - const int max_dist = diagonal / div; // 8 pixels for 640x480 + constexpr unsigned sz = PointModel::N_POINTS; // set correspondences by minimum distance to projected model point - bool point_taken[PointModel::N_POINTS]; - for (unsigned i=0; i<PointModel::N_POINTS; ++i) - point_taken[i] = false; + bool point_taken[sz] {}; - for (unsigned i=0; i<PointModel::N_POINTS; ++i) + for (unsigned i=0; i < sz; ++i) { f min_sdist = 0; unsigned min_idx = 0; // find closest point to projected model point i - for (unsigned j=0; j<PointModel::N_POINTS; ++j) + for (unsigned j=0; j < sz; ++j) { vec2 d = p[i]-points[j]; f sdist = d.dot(d); - if (sdist < min_sdist || j==0) + if (sdist < min_sdist || j == 0) { min_idx = j; min_sdist = sdist; } } - if (min_sdist > max_dist) - return find_correspondences(points, model); // if one point is closest to more than one model point, fallback if (point_taken[min_idx]) { - init_phase = true; + reset_state(); return find_correspondences(points, model); } point_taken[min_idx] = true; @@ -139,55 +135,61 @@ PointTracker::PointOrder PointTracker::find_correspondences_previous(const vec2* void PointTracker::track(const std::vector<vec2>& points, const PointModel& model, - const CamInfo& info, - int init_phase_timeout) + const pt_camera_info& info, + int init_phase_timeout, + point_filter& filter, + f deadzone_amount) { - f fx; - info.get_focal_length(fx); + const f fx = pt_camera_info::get_focal_length(info.fov, info.res_x, info.res_y); PointOrder order; - if (init_phase_timeout > 0 && t.elapsed_ms() > init_phase_timeout) + if (init_phase || init_phase_timeout <= 0 || t.elapsed_ms() > init_phase_timeout) { - t.start(); - init_phase = true; - } - - if (!(init_phase_timeout > 0 && !init_phase)) + reset_state(); order = find_correspondences(points.data(), model); + } else order = find_correspondences_previous(points.data(), model, info); - if (POSIT(model, order, fx) != -1) + if (POSIT(model, filter(order, deadzone_amount), fx) != -1) { init_phase = false; t.start(); } + else + reset_state(); } PointTracker::PointOrder PointTracker::find_correspondences(const vec2* points, const PointModel& model) { - static const Affine a(mat33::eye(), vec3(0, 0, 1)); + constexpr unsigned cnt = PointModel::N_POINTS; // We do a simple freetrack-like sorting in the init phase... - unsigned point_d_order[PointModel::N_POINTS]; - unsigned model_d_order[PointModel::N_POINTS]; - // sort points + unsigned point_d_order[cnt]; + unsigned model_d_order[cnt]; + // calculate d and d_order for simple freetrack-like point correspondence vec2 d(model.M01[0]-model.M02[0], model.M01[1]-model.M02[1]); + // sort points model.get_d_order(points, point_d_order, d); - // calculate d and d_order for simple freetrack-like point correspondence - vec2 pts[3] = { - vec2(0, 0), - vec2(model.M01[0], model.M01[1]), - vec2(model.M02[0], model.M02[1]) + vec2 pts[cnt] { + { 0, 0 }, + { model.M01[0], model.M01[1] }, + { model.M02[0], model.M02[1] }, }; model.get_d_order(pts, model_d_order, d); + // set correspondences PointOrder p; - for (unsigned i = 0; i < PointModel::N_POINTS; ++i) + for (unsigned i = 0; i < cnt; ++i) p[model_d_order[i]] = points[point_d_order[i]]; return p; } +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wfloat-equal" +#endif + int PointTracker::POSIT(const PointModel& model, const PointOrder& order, f focal_length) { // POSIT algorithm for coplanar points as presented in @@ -196,17 +198,16 @@ int PointTracker::POSIT(const PointModel& model, const PointOrder& order, f foca // The expected rotation used for resolving the ambiguity in POSIT: // In every iteration step the rotation closer to R_expected is taken - static const mat33 R_expected(mat33::eye()); + const mat33& R_expected{X_CM_expected.R}; // initial pose = last (predicted) pose vec3 k; get_row(R_expected, 2, k); - f Z0 = f(1000); + f Z0 = X_CM.t[2] < f(1e-4) ? f(1000) : X_CM.t[2]; f old_epsilon_1 = 0; f old_epsilon_2 = 0; - f epsilon_1 = 1; - f epsilon_2 = 1; + f epsilon_1, epsilon_2; vec3 I0, J0; vec2 I0_coeff, J0_coeff; @@ -215,16 +216,10 @@ int PointTracker::POSIT(const PointModel& model, const PointOrder& order, f foca mat33 R_1, R_2; mat33* R_current = &R_1; - static constexpr int max_iter = 100; - - using std::sqrt; - using std::atan; - using std::cos; - using std::sin; - using std::fabs; + constexpr int max_iter = 100; - int i=1; - for (; i<max_iter; ++i) + int i; + for (i = 1; i < max_iter; ++i) { epsilon_1 = k.dot(model.M01)/Z0; epsilon_2 = k.dot(model.M02)/Z0; @@ -249,14 +244,14 @@ int PointTracker::POSIT(const PointModel& model, const PointOrder& order, f foca // CAVEAT don't change to comparison with an epsilon -sh 20160423 if (JJ0 == II0) { rho = sqrt(fabs(2*IJ0)); - theta = -M_PI/4; + theta = -pi/4; if (IJ0<0) theta *= -1; } else { rho = sqrt(sqrt( (JJ0-II0)*(JJ0-II0) + 4*IJ0*IJ0 )); theta = atan( -2*IJ0 / (JJ0-II0) ); // avoid branch misprediction - theta += (JJ0 - II0 < 0) * M_PI; + theta += (JJ0 - II0 < 0) * pi; theta *= f(.5); } @@ -267,7 +262,7 @@ int PointTracker::POSIT(const PointModel& model, const PointOrder& order, f foca J_1 = J0 + rho*sin(theta)*model.u; J_2 = J0 - rho*sin(theta)*model.u; - f norm_const = 1/cv::norm(I_1); // all have the same norm + f norm_const = (f)(1/cv::norm(I_1)); // all have the same norm // create rotation matrices I_1 *= norm_const; J_1 *= norm_const; @@ -286,8 +281,8 @@ int PointTracker::POSIT(const PointModel& model, const PointOrder& order, f foca // pick the rotation solution closer to the expected one // in simple metric d(A,B) = || I - A * B^T || - f R_1_deviation = cv::norm(mat33::eye() - R_expected * R_1.t()); - f R_2_deviation = cv::norm(mat33::eye() - R_expected * R_2.t()); + f R_1_deviation = (f)(cv::norm(mat33::eye() - R_expected * R_1.t())); + f R_2_deviation = (f)(cv::norm(mat33::eye() - R_expected * R_2.t())); if (R_1_deviation < R_2_deviation) R_current = &R_1; @@ -299,7 +294,7 @@ int PointTracker::POSIT(const PointModel& model, const PointOrder& order, f foca // check for convergence condition const f delta = fabs(epsilon_1 - old_epsilon_1) + fabs(epsilon_2 - old_epsilon_2); - if (!(delta > constants::eps)) + if (delta < eps) break; old_epsilon_1 = epsilon_1; @@ -315,18 +310,24 @@ int PointTracker::POSIT(const PointModel& model, const PointOrder& order, f foca for (int i = 0; i < 3; i++) for (int j = 0; j < 3; j++) - if (nanp(r(i, j))) + { + int ret = std::fpclassify(r(i, j)); + if (ret == FP_NAN || ret == FP_INFINITE) { - qDebug() << "posit nan"; + qDebug() << "posit nan R"; return -1; } + } - for (unsigned i = 0; i < 3; i++) - if (nanp(t[i])) + for (unsigned i = 0; i < 3; i++) // NOLINT(modernize-loop-convert) + { + int ret = std::fpclassify(t[i]); + if (ret == FP_NAN || ret == FP_INFINITE) { - qDebug() << "posit nan"; + qDebug() << "posit nan T"; return -1; } + } // apply results X_CM.R = r; @@ -334,11 +335,17 @@ int PointTracker::POSIT(const PointModel& model, const PointOrder& order, f foca X_CM.t[1] = t[1]; X_CM.t[2] = t[2]; + X_CM_expected = X_CM; + //qDebug() << "iter:" << i; return i; } +#ifdef __clang__ +# pragma clang diagnostic pop +#endif + vec2 PointTracker::project(const vec3& v_M, f focal_length) { return project(v_M, focal_length, X_CM); @@ -349,3 +356,11 @@ vec2 PointTracker::project(const vec3& v_M, f focal_length, const Affine& X_CM) vec3 v_C = X_CM * v_M; return vec2(focal_length*v_C[0]/v_C[2], focal_length*v_C[1]/v_C[2]); } + +void PointTracker::reset_state() +{ + init_phase = true; + X_CM_expected = {}; +} + +} // ns pt_impl diff --git a/tracker-pt/point_tracker.h b/tracker-pt/point_tracker.h index 63caf0dd..7492b4eb 100644 --- a/tracker-pt/point_tracker.h +++ b/tracker-pt/point_tracker.h @@ -8,26 +8,24 @@ #pragma once #include "compat/timer.hpp" -#include "ftnoir_tracker_pt_settings.h" -#include "affine.hpp" -#include "numeric.hpp" -#include "camera.h" - -#include <opencv2/core.hpp> -#include <cstddef> -#include <memory> +#include "cv/affine.hpp" +#include "cv/numeric.hpp" +#include "pt-api.hpp" +#include "point-filter.hpp" + #include <vector> #include <array> + #include <QObject> -namespace impl { +namespace pt_impl { // ---------------------------------------------------------------------------- // Describes a 3-point model // nomenclature as in // [Denis Oberkampf, Daniel F. DeMenthon, Larry S. Davis: "Iterative Pose Estimation Using Coplanar Feature Points"] -using namespace types; +using namespace numeric_types; struct PointModel final { @@ -42,9 +40,10 @@ struct PointModel final enum Model { Clip, Cap, Custom }; - PointModel(settings_pt& s); - void set_model(settings_pt& s); - void get_d_order(const vec2* points, unsigned* d_order, const vec2& d) const; + explicit PointModel(const pt_settings& s); + void set_model(const pt_settings& s); + + static void get_d_order(const vec2* points, unsigned* d_order, const vec2& d); }; // ---------------------------------------------------------------------------- @@ -58,26 +57,33 @@ public: // track the pose using the set of normalized point coordinates (x pos in range -0.5:0.5) // f : (focal length)/(sensor width) // dt : time since last call - void track(const std::vector<vec2>& projected_points, const PointModel& model, const CamInfo& info, int init_phase_timeout); - Affine pose() { return X_CM; } + void track(const std::vector<vec2>& projected_points, + const PointModel& model, + const pt_camera_info& info, + int init_phase_timeout, + point_filter& filter, + f deadzone_amount); + Affine pose() const { return X_CM; } vec2 project(const vec3& v_M, f focal_length); vec2 project(const vec3& v_M, f focal_length, const Affine& X_CM); + void reset_state(); private: // the points in model order using PointOrder = std::array<vec2, 3>; - PointOrder find_correspondences(const vec2* projected_points, const PointModel &model); - PointOrder find_correspondences_previous(const vec2* points, const PointModel &model, const CamInfo& info); - int POSIT(const PointModel& point_model, const PointOrder& order, f focal_length); // The POSIT algorithm, returns the number of iterations - - Affine X_CM; // trafo from model to camera + static PointOrder find_correspondences(const vec2* projected_points, const PointModel &model); + PointOrder find_correspondences_previous(const vec2* points, const PointModel &model, const pt_camera_info& info); + // The POSIT algorithm, returns the number of iterations + int POSIT(const PointModel& point_model, const PointOrder& order, f focal_length); + Affine X_CM; // transform from model to camera + Affine X_CM_expected; Timer t; - bool init_phase; + bool init_phase = true; }; -} // ns types +} // ns pt_impl -using impl::PointTracker; -using impl::PointModel; +using PointTracker = pt_impl::PointTracker; +using PointModel = pt_impl::PointModel; diff --git a/tracker-pt/pt-api.cpp b/tracker-pt/pt-api.cpp new file mode 100644 index 00000000..d71c6e13 --- /dev/null +++ b/tracker-pt/pt-api.cpp @@ -0,0 +1,53 @@ +#include "pt-api.hpp" +#include "cv/numeric.hpp" + +using namespace numeric_types; + +pt_camera_info::pt_camera_info() = default; + +f pt_camera_info::get_focal_length(f fov, int res_x, int res_y) +{ + const f diag_len = std::sqrt(f(res_x*res_x + res_y*res_y)); + const f aspect_x = res_x / diag_len; + //const double aspect_y = res_y / diag_len; + const f diag_fov = fov * pi/180; + const f fov_x = 2*std::atan(std::tan(diag_fov*f{.5}) * aspect_x); + //const double fov_y = 2*atan(tan(diag_fov*.5) * aspect_y); + const f fx = f{.5} / std::tan(fov_x * f{.5}); + return fx; + //fy = .5 / tan(fov_y * .5); + //static bool once = false; if (!once) { once = true; qDebug() << "f" << ret << "fov" << (fov * 180/M_PI); } +} + +pt_camera::pt_camera() = default; +pt_camera::~pt_camera() = default; +pt_runtime_traits::pt_runtime_traits() = default; +pt_runtime_traits::~pt_runtime_traits() = default; +pt_point_extractor::pt_point_extractor() = default; +pt_point_extractor::~pt_point_extractor() = default; + +f pt_point_extractor::threshold_radius_value(int w, int h, int threshold) +{ + f cx = w / f{640}, cy = h / f{480}; + + const f min_radius = f{1.75} * cx; + const f max_radius = f{30} * cy; + + const f radius = std::fmax(f{0}, (max_radius-min_radius) * threshold / f(255) + min_radius); + + return radius; +} + +std::tuple<f, f> pt_pixel_pos_mixin::to_pixel_pos(f x, f y, int w, int h) +{ + return { w*(x+f{.5}), f{.5}*(h - 2*y*w) }; +} + +std::tuple<f, f> pt_pixel_pos_mixin::to_screen_pos(f px, f py, int w, int h) +{ + px *= w/(w-f{1}); py *= h/(h-f{1}); + return { (px - w/f{2})/w, -(py - h/f{2})/w }; +} + +pt_frame::pt_frame() = default; +pt_frame::~pt_frame() = default; diff --git a/tracker-pt/pt-api.hpp b/tracker-pt/pt-api.hpp new file mode 100644 index 00000000..15021ff3 --- /dev/null +++ b/tracker-pt/pt-api.hpp @@ -0,0 +1,139 @@ +#pragma once + +#include "pt-settings.hpp" + +#include "cv/numeric.hpp" +#include "options/options.hpp" + +#include <tuple> +#include <vector> +#include <memory> + +#include <QImage> +#include <QString> + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wweak-vtables" +#endif + +struct pt_camera_info final +{ + using f = numeric_types::f; + + pt_camera_info(); + static f get_focal_length(f fov, int res_x, int res_y); + + f fov = 0; + f fps = 0; + + int res_x = 0; + int res_y = 0; + QString name; + bool use_mjpeg = false; +}; + +struct pt_pixel_pos_mixin +{ + using f = numeric_types::f; + + static std::tuple<f, f> to_pixel_pos(f x, f y, int w, int h); + static std::tuple<f, f> to_screen_pos(f px, f py, int w, int h); +}; + +struct pt_frame : pt_pixel_pos_mixin +{ + pt_frame(); + virtual ~pt_frame(); + + template<typename t> + t* as() & + { + return static_cast<t*>(this); + } + + template<typename t> + t const* as_const() const& + { + return static_cast<t const*>(this); + } + +protected: + pt_frame(const pt_frame&) = default; + pt_frame(pt_frame&&) = default; + pt_frame& operator=(const pt_frame&) = default; + pt_frame& operator=(pt_frame&&) = default; +}; + +struct pt_preview : pt_frame +{ + pt_preview() = default; + + OTR_DISABLE_MOVE_COPY(pt_preview); + + virtual void set_last_frame(const pt_frame&) = 0; + virtual QImage get_bitmap() = 0; + virtual void draw_head_center(f x, f y) = 0; +}; + +struct pt_camera +{ + using result = std::tuple<bool, pt_camera_info>; + using f = numeric_types::f; + + pt_camera(); + virtual ~pt_camera(); + + OTR_DISABLE_MOVE_COPY(pt_camera); + + [[nodiscard]] virtual bool start(const pt_settings& s) = 0; + virtual void stop() = 0; + + virtual result get_frame(pt_frame& frame) = 0; + virtual result get_info() const = 0; + virtual pt_camera_info get_desired() const = 0; + + virtual QString get_desired_name() const = 0; + virtual QString get_active_name() const = 0; + + virtual void set_fov(f value) = 0; + virtual void show_camera_settings() = 0; + virtual f deadzone_amount() const { return 1; } +}; + +struct pt_point_extractor : pt_pixel_pos_mixin +{ + using vec2 = numeric_types::vec2; + using f = numeric_types::f; + + OTR_DISABLE_MOVE_COPY(pt_point_extractor); + + pt_point_extractor(); + virtual ~pt_point_extractor(); + virtual void extract_points(const pt_frame& image, pt_preview& preview_frame, bool preview_visible, std::vector<vec2>& points) = 0; + + static f threshold_radius_value(int w, int h, int threshold); +}; + +struct pt_runtime_traits +{ + template<typename t> using pointer = std::shared_ptr<t>; + + OTR_DISABLE_MOVE_COPY(pt_runtime_traits); + + pt_runtime_traits(); + virtual ~pt_runtime_traits(); + + virtual pointer<pt_camera> make_camera() const = 0; + virtual pointer<pt_point_extractor> make_point_extractor() const = 0; + virtual pointer<pt_frame> make_frame() const = 0; + virtual pointer<pt_preview> make_preview(int w, int h) const = 0; + virtual QString get_module_name() const = 0; +}; + +template<typename t> +using pt_pointer = typename pt_runtime_traits::pointer<t>; + +#ifdef __clang__ +# pragma clang diagnostic pop +#endif diff --git a/tracker-pt/pt-settings.hpp b/tracker-pt/pt-settings.hpp new file mode 100644 index 00000000..5d16d973 --- /dev/null +++ b/tracker-pt/pt-settings.hpp @@ -0,0 +1,87 @@ +#pragma once + +#include "options/options.hpp" + +#include <QString> + +enum pt_color_type +{ + // explicit values, gotta preserve the numbering in .ini + // don't reuse when removing some of the modes + pt_color_bt709 = 2, + pt_color_hardware = 14, + pt_color_red_only = 3, + pt_color_blue_only = 6, + pt_color_green_only = 7, + pt_color_red_chromakey = 8, + pt_color_green_chromakey = 9, + pt_color_blue_chromakey = 10, + pt_color_cyan_chromakey = 11, + pt_color_yellow_chromakey = 12, + pt_color_magenta_chromakey = 13, +}; + +namespace pt_impl { + +using namespace options; + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wweak-vtables" +#endif + +struct pt_settings final : options::opts +{ + using slider_value = options::slider_value; + + value<QString> camera_name { b, "camera-name", "" }; + value<int> cam_res_x { b, "camera-res-width", 640 }, + cam_res_y { b, "camera-res-height", 480 }, + cam_fps { b, "camera-fps", 30 }; + value<double> min_point_size { b, "min-point-size", 2.5 }, + max_point_size { b, "max-point-size", 50 }; + + value<int> m01_x { b, "m_01-x", 0 }, m01_y { b, "m_01-y", 0 }, m01_z { b, "m_01-z", 0 }; + value<int> m02_x { b, "m_02-x", 0 }, m02_y { b, "m_02-y", 0 }, m02_z { b, "m_02-z", 0 }; + + value<int> t_MH_x { b, "model-centroid-x", 0 }, + t_MH_y { b, "model-centroid-y", 0 }, + t_MH_z { b, "model-centroid-z", 0 }; + + value<int> clip_ty { b, "clip-ty", 40 }, + clip_tz { b, "clip-tz", 30 }, + clip_by { b, "clip-by", 70 }, + clip_bz { b, "clip-bz", 80 }; + + value<int> active_model_panel { b, "active-model-panel", 0 }, + cap_x { b, "cap-x", 40 }, + cap_y { b, "cap-y", 60 }, + cap_z { b, "cap-z", 100 }; + + value<int> fov { b, "camera-fov", 56 }; + + value<bool> dynamic_pose { b, "dynamic-pose-resolution", false }; + value<int> init_phase_timeout { b, "init-phase-timeout", 250 }; + value<bool> auto_threshold { b, "automatic-threshold", true }; + value<pt_color_type> blob_color { b, "blob-color", pt_color_bt709 }; + value<bool> use_mjpeg { b, "use-mjpeg", false }; + value<slider_value> chroma_key_strength{ b, "chroma-key-strength", { 1.0, 0.5, 4. } }; + value<bool> chroma_key_overexposed{ b, "chroma-key-overexposed", false }; + + value<slider_value> threshold_slider { b, "threshold-slider", { 128, 0, 255 } }; + + value<bool> enable_point_filter{ b, "enable-point-filter", false }; + value<slider_value> point_filter_coefficient { b, "point-filter-coefficient", { 1.0, 0, 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} }; + + explicit pt_settings(const QString& name) : opts(name) {} +}; + +#ifdef __clang__ +# pragma clang diagnostic pop +#endif + +} // ns pt_impl + +using pt_settings = pt_impl::pt_settings; diff --git a/tracker-pt/ftnoir_tracker_pt.qrc b/tracker-pt/tracker_pt_base.qrc index a8f9a1af..8c270540 100644 --- a/tracker-pt/ftnoir_tracker_pt.qrc +++ b/tracker-pt/tracker_pt_base.qrc @@ -4,6 +4,5 @@ <file>Resources/cap_side.png</file> <file>Resources/clip_front.png</file> <file>Resources/clip_side.png</file> - <file>Resources/Logo_IR.png</file> </qresource> </RCC> |
