summaryrefslogtreecommitdiffhomepage
path: root/ftnoir_tracker_pt
diff options
context:
space:
mode:
Diffstat (limited to 'ftnoir_tracker_pt')
-rw-r--r--ftnoir_tracker_pt/FTNoIR_PT_Controls.ui1790
-rw-r--r--ftnoir_tracker_pt/Resources/Logo_IR.pngbin0 -> 10386 bytes
-rw-r--r--ftnoir_tracker_pt/Resources/cap_front.pngbin0 -> 1164 bytes
-rw-r--r--ftnoir_tracker_pt/Resources/cap_side.pngbin0 -> 1733 bytes
-rw-r--r--ftnoir_tracker_pt/Resources/clip_front.pngbin0 -> 571 bytes
-rw-r--r--ftnoir_tracker_pt/Resources/clip_side.pngbin0 -> 2677 bytes
-rw-r--r--ftnoir_tracker_pt/boost-compat.h5
-rw-r--r--ftnoir_tracker_pt/camera.cpp351
-rw-r--r--ftnoir_tracker_pt/camera.h145
-rw-r--r--ftnoir_tracker_pt/doc/index.htm262
-rw-r--r--ftnoir_tracker_pt/doc/logo.pngbin0 -> 10386 bytes
-rw-r--r--ftnoir_tracker_pt/doc/ptrack.icobin0 -> 4286 bytes
-rw-r--r--ftnoir_tracker_pt/doc/settings1.pngbin0 -> 25013 bytes
-rw-r--r--ftnoir_tracker_pt/doc/settings2.pngbin0 -> 26841 bytes
-rw-r--r--ftnoir_tracker_pt/doc/settings3.pngbin0 -> 29547 bytes
-rw-r--r--ftnoir_tracker_pt/doc/style.css131
-rw-r--r--ftnoir_tracker_pt/frame_observer.cpp18
-rw-r--r--ftnoir_tracker_pt/frame_observer.h76
-rw-r--r--ftnoir_tracker_pt/ftnoir_tracker_pt.cpp265
-rw-r--r--ftnoir_tracker_pt/ftnoir_tracker_pt.h95
-rw-r--r--ftnoir_tracker_pt/ftnoir_tracker_pt.qrc9
-rw-r--r--ftnoir_tracker_pt/ftnoir_tracker_pt_dialog.cpp321
-rw-r--r--ftnoir_tracker_pt/ftnoir_tracker_pt_dialog.h70
-rw-r--r--ftnoir_tracker_pt/ftnoir_tracker_pt_dll.cpp42
-rw-r--r--ftnoir_tracker_pt/ftnoir_tracker_pt_dll.h27
-rw-r--r--ftnoir_tracker_pt/ftnoir_tracker_pt_settings.h90
-rw-r--r--ftnoir_tracker_pt/point_extractor.cpp163
-rw-r--r--ftnoir_tracker_pt/point_extractor.h35
-rw-r--r--ftnoir_tracker_pt/point_tracker.cpp380
-rw-r--r--ftnoir_tracker_pt/point_tracker.h129
-rw-r--r--ftnoir_tracker_pt/pt_video_widget.cpp64
-rw-r--r--ftnoir_tracker_pt/pt_video_widget.h71
-rw-r--r--ftnoir_tracker_pt/trans_calib.cpp44
-rw-r--r--ftnoir_tracker_pt/trans_calib.h39
34 files changed, 4622 insertions, 0 deletions
diff --git a/ftnoir_tracker_pt/FTNoIR_PT_Controls.ui b/ftnoir_tracker_pt/FTNoIR_PT_Controls.ui
new file mode 100644
index 00000000..0bbec7e1
--- /dev/null
+++ b/ftnoir_tracker_pt/FTNoIR_PT_Controls.ui
@@ -0,0 +1,1790 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>UICPTClientControls</class>
+ <widget class="QWidget" name="UICPTClientControls">
+ <property name="windowModality">
+ <enum>Qt::NonModal</enum>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>459</width>
+ <height>621</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="windowTitle">
+ <string>PointTracker Settings</string>
+ </property>
+ <property name="windowIcon">
+ <iconset resource="ftnoir_tracker_pt.qrc">
+ <normaloff>:/Resources/Logo_IR.png</normaloff>:/Resources/Logo_IR.png</iconset>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="autoFillBackground">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="sizeConstraint">
+ <enum>QLayout::SetFixedSize</enum>
+ </property>
+ <item>
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+ <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>
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="tab">
+ <attribute name="title">
+ <string>General</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QGroupBox" name="groupBox_6">
+ <property name="title">
+ <string>Tracker Thread</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_43">
+ <property name="text">
+ <string>Auto-reset time</string>
+ </property>
+ <property name="buddy">
+ <cstring>reset_spin</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QSpinBox" name="reset_spin">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Time until automatic reset of tracker's internal state when no valid tracking result is found</string>
+ </property>
+ <property name="suffix">
+ <string>ms</string>
+ </property>
+ <property name="maximum">
+ <number>9999</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_17">
+ <property name="text">
+ <string>Dynamic Pose Resolution</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QCheckBox" name="dynpose_check">
+ <property name="toolTip">
+ <string/>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QPushButton" name="reset_button">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="toolTip">
+ <string>Reset the tracker's internal state</string>
+ </property>
+ <property name="text">
+ <string>Reset</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_3">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>85</height>
+ </size>
+ </property>
+ <property name="title">
+ <string>Enable Axis</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <item>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_6">
+ <property name="text">
+ <string>Roll:</string>
+ </property>
+ <property name="buddy">
+ <cstring>chkEnableRoll</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_9">
+ <property name="text">
+ <string>Pitch:</string>
+ </property>
+ <property name="buddy">
+ <cstring>chkEnablePitch</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_11">
+ <property name="text">
+ <string>Yaw:</string>
+ </property>
+ <property name="buddy">
+ <cstring>chkEnableYaw</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QCheckBox" name="chkEnableRoll">
+ <property name="maximumSize">
+ <size>
+ <width>20</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QCheckBox" name="chkEnablePitch">
+ <property name="maximumSize">
+ <size>
+ <width>20</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QCheckBox" name="chkEnableYaw">
+ <property name="maximumSize">
+ <size>
+ <width>20</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3">
+ <widget class="QLabel" name="label_14">
+ <property name="text">
+ <string>X:</string>
+ </property>
+ <property name="buddy">
+ <cstring>chkEnableX</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="4">
+ <widget class="QCheckBox" name="chkEnableX">
+ <property name="maximumSize">
+ <size>
+ <width>20</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="QLabel" name="label_15">
+ <property name="text">
+ <string>Y:</string>
+ </property>
+ <property name="buddy">
+ <cstring>chkEnableY</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="4">
+ <widget class="QCheckBox" name="chkEnableY">
+ <property name="maximumSize">
+ <size>
+ <width>20</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="3">
+ <widget class="QLabel" name="label_16">
+ <property name="text">
+ <string>Z:</string>
+ </property>
+ <property name="buddy">
+ <cstring>chkEnableZ</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="4">
+ <widget class="QCheckBox" name="chkEnableZ">
+ <property name="maximumSize">
+ <size>
+ <width>20</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <spacer name="horizontalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Minimum</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="2">
+ <spacer name="horizontalSpacer_5">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Minimum</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="2" column="2">
+ <spacer name="horizontalSpacer_8">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Minimum</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_4">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </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>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_2">
+ <attribute name="title">
+ <string>Camera</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_7">
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="toolTip">
+ <string>The camera device used as input</string>
+ </property>
+ <property name="title">
+ <string>Camera Settings</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="minimumSize">
+ <size>
+ <width>55</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Device</string>
+ </property>
+ <property name="buddy">
+ <cstring>camdevice_combo</cstring>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="camdevice_combo">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Camera device used as input</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_8">
+ <item>
+ <layout class="QGridLayout" name="gridLayout_8">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_36">
+ <property name="minimumSize">
+ <size>
+ <width>55</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Resolution</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="5">
+ <widget class="QLabel" name="label_37">
+ <property name="text">
+ <string>FPS</string>
+ </property>
+ <property name="buddy">
+ <cstring>fps_spin</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="6">
+ <widget class="QSpinBox" name="fps_spin">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Desired capture framerate</string>
+ </property>
+ <property name="maximum">
+ <number>999</number>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="label_41">
+ <property name="text">
+ <string>x</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QSpinBox" name="res_x_spin">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Desired capture width</string>
+ </property>
+ <property name="maximum">
+ <number>2000</number>
+ </property>
+ <property name="singleStep">
+ <number>10</number>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3">
+ <widget class="QSpinBox" name="res_y_spin">
+ <property name="toolTip">
+ <string>Desired capture height</string>
+ </property>
+ <property name="maximum">
+ <number>2000</number>
+ </property>
+ <property name="singleStep">
+ <number>10</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_34">
+ <property name="text">
+ <string>F/W</string>
+ </property>
+ <property name="buddy">
+ <cstring>f_dspin</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QDoubleSpinBox" name="f_dspin">
+ <property name="toolTip">
+ <string>The camera's focal length devided by its sensor width</string>
+ </property>
+ <property name="decimals">
+ <number>2</number>
+ </property>
+ <property name="singleStep">
+ <double>0.100000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="4">
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="7">
+ <spacer name="horizontalSpacer_9">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_7"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_4">
+ <property name="title">
+ <string>Camera Orientation</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_8">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_6">
+ <item>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>Pitch</string>
+ </property>
+ <property name="buddy">
+ <cstring>campitch_spin</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QSpinBox" name="campitch_spin">
+ <property name="contextMenuPolicy">
+ <enum>Qt::DefaultContextMenu</enum>
+ </property>
+ <property name="toolTip">
+ <string>The angle the camera is facing upwards</string>
+ </property>
+ <property name="minimum">
+ <number>-99</number>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_20">
+ <property name="text">
+ <string>Yaw</string>
+ </property>
+ <property name="buddy">
+ <cstring>camyaw_spin</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QSpinBox" name="camyaw_spin">
+ <property name="contextMenuPolicy">
+ <enum>Qt::DefaultContextMenu</enum>
+ </property>
+ <property name="toolTip">
+ <string>The angle the camera is facing leftwards</string>
+ </property>
+ <property name="minimum">
+ <number>-99</number>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QLabel" name="label_21">
+ <property name="text">
+ <string>deg (positve = leftwards)</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="camroll_combo">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="toolTip">
+ <string>Rotation of the camera image</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>deg (positive = upwards)</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="label_19">
+ <property name="text">
+ <string>deg</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_18">
+ <property name="text">
+ <string>Roll</string>
+ </property>
+ <property name="buddy">
+ <cstring>camroll_combo</cstring>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_10">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_2">
+ <property name="title">
+ <string>Point Extraction</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Threshold</string>
+ </property>
+ <property name="buddy">
+ <cstring>threshold_slider</cstring>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSlider" name="threshold_slider">
+ <property name="toolTip">
+ <string>Intensity threshold for point extraction</string>
+ </property>
+ <property name="maximum">
+ <number>255</number>
+ </property>
+ <property name="value">
+ <number>127</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_secondary">
+ <item>
+ <widget class="QLabel" name="label_secondary">
+ <property name="text">
+ <string>Hysteresis</string>
+ </property>
+ <property name="buddy">
+ <cstring>threshold_secondary_slider</cstring>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSlider" name="threshold_secondary_slider">
+ <property name="toolTip">
+ <string>Per pixel hysteresis width (leave left if there is little difference between dot and non-dot, move right for increased stability against pixel noise)</string>
+ </property>
+ <property name="maximum">
+ <number>255</number>
+ </property>
+ <property name="pageStep">
+ <number>1</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QLabel" name="label_7">
+ <property name="text">
+ <string>Min Diameter</string>
+ </property>
+ <property name="buddy">
+ <cstring>mindiam_spin</cstring>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="mindiam_spin">
+ <property name="toolTip">
+ <string>Minimum point diameter</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_12">
+ <property name="text">
+ <string>px</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_8">
+ <property name="text">
+ <string>Max Diameter</string>
+ </property>
+ <property name="buddy">
+ <cstring>maxdiam_spin</cstring>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="maxdiam_spin">
+ <property name="toolTip">
+ <string>Maximum point diameter</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_13">
+ <property name="text">
+ <string>px</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_4">
+ <attribute name="title">
+ <string>Model</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_16">
+ <item>
+ <widget class="QTabWidget" name="model_tabs">
+ <property name="tabShape">
+ <enum>QTabWidget::Rounded</enum>
+ </property>
+ <property name="currentIndex">
+ <number>2</number>
+ </property>
+ <property name="usesScrollButtons">
+ <bool>false</bool>
+ </property>
+ <property name="documentMode">
+ <bool>false</bool>
+ </property>
+ <property name="tabsClosable">
+ <bool>false</bool>
+ </property>
+ <widget class="QWidget" name="tab_5">
+ <attribute name="title">
+ <string>Clip</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_13">
+ <item>
+ <widget class="QGroupBox" name="groupBox_8">
+ <property name="title">
+ <string>Model Dimensions (mm)</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_16">
+ <item>
+ <widget class="QWidget" name="widget_4" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>150</width>
+ <height>160</height>
+ </size>
+ </property>
+ <widget class="QLabel" name="label_44">
+ <property name="geometry">
+ <rect>
+ <x>30</x>
+ <y>30</y>
+ <width>71</width>
+ <height>111</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="ftnoir_tracker_pt.qrc">:/Resources/clip_side.png</pixmap>
+ </property>
+ </widget>
+ <widget class="QSpinBox" name="clip_theight_spin">
+ <property name="geometry">
+ <rect>
+ <x>100</x>
+ <y>50</y>
+ <width>46</width>
+ <height>22</height>
+ </rect>
+ </property>
+ <property name="maximum">
+ <number>999</number>
+ </property>
+ </widget>
+ <widget class="QSpinBox" name="clip_tlength_spin">
+ <property name="geometry">
+ <rect>
+ <x>60</x>
+ <y>10</y>
+ <width>46</width>
+ <height>22</height>
+ </rect>
+ </property>
+ <property name="maximum">
+ <number>999</number>
+ </property>
+ </widget>
+ <widget class="QSpinBox" name="clip_bheight_spin">
+ <property name="geometry">
+ <rect>
+ <x>100</x>
+ <y>90</y>
+ <width>46</width>
+ <height>22</height>
+ </rect>
+ </property>
+ <property name="maximum">
+ <number>999</number>
+ </property>
+ </widget>
+ <widget class="QLabel" name="label_50">
+ <property name="geometry">
+ <rect>
+ <x>10</x>
+ <y>10</y>
+ <width>46</width>
+ <height>13</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Side</string>
+ </property>
+ </widget>
+ <widget class="QSpinBox" name="clip_blength_spin">
+ <property name="geometry">
+ <rect>
+ <x>40</x>
+ <y>140</y>
+ <width>46</width>
+ <height>22</height>
+ </rect>
+ </property>
+ <property name="maximum">
+ <number>999</number>
+ </property>
+ </widget>
+ <widget class="QLabel" name="label_52">
+ <property name="geometry">
+ <rect>
+ <x>70</x>
+ <y>70</y>
+ <width>16</width>
+ <height>16</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>R</string>
+ </property>
+ </widget>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="widget_3" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>140</height>
+ </size>
+ </property>
+ <widget class="QLabel" name="label_51">
+ <property name="geometry">
+ <rect>
+ <x>10</x>
+ <y>10</y>
+ <width>46</width>
+ <height>13</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Front</string>
+ </property>
+ </widget>
+ <widget class="QLabel" name="label_45">
+ <property name="geometry">
+ <rect>
+ <x>40</x>
+ <y>30</y>
+ <width>21</width>
+ <height>111</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="ftnoir_tracker_pt.qrc">:/Resources/clip_front.png</pixmap>
+ </property>
+ </widget>
+ <widget class="QLabel" name="label_53">
+ <property name="geometry">
+ <rect>
+ <x>60</x>
+ <y>70</y>
+ <width>16</width>
+ <height>16</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>R</string>
+ </property>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_6">
+ <attribute name="title">
+ <string>Cap</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_14">
+ <item>
+ <widget class="QGroupBox" name="groupBox_9">
+ <property name="title">
+ <string>Model Dimensions (mm)</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_15">
+ <item>
+ <widget class="QWidget" name="widget" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>140</width>
+ <height>130</height>
+ </size>
+ </property>
+ <widget class="QLabel" name="label_46">
+ <property name="geometry">
+ <rect>
+ <x>20</x>
+ <y>50</y>
+ <width>111</width>
+ <height>81</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="ftnoir_tracker_pt.qrc">:/Resources/cap_side.png</pixmap>
+ </property>
+ </widget>
+ <widget class="QSpinBox" name="cap_height_spin">
+ <property name="geometry">
+ <rect>
+ <x>30</x>
+ <y>80</y>
+ <width>46</width>
+ <height>22</height>
+ </rect>
+ </property>
+ <property name="maximum">
+ <number>999</number>
+ </property>
+ </widget>
+ <widget class="QLabel" name="label_54">
+ <property name="geometry">
+ <rect>
+ <x>130</x>
+ <y>50</y>
+ <width>16</width>
+ <height>16</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>R</string>
+ </property>
+ </widget>
+ <widget class="QLabel" name="label_48">
+ <property name="geometry">
+ <rect>
+ <x>10</x>
+ <y>10</y>
+ <width>46</width>
+ <height>13</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Side</string>
+ </property>
+ </widget>
+ <widget class="QSpinBox" name="cap_length_spin">
+ <property name="geometry">
+ <rect>
+ <x>50</x>
+ <y>40</y>
+ <width>46</width>
+ <height>22</height>
+ </rect>
+ </property>
+ <property name="maximum">
+ <number>999</number>
+ </property>
+ </widget>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="widget_2" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>130</height>
+ </size>
+ </property>
+ <widget class="QLabel" name="label_49">
+ <property name="geometry">
+ <rect>
+ <x>10</x>
+ <y>10</y>
+ <width>46</width>
+ <height>13</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Front</string>
+ </property>
+ </widget>
+ <widget class="QLabel" name="label_55">
+ <property name="geometry">
+ <rect>
+ <x>30</x>
+ <y>50</y>
+ <width>16</width>
+ <height>16</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>R</string>
+ </property>
+ </widget>
+ <widget class="QLabel" name="label_47">
+ <property name="geometry">
+ <rect>
+ <x>10</x>
+ <y>50</y>
+ <width>81</width>
+ <height>81</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="ftnoir_tracker_pt.qrc">:/Resources/cap_front.png</pixmap>
+ </property>
+ </widget>
+ <widget class="QSpinBox" name="cap_width_spin">
+ <property name="geometry">
+ <rect>
+ <x>50</x>
+ <y>30</y>
+ <width>46</width>
+ <height>22</height>
+ </rect>
+ </property>
+ <property name="maximum">
+ <number>999</number>
+ </property>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_7">
+ <attribute name="title">
+ <string>Custom</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_15">
+ <item>
+ <widget class="QGroupBox" name="groupBox_7">
+ <property name="title">
+ <string>Model Dimensions (mm)</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_12">
+ <item>
+ <widget class="QLabel" name="label_56">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Location of the two remaining model points&lt;br/&gt;with respect to the reference point in default pose&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_4">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_14">
+ <item>
+ <spacer name="horizontalSpacer_14">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>10</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="gridLayout_4">
+ <item row="3" column="2">
+ <widget class="QSpinBox" name="m1z_spin">
+ <property name="minimum">
+ <number>-999</number>
+ </property>
+ <property name="maximum">
+ <number>999</number>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLabel" name="label_58">
+ <property name="text">
+ <string>y:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QSpinBox" name="m1y_spin">
+ <property name="minimum">
+ <number>-999</number>
+ </property>
+ <property name="maximum">
+ <number>999</number>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLabel" name="label_57">
+ <property name="text">
+ <string>z:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_60">
+ <property name="text">
+ <string>M1:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QSpinBox" name="m1x_spin">
+ <property name="minimum">
+ <number>-999</number>
+ </property>
+ <property name="maximum">
+ <number>999</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="label_63">
+ <property name="text">
+ <string>x:</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_15">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="gridLayout_5">
+ <item row="1" column="2">
+ <widget class="QSpinBox" name="m2x_spin">
+ <property name="minimum">
+ <number>-999</number>
+ </property>
+ <property name="maximum">
+ <number>999</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="label_67">
+ <property name="text">
+ <string>x:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLabel" name="label_69">
+ <property name="text">
+ <string>z:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QSpinBox" name="m2y_spin">
+ <property name="minimum">
+ <number>-999</number>
+ </property>
+ <property name="maximum">
+ <number>999</number>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLabel" name="label_70">
+ <property name="text">
+ <string>y:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_64">
+ <property name="text">
+ <string>M2:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="2">
+ <widget class="QSpinBox" name="m2z_spin">
+ <property name="suffix">
+ <string/>
+ </property>
+ <property name="minimum">
+ <number>-999</number>
+ </property>
+ <property name="maximum">
+ <number>999</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_16">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>10</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_10">
+ <property name="title">
+ <string>Model Position (mm)</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_11">
+ <item>
+ <widget class="QLabel" name="label_59">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Translation from head center to model reference point&lt;br/&gt; in default pose&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_17">
+ <item>
+ <spacer name="horizontalSpacer_17">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>10</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="gridLayout_6">
+ <item row="3" column="1">
+ <widget class="QSpinBox" name="tz_spin">
+ <property name="suffix">
+ <string/>
+ </property>
+ <property name="minimum">
+ <number>-999</number>
+ </property>
+ <property name="maximum">
+ <number>999</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_61">
+ <property name="text">
+ <string>x:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_62">
+ <property name="text">
+ <string>y:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_66">
+ <property name="text">
+ <string>z:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QSpinBox" name="ty_spin">
+ <property name="minimum">
+ <number>-999</number>
+ </property>
+ <property name="maximum">
+ <number>999</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QSpinBox" name="tx_spin">
+ <property name="minimum">
+ <number>-999</number>
+ </property>
+ <property name="maximum">
+ <number>999</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_18">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="tcalib_button">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Calibrate</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_19">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>10</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_3">
+ <attribute name="title">
+ <string>About</string>
+ </attribute>
+ <widget class="QLabel" name="label_10">
+ <property name="geometry">
+ <rect>
+ <x>30</x>
+ <y>30</y>
+ <width>161</width>
+ <height>111</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;FTNoIR PointTracker Plugin&lt;br/&gt;Version 1.1&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;by Patrick Ruoff&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;http://ftnoirpt.sourceforge.net/&quot;&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline; color:#0000ff;&quot;&gt;Manual (external)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ <widget class="QLabel" name="label_35">
+ <property name="geometry">
+ <rect>
+ <x>200</x>
+ <y>30</y>
+ <width>141</width>
+ <height>141</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="ftnoir_tracker_pt.qrc">:/Resources/Logo_IR.png</pixmap>
+ </property>
+ </widget>
+ </widget>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_5">
+ <property name="title">
+ <string>Status</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <item>
+ <layout class="QFormLayout" name="formLayout">
+ <property name="fieldGrowthPolicy">
+ <enum>QFormLayout::AllNonFixedFieldsGrow</enum>
+ </property>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_38">
+ <property name="text">
+ <string>Camera Info:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="caminfo_label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>120</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Extracted Points:</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>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <item>
+ <widget class="QPushButton" name="btnApply">
+ <property name="text">
+ <string>Save</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_6">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="ok_button">
+ <property name="locale">
+ <locale language="English" country="UnitedStates"/>
+ </property>
+ <property name="text">
+ <string>Ok</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="cancel_button">
+ <property name="locale">
+ <locale language="English" country="UnitedStates"/>
+ </property>
+ <property name="text">
+ <string>Cancel</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>tabWidget</tabstop>
+ <tabstop>reset_spin</tabstop>
+ <tabstop>chkEnableRoll</tabstop>
+ <tabstop>chkEnablePitch</tabstop>
+ <tabstop>chkEnableYaw</tabstop>
+ <tabstop>chkEnableX</tabstop>
+ <tabstop>chkEnableY</tabstop>
+ <tabstop>chkEnableZ</tabstop>
+ <tabstop>camdevice_combo</tabstop>
+ <tabstop>res_x_spin</tabstop>
+ <tabstop>res_y_spin</tabstop>
+ <tabstop>fps_spin</tabstop>
+ <tabstop>f_dspin</tabstop>
+ <tabstop>camroll_combo</tabstop>
+ <tabstop>campitch_spin</tabstop>
+ <tabstop>camyaw_spin</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>
+ <tabstop>clip_bheight_spin</tabstop>
+ <tabstop>clip_blength_spin</tabstop>
+ <tabstop>cap_length_spin</tabstop>
+ <tabstop>cap_height_spin</tabstop>
+ <tabstop>cap_width_spin</tabstop>
+ <tabstop>m1x_spin</tabstop>
+ <tabstop>m1y_spin</tabstop>
+ <tabstop>m1z_spin</tabstop>
+ <tabstop>m2x_spin</tabstop>
+ <tabstop>m2y_spin</tabstop>
+ <tabstop>m2z_spin</tabstop>
+ <tabstop>tx_spin</tabstop>
+ <tabstop>ty_spin</tabstop>
+ <tabstop>tz_spin</tabstop>
+ <tabstop>tcalib_button</tabstop>
+ <tabstop>ok_button</tabstop>
+ <tabstop>cancel_button</tabstop>
+ </tabstops>
+ <resources>
+ <include location="ftnoir_tracker_pt.qrc"/>
+ </resources>
+ <connections>
+ <connection>
+ <sender>dynpose_check</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>reset_spin</receiver>
+ <slot>setEnabled(bool)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>172</x>
+ <y>110</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>351</x>
+ <y>112</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+ <slots>
+ <slot>startEngineClicked()</slot>
+ <slot>stopEngineClicked()</slot>
+ <slot>cameraSettingsClicked()</slot>
+ </slots>
+</ui>
diff --git a/ftnoir_tracker_pt/Resources/Logo_IR.png b/ftnoir_tracker_pt/Resources/Logo_IR.png
new file mode 100644
index 00000000..95032a25
--- /dev/null
+++ b/ftnoir_tracker_pt/Resources/Logo_IR.png
Binary files differ
diff --git a/ftnoir_tracker_pt/Resources/cap_front.png b/ftnoir_tracker_pt/Resources/cap_front.png
new file mode 100644
index 00000000..14207a67
--- /dev/null
+++ b/ftnoir_tracker_pt/Resources/cap_front.png
Binary files differ
diff --git a/ftnoir_tracker_pt/Resources/cap_side.png b/ftnoir_tracker_pt/Resources/cap_side.png
new file mode 100644
index 00000000..5ad4ee65
--- /dev/null
+++ b/ftnoir_tracker_pt/Resources/cap_side.png
Binary files differ
diff --git a/ftnoir_tracker_pt/Resources/clip_front.png b/ftnoir_tracker_pt/Resources/clip_front.png
new file mode 100644
index 00000000..04880138
--- /dev/null
+++ b/ftnoir_tracker_pt/Resources/clip_front.png
Binary files differ
diff --git a/ftnoir_tracker_pt/Resources/clip_side.png b/ftnoir_tracker_pt/Resources/clip_side.png
new file mode 100644
index 00000000..72667ac7
--- /dev/null
+++ b/ftnoir_tracker_pt/Resources/clip_side.png
Binary files differ
diff --git a/ftnoir_tracker_pt/boost-compat.h b/ftnoir_tracker_pt/boost-compat.h
new file mode 100644
index 00000000..612f2c4d
--- /dev/null
+++ b/ftnoir_tracker_pt/boost-compat.h
@@ -0,0 +1,5 @@
+#pragma once
+#include <memory>
+namespace boost {
+ using std::shared_ptr;
+}
diff --git a/ftnoir_tracker_pt/camera.cpp b/ftnoir_tracker_pt/camera.cpp
new file mode 100644
index 00000000..754533c5
--- /dev/null
+++ b/ftnoir_tracker_pt/camera.cpp
@@ -0,0 +1,351 @@
+/* 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.
+ */
+
+ #if defined(OPENTRACK_API) && defined(_WIN32)
+#include <windows.h>
+#include <dshow.h>
+#endif
+
+#include "camera.h"
+#include <string>
+#include <QDebug>
+
+using namespace cv;
+
+#if defined(OPENTRACK_API) && (defined(__unix) || defined(__linux) || defined(__APPLE__))
+#include <unistd.h>
+#endif
+
+#ifdef OPENTRACK_API
+void get_camera_device_names(std::vector<std::string>& device_names) {
+# if defined(_WIN32)
+ // Create the System Device Enumerator.
+ HRESULT hr;
+ ICreateDevEnum *pSysDevEnum = NULL;
+ hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void **)&pSysDevEnum);
+ if (FAILED(hr))
+ {
+ return;
+ }
+ // Obtain a class enumerator for the video compressor category.
+ IEnumMoniker *pEnumCat = NULL;
+ hr = pSysDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnumCat, 0);
+
+ if (hr == S_OK) {
+ // Enumerate the monikers.
+ IMoniker *pMoniker = NULL;
+ ULONG cFetched;
+ while (pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK) {
+ IPropertyBag *pPropBag;
+ hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pPropBag);
+ if (SUCCEEDED(hr)) {
+ // To retrieve the filter's friendly name, do the following:
+ VARIANT varName;
+ VariantInit(&varName);
+ hr = pPropBag->Read(L"FriendlyName", &varName, 0);
+ if (SUCCEEDED(hr))
+ {
+ auto wstr = std::wstring(varName.bstrVal);
+ auto str = std::string(wstr.begin(), wstr.end());
+ device_names.push_back(str);
+ }
+ VariantClear(&varName);
+
+ ////// To create an instance of the filter, do the following:
+ ////IBaseFilter *pFilter;
+ ////hr = pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter,
+ //// (void**)&pFilter);
+ // Now add the filter to the graph.
+ //Remember to release pFilter later.
+ pPropBag->Release();
+ }
+ pMoniker->Release();
+ }
+ pEnumCat->Release();
+ }
+ pSysDevEnum->Release();
+# else
+ for (int i = 0; i < 16; i++) {
+ char buf[128];
+ sprintf(buf, "/dev/video%d", i);
+ if (access(buf, R_OK | W_OK) == 0) {
+ device_names.push_back(std::string(buf));
+ }
+ }
+# endif
+}
+#else
+// ----------------------------------------------------------------------------
+void get_camera_device_names(std::vector<std::string>& device_names)
+{
+ videoInput VI;
+ VI.listDevices();
+ std::string device_name;
+ for(int index = 0; ; ++index) {
+ device_name = VI.getDeviceName(index);
+ if (device_name.empty()) break;
+ device_names.push_back(device_name);
+ }
+}
+#endif
+
+// ----------------------------------------------------------------------------
+void Camera::set_device_index(int index)
+{
+ if (desired_index != index)
+ {
+ desired_index = index;
+ _set_device_index();
+
+ // reset fps
+ dt_valid = 0;
+ dt_mean = 0;
+ active_index = index;
+ }
+}
+
+void Camera::set_f(float f)
+{
+ if (cam_desired.f != f)
+ {
+ cam_desired.f = f;
+ _set_f();
+ }
+}
+void Camera::set_fps(int fps)
+{
+ if (cam_desired.fps != fps)
+ {
+ cam_desired.fps = fps;
+ _set_fps();
+ }
+}
+
+void Camera::set_res(int x_res, int y_res)
+{
+ if (cam_desired.res_x != x_res || cam_desired.res_y != y_res)
+ {
+ cam_desired.res_x = x_res;
+ cam_desired.res_y = y_res;
+ _set_res();
+ _set_fps();
+ }
+}
+
+bool Camera::get_frame(float dt, cv::Mat* frame)
+{
+ bool new_frame = _get_frame(frame);
+ // measure fps of valid frames
+ const float dt_smoothing_const = 0.9;
+ dt_valid += dt;
+ if (new_frame)
+ {
+ dt_mean = dt_smoothing_const * dt_mean + (1.0 - dt_smoothing_const) * dt_valid;
+ cam_info.fps = 1.0 / dt_mean;
+ dt_valid = 0;
+ }
+ return new_frame;
+}
+
+// ----------------------------------------------------------------------------
+#ifdef OPENTRACK_API
+void CVCamera::start()
+{
+ cap = new VideoCapture(desired_index);
+ // extract camera info
+ if (cap->isOpened())
+ {
+ active = true;
+ active_index = desired_index;
+ cam_info.res_x = cap->get(CV_CAP_PROP_FRAME_WIDTH);
+ cam_info.res_y = cap->get(CV_CAP_PROP_FRAME_HEIGHT);
+ } else {
+ delete cap;
+ cap = nullptr;
+ }
+}
+
+void CVCamera::stop()
+{
+ if (cap)
+ {
+ cap->release();
+ delete cap;
+ }
+ active = false;
+}
+
+bool CVCamera::_get_frame(Mat* frame)
+{
+ if (cap && cap->isOpened())
+ {
+ Mat img;
+ /*
+ * XXX some Windows webcams fail to decode first
+ * frames and then some every once in a while
+ * -sh
+ */
+ for (int i = 0; i < 100 && !cap->read(img); i++)
+ ;;
+
+ if (img.empty())
+ return false;
+
+ *frame = img;
+ return true;
+ }
+ return false;
+}
+
+void CVCamera::_set_index()
+{
+ if (active) restart();
+}
+
+void CVCamera::_set_f()
+{
+ cam_info.f = cam_desired.f;
+}
+
+void CVCamera::_set_fps()
+{
+ if (cap) cap->set(CV_CAP_PROP_FPS, cam_desired.fps);
+}
+
+void CVCamera::_set_res()
+{
+ if (cap)
+ {
+ cap->set(CV_CAP_PROP_FRAME_WIDTH, cam_desired.res_x);
+ cap->set(CV_CAP_PROP_FRAME_HEIGHT, cam_desired.res_y);
+ cam_info.res_x = cap->get(CV_CAP_PROP_FRAME_WIDTH);
+ cam_info.res_y = cap->get(CV_CAP_PROP_FRAME_HEIGHT);
+ }
+}
+void CVCamera::_set_device_index()
+{
+ if (cap)
+ {
+ cap->release();
+ delete cap;
+ }
+ cap = new VideoCapture(desired_index);
+}
+
+#else
+// ----------------------------------------------------------------------------
+VICamera::VICamera() : frame_buffer(NULL)
+{
+ VI.listDevices();
+}
+
+void VICamera::start()
+{
+ if (desired_index >= 0)
+ {
+ if (cam_desired.res_x == 0 || cam_desired.res_y == 0)
+ VI.setupDevice(desired_index);
+ else
+ VI.setupDevice(desired_index, cam_desired.res_x, cam_desired.res_y);
+
+ active = true;
+ active_index = desired_index;
+
+ cam_info.res_x = VI.getWidth(active_index);
+ cam_info.res_y = VI.getHeight(active_index);
+ new_frame = cv::Mat(cam_info.res_y, cam_info.res_x, CV_8UC3);
+ // If matrix is not continuous we have to copy manually via frame_buffer
+ if (!new_frame.isContinuous()) {
+ unsigned int size = VI.getSize(active_index);
+ frame_buffer = new unsigned char[size];
+ }
+ }
+}
+
+void VICamera::stop()
+{
+ if (active)
+ {
+ VI.stopDevice(active_index);
+ }
+ if (frame_buffer)
+ {
+ delete[] frame_buffer;
+ frame_buffer = NULL;
+ }
+ active = false;
+}
+
+bool VICamera::_get_frame(Mat* frame)
+{
+ if (active && VI.isFrameNew(active_index))
+ {
+ if (new_frame.isContinuous())
+ {
+ VI.getPixels(active_index, new_frame.data, false, true);
+ }
+ else
+ {
+ // If matrix is not continuous we have to copy manually via frame_buffer
+ VI.getPixels(active_index, frame_buffer, false, true);
+ new_frame = cv::Mat(cam_info.res_y, cam_info.res_x, CV_8UC3, frame_buffer).clone();
+ }
+ *frame = new_frame;
+ return true;
+ }
+ return false;
+}
+
+void VICamera::_set_device_index()
+{
+ if (active) restart();
+}
+
+void VICamera::_set_f()
+{
+ cam_info.f = cam_desired.f;
+}
+
+void VICamera::_set_fps()
+{
+ bool was_active = active;
+ if (active) stop();
+ VI.setIdealFramerate(desired_index, cam_desired.fps);
+ if (was_active) start();
+}
+
+void VICamera::_set_res()
+{
+ if (active) restart();
+}
+#endif
+
+// ----------------------------------------------------------------------------
+Mat FrameRotation::rotate_frame(Mat frame)
+{
+ switch (rotation)
+ {
+ case CLOCKWISE:
+ {
+ Mat dst;
+ transpose(frame, dst);
+ flip(dst, dst, 1);
+ return dst;
+ }
+
+ case COUNTER_CLOCKWISE:
+ {
+ Mat dst;
+ transpose(frame, dst);
+ flip(dst, dst, 0);
+ return dst;
+ }
+
+ default:
+ return frame;
+ }
+}
diff --git a/ftnoir_tracker_pt/camera.h b/ftnoir_tracker_pt/camera.h
new file mode 100644
index 00000000..ea68c387
--- /dev/null
+++ b/ftnoir_tracker_pt/camera.h
@@ -0,0 +1,145 @@
+/* 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.
+ */
+
+#ifndef CAMERA_H
+#define CAMERA_H
+
+#include <opencv2/opencv.hpp>
+#ifndef OPENTRACK_API
+# include <boost/shared_ptr.hpp>
+#else
+# include "FTNoIR_Tracker_PT/boost-compat.h"
+# include <opencv2/highgui/highgui.hpp>
+# include <opencv2/highgui/highgui_c.h>
+#endif
+#include <string>
+
+// ----------------------------------------------------------------------------
+void get_camera_device_names(std::vector<std::string>& device_names);
+
+
+// ----------------------------------------------------------------------------
+struct CamInfo
+{
+ CamInfo() : res_x(0), res_y(0), fps(0), f(1) {}
+
+ int res_x;
+ int res_y;
+ int fps;
+ float f; // (focal length) / (sensor width)
+};
+
+// ----------------------------------------------------------------------------
+// Base class for cameras, calculates the frame rate
+class Camera
+{
+public:
+ Camera() : dt_valid(0), dt_mean(0), desired_index(0), active_index(-1), active(false) {}
+ virtual ~Camera() {}
+
+ // start/stop capturing
+ virtual void start() = 0;
+ virtual void stop() = 0;
+ void restart() { stop(); start(); }
+
+ // calls corresponding template methods and reinitializes frame rate calculation
+ void set_device_index(int index);
+ void set_f(float f);
+ void set_fps(int fps);
+ void set_res(int x_res, int y_res);
+
+ // gets a frame from the camera, dt: time since last call in seconds
+ bool get_frame(float dt, cv::Mat* frame);
+
+ // WARNING: returned references are valid as long as object
+ const CamInfo& get_info() const { return cam_info; }
+ const CamInfo& get_desired() const { return cam_desired; }
+
+protected:
+ // get a frame from the camera
+ virtual bool _get_frame(cv::Mat* frame) = 0;
+
+ // update the camera using cam_desired, write res and f to cam_info if successful
+ virtual void _set_device_index() = 0;
+ virtual void _set_f() = 0;
+ virtual void _set_fps() = 0;
+ virtual void _set_res() = 0;
+
+ float dt_valid;
+ float dt_mean;
+ int desired_index;
+ int active_index;
+ bool active;
+ CamInfo cam_info;
+ CamInfo cam_desired;
+};
+
+
+// ----------------------------------------------------------------------------
+// camera based on OpenCV's videoCapture
+#ifdef OPENTRACK_API
+class CVCamera : public Camera
+{
+public:
+ CVCamera() : cap(NULL) {}
+ ~CVCamera() { stop(); }
+
+ virtual void start();
+ virtual void stop();
+
+protected:
+ virtual bool _get_frame(cv::Mat* frame);
+ virtual void _set_index();
+ virtual void _set_f();
+ virtual void _set_fps();
+ virtual void _set_res();
+ virtual void _set_device_index();
+
+ cv::VideoCapture* cap;
+};
+#else
+// ----------------------------------------------------------------------------
+// Camera based on the videoInput library
+class VICamera : public Camera
+{
+public:
+ VICamera();
+ ~VICamera() { stop(); }
+
+ virtual void start();
+ virtual void stop();
+
+protected:
+ virtual bool _get_frame(cv::Mat* frame);
+ virtual void _set_device_index();
+ virtual void _set_f();
+ virtual void _set_fps();
+ virtual void _set_res();
+
+ videoInput VI;
+ cv::Mat new_frame;
+ unsigned char* frame_buffer;
+};
+#endif
+
+enum RotationType
+{
+ CLOCKWISE = 0,
+ ZERO = 1,
+ COUNTER_CLOCKWISE = 2
+};
+
+// ----------------------------------------------------------------------------
+class FrameRotation
+{
+public:
+ RotationType rotation;
+
+ cv::Mat rotate_frame(cv::Mat frame);
+};
+
+#endif //CAMERA_H
diff --git a/ftnoir_tracker_pt/doc/index.htm b/ftnoir_tracker_pt/doc/index.htm
new file mode 100644
index 00000000..87b7356f
--- /dev/null
+++ b/ftnoir_tracker_pt/doc/index.htm
@@ -0,0 +1,262 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+
+<head>
+ <title>FTNoIR PointTracker Help</title>
+
+ <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
+
+ <meta name="author"
+ content="Patrick Ruoff (C14)"/>
+ <meta name="keywords"
+ content="facetracknoir infrared point model tracker plugin"/>
+ <meta name="description"
+ content="Pointtracker plugin for FaceTrackNoIR"/>
+
+ <link rel="shortcut icon" href="ptrack.ico" type="image/vnd.microsoft.icon" />
+ <link rel="stylesheet" type="text/css" href="style.css" />
+</head>
+
+<body>
+<div id="navbar">
+<ul class="navbar">
+<li class="navbar"><a class="navbar" href="#about">About</a></li>
+<li class="navbar"><a class="navbar" href="#settings">Settings</a></li>
+<li class="navbar"><a class="navbar" href="#setup">Filter Setup</a></li>
+<li class="navbar"><a class="navbar" href="#support">Support</a></li>
+<li class="navbar"><a class="navbar" href="#changelog">ChangeLog</a></li>
+<li class="navbar"><a class="navbar" href="#build_instructions">Build Instructions</a></li>
+</ul>
+</div>
+
+<div id="content">
+<div style="text-align:center"><h1>FaceTrackNoIR PointTracker Plugin</h1><img src="logo.png" alt="PointTracker Plugin Logo" /></div>
+
+<a class="nav" id="about"></a>
+<h2>About</h2>
+<div class="indent">
+<p>
+PointTracker is a plugin for the free head tracking software <a href="http://facetracknoir.sourceforge.net">FaceTrackNoIR</a>
+which introduces the capability to track a (typically IR-) point model comprising 3 bright points to FaceTrackNoIR,
+much like the popular free tracking software <a href="http://www.free-track.net/">Freetrack</a> does.<br/>
+It was created as a stable modular alternative to Freetrack, which has some stability issues with newer systems and seems to be no longer actively developped.
+</p>
+</div>
+
+<a class="nav" id="settings"></a>
+<h2>Settings</h2>
+<div class="indent">
+<p>
+This section desribes the various settings of the PointTracker plugin in detail.
+</p>
+
+<img src="settings1.png" alt="Settings Pane 1"/>
+<dl>
+<dt>Show VideoWidget</dt><dd>Whether the video widget is updated or not. It may save some performance to turn this off when not needed</dd>
+<dt>Sleep time</dt><dd>Time the tracking thread sleeps after each processed image. It's inverse should be below the framefrate you want to achieve.
+(check the framerate in the status region when tracker is active, in case the sleep time is too high, the framerate will decrease).
+Low values will result in more CPU-load.</dd>
+<dt>Dynamic Pose Resolution</dt><dd>Whether the point correspondence and pose ambiquity is resolved using a more sophisticated dynamic algorithm (constant velocity prediction) or a simple static resolution.
+Dynamic pose resolution can capture more extreme poses but may occasionally get stuck in a wrong pose estimates so that a reset of the internal state becomes neccessary.</dd>
+<dt>Auto-reset time</dt><dd>If no valid tracking result can be found when using dynamic pose resolution, the tracker will automatically reset its internal state (used for resolving the pose ambiguity and point correspondence)
+and return to a fail-safe initialization phase that assumes a neutral pose after this time.
+Decrease this time, if you get stuck in a wrong pose too often.</dd>
+<dt>Reset</dt><dd>Manually reset the trackers internal state used for dynamic pose resolution and return to a fail-safe initialization phase that assumes a neutral pose.
+You may use this in case you get stuck in a wrong pose.</dd>
+<dt>Enable Axis ...</dt><dd>Which axis to use for FTNoIR.</dd>
+</dl>
+
+<img src="settings2.png" alt="Settings Pane 2"/>
+<dl>
+<dt>Device</dt><dd>The camera used for tracking.</dd>
+<dt>Resolution</dt><dd>The desired capture resolution. If your camera does not support the entered resolution the true output resolution may be different or even invalid.
+You may check the true capture resolution in the status area while the tracker is running. A higher resolution results in more accurate point positions and will increase the
+stability of the tracking result, as long as the signal/noise ratio is sufficiently high.</dd>
+<dt>FPS</dt><dd>The desired capture framerate. Again, if your camera does not support the entered framerate, the true caputre framerate may be different or invalid.
+You may check the true processing framerate in the status area while the tracker is running.</dd>
+<dt>F/W</dt><dd>The focal length of the camera divided by the sensor width (of course in the same units).
+In case you don't have access to your camera's specifications, you can measure this yourself by placing a plane object of known width (for example a piece of cardboard) in front of the camera until it fills the whole image width.
+Then measure the distance between the object and the camera and divide by the object width.</dd>
+<dt>VideoWidget</dt><dd>Shows a resizable stand-alone video widget that shows the same content as the integrated video widget in FTNoIR.
+Update rate is only 10 fps and may lag behind a bit. Mainly useful during calibration of the point extraction. Same as for the integrated wiget, to save resources, this widget should only be shown when needed.</dd>
+<dt>Roll Pitch Yaw...</dt><dd>The orientation of the camera relative to the reference frame.
+If these angles are setup properly, the direction of translations may not be correct.
+Roll is treated in a special way since it is implemented as a frame rotation by +/- 90 deg that is transparent to the rest of the processing pipeline.
+</dd>
+<dt>Threshold</dt><dd>The threshold for point recognition. Areas above the threshold are shown in blue in the VideoWidget.
+Since point accuracy is best if the points are as big as possible in pixels, the theshold should be chosen as low as possible (stop before the contour of the points becomes "noisy").
+If small reflections are being falsely classified as points, increasing the minimum point diameter (see below) may help.</dd>
+<dt>Min Diameter</dt><dd>Minimum diameter of blobs to be classified as a pointmodel-point.</dd>
+<dt>Max Diameter</dt><dd>Maximum diameter of blobs to be classified as a pointmodel-point.</dd>
+<dt>Status</dt><dd>The tracker's status is shown in this area while the tracker is running.
+The FPS shown here correspond to the framerate of the whole tracker processing chain and may be lower than what your camera is able to provide, when<br/>
+1. The processing gets not enough CPU time<br/>
+2. The sleep time of the tracking thread is set too high<br/></dd>
+</dl>
+
+<img src="settings3.png" alt="Settings Pane 3"/>
+<dl>
+<dt>Model Selection and Dimensions ...</dt><dd>
+First select your model type (point, clip, custom), then enter the dimensions of your model in milimeters here.<br/>
+For the custom setting, the coordinates of the two remaining model points have to be entered (reference point M0 is at (0,0,0)) in a pose where the model roughly faces the camera.
+For orientation, the coordinates for the standard Freetrack clip are (0,40,-30), (0,-70,-80) and the ones for the cap (40,-60,-100), (-40,-60,-100).<br/>
+When using a custom point-model configuration, the following restrictions should be observed:<br/>
+The plane in which the 3 points lie should never be parallel to the image plane, M0-M1 and M0-M2 should be roughly perpendicular.</dd>
+
+<dt>Model Position</dt><dd>The vector from the model to the center of the head in the model frame. Can be calibrated automatically.</dd>
+<dt>Calibrate</dt><dd>In order to automatically calibrate the model-head offset, do the following:<br/>Press the Calibrate button, then look around while not moving your shoulder. (i.e. only rotation, no translation).
+Do not stay in one pose for too long. The current translation estimate will be updated in real time. As soon as the values stabilized sufficiently, press the Calibrate button again to stop the calibration process.</dd>
+</dl>
+</div>
+
+<a class="nav" id="setup"></a>
+<h2>Filter Setup</h2>
+<div class="indent">
+<p>
+This section desribes how the FTNoIR filter work and what the recommended settings for PointTracker are.
+</p>
+<p>
+Filtering is always a tradeoff between stability, accuracy and responsiveness.
+</p>
+<p>
+The <q>Smoothing</q> filter in FTNoIR is just a simple average over the last n samples.
+Since this filter produces input lag no matter how fast the head-movements are, it is recommended to turn it off by setting samples to 1.
+</p>
+<p>
+In the filter tab, it is recommended to select <q>Accela Filter Mk2</q>.
+Accela is a non-linear filter that works as follows:<br/>
+It looks at the difference between the new raw values <i>new_val</i> from the tracker and the last filtered value <i>old_val</i>
+and maps this difference via the customizable response function <i>f</i> via:<br/>
+</p>
+<p style="text-align: center">
+<i>new_val = old_val + f(new_val - old_val) / reduction_factor</i>
+</p>
+<p>
+So by setting <i>f(x) = reduction_factor * x</i>, one will get no filtering at all.<br/>
+If you set lower values for small x, small deviations (usually noise) will get dampened.
+This results in a dynamic dead-zone around the current position.
+</p>
+<p>
+The last two points are used by accela to extrapolate for large deviations.
+So in order to get a fast unfiltered response for large deviations, the line connecting the last two points should have a slope >= <i>reduction_factor</i>.
+</p>
+<p>
+More aggressive accela settings than the default FTNoIR accela settings are recommended in order to decrease the filtering lag and fully use the potential of point tracking.<br/>
+My current settings are:
+</p>
+<pre class="indent"><code>
+[Accela]
+Reduction=20
+
+[Curves-Accela-Scaling-Rotation]
+point-count=4
+point-0-x=0.1
+point-0-y=0
+point-1-x=1.43
+point-1-y=2.45
+point-2-x=2.0
+point-2-y=5.44
+point-3-x=2.06
+point-3-y=6
+</code></pre>
+<p>
+The curve is not too different from the standard one (except that I like a small dynamic dead zone for steady aiming, that's why the curve has a slope of 0 at the beginning).<br/>
+However, the reduction factor is decreased to a value of 20 (compared to the standard value of 100). This implies that each value of the curve is effectively 5 times higher than in standard FTNoIR (see formula above), which means higher responsiveness but can also lead to jitter/shaking.<br/>
+Keep in mind that there are no <q>best filter settings</q>. Since filtering is always a compromise it's a matter of personal taste and
+playing around with the filter settings is highly recommended.
+</p>
+</div>
+
+<a class="nav" id="support"></a>
+<h2>Support</h2>
+<div class="indent">
+<p>
+For questions/feedback about the plugin, post to the <a href="https://sourceforge.net/projects/facetracknoir/forums">FTNoIR-Forum</a>.<br/>
+In case you like this plugin and would like to support the author, you may consider making a donation.
+</p>
+<div style="text-align:center">
+<form action="https://www.paypal.com/cgi-bin/webscr" method="post">
+<fieldset class="blind">
+<input type="hidden" name="cmd" value="_s-xclick"/>
+<input type="hidden" name="encrypted" value="-----BEGIN PKCS7-----MIIHJwYJKoZIhvcNAQcEoIIHGDCCBxQCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYCa+2zPZ+6vFPqveJsBIjFLpy54m7tl0AdojRr/K5qa3QJDyRBhGwGAP2jRihkmZFE2oKlfLpkz7nrwOQY/wFEPkggO+cABxUfjcQVpIupHEtwdV0hMklLs0RmACJy802yfi1yTiCpJ4hvWN+VfUI3gOiZ9uRZ3L4iGXES7xtqJbDELMAkGBSsOAwIaBQAwgaQGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIeopHzcJ8XBOAgYCYJFyTejSplEOwF21aQ01qQOads9Z+RUVI+hlvM/pHTjimaZPKSis3poAeqv6wKn40DpLNxDnmcT+Y9KXhrV+Gy4GZCPaeNzq2vquQ2ZVN0fTr84QVmKqPkjMBGmJAHSLCcZswUddemJgoD1uyvS0kNbchvxw7gDXJnJeBRNyXXKCCA4cwggODMIIC7KADAgECAgEAMA0GCSqGSIb3DQEBBQUAMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTAeFw0wNDAyMTMxMDEzMTVaFw0zNTAyMTMxMDEzMTVaMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwUdO3fxEzEtcnI7ZKZL412XvZPugoni7i7D7prCe0AtaHTc97CYgm7NsAtJyxNLixmhLV8pyIEaiHXWAh8fPKW+R017+EmXrr9EaquPmsVvTywAAE1PMNOKqo2kl4Gxiz9zZqIajOm1fZGWcGS0f5JQ2kBqNbvbg2/Za+GJ/qwUCAwEAAaOB7jCB6zAdBgNVHQ4EFgQUlp98u8ZvF71ZP1LXChvsENZklGswgbsGA1UdIwSBszCBsIAUlp98u8ZvF71ZP1LXChvsENZklGuhgZSkgZEwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAgV86VpqAWuXvX6Oro4qJ1tYVIT5DgWpE692Ag422H7yRIr/9j/iKG4Thia/Oflx4TdL+IFJBAyPK9v6zZNZtBgPBynXb048hsP16l2vi0k5Q2JKiPDsEfBhGI+HnxLXEaUWAcVfCsQFvd2A1sxRr67ip5y2wwBelUecP3AjJ+YcxggGaMIIBlgIBATCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTEyMDkyMzA5NTcwOFowIwYJKoZIhvcNAQkEMRYEFG/qW7uo4R4m5uFYegcZaZsTPAcUMA0GCSqGSIb3DQEBAQUABIGAGygLfrR6IQbG2xZY2OrwKkfmRwiwtnXpLBnSbnWb7XxUOMhvM6962RiKBQBGP0+XYw0S9yu8ZHx7tqz/3bcMfGjtz7PwixYx6Rm8Z29ja78aUy5FmU7fc9yAWFxLHptSliK1dJBPxdQa9J2YSDvPQPAj+AdB9sJvqJoMoxTFGM4=-----END PKCS7-----
+"/>
+<input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif" name="submit" alt="PayPal - The safer, easier way to pay online!"/>
+<img alt="" src="https://www.paypalobjects.com/de_DE/i/scr/pixel.gif" width="1" height="1"/>
+</fieldset>
+</form>
+</div>
+</div>
+
+<a class="nav" id="changelog"></a>
+<h2>ChangeLog</h2>
+<div class="indent">
+<h3>1.1</h3>
+<ul>
+<li>Added camera yaw and roll correction (intended for vertically mounted cameras)</li>
+<li>Improved point extraction algorithm, thanks to Michael Welter</li>
+<li>UI improvements: Select camera by device name, different VideoWidget architecture</li>
+<li>Bugfixes: Removed 99 FPS limitation</li>
+</ul>
+
+<h3>1.0</h3>
+<ul>
+<li>Added camera pitch correction</li>
+<li>Better communication with FTNoIR: output axis configuration, status report</li>
+</ul>
+
+<h3>1.0 beta</h3>
+<ul>
+<li>Switchted to videoInput library for capture. Desired capture resolution and fps can now be customized</li>
+<li>Introduced dynamic point-correspondence and POSIT-ambiguity resolution, which allows for the reconstruction of more extreme poses</li>
+<li>More convenient freetrack-like model dimension GUI</li>
+<li>Bugfixes: VideoWidget skipping frames, Timer resolution too low for accurate FPS measurement</li>
+</ul>
+</div>
+
+<a class="nav" id="build_instructions"></a>
+<h2>Build Instructions</h2>
+<div class="indent">
+<p>
+This section describes what you need to do in order to build PointTracker yourself.<br/>
+You can find the sources at the <a href="https://sourceforge.net/projects/ftnoirpt/">project site</a>
+or as part of the <a href="https://sourceforge.net/projects/facetracknoir/">FTNoIR sources</a>.
+</p>
+<p> The project was created with Visual Studio. </p>
+
+<h3>Dependencies</h3>
+<ul>
+<li>Qt 4.8.2 library</li>
+<li>Qt plugin for Visual studio</li>
+<li>OpenCV 2.4 prebuilt for Windows</li>
+<li>Boost 1.47</li>
+</ul>
+
+<h3>Details</h3>
+<div class="indent">
+<h4>Common</h4>
+<ul>
+<li>setup environment variable "QTDIR" (example value "D:\Devel\Libs\Qt\4.8.2")</li>
+<li>add "%QTDIR%\bin" to PATH</li>
+<li>setup environment variable "BOOST_DIR" (example value "D:\Devel\Libs\boost_1_47_0")</li>
+<li>setup environment variable "OPENCV_DIR" (example value "D:\Devel\Libs\opencv\build")</li>
+</ul>
+<h4>Debug</h4>
+<p>opencv linked dynamically:</p>
+<ul>
+<li>add "%OPENCV_DIR%\x86\vc9\bin" to PATH</li>
+</ul>
+<p>(in case of different Visual studio, change PATH and linker dependencies accordingly)</p>
+<h4>Release</h4>
+<p>opencv linked statically:</p>
+<ul>
+<li>custom build a statically linked version of opencv with the buil-option BUILD_WITH_STATIC_CRT set to OFF!</li>
+<li>copy resulting libaries to "%OPENCV_DIR%\x86\vc9\static_lib"</li>
+</ul>
+<p>(in case of different Visual studio, change PATH and linker dependencies accordingly)</p>
+</div>
+</div>
+
+</div>
+
+</body>
+</html> \ No newline at end of file
diff --git a/ftnoir_tracker_pt/doc/logo.png b/ftnoir_tracker_pt/doc/logo.png
new file mode 100644
index 00000000..95032a25
--- /dev/null
+++ b/ftnoir_tracker_pt/doc/logo.png
Binary files differ
diff --git a/ftnoir_tracker_pt/doc/ptrack.ico b/ftnoir_tracker_pt/doc/ptrack.ico
new file mode 100644
index 00000000..c4b2aedc
--- /dev/null
+++ b/ftnoir_tracker_pt/doc/ptrack.ico
Binary files differ
diff --git a/ftnoir_tracker_pt/doc/settings1.png b/ftnoir_tracker_pt/doc/settings1.png
new file mode 100644
index 00000000..35b84c5c
--- /dev/null
+++ b/ftnoir_tracker_pt/doc/settings1.png
Binary files differ
diff --git a/ftnoir_tracker_pt/doc/settings2.png b/ftnoir_tracker_pt/doc/settings2.png
new file mode 100644
index 00000000..c6cfd1f3
--- /dev/null
+++ b/ftnoir_tracker_pt/doc/settings2.png
Binary files differ
diff --git a/ftnoir_tracker_pt/doc/settings3.png b/ftnoir_tracker_pt/doc/settings3.png
new file mode 100644
index 00000000..5922403d
--- /dev/null
+++ b/ftnoir_tracker_pt/doc/settings3.png
Binary files differ
diff --git a/ftnoir_tracker_pt/doc/style.css b/ftnoir_tracker_pt/doc/style.css
new file mode 100644
index 00000000..a8d3e333
--- /dev/null
+++ b/ftnoir_tracker_pt/doc/style.css
@@ -0,0 +1,131 @@
+body {
+ width: 1000px;
+ font-size: 13px;
+ color: #000000;
+ padding: 0;
+ margin: 0 auto;
+ background: #444444;
+ font-family: verdana,arial;
+}
+
+table {
+ border-width: 3px;
+ border-color: #0000FF;
+ border-style: ridge;
+ margin-top: 5px;
+ background-color: #E0E0FF;
+}
+
+table.blind {
+ border: none;
+ background-color: #E6E6E6;
+}
+
+fieldset.blind {
+ border: none;
+}
+
+h1 { font-size: 160%; }
+h2 { font-size: 140%; }
+h3 { font-size: 115%; }
+
+.indent {
+ margin-left: 25px;
+}
+
+p
+{
+ margin-left: 10px;
+}
+
+li
+{
+ margin: 10px;
+}
+
+
+dl
+{
+ /*width: 80%;*/
+ border-bottom: 1px solid #999;
+}
+
+dt
+{
+ padding-top: 5px;
+ font-weight: bold;
+ border-top: 1px solid #999;
+}
+
+dd
+{
+ padding: 5px;
+}
+
+
+hr {
+ color: #688938;
+}
+
+a:link, a:visited {
+ color: #0000BF;
+}
+a:hover {
+ color: #0000FF;
+}
+
+a.nav {
+ position: relative;
+ top: -30px;
+ display: block;
+ visibility: hidden;
+}
+
+#navbar {
+ width: 1000px;
+ height: 30px;
+ background-color:#1a1a1b;
+ position: fixed;
+ margin: 0 auto;
+ padding: 0;
+}
+
+#navbar ul
+{
+ list-style-type: none;
+ margin: 0 auto;
+ padding: 0;
+ overflow: hidden;
+}
+
+#navbar li
+{
+ margin: 0 auto;
+ padding: 5px;
+ float:left;
+}
+
+#navbar a:link,a:visited
+{
+ display:block;
+ width:150px;
+ font-weight:bold;
+ color:#e85d02;
+ text-align:center;
+ /*padding:4px;*/
+ text-decoration:none;
+ /*text-transform:uppercase;*/
+}
+
+#navbar a:hover,a:active
+{
+ color:#ffffff;
+}
+
+#content {
+ background-color:#ffffff;
+ padding: 15px;
+ padding-top: 40px;
+ padding-right: 40px;
+ margin: 0 auto;
+}
diff --git a/ftnoir_tracker_pt/frame_observer.cpp b/ftnoir_tracker_pt/frame_observer.cpp
new file mode 100644
index 00000000..281f3d57
--- /dev/null
+++ b/ftnoir_tracker_pt/frame_observer.cpp
@@ -0,0 +1,18 @@
+/* Copyright (c) 2013 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 "frame_observer.h"
+
+//-----------------------------------------------------------------------------
+FrameProvider::~FrameProvider()
+{
+ QMutexLocker lock(&observer_mutex);
+ for (std::set<FrameObserver*>::iterator iter=frame_observers.begin(); iter!=frame_observers.end(); ++iter)
+ {
+ (*iter)->on_frame_provider_destroy();
+ }
+}
diff --git a/ftnoir_tracker_pt/frame_observer.h b/ftnoir_tracker_pt/frame_observer.h
new file mode 100644
index 00000000..585a6ee7
--- /dev/null
+++ b/ftnoir_tracker_pt/frame_observer.h
@@ -0,0 +1,76 @@
+/* Copyright (c) 2013 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.
+ */
+
+#ifndef FRAME_OBSERVER_H
+#define FRAME_OBSERVER_H
+
+#include <QMutex>
+#include <opencv2/opencv.hpp>
+#ifndef OPENTRACK_API
+# include <boost/shared_ptr.hpp>
+#else
+# include "FTNoIR_Tracker_PT/boost-compat.h"
+#endif
+#include <set>
+
+//-----------------------------------------------------------------------------
+// Forward declarations
+class FrameObserver;
+
+//-----------------------------------------------------------------------------
+// Provides means to copy frame and point information if it has observers
+// Instantiate a FrameObserver to get the information
+class FrameProvider
+{
+ friend class FrameObserver;
+public:
+ ~FrameProvider();
+
+protected:
+ virtual bool get_frame_and_points(cv::Mat& frame, boost::shared_ptr< std::vector<cv::Vec2f> >& points) = 0;
+
+ bool has_observers() const { QMutexLocker lock(&observer_mutex); return !frame_observers.empty(); }
+
+private:
+ mutable QMutex observer_mutex;
+ void add_observer(FrameObserver* obs) { QMutexLocker lock(&observer_mutex); frame_observers.insert(obs); }
+ void remove_observer(FrameObserver* obs) { QMutexLocker lock(&observer_mutex); frame_observers.erase(obs); }
+ std::set<FrameObserver*> frame_observers;
+};
+
+//-----------------------------------------------------------------------------
+// Used to get frame and point information from MutexedFrameProvider
+// Destroy instance if not interested anymore since a living
+// FrameObserver instance causes MutexedFrameProvider to provide the information,
+// potentially reducing its performance
+class FrameObserver
+{
+public:
+ FrameObserver(FrameProvider* provider) : provider(provider) {
+ provider->add_observer(this);
+ }
+
+ ~FrameObserver() {
+ if (provider) provider->remove_observer(this);
+ }
+
+ bool get_frame_and_points(cv::Mat& frame, boost::shared_ptr< std::vector<cv::Vec2f> >& points) {
+ return provider ? provider->get_frame_and_points(frame, points) : false;
+ }
+
+ void on_frame_provider_destroy() {
+ provider = NULL;
+ }
+
+protected:
+ FrameProvider* provider;
+
+private:
+ FrameObserver(const FrameObserver&);
+};
+
+#endif //FRAME_OBSERVER_H
diff --git a/ftnoir_tracker_pt/ftnoir_tracker_pt.cpp b/ftnoir_tracker_pt/ftnoir_tracker_pt.cpp
new file mode 100644
index 00000000..ef72f9a2
--- /dev/null
+++ b/ftnoir_tracker_pt/ftnoir_tracker_pt.cpp
@@ -0,0 +1,265 @@
+/* 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 "ftnoir_tracker_pt.h"
+#include <QHBoxLayout>
+#include <cmath>
+#include <QDebug>
+#include <QFile>
+#include <QCoreApplication>
+
+using namespace std;
+using namespace cv;
+using namespace boost;
+
+//#define PT_PERF_LOG //log performance
+
+const float rad2deg = 180.0/3.14159265;
+const float deg2rad = 1.0/rad2deg;
+
+//-----------------------------------------------------------------------------
+Tracker::Tracker()
+ : mutex(QMutex::Recursive),
+ commands(0),
+ video_widget(NULL),
+ video_frame(NULL),
+ tracking_valid(false),
+ new_settings(nullptr)
+
+{
+ qDebug()<<"Tracker::Tracker";
+}
+
+Tracker::~Tracker()
+{
+ qDebug()<<"Tracker::~Tracker";
+ // terminate tracker thread
+ set_command(ABORT);
+ wait();
+ s.video_widget = false;
+ delete video_widget;
+ video_widget = NULL;
+ if (video_frame->layout()) delete video_frame->layout();
+}
+
+void Tracker::set_command(Command command)
+{
+ //QMutexLocker lock(&mutex);
+ commands |= command;
+}
+
+void Tracker::reset_command(Command command)
+{
+ //QMutexLocker lock(&mutex);
+ commands &= ~command;
+}
+
+void Tracker::run()
+{
+ qDebug()<<"Tracker:: Thread started";
+
+#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
+
+ time.start();
+ double dt;
+ bool new_frame;
+ forever
+ {
+ if (commands & ABORT) break;
+ if (commands & PAUSE) continue;
+ commands = 0;
+ apply_inner();
+ dt = time.start() / 1000000000.;
+
+ new_frame = camera.get_frame(dt, &frame);
+
+ if (new_frame && !frame.empty())
+ {
+ QMutexLocker lock(&mutex);
+
+ frame = frame_rotation.rotate_frame(frame);
+ const std::vector<cv::Vec2f>& points = point_extractor.extract_points(frame, dt, true);
+ for (auto p : points)
+ {
+ auto p2 = cv::Point(p[0] * frame.cols + frame.cols/2, -p[1] * frame.cols + frame.rows/2);
+ cv::Scalar color(0, 255, 0);
+ cv::line(frame,
+ cv::Point(p2.x - 20, p2.y),
+ cv::Point(p2.x + 20, p2.y),
+ color,
+ 4);
+ cv::line(frame,
+ cv::Point(p2.x, p2.y - 20),
+ cv::Point(p2.x, p2.y + 20),
+ color,
+ 4);
+ }
+ tracking_valid = point_tracker.track(points, camera.get_info().f, dt);
+ video_widget->update_image(frame);
+ }
+#ifdef PT_PERF_LOG
+ log_stream<<"dt: "<<dt;
+ if (!frame.empty()) log_stream<<" fps: "<<camera.get_info().fps;
+ log_stream<<"\n";
+#endif
+ }
+
+ qDebug()<<"Tracker:: Thread stopping";
+}
+void Tracker::apply(settings& s)
+{
+ // caller guarantees object lifetime
+ new_settings = &s;
+}
+
+void Tracker::apply_inner()
+{
+ settings* tmp = new_settings.exchange(nullptr);
+ if (tmp == nullptr)
+ return;
+ auto& s = *tmp;
+ qDebug()<<"Tracker:: Applying settings";
+ camera.set_device_index(s.cam_index);
+ camera.set_res(s.cam_res_x, s.cam_res_y);
+ camera.set_fps(s.cam_fps);
+ camera.set_f(s.cam_f);
+ frame_rotation.rotation = static_cast<RotationType>(static_cast<int>(s.cam_roll));
+ point_extractor.threshold_val = s.threshold;
+ point_extractor.threshold_secondary_val = s.threshold_secondary;
+ point_extractor.min_size = s.min_point_size;
+ point_extractor.max_size = s.max_point_size;
+ {
+ cv::Vec3f M01(s.m01_x, s.m01_y, s.m01_z);
+ cv::Vec3f M02(s.m02_x, s.m02_y, s.m02_z);
+ point_tracker.point_model = boost::shared_ptr<PointModel>(new PointModel(M01, M02));
+ }
+ point_tracker.dynamic_pose_resolution = s.dyn_pose_res;
+ point_tracker.dt_reset = s.reset_time / 1000.0;
+ t_MH = cv::Vec3f(s.t_MH_x, s.t_MH_y, s.t_MH_z);
+ R_GC = Matx33f( cos(deg2rad*s.cam_yaw), 0, sin(deg2rad*s.cam_yaw),
+ 0, 1, 0,
+ -sin(deg2rad*s.cam_yaw), 0, cos(deg2rad*s.cam_yaw));
+ R_GC = R_GC * Matx33f( 1, 0, 0,
+ 0, cos(deg2rad*s.cam_pitch), sin(deg2rad*s.cam_pitch),
+ 0, -sin(deg2rad*s.cam_pitch), cos(deg2rad*s.cam_pitch));
+
+ FrameTrafo X_MH(Matx33f::eye(), t_MH);
+ X_GH_0 = R_GC * X_MH;
+
+ qDebug()<<"Tracker::apply ends";
+}
+
+void Tracker::reset()
+{
+ QMutexLocker lock(&mutex);
+ point_tracker.reset();
+}
+
+void Tracker::center()
+{
+ point_tracker.reset(); // we also do a reset here since there is no reset shortkey yet
+ QMutexLocker lock(&mutex);
+ FrameTrafo X_CM_0 = point_tracker.get_pose();
+ FrameTrafo X_MH(Matx33f::eye(), t_MH);
+ X_GH_0 = R_GC * X_CM_0 * X_MH;
+}
+
+bool Tracker::get_frame_and_points(cv::Mat& frame_copy, boost::shared_ptr< std::vector<Vec2f> >& points)
+{
+ QMutexLocker lock(&mutex);
+ if (frame.empty()) return false;
+
+ // copy the frame and points from the tracker thread
+ frame_copy = frame.clone();
+ points = boost::shared_ptr< vector<Vec2f> >(new vector<Vec2f>(point_extractor.get_points()));
+ return true;
+}
+
+void Tracker::refreshVideo()
+{
+ if (video_widget) video_widget->update_frame_and_points();
+}
+
+void Tracker::StartTracker(QFrame *parent_window)
+{
+ this->video_frame = parent_window;
+ video_frame->setAttribute(Qt::WA_NativeWindow);
+ video_frame->show();
+ video_widget = new PTVideoWidget(video_frame, this);
+ QHBoxLayout* video_layout = new QHBoxLayout(parent_window);
+ video_layout->setContentsMargins(0, 0, 0, 0);
+ video_layout->addWidget(video_widget);
+ video_frame->setLayout(video_layout);
+ video_widget->resize(video_frame->width(), video_frame->height());
+ camera.start();
+ apply(s);
+ start();
+ reset_command(PAUSE);
+}
+
+#ifndef OPENTRACK_API
+void Tracker::StopTracker(bool exit)
+{
+ set_command(PAUSE);
+}
+#endif
+
+#ifdef OPENTRACK_API
+#define THeadPoseData double
+#endif
+
+void Tracker::GetHeadPoseData(THeadPoseData *data)
+{
+ {
+ QMutexLocker lock(&mutex);
+
+ if (!tracking_valid) return;
+
+ FrameTrafo X_CM = point_tracker.get_pose();
+ FrameTrafo X_MH(Matx33f::eye(), t_MH);
+ FrameTrafo X_GH = R_GC * X_CM * X_MH;
+ Matx33f R = X_GH.R * X_GH_0.R.t();
+ Vec3f t = X_GH.t - X_GH_0.t;
+
+ // get translation(s)
+ if (s.bEnableX) data[TX] = t[0] / 10.0; // convert to cm
+ if (s.bEnableY) data[TY] = t[1] / 10.0;
+ if (s.bEnableZ) data[TZ] = t[2] / 10.0;
+
+ // translate rotation matrix from opengl (G) to roll-pitch-yaw (E) frame
+ // -z -> x, y -> z, x -> -y
+ Matx33f R_EG( 0, 0,-1,
+ -1, 0, 0,
+ 0, 1, 0);
+ R = R_EG * R * R_EG.t();
+
+ // extract rotation angles
+ float 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));
+
+ if (s.bEnableYaw) data[Yaw] = rad2deg * alpha;
+ if (s.bEnablePitch) data[Pitch] = - rad2deg * beta; // FTNoIR expects a minus here
+ if (s.bEnableRoll) data[Roll] = rad2deg * gamma;
+ }
+}
+
+//-----------------------------------------------------------------------------
+#ifdef OPENTRACK_API
+extern "C" FTNOIR_TRACKER_BASE_EXPORT ITracker* CALLING_CONVENTION GetConstructor()
+#else
+#pragma comment(linker, "/export:GetTracker=_GetTracker@0")
+FTNOIR_TRACKER_BASE_EXPORT ITrackerPtr __stdcall GetTracker()
+#endif
+{
+ return new Tracker;
+}
diff --git a/ftnoir_tracker_pt/ftnoir_tracker_pt.h b/ftnoir_tracker_pt/ftnoir_tracker_pt.h
new file mode 100644
index 00000000..79f16629
--- /dev/null
+++ b/ftnoir_tracker_pt/ftnoir_tracker_pt.h
@@ -0,0 +1,95 @@
+/* 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.
+ */
+
+#ifndef FTNOIR_TRACKER_PT_H
+#define FTNOIR_TRACKER_PT_H
+
+#ifdef OPENTRACK_API
+# include "ftnoir_tracker_base/ftnoir_tracker_base.h"
+# include "facetracknoir/plugin-support.h"
+#endif
+#include "ftnoir_tracker_pt_settings.h"
+#include "frame_observer.h"
+#include "camera.h"
+#include "point_extractor.h"
+#include "point_tracker.h"
+#include "pt_video_widget.h"
+#include "facetracknoir/timer.hpp"
+
+#include <QThread>
+#include <QMutex>
+#include <QMutexLocker>
+#include <QTime>
+#include <opencv2/opencv.hpp>
+#include <atomic>
+#ifndef OPENTRACK_API
+# include <boost/shared_ptr.hpp>
+#else
+# include "FTNoIR_Tracker_PT/boost-compat.h"
+#endif
+#include <vector>
+
+//-----------------------------------------------------------------------------
+// Constantly processes the tracking chain in a separate thread
+class Tracker : public ITracker, QThread, public FrameProvider
+{
+public:
+ Tracker();
+ virtual ~Tracker();
+ virtual void StartTracker(QFrame* parent_window);
+ virtual void GetHeadPoseData(double* data);
+ virtual void refreshVideo();
+
+ void apply(settings& s);
+ void apply_inner();
+ void center();
+ void reset(); // reset the trackers internal state variables
+ void run();
+
+ void get_pose(FrameTrafo* X_CM) { QMutexLocker lock(&mutex); *X_CM = point_tracker.get_pose(); }
+ int get_n_points() { QMutexLocker lock(&mutex); return point_extractor.get_points().size(); }
+ void get_cam_info(CamInfo* info) { QMutexLocker lock(&mutex); *info = camera.get_info(); }
+
+protected:
+ // --- MutexedFrameProvider interface ---
+ virtual bool get_frame_and_points(cv::Mat& frame, boost::shared_ptr< std::vector<cv::Vec2f> >& points);
+
+ // --- thread ---
+ QMutex mutex;
+ // thread commands
+ enum Command {
+ ABORT = 1<<0,
+ PAUSE = 1<<1
+ };
+ void set_command(Command command);
+ void reset_command(Command command);
+ volatile int commands;
+
+ CVCamera camera;
+ FrameRotation frame_rotation;
+ PointExtractor point_extractor;
+ PointTracker point_tracker;
+
+ FrameTrafo X_GH_0; // for centering
+ cv::Vec3f t_MH; // translation from model frame to head frame
+ cv::Matx33f R_GC; // rotation from opengl reference frame to camera frame
+
+ // --- ui ---
+ cv::Mat frame; // the output frame for display
+
+ PTVideoWidget* video_widget;
+ QFrame* video_frame;
+ bool tracking_valid;
+
+ settings s;
+ std::atomic<settings*> new_settings;
+ Timer time;
+};
+
+#undef VideoWidget
+
+#endif // FTNOIR_TRACKER_PT_H
diff --git a/ftnoir_tracker_pt/ftnoir_tracker_pt.qrc b/ftnoir_tracker_pt/ftnoir_tracker_pt.qrc
new file mode 100644
index 00000000..a8f9a1af
--- /dev/null
+++ b/ftnoir_tracker_pt/ftnoir_tracker_pt.qrc
@@ -0,0 +1,9 @@
+<RCC>
+ <qresource prefix="/">
+ <file>Resources/cap_front.png</file>
+ <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>
diff --git a/ftnoir_tracker_pt/ftnoir_tracker_pt_dialog.cpp b/ftnoir_tracker_pt/ftnoir_tracker_pt_dialog.cpp
new file mode 100644
index 00000000..c103b78c
--- /dev/null
+++ b/ftnoir_tracker_pt/ftnoir_tracker_pt_dialog.cpp
@@ -0,0 +1,321 @@
+/* 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 "ftnoir_tracker_pt_dialog.h"
+
+#include <QMessageBox>
+#include <QDebug>
+#include <opencv2/opencv.hpp>
+#ifndef OPENTRACK_API
+# include <boost/shared_ptr.hpp>
+#else
+# include "FTNoIR_Tracker_PT/boost-compat.h"
+#endif
+#include <vector>
+
+using namespace std;
+
+//-----------------------------------------------------------------------------
+TrackerDialog::TrackerDialog()
+ : tracker(NULL),
+ video_widget_dialog(NULL),
+ timer(this),
+ trans_calib_running(false)
+{
+ qDebug()<<"TrackerDialog::TrackerDialog";
+ setAttribute(Qt::WA_DeleteOnClose, false);
+
+ ui.setupUi( this );
+
+ vector<string> device_names;
+ get_camera_device_names(device_names);
+ for (vector<string>::iterator iter = device_names.begin(); iter != device_names.end(); ++iter)
+ {
+ ui.camdevice_combo->addItem(iter->c_str());
+ }
+
+ ui.camroll_combo->addItem("-90");
+ ui.camroll_combo->addItem("0");
+ ui.camroll_combo->addItem("90");
+
+ tie_setting(s.dyn_pose_res, ui.dynpose_check);
+ tie_setting(s.reset_time, ui.reset_spin);
+
+ tie_setting(s.cam_index, ui.camdevice_combo);
+ tie_setting(s.cam_f, ui.f_dspin);
+ 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.cam_roll, ui.camroll_combo);
+ tie_setting(s.cam_pitch, ui.campitch_spin);
+ tie_setting(s.cam_yaw, ui.camyaw_spin);
+
+ tie_setting(s.threshold_secondary, ui.threshold_secondary_slider);
+ tie_setting(s.threshold, ui.threshold_slider);
+
+ tie_setting(s.bEnableYaw, ui.chkEnableYaw);
+ tie_setting(s.bEnablePitch, ui.chkEnablePitch);
+ tie_setting(s.bEnableRoll, ui.chkEnableRoll);
+ tie_setting(s.bEnableX, ui.chkEnableX);
+ tie_setting(s.bEnableY, ui.chkEnableY);
+ tie_setting(s.bEnableZ, ui.chkEnableZ);
+
+ tie_setting(s.min_point_size, ui.mindiam_spin);
+ tie_setting(s.max_point_size, ui.maxdiam_spin);
+
+ tie_setting(s.clip_by, ui.clip_bheight_spin);
+ tie_setting(s.clip_bz, ui.clip_blength_spin);
+ tie_setting(s.clip_ty, ui.clip_theight_spin);
+ tie_setting(s.clip_tz, ui.clip_tlength_spin);
+
+ tie_setting(s.cap_x, ui.cap_width_spin);
+ tie_setting(s.cap_y, ui.cap_height_spin);
+ tie_setting(s.cap_z, ui.cap_length_spin);
+
+ tie_setting(s.m01_x, ui.m1x_spin);
+ tie_setting(s.m01_y, ui.m1y_spin);
+ tie_setting(s.m01_z, ui.m1z_spin);
+
+ tie_setting(s.m02_x, ui.m2x_spin);
+ tie_setting(s.m02_y, ui.m2y_spin);
+ tie_setting(s.m02_z, ui.m2z_spin);
+
+ tie_setting(s.t_MH_x, ui.tx_spin);
+ tie_setting(s.t_MH_y, ui.ty_spin);
+ tie_setting(s.t_MH_z, ui.tz_spin);
+
+ connect( ui.tcalib_button,SIGNAL(toggled(bool)), this,SLOT(startstop_trans_calib(bool)) );
+ connect(ui.reset_button, SIGNAL(clicked()), this, SLOT(doReset()));
+
+ connect(ui.ok_button, SIGNAL(clicked()), this, SLOT(doOK()));
+ connect(ui.cancel_button, SIGNAL(clicked()), this, SLOT(doCancel()));
+ connect(ui.btnApply, SIGNAL(clicked()), this, SLOT(doApply()));
+
+ ui.model_tabs->setCurrentIndex(s.active_model_panel);
+
+ connect(ui.model_tabs, SIGNAL(currentChanged(int)), this, SLOT(set_model(int)));
+ connect(&timer,SIGNAL(timeout()), this,SLOT(poll_tracker_info()));
+ timer.start(100);
+
+ connect(s.b.get(), SIGNAL(bundleChanged()), this, SLOT(do_apply_without_saving()));
+}
+
+void TrackerDialog::set_model_clip()
+{
+ s.m01_x = 0;
+ s.m01_y = static_cast<double>(s.clip_ty);
+ s.m01_z = -static_cast<double>(s.clip_tz);
+ s.m02_x = 0;
+ s.m02_y = -static_cast<double>(s.clip_by);
+ s.m02_z = -static_cast<double>(s.clip_bz);
+
+ settings_changed();
+}
+
+void TrackerDialog::set_model_cap()
+{
+ s.m01_x = -static_cast<double>(s.cap_x);
+ s.m01_y = -static_cast<double>(s.cap_y);
+ s.m01_z = -static_cast<double>(s.cap_z);
+ s.m02_x = static_cast<double>(s.cap_x);
+ s.m02_y = -static_cast<double>(s.cap_y);
+ s.m02_z = -static_cast<double>(s.cap_z);
+
+ settings_changed();
+}
+
+void TrackerDialog::set_model_custom()
+{
+ settings_changed();
+}
+
+void TrackerDialog::set_model(int val)
+{
+ s.active_model_panel = val;
+}
+
+void TrackerDialog::startstop_trans_calib(bool start)
+{
+ if (start)
+ {
+ qDebug()<<"TrackerDialog:: Starting translation calibration";
+ trans_calib.reset();
+ trans_calib_running = true;
+ }
+ else
+ {
+ qDebug()<<"TrackerDialog:: Stoppping translation calibration";
+ trans_calib_running = false;
+ {
+ auto tmp = trans_calib.get_estimate();
+ s.t_MH_x = tmp[0];
+ s.t_MH_y = tmp[1];
+ s.t_MH_z = tmp[2];
+ }
+ settings_changed();
+ }
+}
+
+void TrackerDialog::trans_calib_step()
+{
+ if (tracker)
+ {
+ FrameTrafo X_CM;
+ tracker->get_pose(&X_CM);
+ trans_calib.update(X_CM.R, X_CM.t);
+ cv::Vec3f t_MH = trans_calib.get_estimate();
+ s.t_MH_x = t_MH[0];
+ s.t_MH_y = t_MH[1];
+ s.t_MH_z = t_MH[2];
+ }
+}
+
+void TrackerDialog::settings_changed()
+{
+ if (tracker) tracker->apply(s);
+}
+
+void TrackerDialog::doCenter()
+{
+ if (tracker) tracker->center();
+}
+
+void TrackerDialog::doReset()
+{
+ if (tracker) tracker->reset();
+}
+
+void TrackerDialog::save()
+{
+ do_apply_without_saving();
+ s.b->save();
+}
+
+void TrackerDialog::doOK()
+{
+ save();
+ close();
+}
+
+void TrackerDialog::do_apply_without_saving()
+{
+ switch (s.active_model_panel) {
+ default:
+ case 0:
+ set_model_clip();
+ break;
+ case 1:
+ set_model_cap();
+ break;
+ case 2:
+ set_model_custom();
+ break;
+ }
+ if (tracker) tracker->apply(s);
+}
+
+void TrackerDialog::doApply()
+{
+ save();
+}
+
+void TrackerDialog::doCancel()
+{
+ s.b->revert();
+ close();
+}
+
+void TrackerDialog::widget_destroyed(QObject* obj)
+{
+ if (obj == video_widget_dialog) {
+ // widget was / will be already deleted by Qt
+ destroy_video_widget(false);
+ }
+}
+
+void TrackerDialog::create_video_widget()
+{
+ // this should not happen but better be sure
+ if (video_widget_dialog) destroy_video_widget();
+ if (!tracker) return;
+
+ video_widget_dialog = new VideoWidgetDialog(this, tracker);
+ video_widget_dialog->setAttribute( Qt::WA_DeleteOnClose );
+ connect( video_widget_dialog, SIGNAL(destroyed(QObject*)), this, SLOT(widget_destroyed(QObject*)) );
+ video_widget_dialog->show();
+}
+
+void TrackerDialog::destroy_video_widget(bool do_delete /*= true*/)
+{
+ if (video_widget_dialog) {
+ if (do_delete) delete video_widget_dialog;
+ video_widget_dialog = NULL;
+ }
+}
+
+void TrackerDialog::poll_tracker_info()
+{
+ if (tracker)
+ {
+ QString to_print;
+
+ // display caminfo
+ CamInfo info;
+ tracker->get_cam_info(&info);
+ to_print = QString::number(info.res_x)+"x"+QString::number(info.res_y)+" @ "+QString::number(info.fps)+" FPS";
+ ui.caminfo_label->setText(to_print);
+
+ // display pointinfo
+ int n_points = tracker->get_n_points();
+ to_print = QString::number(n_points);
+ if (n_points == 3)
+ to_print += " OK!";
+ else
+ to_print += " BAD!";
+ ui.pointinfo_label->setText(to_print);
+
+ // update calibration
+ if (trans_calib_running) trans_calib_step();
+
+ // update videowidget
+ if (video_widget_dialog) {
+ video_widget_dialog->get_video_widget()->update_frame_and_points();
+ }
+ }
+ else
+ {
+ QString to_print = "Tracker offline";
+ ui.caminfo_label->setText(to_print);
+ ui.pointinfo_label->setText(to_print);
+ }
+}
+
+void TrackerDialog::registerTracker(ITracker *t)
+{
+ qDebug()<<"TrackerDialog:: Tracker registered";
+ tracker = static_cast<Tracker*>(t);
+ if (isVisible() & s.b->modifiedp())
+ tracker->apply(s);
+ ui.tcalib_button->setEnabled(true);
+ //ui.center_button->setEnabled(true);
+ ui.reset_button->setEnabled(true);
+}
+
+void TrackerDialog::unRegisterTracker()
+{
+ qDebug()<<"TrackerDialog:: Tracker un-registered";
+ tracker = NULL;
+ destroy_video_widget();
+ ui.tcalib_button->setEnabled(false);
+ //ui.center_button->setEnabled(false);
+ ui.reset_button->setEnabled(false);
+}
+
+extern "C" FTNOIR_TRACKER_BASE_EXPORT ITrackerDialog* CALLING_CONVENTION GetDialog( )
+{
+ return new TrackerDialog;
+}
diff --git a/ftnoir_tracker_pt/ftnoir_tracker_pt_dialog.h b/ftnoir_tracker_pt/ftnoir_tracker_pt_dialog.h
new file mode 100644
index 00000000..0325160d
--- /dev/null
+++ b/ftnoir_tracker_pt/ftnoir_tracker_pt_dialog.h
@@ -0,0 +1,70 @@
+/* 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.
+ */
+
+#ifndef FTNOIR_TRACKER_PT_DIALOG_H
+#define FTNOIR_TRACKER_PT_DIALOG_H
+
+#ifdef OPENTRACK_API
+#include "ftnoir_tracker_base/ftnoir_tracker_base.h"
+#else
+#include "..\ftnoir_tracker_base\ftnoir_tracker_base.h"
+#endif
+#include "ftnoir_tracker_pt_settings.h"
+#include "ftnoir_tracker_pt.h"
+#include "trans_calib.h"
+#include "pt_video_widget.h"
+#include "ui_FTNoIR_PT_Controls.h"
+
+#include <QTimer>
+
+//-----------------------------------------------------------------------------
+// The dialog that shows up when the user presses "Settings"
+class TrackerDialog : public QWidget, Ui::UICPTClientControls, public ITrackerDialog
+{
+ Q_OBJECT
+public:
+ TrackerDialog();
+ void registerTracker(ITracker *tracker);
+ void unRegisterTracker();
+ void save();
+ void trans_calib_step();
+
+public slots:
+ void doCenter();
+ void doReset();
+ void doOK();
+ void doApply();
+ void doCancel();
+ void do_apply_without_saving();
+
+ void startstop_trans_calib(bool start);
+ void widget_destroyed(QObject* obj);
+ void create_video_widget();
+ void poll_tracker_info();
+ void set_model(int idx);
+
+protected:
+ void destroy_video_widget(bool do_delete = true);
+
+ void set_model_clip();
+ void set_model_cap();
+ void set_model_custom();
+
+ void settings_changed();
+
+ settings s;
+ Tracker* tracker;
+ VideoWidgetDialog* video_widget_dialog;
+ QTimer timer;
+
+ TranslationCalibrator trans_calib;
+ bool trans_calib_running;
+
+ Ui::UICPTClientControls ui;
+};
+
+#endif //FTNOIR_TRACKER_PT_DIALOG_H
diff --git a/ftnoir_tracker_pt/ftnoir_tracker_pt_dll.cpp b/ftnoir_tracker_pt/ftnoir_tracker_pt_dll.cpp
new file mode 100644
index 00000000..15e830a4
--- /dev/null
+++ b/ftnoir_tracker_pt/ftnoir_tracker_pt_dll.cpp
@@ -0,0 +1,42 @@
+/* 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 "ftnoir_tracker_pt_dll.h"
+#include <QIcon>
+
+//-----------------------------------------------------------------------------
+void TrackerDll::getFullName(QString *strToBeFilled)
+{
+ *strToBeFilled = "PointTracker 1.1";
+}
+
+void TrackerDll::getShortName(QString *strToBeFilled)
+{
+ *strToBeFilled = "PointTracker";
+}
+
+void TrackerDll::getDescription(QString *strToBeFilled)
+{
+ *strToBeFilled = "Tracks a 3-point model with know geometry like Freetrack / TrackIR";
+}
+
+void TrackerDll::getIcon(QIcon *icon)
+{
+ *icon = QIcon(":/Resources/Logo_IR.png");
+}
+
+
+#ifdef OPENTRACK_API
+# include "facetracknoir/plugin-support.h"
+extern "C" FTNOIR_TRACKER_BASE_EXPORT Metadata* CALLING_CONVENTION GetMetadata()
+#else
+# pragma comment(linker, "/export:GetTrackerDll=_GetTrackerDll@0")
+FTNOIR_TRACKER_BASE_EXPORT ITrackerDllPtr __stdcall GetTrackerDll()
+#endif
+{
+ return new TrackerDll;
+}
diff --git a/ftnoir_tracker_pt/ftnoir_tracker_pt_dll.h b/ftnoir_tracker_pt/ftnoir_tracker_pt_dll.h
new file mode 100644
index 00000000..22e1ff29
--- /dev/null
+++ b/ftnoir_tracker_pt/ftnoir_tracker_pt_dll.h
@@ -0,0 +1,27 @@
+/* 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.
+ */
+
+#if defined(OPENTRACK_API)
+# include "ftnoir_tracker_base/ftnoir_tracker_base.h"
+# include "facetracknoir/plugin-support.h"
+#else
+# include "../ftnoir_tracker_base/ftnoir_tracker_base.h"
+#endif
+
+//-----------------------------------------------------------------------------
+class TrackerDll :
+#if defined(OPENTRACK_API)
+ public Metadata
+#else
+ public ITrackerDll
+#endif
+{
+ void getFullName(QString *strToBeFilled);
+ void getShortName(QString *strToBeFilled);
+ void getDescription(QString *strToBeFilled);
+ void getIcon(QIcon *icon);
+};
diff --git a/ftnoir_tracker_pt/ftnoir_tracker_pt_settings.h b/ftnoir_tracker_pt/ftnoir_tracker_pt_settings.h
new file mode 100644
index 00000000..109090b3
--- /dev/null
+++ b/ftnoir_tracker_pt/ftnoir_tracker_pt_settings.h
@@ -0,0 +1,90 @@
+/* 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.
+ */
+
+#ifndef FTNOIR_TRACKER_PT_SETTINGS_H
+#define FTNOIR_TRACKER_PT_SETTINGS_H
+
+#include <opencv2/opencv.hpp>
+#include "point_tracker.h"
+
+#include "facetracknoir/options.h"
+using namespace options;
+
+struct settings
+{
+ pbundle b;
+ value<int> cam_index,
+ cam_res_x,
+ cam_res_y,
+ cam_fps,
+ cam_roll,
+ cam_pitch,
+ cam_yaw,
+ threshold,
+ threshold_secondary,
+ min_point_size,
+ max_point_size;
+ value<double> cam_f;
+
+ value<int> m01_x, m01_y, m01_z;
+ value<int> m02_x, m02_y, m02_z;
+ value<bool> dyn_pose_res, video_widget;
+
+ value<int> t_MH_x, t_MH_y, t_MH_z;
+
+ value<int> reset_time;
+
+ value<bool> bEnableYaw, bEnablePitch, bEnableRoll;
+ value<bool> bEnableX, bEnableY, bEnableZ;
+
+ value<int> clip_ty, clip_tz, clip_by, clip_bz;
+ value<int> active_model_panel, cap_x, cap_y, cap_z;
+
+ settings() :
+ b(bundle("tracker-pt")),
+ cam_index(b, "camera-index", 0),
+ cam_res_x(b, "camera-res-width", 640),
+ cam_res_y(b, "camera-res-height", 480),
+ cam_fps(b, "camera-fps", 30),
+ cam_roll(b, "camera-roll", 1),
+ cam_pitch(b, "camera-pitch", 0),
+ cam_yaw(b, "camera-yaw", 0),
+ threshold(b, "threshold-primary", 128),
+ threshold_secondary(b, "threshold-secondary", 128),
+ min_point_size(b, "min-point-size", 10),
+ max_point_size(b, "max-point-size", 50),
+ cam_f(b, "camera-focal-length", 1),
+ 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),
+ dyn_pose_res(b, "dynamic-pose-resolution", false),
+ video_widget(b, "video-widget", true),
+ t_MH_x(b, "model-centroid-x", 0),
+ t_MH_y(b, "model-centroid-y", 0),
+ t_MH_z(b, "model-centroid-z", 0),
+ reset_time(b, "reset-time", 2000),
+ bEnableYaw(b, "enable-yaw", true),
+ bEnablePitch(b, "enable-pitch", true),
+ bEnableRoll(b, "enable-roll", true),
+ bEnableX(b, "enable-x", true),
+ bEnableY(b, "enable-y", true),
+ bEnableZ(b, "enable-z", true),
+ clip_ty(b, "clip-ty", 0),
+ clip_tz(b, "clip-tz", 0),
+ clip_by(b, "clip-by", 0),
+ clip_bz(b, "clip-bz", 0),
+ active_model_panel(b, "active-model-panel", 0),
+ cap_x(b, "cap-x", 0),
+ cap_y(b, "cap-y", 0),
+ cap_z(b, "cap-z", 0)
+ {}
+};
+
+#endif //FTNOIR_TRACKER_PT_SETTINGS_H
diff --git a/ftnoir_tracker_pt/point_extractor.cpp b/ftnoir_tracker_pt/point_extractor.cpp
new file mode 100644
index 00000000..d9ff0a5b
--- /dev/null
+++ b/ftnoir_tracker_pt/point_extractor.cpp
@@ -0,0 +1,163 @@
+/* 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 "point_extractor.h"
+#include <QDebug>
+
+
+using namespace cv;
+using namespace std;
+
+
+PointExtractor::PointExtractor(){
+ //if (!AllocConsole()){}
+ //else SetConsoleTitle("debug");
+ //freopen("CON", "w", stdout);
+ //freopen("CON", "w", stderr);
+}
+// ----------------------------------------------------------------------------
+const vector<Vec2f>& PointExtractor::extract_points(Mat frame, float dt, bool draw_output)
+{
+ const int W = frame.cols;
+ const int H = frame.rows;
+
+ if (frame_last.cols != W || frame_last.rows != H)
+ {
+ frame_last = cv::Mat();
+ }
+
+ // clear old points
+ points.clear();
+
+ // convert to grayscale
+ Mat frame_gray;
+ cvtColor(frame, frame_gray, CV_RGB2GRAY);
+
+ int secondary = threshold_secondary_val;
+
+ // mask for everything that passes the threshold (or: the upper threshold of the hysteresis)
+ Mat frame_bin;
+ // only used if draw_output
+ Mat frame_bin_copy;
+ // mask for everything that passes
+ Mat frame_bin_low;
+ // mask for lower-threshold && combined result of last, needs to remain in scope until drawing, but is only used if secondary != 0
+ Mat frame_last_and_low;
+
+ if(secondary==0){
+ threshold(frame_gray, frame_bin, threshold_val, 255, THRESH_BINARY);
+ }else{
+ // we recombine a number of buffers, this might be slower than a single loop of per-pixel logic
+ // but it might as well be faster if openCV makes good use of SIMD
+ float t = threshold_val;
+ //float hyst = float(threshold_secondary_val)/512.;
+ //threshold(frame_gray, frame_bin, (t + ((255.-t)*hyst)), 255, THRESH_BINARY);
+ float hyst = float(threshold_secondary_val)/256.;
+ threshold(frame_gray, frame_bin, t, 255, THRESH_BINARY);
+ threshold(frame_gray, frame_bin_low,std::max(float(1), t - (t*hyst)), 255, THRESH_BINARY);
+
+ if(draw_output) frame_bin.copyTo(frame_bin_copy);
+ if(frame_last.empty()){
+ frame_bin.copyTo(frame_last);
+ }else{
+ // keep pixels from last if they are above lower threshold
+ bitwise_and(frame_last, frame_bin_low, frame_last_and_low);
+ // union of pixels >= higher threshold and pixels >= lower threshold
+ bitwise_or(frame_bin, frame_last_and_low, frame_last);
+ frame_last.copyTo(frame_bin);
+ }
+ }
+ unsigned int region_size_min = 3.14*min_size*min_size/4.0;
+ unsigned int region_size_max = 3.14*max_size*max_size/4.0;
+
+ int blob_index = 1;
+ for (int y=0; y<H; y++)
+ {
+ if (blob_index >= 255) break;
+ for (int x=0; x<W; x++)
+ {
+ if (blob_index >= 255) break;
+
+ // find connected components with floodfill
+ if (frame_bin.at<unsigned char>(y,x) != 255) continue;
+ Rect rect;
+
+ floodFill(frame_bin, Point(x,y), Scalar(blob_index), &rect, Scalar(0), Scalar(0), FLOODFILL_FIXED_RANGE);
+ blob_index++;
+
+ // calculate the size of the connected component
+ unsigned int region_size = 0;
+ for (int i=rect.y; i < (rect.y+rect.height); i++)
+ {
+ for (int j=rect.x; j < (rect.x+rect.width); j++)
+ {
+ if (frame_bin.at<unsigned char>(i,j) != blob_index-1) continue;
+ region_size++;
+ }
+ }
+
+ if (region_size < region_size_min || region_size > region_size_max) continue;
+
+ // calculate the center of mass:
+ // mx = (sum_ij j*f(frame_grey_ij)) / (sum_ij f(frame_grey_ij))
+ // my = ...
+ // f maps from [threshold,256] -> [0, 1], lower values are mapped to 0
+ float m = 0;
+ float mx = 0;
+ float my = 0;
+ for (int i=rect.y; i < (rect.y+rect.height); i++)
+ {
+ for (int j=rect.x; j < (rect.x+rect.width); j++)
+ {
+ if (frame_bin.at<unsigned char>(i,j) != blob_index-1) continue;
+ float val;
+
+ if(secondary==0){
+ val = frame_gray.at<unsigned char>(i,j);
+ val = float(val - threshold_val)/(256 - threshold_val);
+ val = val*val; // makes it more stable (less emphasis on low values, more on the peak)
+ }else{
+ //hysteresis point detection gets stability from ignoring pixel noise so we decidedly leave the actual pixel values out of the picture
+ val = frame_last.at<unsigned char>(i,j) / 256.;
+ }
+
+ m += val;
+ mx += j * val;
+ my += i * val;
+ }
+ }
+
+ // convert to centered camera coordinate system with y axis upwards
+ Vec2f c;
+ c[0] = (mx/m - W/2)/W;
+ c[1] = -(my/m - H/2)/W;
+ //qDebug()<<blob_index<<" => "<<c[0]<<" "<<c[1];
+ points.push_back(c);
+ }
+ }
+
+ // draw output image
+ if (draw_output) {
+ vector<Mat> channels;
+ if(secondary==0){
+ frame_bin.setTo(170, frame_bin);
+ channels.push_back(frame_gray + frame_bin);
+ channels.push_back(frame_gray - frame_bin);
+ channels.push_back(frame_gray - frame_bin);
+ }else{
+ frame_bin_copy.setTo(120, frame_bin_copy);
+ frame_bin_low.setTo(90, frame_bin_low);
+ channels.push_back(frame_gray + frame_bin_copy);
+ channels.push_back(frame_gray + frame_last_and_low);
+ channels.push_back(frame_gray + frame_bin_low);
+ //channels.push_back(frame_gray + frame_bin);
+ }
+ merge(channels, frame);
+ }
+
+ return points;
+}
diff --git a/ftnoir_tracker_pt/point_extractor.h b/ftnoir_tracker_pt/point_extractor.h
new file mode 100644
index 00000000..ff36f3ce
--- /dev/null
+++ b/ftnoir_tracker_pt/point_extractor.h
@@ -0,0 +1,35 @@
+/* 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.
+ */
+
+#ifndef POINTEXTRACTOR_H
+#define POINTEXTRACTOR_H
+
+#include <opencv2/opencv.hpp>
+#include <opencv2/imgproc/imgproc_c.h>
+
+// ----------------------------------------------------------------------------
+// Extracts points from an opencv image
+class PointExtractor
+{
+public:
+ // extracts points from frame and draws some processing info into frame, if draw_output is set
+ // dt: time since last call in seconds
+ // WARNING: returned reference is valid as long as object
+ const std::vector<cv::Vec2f>& extract_points(cv::Mat frame, float dt, bool draw_output);
+ const std::vector<cv::Vec2f>& get_points() { return points; }
+ PointExtractor();
+
+ int threshold_val;
+ int threshold_secondary_val;
+ int min_size, max_size;
+
+protected:
+ std::vector<cv::Vec2f> points;
+ cv::Mat frame_last;
+};
+
+#endif //POINTEXTRACTOR_H
diff --git a/ftnoir_tracker_pt/point_tracker.cpp b/ftnoir_tracker_pt/point_tracker.cpp
new file mode 100644
index 00000000..dfefdaf8
--- /dev/null
+++ b/ftnoir_tracker_pt/point_tracker.cpp
@@ -0,0 +1,380 @@
+/* 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 "point_tracker.h"
+
+#include <vector>
+#include <algorithm>
+#include <cmath>
+
+#include <QDebug>
+
+using namespace cv;
+using namespace boost;
+using namespace std;
+
+const float PI = 3.14159265358979323846f;
+
+// ----------------------------------------------------------------------------
+static void get_row(const Matx33f& m, int i, Vec3f& v)
+{
+ v[0] = m(i,0);
+ v[1] = m(i,1);
+ v[2] = m(i,2);
+}
+
+static void set_row(Matx33f& m, int i, const Vec3f& v)
+{
+ m(i,0) = v[0];
+ m(i,1) = v[1];
+ m(i,2) = v[2];
+}
+
+// ----------------------------------------------------------------------------
+PointModel::PointModel(Vec3f M01, Vec3f M02)
+ : M01(M01),
+ M02(M02)
+{
+ // calculate u
+ u = M01.cross(M02);
+ u /= norm(u);
+
+ // calculate projection matrix on M01,M02 plane
+ float s11 = M01.dot(M01);
+ float s12 = M01.dot(M02);
+ float s22 = M02.dot(M02);
+ P = 1.0/(s11*s22-s12*s12) * Matx22f(s22, -s12,
+ -s12, s11);
+
+ // calculate d and d_order for simple freetrack-like point correspondence
+ vector<Vec2f> points;
+ points.push_back(Vec2f(0,0));
+ points.push_back(Vec2f(M01[0], M01[1]));
+ points.push_back(Vec2f(M02[0], M02[1]));
+ // fit line to orthographically projected points
+ // ERROR: yields wrong results with colinear points?!
+ /*
+ Vec4f line;
+ fitLine(points, line, CV_DIST_L2, 0, 0.01, 0.01);
+ d[0] = line[0]; d[1] = line[1];
+ */
+ // TODO: fix this
+ d = Vec2f(M01[0]-M02[0], M01[1]-M02[1]);
+
+ // sort model points
+ get_d_order(points, d_order);
+}
+
+#ifdef OPENTRACK_API
+static bool d_vals_sort(const pair<float,int> a, const pair<float,int> b)
+{
+ return a.first < b.first;
+}
+#endif
+
+void PointModel::get_d_order(const std::vector<cv::Vec2f>& points, int d_order[]) const
+{
+ // get sort indices with respect to d scalar product
+ vector< pair<float,int> > d_vals;
+ for (int i = 0; i<points.size(); ++i)
+ d_vals.push_back(pair<float, int>(d.dot(points[i]), i));
+
+ struct
+ {
+ bool operator()(const pair<float, int>& a, const pair<float, int>& b) { return a.first < b.first; }
+ } comp;
+ std::sort(d_vals.begin(),
+ d_vals.end(),
+#ifdef OPENTRACK_API
+ d_vals_sort
+#else
+ comp
+#endif
+ );
+
+ for (int i = 0; i<points.size(); ++i)
+ d_order[i] = d_vals[i].second;
+}
+
+
+// ----------------------------------------------------------------------------
+PointTracker::PointTracker()
+ : init_phase(true),
+ dt_valid(0),
+ dt_reset(1),
+ v_t(0,0,0),
+ v_r(0,0,0),
+ dynamic_pose_resolution(true)
+{
+ X_CM.t[2] = 1000; // default position: 1 m away from cam;
+}
+
+void PointTracker::reset()
+{
+ // enter init phase and reset velocities
+ init_phase = true;
+ dt_valid = 0;
+ reset_velocities();
+}
+
+void PointTracker::reset_velocities()
+{
+ v_t = Vec3f(0,0,0);
+ v_r = Vec3f(0,0,0);
+}
+
+
+bool PointTracker::track(const vector<Vec2f>& points, float f, float dt)
+{
+ if (!dynamic_pose_resolution) init_phase = true;
+
+ dt_valid += dt;
+ // if there was no valid tracking result for too long, do a reset
+ if (dt_valid > dt_reset)
+ {
+ //qDebug()<<"dt_valid "<<dt_valid<<" > dt_reset "<<dt_reset;
+ reset();
+ }
+
+ bool no_model =
+#ifdef OPENTRACK_API
+ point_model.get() == NULL;
+#else
+ !point_model;
+#endif
+
+ // if there is a pointtracking problem, reset the velocities
+ if (no_model || points.size() != PointModel::N_POINTS)
+ {
+ //qDebug()<<"Wrong number of points!";
+ reset_velocities();
+ return false;
+ }
+
+ X_CM_old = X_CM; // backup old transformation for velocity calculation
+
+ if (!init_phase)
+ predict(dt_valid);
+
+ // if there is a point correspondence problem something has gone wrong, do a reset
+ if (!find_correspondences(points, f))
+ {
+ //qDebug()<<"Error in finding point correspondences!";
+ X_CM = X_CM_old; // undo prediction
+ reset();
+ return false;
+ }
+
+ int n_iter = POSIT(f);
+ //qDebug()<<"Number of POSIT iterations: "<<n_iter;
+
+ if (!init_phase)
+ update_velocities(dt_valid);
+
+ // we have a valid tracking result, leave init phase and reset time since valid result
+ init_phase = false;
+ dt_valid = 0;
+ return true;
+}
+
+void PointTracker::predict(float dt)
+{
+ // predict with constant velocity
+ Matx33f R;
+ Rodrigues(dt*v_r, R);
+ X_CM.R = R*X_CM.R;
+ X_CM.t += dt * v_t;
+}
+
+void PointTracker::update_velocities(float dt)
+{
+ // update velocities
+ Rodrigues(X_CM.R*X_CM_old.R.t(), v_r);
+ v_r /= dt;
+ v_t = (X_CM.t - X_CM_old.t)/dt;
+}
+
+bool PointTracker::find_correspondences(const vector<Vec2f>& points, float f)
+{
+ if (init_phase) {
+ // We do a simple freetrack-like sorting in the init phase...
+ // sort points
+ int point_d_order[PointModel::N_POINTS];
+ point_model->get_d_order(points, point_d_order);
+
+ // set correspondences
+ for (int i=0; i<PointModel::N_POINTS; ++i)
+ {
+ p[point_model->d_order[i]] = points[point_d_order[i]];
+ }
+ }
+ else {
+ // ... otherwise we look at the distance to the projection of the expected model points
+ // project model points under current pose
+ p_exp[0] = project(Vec3f(0,0,0), f);
+ p_exp[1] = project(point_model->M01, f);
+ p_exp[2] = project(point_model->M02, f);
+
+ // set correspondences by minimum distance to projected model point
+ bool point_taken[PointModel::N_POINTS];
+ for (int i=0; i<PointModel::N_POINTS; ++i)
+ point_taken[i] = false;
+
+ float min_sdist = 0;
+ int min_idx = 0;
+
+ for (int i=0; i<PointModel::N_POINTS; ++i)
+ {
+ // find closest point to projected model point i
+ for (int j=0; j<PointModel::N_POINTS; ++j)
+ {
+ Vec2f d = p_exp[i]-points[j];
+ float sdist = d.dot(d);
+ if (sdist < min_sdist || j==0)
+ {
+ min_idx = j;
+ min_sdist = sdist;
+ }
+ }
+ // if one point is closest to more than one model point, abort
+ if (point_taken[min_idx]) return false;
+ point_taken[min_idx] = true;
+ p[i] = points[min_idx];
+ }
+ }
+ return true;
+}
+
+
+
+int PointTracker::POSIT(float f)
+{
+ // POSIT algorithm for coplanar points as presented in
+ // [Denis Oberkampf, Daniel F. DeMenthon, Larry S. Davis: "Iterative Pose Estimation Using Coplanar Feature Points"]
+ // we use the same notation as in the paper here
+
+ // The expected rotation used for resolving the ambiguity in POSIT:
+ // In every iteration step the rotation closer to R_expected is taken
+ Matx33f R_expected;
+ if (init_phase)
+ R_expected = Matx33f::eye(); // in the init phase, we want to be close to the default pose = no rotation
+ else
+ R_expected = X_CM.R; // later we want to be close to the last (predicted) rotation
+
+ // initial pose = last (predicted) pose
+ Vec3f k;
+ get_row(R_expected, 2, k);
+ float Z0 = init_phase ? 1000 : X_CM.t[2];
+
+ float old_epsilon_1 = 0;
+ float old_epsilon_2 = 0;
+ float epsilon_1 = 1;
+ float epsilon_2 = 1;
+
+ Vec3f I0, J0;
+ Vec2f I0_coeff, J0_coeff;
+
+ Vec3f I_1, J_1, I_2, J_2;
+ Matx33f R_1, R_2;
+ Matx33f* R_current;
+
+ const int MAX_ITER = 100;
+ const float EPS_THRESHOLD = 1e-4;
+
+ int i=1;
+ for (; i<MAX_ITER; ++i)
+ {
+ epsilon_1 = k.dot(point_model->M01)/Z0;
+ epsilon_2 = k.dot(point_model->M02)/Z0;
+
+ // vector of scalar products <I0, M0i> and <J0, M0i>
+ Vec2f I0_M0i(p[1][0]*(1.0 + epsilon_1) - p[0][0],
+ p[2][0]*(1.0 + epsilon_2) - p[0][0]);
+ Vec2f J0_M0i(p[1][1]*(1.0 + epsilon_1) - p[0][1],
+ p[2][1]*(1.0 + epsilon_2) - p[0][1]);
+
+ // construct projection of I, J onto M0i plane: I0 and J0
+ I0_coeff = point_model->P * I0_M0i;
+ J0_coeff = point_model->P * J0_M0i;
+ I0 = I0_coeff[0]*point_model->M01 + I0_coeff[1]*point_model->M02;
+ J0 = J0_coeff[0]*point_model->M01 + J0_coeff[1]*point_model->M02;
+
+ // calculate u component of I, J
+ float II0 = I0.dot(I0);
+ float IJ0 = I0.dot(J0);
+ float JJ0 = J0.dot(J0);
+ float rho, theta;
+ if (JJ0 == II0) {
+ rho = sqrt(abs(2*IJ0));
+ 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) );
+ if (JJ0 - II0 < 0) theta += PI;
+ theta /= 2;
+ }
+
+ // construct the two solutions
+ I_1 = I0 + rho*cos(theta)*point_model->u;
+ I_2 = I0 - rho*cos(theta)*point_model->u;
+
+ J_1 = J0 + rho*sin(theta)*point_model->u;
+ J_2 = J0 - rho*sin(theta)*point_model->u;
+
+ float norm_const = 1.0/norm(I_1); // all have the same norm
+
+ // create rotation matrices
+ I_1 *= norm_const; J_1 *= norm_const;
+ I_2 *= norm_const; J_2 *= norm_const;
+
+ set_row(R_1, 0, I_1);
+ set_row(R_1, 1, J_1);
+ set_row(R_1, 2, I_1.cross(J_1));
+
+ set_row(R_2, 0, I_2);
+ set_row(R_2, 1, J_2);
+ set_row(R_2, 2, I_2.cross(J_2));
+
+ // the single translation solution
+ Z0 = norm_const * f;
+
+ // pick the rotation solution closer to the expected one
+ // in simple metric d(A,B) = || I - A * B^T ||
+ float R_1_deviation = norm(Matx33f::eye() - R_expected * R_1.t());
+ float R_2_deviation = norm(Matx33f::eye() - R_expected * R_2.t());
+
+ if (R_1_deviation < R_2_deviation)
+ R_current = &R_1;
+ else
+ R_current = &R_2;
+
+ get_row(*R_current, 2, k);
+
+ // check for convergence condition
+ if (abs(epsilon_1 - old_epsilon_1) + abs(epsilon_2 - old_epsilon_2) < EPS_THRESHOLD)
+ break;
+ old_epsilon_1 = epsilon_1;
+ old_epsilon_2 = epsilon_2;
+ }
+
+ // apply results
+ X_CM.R = *R_current;
+ X_CM.t[0] = p[0][0] * Z0/f;
+ X_CM.t[1] = p[0][1] * Z0/f;
+ X_CM.t[2] = Z0;
+
+ return i;
+
+ //Rodrigues(X_CM.R, r);
+ //qDebug()<<"iter: "<<i;
+ //qDebug()<<"t: "<<X_CM.t[0]<<' '<<X_CM.t[1]<<' '<<X_CM.t[2];
+ //Vec3f r;
+ //
+ //qDebug()<<"r: "<<r[0]<<' '<<r[1]<<' '<<r[2]<<'\n';
+}
diff --git a/ftnoir_tracker_pt/point_tracker.h b/ftnoir_tracker_pt/point_tracker.h
new file mode 100644
index 00000000..11034100
--- /dev/null
+++ b/ftnoir_tracker_pt/point_tracker.h
@@ -0,0 +1,129 @@
+/* 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.
+ */
+
+#ifndef POINTTRACKER_H
+#define POINTTRACKER_H
+
+#include <opencv2/opencv.hpp>
+#ifndef OPENTRACK_API
+# include <boost/shared_ptr.hpp>
+#else
+# include "FTNoIR_Tracker_PT/boost-compat.h"
+#endif
+#include <list>
+
+// ----------------------------------------------------------------------------
+// Affine frame trafo
+class FrameTrafo
+{
+public:
+ FrameTrafo() : R(cv::Matx33f::eye()), t(0,0,0) {}
+ FrameTrafo(const cv::Matx33f& R, const cv::Vec3f& t) : R(R),t(t) {}
+
+ cv::Matx33f R;
+ cv::Vec3f t;
+};
+
+inline FrameTrafo operator*(const FrameTrafo& X, const FrameTrafo& Y)
+{
+ return FrameTrafo(X.R*Y.R, X.R*Y.t + X.t);
+}
+
+inline FrameTrafo operator*(const cv::Matx33f& X, const FrameTrafo& Y)
+{
+ return FrameTrafo(X*Y.R, X*Y.t);
+}
+
+inline FrameTrafo operator*(const FrameTrafo& X, const cv::Matx33f& Y)
+{
+ return FrameTrafo(X.R*Y, X.t);
+}
+
+inline cv::Vec3f operator*(const FrameTrafo& X, const cv::Vec3f& v)
+{
+ return X.R*v + X.t;
+}
+
+
+// ----------------------------------------------------------------------------
+// Describes a 3-point model
+// nomenclature as in
+// [Denis Oberkampf, Daniel F. DeMenthon, Larry S. Davis: "Iterative Pose Estimation Using Coplanar Feature Points"]
+class PointModel
+{
+ friend class PointTracker;
+public:
+ static const int N_POINTS = 3;
+
+ PointModel(cv::Vec3f M01, cv::Vec3f M02);
+
+ const cv::Vec3f& get_M01() const { return M01; };
+ const cv::Vec3f& get_M02() const { return M02; };
+
+protected:
+ cv::Vec3f M01; // M01 in model frame
+ cv::Vec3f M02; // M02 in model frame
+
+ cv::Vec3f u; // unit vector perpendicular to M01,M02-plane
+
+ cv::Matx22f P;
+
+ cv::Vec2f d; // discriminant vector for point correspondence
+ int d_order[3]; // sorting of projected model points with respect to d scalar product
+
+ void get_d_order(const std::vector<cv::Vec2f>& points, int d_order[]) const;
+};
+
+// ----------------------------------------------------------------------------
+// Tracks a 3-point model
+// implementing the POSIT algorithm for coplanar points as presented in
+// [Denis Oberkampf, Daniel F. DeMenthon, Larry S. Davis: "Iterative Pose Estimation Using Coplanar Feature Points"]
+class PointTracker
+{
+public:
+ PointTracker();
+
+ // 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
+ bool track(const std::vector<cv::Vec2f>& points, float f, float dt);
+ boost::shared_ptr<PointModel> point_model;
+
+ bool dynamic_pose_resolution;
+ float dt_reset;
+
+ FrameTrafo get_pose() const { return X_CM; }
+ void reset();
+
+protected:
+ inline cv::Vec2f project(const cv::Vec3f& v_M, float f)
+ {
+ cv::Vec3f v_C = X_CM * v_M;
+ return cv::Vec2f(f*v_C[0]/v_C[2], f*v_C[1]/v_C[2]);
+ }
+
+ bool find_correspondences(const std::vector<cv::Vec2f>& points, float f);
+
+ cv::Vec2f p[PointModel::N_POINTS]; // the points in model order
+ cv::Vec2f p_exp[PointModel::N_POINTS]; // the expected point positions
+
+ void predict(float dt);
+ void update_velocities(float dt);
+ void reset_velocities();
+
+
+ int POSIT(float f); // The POSIT algorithm, returns the number of iterations
+
+ bool init_phase;
+ float dt_valid; // time since last valid tracking result
+ cv::Vec3f v_t; // velocities
+ cv::Vec3f v_r;
+ FrameTrafo X_CM; // trafo from model to camera
+ FrameTrafo X_CM_old;
+};
+
+#endif //POINTTRACKER_H
diff --git a/ftnoir_tracker_pt/pt_video_widget.cpp b/ftnoir_tracker_pt/pt_video_widget.cpp
new file mode 100644
index 00000000..02817cbf
--- /dev/null
+++ b/ftnoir_tracker_pt/pt_video_widget.cpp
@@ -0,0 +1,64 @@
+/* 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.
+ *
+ * 20130312, WVR: Add 7 lines to resizeGL after resize_frame. This should lower CPU-load.
+ */
+
+#include "pt_video_widget.h"
+
+#include <QDebug>
+#include <QHBoxLayout>
+
+using namespace cv;
+using namespace std;
+
+void PTVideoWidget::update_image(const cv::Mat& frame)
+{
+ QMutexLocker foo(&mtx);
+ _frame = frame.clone();
+ freshp = true;
+}
+
+// ----------------------------------------------------------------------------
+VideoWidgetDialog::VideoWidgetDialog(QWidget *parent, FrameProvider* provider)
+ : QDialog(parent),
+ video_widget(NULL)
+{
+ const int VIDEO_FRAME_WIDTH = 640;
+ const int VIDEO_FRAME_HEIGHT = 480;
+
+ video_widget = new PTVideoWidget(this, provider);
+
+ QHBoxLayout* layout = new QHBoxLayout();
+ layout->setContentsMargins(0, 0, 0, 0);
+ layout->addWidget(video_widget);
+ if (this->layout()) delete this->layout();
+ setLayout(layout);
+ resize(VIDEO_FRAME_WIDTH, VIDEO_FRAME_HEIGHT);
+}
+
+void PTVideoWidget::update_and_repaint()
+{
+ QMutexLocker foo(&mtx);
+ if (_frame.empty() || !freshp)
+ return;
+ freshp = false;
+ QImage qframe = QImage(_frame.cols, _frame.rows, QImage::Format_RGB888);
+ uchar* data = qframe.bits();
+ const int pitch = qframe.bytesPerLine();
+ for (int y = 0; y < _frame.rows; y++)
+ for (int x = 0; x < _frame.cols; x++)
+ {
+ const auto& elt = _frame.at<Vec3b>(y, x);
+ const cv::Scalar elt2 = static_cast<cv::Scalar>(elt);
+ data[y * pitch + x * 3 + 0] = elt2.val[2];
+ data[y * pitch + x * 3 + 1] = elt2.val[1];
+ data[y * pitch + x * 3 + 2] = elt2.val[0];
+ }
+ qframe = qframe.scaled(size(), Qt::IgnoreAspectRatio, Qt::FastTransformation);
+ texture = qframe;
+ update();
+}
diff --git a/ftnoir_tracker_pt/pt_video_widget.h b/ftnoir_tracker_pt/pt_video_widget.h
new file mode 100644
index 00000000..25d593c3
--- /dev/null
+++ b/ftnoir_tracker_pt/pt_video_widget.h
@@ -0,0 +1,71 @@
+/* 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 "frame_observer.h"
+#include <QObject>
+#include <QTime>
+#include <QDialog>
+#include <opencv2/opencv.hpp>
+#ifndef OPENTRACK_API
+# include <QGLWidget>
+# include <boost/shared_ptr.hpp>
+#else
+# include "FTNoIR_Tracker_PT/boost-compat.h"
+# if defined(_WIN32)
+# include <dshow.h>
+# endif
+#endif
+#include <QPainter>
+#include <QPaintEvent>
+#include <QTimer>
+
+class PTVideoWidget : public QWidget, public FrameObserver
+{
+ Q_OBJECT
+
+public:
+ PTVideoWidget(QWidget *parent, FrameProvider* provider) :
+ QWidget(parent),
+ /* to avoid linker errors */ FrameObserver(provider),
+ freshp(false)
+ {
+ connect(&timer, SIGNAL(timeout()), this, SLOT(update_and_repaint()));
+ timer.start(40);
+ }
+ void update_image(const cv::Mat &frame);
+ void update_frame_and_points() {}
+protected slots:
+ void paintEvent( QPaintEvent* e ) {
+ QMutexLocker foo(&mtx);
+ QPainter painter(this);
+ painter.drawImage(e->rect(), texture);
+ }
+ void update_and_repaint();
+private:
+ QMutex mtx;
+ QImage texture;
+ QTimer timer;
+ cv::Mat _frame;
+ bool freshp;
+};
+
+// ----------------------------------------------------------------------------
+// A VideoWidget embedded in a dialog frame
+class VideoWidgetDialog : public QDialog
+{
+ Q_OBJECT
+public:
+ VideoWidgetDialog(QWidget *parent, FrameProvider* provider);
+ virtual ~VideoWidgetDialog() {}
+
+ PTVideoWidget* get_video_widget() { return video_widget; }
+
+private:
+ PTVideoWidget* video_widget;
+};
diff --git a/ftnoir_tracker_pt/trans_calib.cpp b/ftnoir_tracker_pt/trans_calib.cpp
new file mode 100644
index 00000000..9b75a1b6
--- /dev/null
+++ b/ftnoir_tracker_pt/trans_calib.cpp
@@ -0,0 +1,44 @@
+/* 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 "trans_calib.h"
+
+using namespace cv;
+
+//-----------------------------------------------------------------------------
+TranslationCalibrator::TranslationCalibrator()
+{
+ reset();
+}
+
+void TranslationCalibrator::reset()
+{
+ P = Matx66f::zeros();
+ y = Vec6f(0,0,0, 0,0,0);
+}
+
+void TranslationCalibrator::update(const Matx33f& R_CM_k, const Vec3f& t_CM_k)
+{
+ Matx<float, 6,3> H_k_T = Matx<float, 6,3>::zeros();
+ for (int i=0; i<3; ++i) {
+ for (int j=0; j<3; ++j) {
+ H_k_T(i,j) = R_CM_k(j,i);
+ }
+ }
+ for (int i=0; i<3; ++i)
+ {
+ H_k_T(3+i,i) = 1.0;
+ }
+ P += H_k_T * H_k_T.t();
+ y += H_k_T * t_CM_k;
+}
+
+Vec3f TranslationCalibrator::get_estimate()
+{
+ Vec6f x = P.inv() * y;
+ return Vec3f(-x[0], -x[1], -x[2]);
+} \ No newline at end of file
diff --git a/ftnoir_tracker_pt/trans_calib.h b/ftnoir_tracker_pt/trans_calib.h
new file mode 100644
index 00000000..f2521690
--- /dev/null
+++ b/ftnoir_tracker_pt/trans_calib.h
@@ -0,0 +1,39 @@
+/* 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.
+ */
+
+#ifndef TRANSCALIB_H
+#define TRANSCALIB_H
+
+#include <opencv2/opencv.hpp>
+
+//-----------------------------------------------------------------------------
+// Calibrates the translation from head to model = t_MH
+// by recursive least squares /
+// kalman filter in information form with identity noise covariance
+// measurement equation when head position = t_CH is fixed:
+// (R_CM_k , Id)*(-t_MH, t_CH) = t_CM_k
+
+class TranslationCalibrator
+{
+public:
+ TranslationCalibrator();
+
+ // reset the calibration process
+ void reset();
+
+ // update the current estimate
+ void update(const cv::Matx33f& R_CM_k, const cv::Vec3f& t_CM_k);
+
+ // get the current estimate for t_MH
+ cv::Vec3f get_estimate();
+
+protected:
+ cv::Matx66f P; // normalized precision matrix = inverse covariance
+ cv::Vec6f y; // P*(-t_MH, t_CH)
+};
+
+#endif //TRANSCALIB_H \ No newline at end of file