diff options
Diffstat (limited to 'tracker-easy')
35 files changed, 4807 insertions, 0 deletions
diff --git a/tracker-easy/CMakeLists.txt b/tracker-easy/CMakeLists.txt new file mode 100644 index 00000000..ff537877 --- /dev/null +++ b/tracker-easy/CMakeLists.txt @@ -0,0 +1,25 @@ +include(opentrack-opencv) +find_package(OpenCV QUIET) + +if(OpenCV_FOUND) + try_compile(tracker-easy_ocv-check "${CMAKE_CURRENT_BINARY_DIR}" + SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/ocv-check.cxx" + CMAKE_FLAGS "-DINCLUDE_DIRECTORIES=${OpenCV_INCLUDE_DIRS}" + "-DCXX_STANDARD=20" "-DCXX_STANDARD_REQUIRED=1" + OUTPUT_VARIABLE krap) + if(tracker-easy_ocv-check) + foreach(k highgui videoio imgcodecs imgproc calib3d video features2d flann) + otr_install_lib("opencv_${k}" "${opentrack-libexec}") + endforeach() + + if(CMAKE_COMPILER_IS_GNUCXX) + add_compile_options(-Wno-sign-compare) + elseif(MSVC) + add_compile_options(-wd4018) + endif() + + otr_module(tracker-easy) + target_include_directories(${self} SYSTEM PUBLIC ${OpenCV_INCLUDE_DIRS}) + target_link_libraries(${self} opencv_core opencv_imgproc opencv_calib3d opencv_video opencv_highgui opentrack-cv opentrack-video) + endif() +endif() diff --git a/tracker-easy/Resources/cap_front.png b/tracker-easy/Resources/cap_front.png Binary files differnew file mode 100644 index 00000000..14207a67 --- /dev/null +++ b/tracker-easy/Resources/cap_front.png diff --git a/tracker-easy/Resources/cap_side.png b/tracker-easy/Resources/cap_side.png Binary files differnew file mode 100644 index 00000000..5ad4ee65 --- /dev/null +++ b/tracker-easy/Resources/cap_side.png diff --git a/tracker-easy/Resources/clip_front.png b/tracker-easy/Resources/clip_front.png Binary files differnew file mode 100644 index 00000000..04880138 --- /dev/null +++ b/tracker-easy/Resources/clip_front.png diff --git a/tracker-easy/Resources/clip_side.png b/tracker-easy/Resources/clip_side.png Binary files differnew file mode 100644 index 00000000..72667ac7 --- /dev/null +++ b/tracker-easy/Resources/clip_side.png diff --git a/tracker-easy/Resources/easy-tracker-logo.png b/tracker-easy/Resources/easy-tracker-logo.png Binary files differnew file mode 100644 index 00000000..5a7da41c --- /dev/null +++ b/tracker-easy/Resources/easy-tracker-logo.png diff --git a/tracker-easy/doc/index.htm b/tracker-easy/doc/index.htm new file mode 100644 index 00000000..87b7356f --- /dev/null +++ b/tracker-easy/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/tracker-easy/doc/logo.png b/tracker-easy/doc/logo.png Binary files differnew file mode 100644 index 00000000..95032a25 --- /dev/null +++ b/tracker-easy/doc/logo.png diff --git a/tracker-easy/doc/ptrack.ico b/tracker-easy/doc/ptrack.ico Binary files differnew file mode 100644 index 00000000..c4b2aedc --- /dev/null +++ b/tracker-easy/doc/ptrack.ico diff --git a/tracker-easy/doc/settings1.png b/tracker-easy/doc/settings1.png Binary files differnew file mode 100644 index 00000000..35b84c5c --- /dev/null +++ b/tracker-easy/doc/settings1.png diff --git a/tracker-easy/doc/settings2.png b/tracker-easy/doc/settings2.png Binary files differnew file mode 100644 index 00000000..c6cfd1f3 --- /dev/null +++ b/tracker-easy/doc/settings2.png diff --git a/tracker-easy/doc/settings3.png b/tracker-easy/doc/settings3.png Binary files differnew file mode 100644 index 00000000..5922403d --- /dev/null +++ b/tracker-easy/doc/settings3.png diff --git a/tracker-easy/doc/style.css b/tracker-easy/doc/style.css new file mode 100644 index 00000000..0c3d29a6 --- /dev/null +++ b/tracker-easy/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/tracker-easy/export.hpp b/tracker-easy/export.hpp new file mode 100644 index 00000000..a733c9fe --- /dev/null +++ b/tracker-easy/export.hpp @@ -0,0 +1,11 @@ +// generates export.hpp for each module from compat/linkage.hpp + +#pragma once + +#include "compat/linkage-macros.hpp" + +#ifdef BUILD_TRACKER_PT +# define OTR_PT_EXPORT OTR_GENERIC_EXPORT +#else +# define OTR_PT_EXPORT OTR_GENERIC_IMPORT +#endif diff --git a/tracker-easy/kalman-filter-pose.cpp b/tracker-easy/kalman-filter-pose.cpp new file mode 100644 index 00000000..42346bb5 --- /dev/null +++ b/tracker-easy/kalman-filter-pose.cpp @@ -0,0 +1,118 @@ +/* Copyright (c) 2019, Stephane Lenclud <github@lenclud.com> + + * 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 "kalman-filter-pose.h" + +namespace EasyTracker +{ + + KalmanFilterPose::KalmanFilterPose() + { + + } + + + void KalmanFilterPose::Init(int nStates, int nMeasurements, int nInputs, double dt) + { + iMeasurements = cv::Mat(nMeasurements, 1, CV_64FC1); + + init(nStates, nMeasurements, nInputs, CV_64F); // init Kalman Filter + + // TODO: Use parameters instead of magic numbers + setIdentity(processNoiseCov, cv::Scalar::all(1)); //1e-5 // set process noise + setIdentity(measurementNoiseCov, cv::Scalar::all(1)); //1e-2 // set measurement noise + setIdentity(errorCovPost, cv::Scalar::all(1)); // error covariance + + /** DYNAMIC MODEL **/ + + // [1 0 0 dt 0 0 dt2 0 0 0 0 0 0 0 0 0 0 0] + // [0 1 0 0 dt 0 0 dt2 0 0 0 0 0 0 0 0 0 0] + // [0 0 1 0 0 dt 0 0 dt2 0 0 0 0 0 0 0 0 0] + // [0 0 0 1 0 0 dt 0 0 0 0 0 0 0 0 0 0 0] + // [0 0 0 0 1 0 0 dt 0 0 0 0 0 0 0 0 0 0] + // [0 0 0 0 0 1 0 0 dt 0 0 0 0 0 0 0 0 0] + // [0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0] + // [0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0] + // [0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0] + // [0 0 0 0 0 0 0 0 0 1 0 0 dt 0 0 dt2 0 0] + // [0 0 0 0 0 0 0 0 0 0 1 0 0 dt 0 0 dt2 0] + // [0 0 0 0 0 0 0 0 0 0 0 1 0 0 dt 0 0 dt2] + // [0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 dt 0 0] + // [0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 dt 0] + // [0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 dt] + // [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0] + // [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0] + // [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1] + + // position + transitionMatrix.at<double>(0, 3) = dt; + transitionMatrix.at<double>(1, 4) = dt; + transitionMatrix.at<double>(2, 5) = dt; + transitionMatrix.at<double>(3, 6) = dt; + transitionMatrix.at<double>(4, 7) = dt; + transitionMatrix.at<double>(5, 8) = dt; + transitionMatrix.at<double>(0, 6) = 0.5*pow(dt, 2); + transitionMatrix.at<double>(1, 7) = 0.5*pow(dt, 2); + transitionMatrix.at<double>(2, 8) = 0.5*pow(dt, 2); + + // orientation + transitionMatrix.at<double>(9, 12) = dt; + transitionMatrix.at<double>(10, 13) = dt; + transitionMatrix.at<double>(11, 14) = dt; + transitionMatrix.at<double>(12, 15) = dt; + transitionMatrix.at<double>(13, 16) = dt; + transitionMatrix.at<double>(14, 17) = dt; + transitionMatrix.at<double>(9, 15) = 0.5*pow(dt, 2); + transitionMatrix.at<double>(10, 16) = 0.5*pow(dt, 2); + transitionMatrix.at<double>(11, 17) = 0.5*pow(dt, 2); + + + /** MEASUREMENT MODEL **/ + + // [1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] + // [0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] + // [0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] + // [0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0] + // [0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0] + // [0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0] + + // Those will scale the filtered values, 2 will give you half the raw input + measurementMatrix.at<double>(0, 0) = 1; // x + measurementMatrix.at<double>(1, 1) = 1; // y + measurementMatrix.at<double>(2, 2) = 1; // z + measurementMatrix.at<double>(3, 9) = 1; // roll + measurementMatrix.at<double>(4, 10) = 1; // pitch + measurementMatrix.at<double>(5, 11) = 1; // yaw + } + + void KalmanFilterPose::Update(double& aX, double& aY, double& aZ, double& aRoll, double& aPitch, double& aYaw) + { + // Set measurement to predict + iMeasurements.at<double>(0) = aX; // x + iMeasurements.at<double>(1) = aY; // y + iMeasurements.at<double>(2) = aZ; // z + iMeasurements.at<double>(3) = aRoll; // roll + iMeasurements.at<double>(4) = aPitch; // pitch + iMeasurements.at<double>(5) = aYaw; // yaw + + // First predict, to update the internal statePre variable + cv::Mat prediction = predict(); + // The "correct" phase that is going to use the predicted value and our measurement + cv::Mat estimated = correct(iMeasurements); + // Estimated translation + aX = estimated.at<double>(0); + aY = estimated.at<double>(1); + aZ = estimated.at<double>(2); + // Estimated euler angles + aRoll = estimated.at<double>(9); + aPitch = estimated.at<double>(10); + aYaw = estimated.at<double>(11); + } + +} + diff --git a/tracker-easy/kalman-filter-pose.h b/tracker-easy/kalman-filter-pose.h new file mode 100644 index 00000000..c454a27c --- /dev/null +++ b/tracker-easy/kalman-filter-pose.h @@ -0,0 +1,32 @@ +/* Copyright (c) 2019, Stephane Lenclud <github@lenclud.com> + + * Permission to use, copy, modify, and/or distribute this + * software for any purpose with or without fee is hereby granted, + * provided that the above copyright notice and this permission + * notice appear in all copies. + */ + +#pragma once + +#include <opencv2/core.hpp> +#include <opencv2/video/tracking.hpp> + + +namespace EasyTracker +{ + + /// + /// TODO: do not use a constant time difference + /// + class KalmanFilterPose: public cv::KalmanFilter + { + public: + KalmanFilterPose(); + void Init(int aStateCount, int aMeasurementCount, int aInputCount, double aDt); + void Update(double& aX, double& aY, double& aZ, double& aRoll, double& aPitch, double& aYaw); + + + cv::Mat iMeasurements; + }; + +} diff --git a/tracker-easy/lang/de_DE.ts b/tracker-easy/lang/de_DE.ts new file mode 100644 index 00000000..81e52cb0 --- /dev/null +++ b/tracker-easy/lang/de_DE.ts @@ -0,0 +1,250 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="de_DE"> +<context> + <name>EasyTracker::Metadata</name> + <message> + <source>Easy Tracker 1.1</source> + <translation>Easy Tracker 1.1</translation> + </message> +</context> +<context> + <name>UICPTClientControls</name> + <message> + <source>Easy Tracker Settings</source> + <translation>Easy-Tracker-Einstellungen</translation> + </message> + <message> + <source>Tracker</source> + <translation>Tracker</translation> + </message> + <message> + <source>Camera</source> + <translation>Kamera</translation> + </message> + <message> + <source>Desired capture framerate</source> + <translation>Angestrebte Aufnahmebildrate</translation> + </message> + <message> + <source> Hz</source> + <translation> Hz</translation> + </message> + <message> + <source>Camera settings (when available)</source> + <translation>Kamera-Einstellungen (falls verfügbar)</translation> + </message> + <message> + <source>Width</source> + <translation>Breite</translation> + </message> + <message> + <source>Height</source> + <translation>Höhe</translation> + </message> + <message> + <source>FPS</source> + <translation>FPS</translation> + </message> + <message> + <source>Open</source> + <translation>Öffnen</translation> + </message> + <message> + <source>°</source> + <translation>°</translation> + </message> + <message> + <source>Device</source> + <translation>Gerät</translation> + </message> + <message> + <source>Desired capture height</source> + <translation>Angestrebte Aufnahmehöhe</translation> + </message> + <message> + <source> px</source> + <translation> px</translation> + </message> + <message> + <source>Desired capture width</source> + <translation>Angestrebte Aufnahmebreite</translation> + </message> + <message> + <source>Diagonal field of view</source> + <translation>Diagonales Sichtfeld</translation> + </message> + <message> + <source>Settings</source> + <translation>Einstellungen</translation> + </message> + <message> + <source>Debug (full size preview)</source> + <translation>Fehlersuche (Vorschau in voller Größe)</translation> + </message> + <message> + <source>Min size</source> + <translation>Minimale Größe</translation> + </message> + <message> + <source>Minimum point diameter</source> + <translation>Minimaler Punktdurchmesser</translation> + </message> + <message> + <source>Auto center</source> + <translation>Automatisch zentrieren</translation> + </message> + <message> + <source>Max size</source> + <translation>Maximale Größe</translation> + </message> + <message> + <source>Size in pixels of half the edge defining deadzone squares around tracked points</source> + <translation>Größe in Pixeln der halben Kante, die die Totbereichquadrate um die verfolgten Punkte definiert</translation> + </message> + <message> + <source><html><head/><body><p>Use P3P or AP3P for three and four points setup. Use EPNP or ITERATIVE for five points setup. Inconsistent configuration will result in undefined behavior.</p></body></html></source> + <translation><html><head/><body><p>Benutze P3P oder AP3P für ein Drei- oder Vierpunkt-Setup. Benutze EPNP oder ITERATIVE für ein Fünfpunkt-Setup. Eine inkonsistente Konfiguration führt zu einem undefinierten Verhalten.</p></body></html></translation> + </message> + <message> + <source>Perspective-N-Point solver</source> + <translation>Perspektivischer N-Punkt-Löser</translation> + </message> + <message> + <source>Make sure you pick a solver supporting the number of marker you are using. For three points detection use either P3P or AP3P.</source> + <translation>Stelle sicher, einen Löser zu verwenden, der die Anzahl deiner verwendeten Markierungen unterstützt. Für die Erkennung von drei Punkten nutze entweder P3P oder AP3P.</translation> + </message> + <message> + <source>ITERATIVE</source> + <translation>ITERATIVE</translation> + </message> + <message> + <source>EPNP</source> + <translation>EPNP</translation> + </message> + <message> + <source>P3P</source> + <translation>P3P</translation> + </message> + <message> + <source>DLS</source> + <translation>DLS</translation> + </message> + <message> + <source>UPNP</source> + <translation>UPNP</translation> + </message> + <message> + <source>AP3P</source> + <translation>AP3P</translation> + </message> + <message> + <source>Deadzone</source> + <translation>Totbereich</translation> + </message> + <message> + <source>Maximum point diameter</source> + <translation>Maximaler Punktdurchmesser</translation> + </message> + <message> + <source>Auto center timeout</source> + <translation>Timeout für automatische Zentrierung</translation> + </message> + <message> + <source>If no valid pose can be determined after that much time the center pose will be used.</source> + <translation>Falls innerhalb dieses Zeitraums keine Pose erkannt wird, wird die zentrierte Pose verwendet.</translation> + </message> + <message> + <source> ms</source> + <translation> ms</translation> + </message> + <message> + <source>Model</source> + <translation>Modell</translation> + </message> + <message> + <source>Model type:</source> + <translation>Modelltyp:</translation> + </message> + <message> + <source>Hat three vertices</source> + <translation>Hut drei Eckpunkte</translation> + </message> + <message> + <source>Hat four vertices</source> + <translation>Hut vier Eckpunkte</translation> + </message> + <message> + <source>Hat five vertices</source> + <translation>Hut fünf Eckpunkte</translation> + </message> + <message> + <source>Clip three vertices</source> + <translation>Sticker drei Eckpunkte</translation> + </message> + <message> + <source>Vertices: </source> + <translation>Eckpunkte: </translation> + </message> + <message> + <source><html><head/><body><p><span style=" font-size:12pt;">X</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-size:12pt;">X</span></p></body></html></translation> + </message> + <message> + <source><html><head/><body><p><span style=" font-size:12pt;">Y</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-size:12pt;">Y</span></p></body></html></translation> + </message> + <message> + <source><html><head/><body><p><span style=" font-size:12pt;">Z</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-size:12pt;">Z</span></p></body></html></translation> + </message> + <message> + <source>Top:</source> + <translation>Oben:</translation> + </message> + <message> + <source> mm</source> + <translation> mm</translation> + </message> + <message> + <source>Right:</source> + <translation>Rechts:</translation> + </message> + <message> + <source>Left:</source> + <translation>Links:</translation> + </message> + <message> + <source>Center:</source> + <translation>Mitte:</translation> + </message> + <message> + <source>Top right:</source> + <translation>Oben rechts:</translation> + </message> + <message> + <source>Top left:</source> + <translation>Oben links:</translation> + </message> + <message> + <source>Clip top:</source> + <translation>Sticker-Oberkante:</translation> + </message> + <message> + <source>Clip middle:</source> + <translation>Sticker-Mitte:</translation> + </message> + <message> + <source>Clip bottom:</source> + <translation>Sticker-Unterkante:</translation> + </message> + <message> + <source>About</source> + <translation>Über</translation> + </message> + <message> + <source><html><head/><body><p><span style=" font-weight:600;">Easy Tracker<br/>Version 1.1</span></p><p><span style=" font-weight:600;">by Stéphane Lenclud</span></p><p>See <a href="https://github.com/opentrack/opentrack/wiki/Easy-Tracker"><span style=" font-weight:600; text-decoration: underline; color:#9999aa;">documentation on GitHub</span></a></p></body></html></source> + <translation><html><head/><body><p><span style=" font-weight:600;">Easy Tracker<br/>Version 1.1</span></p><p><span style=" font-weight:600;">von Stéphane Lenclud</span></p><p>Siehe <a href="https://github.com/opentrack/opentrack/wiki/Easy-Tracker"><span style=" font-weight:600; text-decoration: underline; color:#9999aa;">Dokumentation auf GitHub</span></a></p></body></html></translation> + </message> +</context> +</TS> diff --git a/tracker-easy/lang/nl_NL.ts b/tracker-easy/lang/nl_NL.ts new file mode 100644 index 00000000..775ec4d9 --- /dev/null +++ b/tracker-easy/lang/nl_NL.ts @@ -0,0 +1,250 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="nl_NL"> +<context> + <name>EasyTracker::Metadata</name> + <message> + <source>Easy Tracker 1.1</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>UICPTClientControls</name> + <message> + <source>Camera</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>°</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Diagonal field of view</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Width</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>FPS</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Desired capture height</source> + <translation type="unfinished"></translation> + </message> + <message> + <source> px</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Desired capture framerate</source> + <translation type="unfinished"></translation> + </message> + <message> + <source> Hz</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Desired capture width</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Height</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Device</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Open</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Camera settings (when available)</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Max size</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Min size</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Maximum point diameter</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Minimum point diameter</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Model</source> + <translation type="unfinished"></translation> + </message> + <message> + <source> mm</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>About</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Debug (full size preview)</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Deadzone</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Size in pixels of half the edge defining deadzone squares around tracked points</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Perspective-N-Point solver</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Make sure you pick a solver supporting the number of marker you are using. For three points detection use either P3P or AP3P.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>P3P</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>ITERATIVE</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>EPNP</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>DLS</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>UPNP</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>AP3P</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Tracker</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Settings</source> + <translation type="unfinished"></translation> + </message> + <message> + <source><html><head/><body><p><span style=" font-size:12pt;">X</span></p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <source><html><head/><body><p><span style=" font-size:12pt;">Y</span></p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <source><html><head/><body><p><span style=" font-size:12pt;">Z</span></p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Auto center</source> + <translation type="unfinished"></translation> + </message> + <message> + <source><html><head/><body><p>Use P3P or AP3P for three and four points setup. Use EPNP or ITERATIVE for five points setup. Inconsistent configuration will result in undefined behavior.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Auto center timeout</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>If no valid pose can be determined after that much time the center pose will be used.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source> ms</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Easy Tracker Settings</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Model type:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Hat three vertices</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Hat four vertices</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Hat five vertices</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Clip three vertices</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Vertices: </source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Top:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Right:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Left:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Center:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Top right:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Top left:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Clip top:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Clip middle:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Clip bottom:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source><html><head/><body><p><span style=" font-weight:600;">Easy Tracker<br/>Version 1.1</span></p><p><span style=" font-weight:600;">by Stéphane Lenclud</span></p><p>See <a href="https://github.com/opentrack/opentrack/wiki/Easy-Tracker"><span style=" font-weight:600; text-decoration: underline; color:#9999aa;">documentation on GitHub</span></a></p></body></html></source> + <translation type="unfinished"></translation> + </message> +</context> +</TS> diff --git a/tracker-easy/lang/ru_RU.ts b/tracker-easy/lang/ru_RU.ts new file mode 100644 index 00000000..2ae262a5 --- /dev/null +++ b/tracker-easy/lang/ru_RU.ts @@ -0,0 +1,250 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="ru_RU"> +<context> + <name>EasyTracker::Metadata</name> + <message> + <source>Easy Tracker 1.1</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>UICPTClientControls</name> + <message> + <source>Camera</source> + <translation>Камера</translation> + </message> + <message> + <source>°</source> + <translation></translation> + </message> + <message> + <source>Diagonal field of view</source> + <translation>Угол обзора камеры</translation> + </message> + <message> + <source>Width</source> + <translation>Ширина</translation> + </message> + <message> + <source>FPS</source> + <translation>FPS (Кадров в секунду)</translation> + </message> + <message> + <source>Desired capture height</source> + <translation></translation> + </message> + <message> + <source> px</source> + <translation></translation> + </message> + <message> + <source>Desired capture framerate</source> + <translation>Желаемая частота кадров</translation> + </message> + <message> + <source> Hz</source> + <translation> Гц</translation> + </message> + <message> + <source>Desired capture width</source> + <translation>Желаемая ширина захвата</translation> + </message> + <message> + <source>Height</source> + <translation>Высота</translation> + </message> + <message> + <source>Device</source> + <translation>Устройство</translation> + </message> + <message> + <source>Open</source> + <translation>Открыть</translation> + </message> + <message> + <source>Camera settings (when available)</source> + <translation>Параметры камеры (если доступно)</translation> + </message> + <message> + <source>Max size</source> + <translation>Макс.размер</translation> + </message> + <message> + <source>Min size</source> + <translation>Мин.размер</translation> + </message> + <message> + <source>Maximum point diameter</source> + <translation></translation> + </message> + <message> + <source>Minimum point diameter</source> + <translation></translation> + </message> + <message> + <source>Model</source> + <translation>Модель</translation> + </message> + <message> + <source> mm</source> + <translation> мм</translation> + </message> + <message> + <source>About</source> + <translation>О программе</translation> + </message> + <message> + <source>Debug (full size preview)</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Deadzone</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Size in pixels of half the edge defining deadzone squares around tracked points</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Perspective-N-Point solver</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Make sure you pick a solver supporting the number of marker you are using. For three points detection use either P3P or AP3P.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>P3P</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>ITERATIVE</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>EPNP</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>DLS</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>UPNP</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>AP3P</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Tracker</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Settings</source> + <translation type="unfinished"></translation> + </message> + <message> + <source><html><head/><body><p><span style=" font-size:12pt;">X</span></p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <source><html><head/><body><p><span style=" font-size:12pt;">Y</span></p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <source><html><head/><body><p><span style=" font-size:12pt;">Z</span></p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Auto center</source> + <translation type="unfinished"></translation> + </message> + <message> + <source><html><head/><body><p>Use P3P or AP3P for three and four points setup. Use EPNP or ITERATIVE for five points setup. Inconsistent configuration will result in undefined behavior.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Auto center timeout</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>If no valid pose can be determined after that much time the center pose will be used.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source> ms</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Easy Tracker Settings</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Model type:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Hat three vertices</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Hat four vertices</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Hat five vertices</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Clip three vertices</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Vertices: </source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Top:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Right:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Left:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Center:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Top right:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Top left:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Clip top:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Clip middle:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Clip bottom:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source><html><head/><body><p><span style=" font-weight:600;">Easy Tracker<br/>Version 1.1</span></p><p><span style=" font-weight:600;">by Stéphane Lenclud</span></p><p>See <a href="https://github.com/opentrack/opentrack/wiki/Easy-Tracker"><span style=" font-weight:600; text-decoration: underline; color:#9999aa;">documentation on GitHub</span></a></p></body></html></source> + <translation type="unfinished"></translation> + </message> +</context> +</TS> diff --git a/tracker-easy/lang/stub.ts b/tracker-easy/lang/stub.ts new file mode 100644 index 00000000..b10f5885 --- /dev/null +++ b/tracker-easy/lang/stub.ts @@ -0,0 +1,250 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1"> +<context> + <name>EasyTracker::Metadata</name> + <message> + <source>Easy Tracker 1.1</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>UICPTClientControls</name> + <message> + <source>Camera</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>°</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Diagonal field of view</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Width</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>FPS</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Desired capture height</source> + <translation type="unfinished"></translation> + </message> + <message> + <source> px</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Desired capture framerate</source> + <translation type="unfinished"></translation> + </message> + <message> + <source> Hz</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Desired capture width</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Height</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Device</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Open</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Camera settings (when available)</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Max size</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Min size</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Maximum point diameter</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Minimum point diameter</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Model</source> + <translation type="unfinished"></translation> + </message> + <message> + <source> mm</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>About</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Debug (full size preview)</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Deadzone</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Size in pixels of half the edge defining deadzone squares around tracked points</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Perspective-N-Point solver</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Make sure you pick a solver supporting the number of marker you are using. For three points detection use either P3P or AP3P.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>P3P</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>ITERATIVE</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>EPNP</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>DLS</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>UPNP</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>AP3P</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Tracker</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Settings</source> + <translation type="unfinished"></translation> + </message> + <message> + <source><html><head/><body><p><span style=" font-size:12pt;">X</span></p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <source><html><head/><body><p><span style=" font-size:12pt;">Y</span></p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <source><html><head/><body><p><span style=" font-size:12pt;">Z</span></p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Auto center</source> + <translation type="unfinished"></translation> + </message> + <message> + <source><html><head/><body><p>Use P3P or AP3P for three and four points setup. Use EPNP or ITERATIVE for five points setup. Inconsistent configuration will result in undefined behavior.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Auto center timeout</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>If no valid pose can be determined after that much time the center pose will be used.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source> ms</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Easy Tracker Settings</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Model type:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Hat three vertices</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Hat four vertices</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Hat five vertices</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Clip three vertices</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Vertices: </source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Top:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Right:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Left:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Center:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Top right:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Top left:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Clip top:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Clip middle:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Clip bottom:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source><html><head/><body><p><span style=" font-weight:600;">Easy Tracker<br/>Version 1.1</span></p><p><span style=" font-weight:600;">by Stéphane Lenclud</span></p><p>See <a href="https://github.com/opentrack/opentrack/wiki/Easy-Tracker"><span style=" font-weight:600; text-decoration: underline; color:#9999aa;">documentation on GitHub</span></a></p></body></html></source> + <translation type="unfinished"></translation> + </message> +</context> +</TS> diff --git a/tracker-easy/lang/zh_CN.ts b/tracker-easy/lang/zh_CN.ts new file mode 100644 index 00000000..71fc6368 --- /dev/null +++ b/tracker-easy/lang/zh_CN.ts @@ -0,0 +1,250 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="zh_CN"> +<context> + <name>EasyTracker::Metadata</name> + <message> + <source>Easy Tracker 1.1</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>UICPTClientControls</name> + <message> + <source>Camera</source> + <translation>摄像头</translation> + </message> + <message> + <source>°</source> + <translation>度</translation> + </message> + <message> + <source>Diagonal field of view</source> + <translation>对角线</translation> + </message> + <message> + <source>Width</source> + <translation>宽度</translation> + </message> + <message> + <source>FPS</source> + <translation>帧数</translation> + </message> + <message> + <source>Desired capture height</source> + <translation>期望高度</translation> + </message> + <message> + <source> px</source> + <translation> 像素点</translation> + </message> + <message> + <source>Desired capture framerate</source> + <translation>期望帧数</translation> + </message> + <message> + <source> Hz</source> + <translation> 赫兹</translation> + </message> + <message> + <source>Desired capture width</source> + <translation>期望宽度</translation> + </message> + <message> + <source>Height</source> + <translation>高度</translation> + </message> + <message> + <source>Device</source> + <translation>设备名称</translation> + </message> + <message> + <source>Open</source> + <translation>打开</translation> + </message> + <message> + <source>Camera settings (when available)</source> + <translation>摄像头设置 (连接时)</translation> + </message> + <message> + <source>Max size</source> + <translation>最大</translation> + </message> + <message> + <source>Min size</source> + <translation>最小</translation> + </message> + <message> + <source>Maximum point diameter</source> + <translation>最大点直径</translation> + </message> + <message> + <source>Minimum point diameter</source> + <translation>最小点直径</translation> + </message> + <message> + <source>Model</source> + <translation>点模式</translation> + </message> + <message> + <source> mm</source> + <translation> 毫米</translation> + </message> + <message> + <source>About</source> + <translation>关于</translation> + </message> + <message> + <source>Debug (full size preview)</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Deadzone</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Size in pixels of half the edge defining deadzone squares around tracked points</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Perspective-N-Point solver</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Make sure you pick a solver supporting the number of marker you are using. For three points detection use either P3P or AP3P.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>P3P</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>ITERATIVE</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>EPNP</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>DLS</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>UPNP</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>AP3P</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Tracker</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Settings</source> + <translation type="unfinished"></translation> + </message> + <message> + <source><html><head/><body><p><span style=" font-size:12pt;">X</span></p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <source><html><head/><body><p><span style=" font-size:12pt;">Y</span></p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <source><html><head/><body><p><span style=" font-size:12pt;">Z</span></p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Auto center</source> + <translation type="unfinished"></translation> + </message> + <message> + <source><html><head/><body><p>Use P3P or AP3P for three and four points setup. Use EPNP or ITERATIVE for five points setup. Inconsistent configuration will result in undefined behavior.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Auto center timeout</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>If no valid pose can be determined after that much time the center pose will be used.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source> ms</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Easy Tracker Settings</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Model type:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Hat three vertices</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Hat four vertices</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Hat five vertices</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Clip three vertices</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Vertices: </source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Top:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Right:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Left:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Center:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Top right:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Top left:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Clip top:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Clip middle:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Clip bottom:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source><html><head/><body><p><span style=" font-weight:600;">Easy Tracker<br/>Version 1.1</span></p><p><span style=" font-weight:600;">by Stéphane Lenclud</span></p><p>See <a href="https://github.com/opentrack/opentrack/wiki/Easy-Tracker"><span style=" font-weight:600; text-decoration: underline; color:#9999aa;">documentation on GitHub</span></a></p></body></html></source> + <translation type="unfinished"></translation> + </message> +</context> +</TS> diff --git a/tracker-easy/module.cpp b/tracker-easy/module.cpp new file mode 100644 index 00000000..8fcfeae1 --- /dev/null +++ b/tracker-easy/module.cpp @@ -0,0 +1,15 @@ +#include "tracker-easy.h" +#include "tracker-easy-dialog.h" +#include "module.hpp" + +#include <memory> + +namespace EasyTracker +{ + QString Metadata::name() { return tr("Easy Tracker 1.1"); } + QIcon Metadata::icon() { return QIcon(":/Resources/easy-tracker-logo.png"); } + +} + + +OPENTRACK_DECLARE_TRACKER(EasyTracker::Tracker, EasyTracker::Dialog, EasyTracker::Metadata) diff --git a/tracker-easy/module.hpp b/tracker-easy/module.hpp new file mode 100644 index 00000000..8a4e3920 --- /dev/null +++ b/tracker-easy/module.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "api/plugin-api.hpp" +#include <QIcon> +#include <QString> + +#include "compat/linkage-macros.hpp" + +namespace EasyTracker +{ + + class OTR_GENERIC_EXPORT Metadata : public ::Metadata + { + Q_OBJECT + + QString name() override; + QIcon icon() override; + }; + +} // ns pt_module diff --git a/tracker-easy/ocv-check.cxx b/tracker-easy/ocv-check.cxx new file mode 100644 index 00000000..912bd3cf --- /dev/null +++ b/tracker-easy/ocv-check.cxx @@ -0,0 +1,18 @@ +#include <opencv2/calib3d.hpp> +void check_solvep3p() +{ + cv::Mat x; + cv::solveP3P(x, x, x, x, x, x, 0); +} + +#include <opencv2/video/tracking.hpp> +void check_kf() +{ + [[maybe_unused]] cv::KalmanFilter kf; +} + +#include <opencv2/highgui.hpp> +void check_highgui() +{ + cv::imshow("foo", cv::noArray()); +} diff --git a/tracker-easy/point-extractor.cpp b/tracker-easy/point-extractor.cpp new file mode 100644 index 00000000..34d93b52 --- /dev/null +++ b/tracker-easy/point-extractor.cpp @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2019 Stephane Lenclud + * + * 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 "preview.h" +#include "tracker-easy.h" + +#include "cv/numeric.hpp" +#include "compat/math.hpp" + +#include <cmath> +#include <algorithm> +#include <cinttypes> +#include <memory> +#include <opencv2/highgui/highgui.hpp> +// Needed for mean shift +#include <opencv2/video/tracking.hpp> + +#include <QDebug> + + +using namespace numeric_types; + +namespace EasyTracker +{ + + PointExtractor::PointExtractor() : iSettings(KModuleName) + { + UpdateSettings(); + } + + /// + void PointExtractor::UpdateSettings() + { + iMinPointSize = iSettings.iMinBlobSize; + iMaxPointSize = iSettings.iMaxBlobSize; + } + + /// + void PointExtractor::ExtractPoints(const cv::Mat& aFrame, cv::Mat* aPreview, int aNeededPointCount, std::vector<cv::Point>& aPoints) + { + //TODO: Assert if channel size is neither one nor two + // Make sure our frame channel is 8 bit + size_t channelSize = aFrame.elemSize1(); + if (channelSize == 2) + { + // We have a 16 bits single channel. Typically coming from Kinect V2 IR sensor + // Resample to 8-bits + double min = std::numeric_limits<uint16_t>::min(); + double max = std::numeric_limits<uint16_t>::max(); + //cv::minMaxLoc(raw, &min, &max); // Should we use 16bit min and max instead? + // For scalling to have more precission in the range we are interrested in + min = max - 255; + // See: https://stackoverflow.com/questions/14539498/change-type-of-mat-object-from-cv-32f-to-cv-8u/14539652 + aFrame.convertTo(iFrameChannelSizeOne, CV_8U, 255.0 / (max - min), -255.0*min / (max - min)); + } + else + { + iFrameChannelSizeOne = aFrame; + } + + + // Make sure our frame has a single channel + // Make an extra copy if needed + const int channelCount = iFrameChannelSizeOne.channels(); + if (channelCount == 3) + { + // Convert to grayscale + // TODO: What's our input format, BRG or RGB? + // That won't make our point extraction work but at least it won't crash + cv::cvtColor(iFrameChannelSizeOne, iFrameGray, cv::COLOR_BGR2GRAY); + // TODO: Instead convert to HSV and use a key color together with cv::inRange to sport the color we want. + // Key color should be defined in settings. + } + else if (channelCount == 1) + { + // No further convertion needed + iFrameGray = iFrameChannelSizeOne; + } + else + { + eval_once(qDebug() << "tracker/easy: camera frame depth not supported" << aFrame.channels()); + return; + } + +//#define DEBUG +#ifdef DEBUG + cv::imshow("iFrameGray", iFrameGray); + cv::erode(iFrameGray, iFrameGray, cv::Mat(), cv::Point(-1, -1), 1); + cv::imshow("Eroded", iFrameGray); + cv::dilate(iFrameGray, iFrameGray, cv::Mat(), cv::Point(-1, -1), 2); + cv::imshow("Dilated", iFrameGray); +#endif + + + // Contours detection + iContours.clear(); + cv::findContours(iFrameGray, iContours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE); + + // Workout which countours are valid points + for (size_t i = 0; i < iContours.size(); i++) + { + if (aPreview) + { + cv::drawContours(*aPreview, iContours, (int)i, CV_RGB(255, 0, 0), 2); + } + + + cv::Rect bBox; + bBox = cv::boundingRect(iContours[i]); + + // Make sure bounding box matches our criteria + if (bBox.width >= iMinPointSize + && bBox.height >= iMinPointSize + && bBox.width <= iMaxPointSize + && bBox.height <= iMaxPointSize) + { + // Do a mean shift or cam shift, it's not bringing much though + //cv::CamShift(iFrameGray, bBox, cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::MAX_ITER, 10, 1)); + // + cv::Point center; + center.x = bBox.x + bBox.width / 2; + center.y = bBox.y + bBox.height / 2; + aPoints.push_back(center); + + if (aPreview) + { + cv::rectangle(*aPreview, bBox, CV_RGB(0, 255, 0), 2); + } + } + } + + // Keep only the three points which are highest, i.e. with lowest Y coordinates + // That's most usefull to discard noise from features below your cap/head. + // Typically noise comming from zippers and metal parts on your clothing. + // With a cap tracker it also successfully discards noise from glasses. + // However it may not work as good with a clip user wearing glasses. + while (aPoints.size() > aNeededPointCount) // Until we have no more than three points + { + int maxY = 0; + size_t index = std::numeric_limits<size_t>::max(); + + // Search for the point with highest Y coordinate + for (size_t i = 0; i < aPoints.size(); i++) + { + if (aPoints[i].y > maxY) + { + maxY = aPoints[i].y; + index = i; + } + } + + if (index < aPoints.size()) // Defensive + { + // Discard it + aPoints.erase(aPoints.begin() + index); + } + + } + } + +} + diff --git a/tracker-easy/point-extractor.h b/tracker-easy/point-extractor.h new file mode 100644 index 00000000..f275769d --- /dev/null +++ b/tracker-easy/point-extractor.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019 Stephane Lenclud + * + * 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 "settings.h" +#include <vector> + +#include <opencv2/core.hpp> +#include <opencv2/imgproc.hpp> + + +namespace EasyTracker +{ + class PointExtractor + { + public: + PointExtractor(); + // extracts points from frame and draws some processing info into frame, if draw_output is set + // dt: time since last call in seconds + void ExtractPoints(const cv::Mat& aFrame, cv::Mat* aPreview, int aNeededPointCount, std::vector<cv::Point>& aPoints); + + void UpdateSettings(); + + // Settings + Settings iSettings; + // Our frame with a channel size of 8 bits + cv::Mat iFrameChannelSizeOne; + // Our frame with a single 8 bits channel + cv::Mat iFrameGray; + // + std::vector<std::vector<cv::Point> > iContours; + + // Take a copy of settings to avoid dead lock + int iMinPointSize; + int iMaxPointSize; + + }; + +} + diff --git a/tracker-easy/preview.cpp b/tracker-easy/preview.cpp new file mode 100644 index 00000000..97c8aeaf --- /dev/null +++ b/tracker-easy/preview.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2019 Stephane Lenclud + * + * 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 "preview.h" + +#include "compat/math.hpp" +#include "compat/macros.h" + +#include <opencv2/imgproc.hpp> +#include <QDebug> + + +namespace EasyTracker +{ + + + Preview& Preview::operator=(const cv::Mat& aFrame) + { + //TODO: Assert if channel size is neither one nor two + + // Make sure our frame channel is 8 bit + size_t channelSize = aFrame.elemSize1(); + if (channelSize == 2) + { + // First resample to 8-bits + double min = std::numeric_limits<uint16_t>::min(); + double max = std::numeric_limits<uint16_t>::max(); + //cv::minMaxLoc(raw, &min, &max); // Should we use 16bit min and max instead? + // For scalling to have more precission in the range we are interrested in + //min = max - 255; + // See: https://stackoverflow.com/questions/14539498/change-type-of-mat-object-from-cv-32f-to-cv-8u/14539652 + aFrame.convertTo(iFrameChannelSizeOne, CV_8U, 255.0 / (max - min), -255.0*min / (max - min)); + } + else + { + iFrameChannelSizeOne = aFrame; + } + + // Make sure our frame is RGB + // Make an extra copy if needed + int channelCount = iFrameChannelSizeOne.channels(); + if (channelCount == 1) + { + // Convert to RGB + cv::cvtColor(iFrameChannelSizeOne, iFrameRgb, cv::COLOR_GRAY2BGR); + } + else if (channelCount == 3) + { + iFrameRgb = iFrameChannelSizeOne; + } + else + { + eval_once(qDebug() << "tracker/easy: camera frame depth not supported" << aFrame.channels()); + return *this; + } + + + return *this; + } + + Preview::Preview(int w, int h) + { + ensure_size(iFrameWidget, w, h, CV_8UC4); + ensure_size(iFrameResized, w, h, CV_8UC3); + + iFrameResized.setTo(cv::Scalar(0, 0, 0)); + } + + QImage Preview::get_bitmap() + { + size_t stride = iFrameWidget.step.p[0]; + + if (stride < 64 || stride < iFrameWidget.cols * 4) + { + eval_once(qDebug() << "bad stride" << stride + << "for bitmap size" << iFrameResized.cols << iFrameResized.rows); + return QImage(); + } + + // Resize if needed + const bool need_resize = iFrameRgb.cols != iFrameWidget.cols || iFrameRgb.rows != iFrameWidget.rows; + if (need_resize) + { + cv::resize(iFrameRgb, iFrameResized, cv::Size(iFrameWidget.cols, iFrameWidget.rows), 0, 0, cv::INTER_NEAREST); + } + else + { + iFrameResized = iFrameRgb; + } + + cv::cvtColor(iFrameResized, iFrameWidget, cv::COLOR_BGR2BGRA); + + return QImage((const unsigned char*)iFrameWidget.data, + iFrameWidget.cols, iFrameWidget.rows, + (int)stride, + QImage::Format_ARGB32); + } + + void Preview::DrawInfo(const std::string& aString) + { + cv::putText(iFrameRgb, aString, cv::Point(4,iFrameRgb.size().height-4), cv::FONT_HERSHEY_PLAIN, 2, cv::Scalar(0, 255, 0),2); + } + + void Preview::DrawCross(const cv::Point& aPoint, const cv::Scalar& aColor) + { + constexpr int len = 9; + cv::line(iFrameRgb, + cv::Point(aPoint.x - len, aPoint.y), + cv::Point(aPoint.x + len, aPoint.y), + aColor, 2); + cv::line(iFrameRgb, + cv::Point(aPoint.x, aPoint.y - len), + cv::Point(aPoint.x, aPoint.y + len), + aColor, 2); + } + + void Preview::ensure_size(cv::Mat& frame, int w, int h, int type) + { + if (frame.cols != w || frame.rows != h || frame.type() != type) + frame = cv::Mat(h, w, type); + } + +} diff --git a/tracker-easy/preview.h b/tracker-easy/preview.h new file mode 100644 index 00000000..d5cc8c30 --- /dev/null +++ b/tracker-easy/preview.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019 Stephane Lenclud + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + */ + + +#pragma once + +#include <opencv2/core.hpp> +#include <QImage> + +namespace EasyTracker +{ + + struct Preview + { + Preview(int w, int h); + + Preview& operator=(const cv::Mat& frame); + QImage get_bitmap(); + void DrawCross(const cv::Point& aPoint, const cv::Scalar& aColor); + void DrawInfo(const std::string& aString); + + operator cv::Mat&() { return iFrameResized; } + operator cv::Mat const&() const { return iFrameResized; } + + private: + static void ensure_size(cv::Mat& frame, int w, int h, int type); + + public: + // Frame with an 8 bits channel size + cv::Mat iFrameChannelSizeOne; + // Frame with three channels for RGB + cv::Mat iFrameRgb; + // Frame rezised to widget dimension + cv::Mat iFrameResized; + // Frame ready to be packed in a QImage for use in our widget + cv::Mat iFrameWidget; + + }; + +} + diff --git a/tracker-easy/settings.h b/tracker-easy/settings.h new file mode 100644 index 00000000..4141ebe0 --- /dev/null +++ b/tracker-easy/settings.h @@ -0,0 +1,65 @@ +#pragma once + +#include "options/options.hpp" +#include <opencv2/calib3d.hpp> + +#include <QString> + + +namespace EasyTracker { + + using namespace options; + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wweak-vtables" +#endif + + struct Settings final : options::opts + { + using slider_value = options::slider_value; + + value<QString> camera_name{ b, "camera-name", "" }; + value<int> cam_res_x{ b, "camera-res-width", 640 }, + cam_res_y{ b, "camera-res-height", 480 }, + cam_fps{ b, "camera-fps", 30 }; + value<int> iMinBlobSize{ b, "iMinBlobSize", 4 }, iMaxBlobSize{ b, "iMaxBlobSize", 15 }; + value<int> DeadzoneRectHalfEdgeSize { b, "deadzone-rect-half-edge-size", 1 }; + + // Type of custom model + value<bool> iCustomModelThree{ b, "iCustomModelThree", true }; + value<bool> iCustomModelFour{ b, "iCustomModelFour", false }; + value<bool> iCustomModelFive{ b, "iCustomModelFive", false }; + value<bool> iClipModelThree{ b, "iClipModelThree", false }; + + // Custom model vertices + value<int> iVertexTopX{ b, "iVertexTopX", 0 }, iVertexTopY{ b, "iVertexTopY", 0 }, iVertexTopZ{ b, "iVertexTopZ", 0 }; + value<int> iVertexRightX{ b, "iVertexRightX", 0 }, iVertexRightY{ b, "iVertexRightY", 0 }, iVertexRightZ{ b, "iVertexRightZ", 0 }; + value<int> iVertexLeftX{ b, "iVertexLeftX", 0 }, iVertexLeftY{ b, "iVertexLeftY", 0 }, iVertexLeftZ{ b, "iVertexLeftZ", 0 }; + value<int> iVertexCenterX{ b, "iVertexCenterX", 0 }, iVertexCenterY{ b, "iVertexCenterY", 0 }, iVertexCenterZ{ b, "iVertexCenterZ", 0 }; + value<int> iVertexTopRightX{ b, "iVertexTopRightX", 0 }, iVertexTopRightY{ b, "iVertexTopRightY", 0 }, iVertexTopRightZ{ b, "iVertexTopRightZ", 0 }; + value<int> iVertexTopLeftX{ b, "iVertexTopLeftX", 0 }, iVertexTopLeftY{ b, "iVertexTopLeftY", 0 }, iVertexTopLeftZ{ b, "iVertexTopLeftZ", 0 }; + // Clip model vertices + value<int> iVertexClipTopX{ b, "iVertexClipTopX", 0 }, iVertexClipTopY{ b, "iVertexClipTopY", 0 }, iVertexClipTopZ{ b, "iVertexClipTopZ", 0 }; + value<int> iVertexClipMiddleX{ b, "iVertexClipMiddleX", 0 }, iVertexClipMiddleY{ b, "iVertexClipMiddleY", 0 }, iVertexClipMiddleZ{ b, "iVertexClipMiddleZ", 0 }; + value<int> iVertexClipBottomX{ b, "iVertexClipBottomX", 0 }, iVertexClipBottomY{ b, "iVertexClipBottomY", 0 }, iVertexClipBottomZ{ b, "iVertexClipBottomZ", 0 }; + + + value<int> fov{ b, "camera-fov", 56 }; + + value<bool> debug{ b, "debug", false }; + value<bool> iAutoCenter{ b, "iAutoCenter", true }; + value<int> iAutoCenterTimeout{ b, "iAutoCenterTimeout", 1000 }; + + + value<int> PnpSolver{ b, "pnp-solver", cv::SOLVEPNP_P3P }; + + + explicit Settings(const QString& name) : opts(name) {} + }; + +#ifdef __clang__ +# pragma clang diagnostic pop +#endif + +} // diff --git a/tracker-easy/tracker-easy-dialog.cpp b/tracker-easy/tracker-easy-dialog.cpp new file mode 100644 index 00000000..b0870b50 --- /dev/null +++ b/tracker-easy/tracker-easy-dialog.cpp @@ -0,0 +1,217 @@ +/* Copyright (c) 2012 Patrick Ruoff + * Copyright (c) 2014-2015 Stanislaw Halik <sthalik@misaki.pl> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + */ + +#include "tracker-easy-dialog.h" +#include "compat/math.hpp" +#include "video/camera.hpp" + +#include <opencv2/core.hpp> +#include <opencv2/calib3d.hpp> + +#include <QString> +#include <QtGlobal> +#include <QDebug> + +using namespace options; + +static void init_resources() { Q_INIT_RESOURCE(tracker_easy); } + +namespace EasyTracker +{ + + Dialog::Dialog() : + s(KModuleName), + tracker(nullptr) + { + init_resources(); + + ui.setupUi(this); + + for (const QString& str : video::camera_names()) + ui.camdevice_combo->addItem(str); + + tie_setting(s.camera_name, ui.camdevice_combo); + tie_setting(s.cam_res_x, ui.res_x_spin); + tie_setting(s.cam_res_y, ui.res_y_spin); + tie_setting(s.cam_fps, ui.fps_spin); + + tie_setting(s.iMinBlobSize, ui.mindiam_spin); + tie_setting(s.iMaxBlobSize, ui.maxdiam_spin); + tie_setting(s.DeadzoneRectHalfEdgeSize, ui.spinDeadzone); + + tie_setting(s.iVertexTopX, ui.iSpinVertexTopX); + tie_setting(s.iVertexTopY, ui.iSpinVertexTopY); + tie_setting(s.iVertexTopZ, ui.iSpinVertexTopZ); + + tie_setting(s.iVertexRightX, ui.iSpinVertexRightX); + tie_setting(s.iVertexRightY, ui.iSpinVertexRightY); + tie_setting(s.iVertexRightZ, ui.iSpinVertexRightZ); + + tie_setting(s.iVertexLeftX, ui.iSpinVertexLeftX); + tie_setting(s.iVertexLeftY, ui.iSpinVertexLeftY); + tie_setting(s.iVertexLeftZ, ui.iSpinVertexLeftZ); + + tie_setting(s.iVertexCenterX, ui.iSpinVertexCenterX); + tie_setting(s.iVertexCenterY, ui.iSpinVertexCenterY); + tie_setting(s.iVertexCenterZ, ui.iSpinVertexCenterZ); + + tie_setting(s.iVertexTopRightX, ui.iSpinVertexTopRightX); + tie_setting(s.iVertexTopRightY, ui.iSpinVertexTopRightY); + tie_setting(s.iVertexTopRightZ, ui.iSpinVertexTopRightZ); + + tie_setting(s.iVertexTopLeftX, ui.iSpinVertexTopLeftX); + tie_setting(s.iVertexTopLeftY, ui.iSpinVertexTopLeftY); + tie_setting(s.iVertexTopLeftZ, ui.iSpinVertexTopLeftZ); + + // Clip model + tie_setting(s.iVertexClipTopX, ui.iSpinVertexClipTopX); + tie_setting(s.iVertexClipTopY, ui.iSpinVertexClipTopY); + tie_setting(s.iVertexClipTopZ, ui.iSpinVertexClipTopZ); + + tie_setting(s.iVertexClipMiddleX, ui.iSpinVertexClipMiddleX); + tie_setting(s.iVertexClipMiddleY, ui.iSpinVertexClipMiddleY); + tie_setting(s.iVertexClipMiddleZ, ui.iSpinVertexClipMiddleZ); + + tie_setting(s.iVertexClipBottomX, ui.iSpinVertexClipBottomX); + tie_setting(s.iVertexClipBottomY, ui.iSpinVertexClipBottomY); + tie_setting(s.iVertexClipBottomZ, ui.iSpinVertexClipBottomZ); + + tie_setting(s.fov, ui.fov); + + tie_setting(s.debug, ui.debug); + + tie_setting(s.iAutoCenter, ui.iCheckBoxAutoCenter); + tie_setting(s.iAutoCenterTimeout, ui.iSpinBoxAutoCenterTimeout); + + + connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(doOK())); + connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(doCancel())); + + connect(ui.camdevice_combo, &QComboBox::currentTextChanged, this, &Dialog::set_camera_settings_available); + set_camera_settings_available(ui.camdevice_combo->currentText()); + connect(ui.camera_settings, &QPushButton::clicked, this, &Dialog::show_camera_settings); + + // Radio Button + connect(ui.iRadioButtonCustomModelThree, &QRadioButton::clicked, this, &Dialog::UpdateCustomModelControls); + connect(ui.iRadioButtonCustomModelFour, &QRadioButton::clicked, this, &Dialog::UpdateCustomModelControls); + connect(ui.iRadioButtonCustomModelFive, &QRadioButton::clicked, this, &Dialog::UpdateCustomModelControls); + connect(ui.iRadioButtonClipModelThree, &QRadioButton::clicked, this, &Dialog::UpdateCustomModelControls); + + tie_setting(s.iCustomModelThree, ui.iRadioButtonCustomModelThree); + tie_setting(s.iCustomModelFour, ui.iRadioButtonCustomModelFour); + tie_setting(s.iCustomModelFive, ui.iRadioButtonCustomModelFive); + tie_setting(s.iClipModelThree, ui.iRadioButtonClipModelThree); + + + for (unsigned k = 0; k < cv::SOLVEPNP_MAX_COUNT; k++) + { + ui.comboBoxSolvers->setItemData(k, k); + } + + + tie_setting(s.PnpSolver, ui.comboBoxSolvers); + + UpdateCustomModelControls(); + } + + void Dialog::UpdateCustomModelControls() + { + if (ui.iRadioButtonCustomModelThree->isChecked()) + { + ui.iGroupBoxCenter->hide(); + ui.iGroupBoxTopRight->hide(); + ui.iGroupBoxTopLeft->hide(); + ui.iGroupBoxTop->show(); + ui.iGroupBoxRight->show(); + ui.iGroupBoxLeft->show(); + ui.iGroupBoxClipTop->hide(); + ui.iGroupBoxClipMiddle->hide(); + ui.iGroupBoxClipBottom->hide(); + } + else if (ui.iRadioButtonCustomModelFour->isChecked()) + { + ui.iGroupBoxCenter->show(); + ui.iGroupBoxTopRight->hide(); + ui.iGroupBoxTopLeft->hide(); + ui.iGroupBoxTop->show(); + ui.iGroupBoxRight->show(); + ui.iGroupBoxLeft->show(); + ui.iGroupBoxClipTop->hide(); + ui.iGroupBoxClipMiddle->hide(); + ui.iGroupBoxClipBottom->hide(); + } + else if (ui.iRadioButtonCustomModelFive->isChecked()) + { + ui.iGroupBoxCenter->hide(); + ui.iGroupBoxTopRight->show(); + ui.iGroupBoxTopLeft->show(); + ui.iGroupBoxTop->show(); + ui.iGroupBoxRight->show(); + ui.iGroupBoxLeft->show(); + ui.iGroupBoxClipTop->hide(); + ui.iGroupBoxClipMiddle->hide(); + ui.iGroupBoxClipBottom->hide(); + } + else if (ui.iRadioButtonClipModelThree->isChecked()) + { + ui.iGroupBoxTop->hide(); + ui.iGroupBoxRight->hide(); + ui.iGroupBoxLeft->hide(); + ui.iGroupBoxCenter->hide(); + ui.iGroupBoxTopRight->hide(); + ui.iGroupBoxTopLeft->hide(); + ui.iGroupBoxClipTop->show(); + ui.iGroupBoxClipMiddle->show(); + ui.iGroupBoxClipBottom->show(); + } + } + + + void Dialog::set_camera_settings_available(const QString& /* camera_name */) + { + ui.camera_settings->setEnabled(true); + } + + void Dialog::show_camera_settings() + { + if (tracker) + { + QMutexLocker l(&tracker->camera_mtx); + (void)tracker->camera->show_dialog(); + } + else + (void)video::show_dialog(s.camera_name); + } + + + void Dialog::save() + { + s.b->save(); + } + + void Dialog::doOK() + { + save(); + close(); + } + + void Dialog::doCancel() + { + close(); + } + + void Dialog::register_tracker(ITracker *t) + { + tracker = static_cast<Tracker*>(t); + } + + void Dialog::unregister_tracker() + { + tracker = nullptr; + } +} diff --git a/tracker-easy/tracker-easy-dialog.h b/tracker-easy/tracker-easy-dialog.h new file mode 100644 index 00000000..7eb1ffb1 --- /dev/null +++ b/tracker-easy/tracker-easy-dialog.h @@ -0,0 +1,48 @@ +/* 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 "tracker-easy.h" +#include "settings.h" +#include "ui_tracker-easy-settings.h" +#include "cv/translation-calibrator.hpp" +#include "video/video-widget.hpp" + +#include <QTimer> +#include <QMutex> + +namespace EasyTracker +{ + class Dialog : public ITrackerDialog + { + Q_OBJECT + public: + Dialog(); + void register_tracker(ITracker *tracker) override; + void unregister_tracker() override; + void save(); + private: + void UpdateCustomModelControls(); + + public slots: + void doOK(); + void doCancel(); + + void set_camera_settings_available(const QString& camera_name); + void show_camera_settings(); + signals: + void poll_tracker_info(); + protected: + + Settings s; + Tracker* tracker; + + Ui::UICPTClientControls ui; + }; + +} diff --git a/tracker-easy/tracker-easy-settings.ui b/tracker-easy/tracker-easy-settings.ui new file mode 100644 index 00000000..d6cc86b6 --- /dev/null +++ b/tracker-easy/tracker-easy-settings.ui @@ -0,0 +1,1118 @@ +<?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>328</width> + <height>923</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="windowTitle"> + <string>Easy Tracker Settings</string> + </property> + <property name="windowIcon"> + <iconset> + <normaloff>:/Resources/tracker-easy-logo.png</normaloff>:/Resources/tracker-easy-logo.png</iconset> + </property> + <property name="layoutDirection"> + <enum>Qt::LeftToRight</enum> + </property> + <property name="autoFillBackground"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item alignment="Qt::AlignTop"> + <widget class="QTabWidget" name="tabWidget"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="locale"> + <locale language="English" country="UnitedStates"/> + </property> + <property name="currentIndex"> + <number>2</number> + </property> + <widget class="QWidget" name="tabTracker"> + <attribute name="title"> + <string>Tracker</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="camera_settings_groupbox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Camera</string> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="3" column="1"> + <widget class="QSpinBox" name="fps_spin"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Desired capture framerate</string> + </property> + <property name="suffix"> + <string> Hz</string> + </property> + <property name="maximum"> + <number>2000</number> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="camdevice_combo"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumContentsLength"> + <number>10</number> + </property> + </widget> + </item> + <item row="5" column="0"> + <widget class="QLabel" name="label_9"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Camera settings (when available)</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_36"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Width</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_41"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Height</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_37"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>FPS</string> + </property> + <property name="buddy"> + <cstring>fps_spin</cstring> + </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QPushButton" name="camera_settings"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Open</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QSpinBox" name="fov"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="suffix"> + <string>°</string> + </property> + <property name="prefix"> + <string/> + </property> + <property name="minimum"> + <number>10</number> + </property> + <property name="maximum"> + <number>90</number> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label_2"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Device</string> + </property> + <property name="buddy"> + <cstring>camdevice_combo</cstring> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QSpinBox" name="res_y_spin"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Desired capture height</string> + </property> + <property name="suffix"> + <string> px</string> + </property> + <property name="maximum"> + <number>2000</number> + </property> + <property name="singleStep"> + <number>10</number> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QSpinBox" name="res_x_spin"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Desired capture width</string> + </property> + <property name="suffix"> + <string> px</string> + </property> + <property name="maximum"> + <number>2000</number> + </property> + <property name="singleStep"> + <number>10</number> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_4"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Diagonal field of view</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_2"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Settings</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="0"> + <widget class="QLabel" name="label_5"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Debug (full size preview)</string> + </property> + </widget> + </item> + <item row="5" column="0"> + <widget class="QLabel" name="label_7"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Min size</string> + </property> + <property name="buddy"> + <cstring>mindiam_spin</cstring> + </property> + </widget> + </item> + <item row="5" column="2"> + <widget class="QSpinBox" name="mindiam_spin"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Minimum point diameter</string> + </property> + <property name="suffix"> + <string> px</string> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QCheckBox" name="iCheckBoxAutoCenter"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QCheckBox" name="debug"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_13"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Auto center</string> + </property> + </widget> + </item> + <item row="6" column="0"> + <widget class="QLabel" name="label_8"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Max size</string> + </property> + </widget> + </item> + <item row="7" column="2"> + <widget class="QSpinBox" name="spinDeadzone"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Size in pixels of half the edge defining deadzone squares around tracked points</string> + </property> + <property name="suffix"> + <string> px</string> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_12"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string><html><head/><body><p>Use P3P or AP3P for three and four points setup. Use EPNP or ITERATIVE for five points setup. Inconsistent configuration will result in undefined behavior.</p></body></html></string> + </property> + <property name="text"> + <string>Perspective-N-Point solver</string> + </property> + </widget> + </item> + <item row="4" column="2"> + <widget class="QComboBox" name="comboBoxSolvers"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Make sure you pick a solver supporting the number of marker you are using. For three points detection use either P3P or AP3P.</string> + </property> + <property name="currentText"> + <string>ITERATIVE</string> + </property> + <item> + <property name="text"> + <string>ITERATIVE</string> + </property> + </item> + <item> + <property name="text"> + <string>EPNP</string> + </property> + </item> + <item> + <property name="text"> + <string>P3P</string> + </property> + </item> + <item> + <property name="text"> + <string>DLS</string> + </property> + </item> + <item> + <property name="text"> + <string>UPNP</string> + </property> + </item> + <item> + <property name="text"> + <string>AP3P</string> + </property> + </item> + </widget> + </item> + <item row="7" column="0"> + <widget class="QLabel" name="labelDeadzone"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Deadzone</string> + </property> + <property name="buddy"> + <cstring>maxdiam_spin</cstring> + </property> + </widget> + </item> + <item row="6" column="2"> + <widget class="QSpinBox" name="maxdiam_spin"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Maximum point diameter</string> + </property> + <property name="suffix"> + <string> px</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_14"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Auto center timeout</string> + </property> + <property name="buddy"> + <cstring>mindiam_spin</cstring> + </property> + </widget> + </item> + <item row="3" column="2"> + <widget class="QSpinBox" name="iSpinBoxAutoCenterTimeout"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>If no valid pose can be determined after that much time the center pose will be used.</string> + </property> + <property name="suffix"> + <string> ms</string> + </property> + <property name="maximum"> + <number>3600000</number> + </property> + <property name="singleStep"> + <number>500</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_4"> + <attribute name="title"> + <string>Model</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QGroupBox" name="groupBoxCustomModelType"> + <property name="title"> + <string>Model type:</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <widget class="QRadioButton" name="iRadioButtonCustomModelThree"> + <property name="text"> + <string>Hat three vertices</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="iRadioButtonCustomModelFour"> + <property name="text"> + <string>Hat four vertices</string> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="iRadioButtonCustomModelFive"> + <property name="text"> + <string>Hat five vertices</string> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="iRadioButtonClipModelThree"> + <property name="text"> + <string>Clip three vertices</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Vertices: </string> + </property> + <property name="flat"> + <bool>false</bool> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_8"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string><html><head/><body><p><span style=" font-size:12pt;">X</span></p></body></html></string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string><html><head/><body><p><span style=" font-size:12pt;">Y</span></p></body></html></string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_11"> + <property name="text"> + <string><html><head/><body><p><span style=" font-size:12pt;">Z</span></p></body></html></string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="iGroupBoxTop"> + <property name="title"> + <string>Top:</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <property name="topMargin"> + <number>9</number> + </property> + <item> + <widget class="QSpinBox" name="iSpinVertexTopX"> + <property name="suffix"> + <string> mm</string> + </property> + <property name="minimum"> + <number>-65535</number> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="iSpinVertexTopY"> + <property name="suffix"> + <string> mm</string> + </property> + <property name="minimum"> + <number>-65535</number> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="iSpinVertexTopZ"> + <property name="suffix"> + <string> mm</string> + </property> + <property name="minimum"> + <number>-65535</number> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="iGroupBoxRight"> + <property name="title"> + <string>Right:</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QSpinBox" name="iSpinVertexRightX"> + <property name="suffix"> + <string> mm</string> + </property> + <property name="minimum"> + <number>-65535</number> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="iSpinVertexRightY"> + <property name="suffix"> + <string> mm</string> + </property> + <property name="minimum"> + <number>-65535</number> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="iSpinVertexRightZ"> + <property name="suffix"> + <string> mm</string> + </property> + <property name="minimum"> + <number>-65535</number> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="iGroupBoxLeft"> + <property name="title"> + <string>Left:</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <widget class="QSpinBox" name="iSpinVertexLeftX"> + <property name="suffix"> + <string> mm</string> + </property> + <property name="minimum"> + <number>-65535</number> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="iSpinVertexLeftY"> + <property name="suffix"> + <string> mm</string> + </property> + <property name="minimum"> + <number>-65535</number> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="iSpinVertexLeftZ"> + <property name="suffix"> + <string> mm</string> + </property> + <property name="minimum"> + <number>-65535</number> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="iGroupBoxCenter"> + <property name="title"> + <string>Center:</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_5"> + <item> + <widget class="QSpinBox" name="iSpinVertexCenterX"> + <property name="suffix"> + <string> mm</string> + </property> + <property name="minimum"> + <number>-65535</number> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="iSpinVertexCenterY"> + <property name="suffix"> + <string> mm</string> + </property> + <property name="minimum"> + <number>-65535</number> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="iSpinVertexCenterZ"> + <property name="suffix"> + <string> mm</string> + </property> + <property name="minimum"> + <number>-65535</number> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="iGroupBoxTopRight"> + <property name="title"> + <string>Top right:</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_6"> + <item> + <widget class="QSpinBox" name="iSpinVertexTopRightX"> + <property name="suffix"> + <string> mm</string> + </property> + <property name="minimum"> + <number>-65535</number> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="iSpinVertexTopRightY"> + <property name="suffix"> + <string> mm</string> + </property> + <property name="minimum"> + <number>-65535</number> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="iSpinVertexTopRightZ"> + <property name="suffix"> + <string> mm</string> + </property> + <property name="minimum"> + <number>-65535</number> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="iGroupBoxTopLeft"> + <property name="title"> + <string>Top left:</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_7"> + <item> + <widget class="QSpinBox" name="iSpinVertexTopLeftX"> + <property name="suffix"> + <string> mm</string> + </property> + <property name="minimum"> + <number>-65535</number> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="iSpinVertexTopLeftY"> + <property name="suffix"> + <string> mm</string> + </property> + <property name="minimum"> + <number>-65535</number> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="iSpinVertexTopLeftZ"> + <property name="suffix"> + <string> mm</string> + </property> + <property name="minimum"> + <number>-65535</number> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="iGroupBoxClipTop"> + <property name="title"> + <string>Clip top:</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_10"> + <property name="topMargin"> + <number>9</number> + </property> + <item> + <widget class="QSpinBox" name="iSpinVertexClipTopX"> + <property name="suffix"> + <string> mm</string> + </property> + <property name="minimum"> + <number>-65535</number> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="iSpinVertexClipTopY"> + <property name="suffix"> + <string> mm</string> + </property> + <property name="minimum"> + <number>-65535</number> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="iSpinVertexClipTopZ"> + <property name="suffix"> + <string> mm</string> + </property> + <property name="minimum"> + <number>-65535</number> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="iGroupBoxClipMiddle"> + <property name="title"> + <string>Clip middle:</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_9"> + <property name="topMargin"> + <number>9</number> + </property> + <item> + <widget class="QSpinBox" name="iSpinVertexClipMiddleX"> + <property name="suffix"> + <string> mm</string> + </property> + <property name="minimum"> + <number>-65535</number> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="iSpinVertexClipMiddleY"> + <property name="suffix"> + <string> mm</string> + </property> + <property name="minimum"> + <number>-65535</number> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="iSpinVertexClipMiddleZ"> + <property name="suffix"> + <string> mm</string> + </property> + <property name="minimum"> + <number>-65535</number> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="iGroupBoxClipBottom"> + <property name="title"> + <string>Clip bottom:</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_11"> + <property name="topMargin"> + <number>9</number> + </property> + <item> + <widget class="QSpinBox" name="iSpinVertexClipBottomX"> + <property name="suffix"> + <string> mm</string> + </property> + <property name="minimum"> + <number>-65535</number> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="iSpinVertexClipBottomY"> + <property name="suffix"> + <string> mm</string> + </property> + <property name="minimum"> + <number>-65535</number> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="iSpinVertexClipBottomZ"> + <property name="suffix"> + <string> mm</string> + </property> + <property name="minimum"> + <number>-65535</number> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_3"> + <attribute name="title"> + <string>About</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_8"> + <item row="0" column="0"> + <widget class="QLabel" name="label_10"> + <property name="text"> + <string><html><head/><body><p><span style=" font-weight:600;">Easy Tracker<br/>Version 1.1</span></p><p><span style=" font-weight:600;">by Stéphane Lenclud</span></p><p>See <a href="https://github.com/opentrack/opentrack/wiki/Easy-Tracker"><span style=" font-weight:600; text-decoration: underline; color:#9999aa;">documentation on GitHub</span></a></p></body></html></string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + <property name="openExternalLinks"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="label_35"> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap>:/Resources/tracker-easy-logo.png</pixmap> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + </item> + <item alignment="Qt::AlignBottom"> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <tabstops> + <tabstop>camdevice_combo</tabstop> + <tabstop>res_x_spin</tabstop> + <tabstop>res_y_spin</tabstop> + <tabstop>fps_spin</tabstop> + <tabstop>fov</tabstop> + <tabstop>camera_settings</tabstop> + </tabstops> + <resources> + <include location="tracker_easy.qrc"/> + </resources> + <connections/> + <slots> + <slot>startEngineClicked()</slot> + <slot>stopEngineClicked()</slot> + <slot>cameraSettingsClicked()</slot> + </slots> +</ui> diff --git a/tracker-easy/tracker-easy.cpp b/tracker-easy/tracker-easy.cpp new file mode 100644 index 00000000..7046a918 --- /dev/null +++ b/tracker-easy/tracker-easy.cpp @@ -0,0 +1,912 @@ +/* Copyright (c) 2019 Stephane Lenclud + * + * 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 "tracker-easy.h" +#include "video/video-widget.hpp" +#include "compat/math-imports.hpp" +#include "compat/check-visible.hpp" +#include "compat/sleep.hpp" +#include "point-extractor.h" +#include "cv/init.hpp" + +#include <QHBoxLayout> +#include <QDebug> +#include <QFile> +#include <QCoreApplication> + +#include <opencv2/calib3d.hpp> +#include <opencv2/highgui/highgui.hpp> + +#include <iostream> + +#ifdef __GNUC__ +# pragma GCC diagnostic ignored "-Wsign-conversion" +# pragma GCC diagnostic ignored "-Wfloat-conversion" + +#endif + +#ifdef __clang__ +# pragma clang diagnostic ignored "-Wimplicit-float-conversion" +#endif + +using namespace options; + +// Disable debug +#define dbgout if (true) {} else std::cout << "\n" <<std::chrono::system_clock::now().time_since_epoch().count() << ": " +//#define infout if (true) {} else std::cout +// Enable debug +//#define dbgout if (false) {} else std::cout +#define infout if (false) {} else std::cout << "\n" << std::chrono::system_clock::now().time_since_epoch().count() << ": " + +// We need at least 3 vertices to be able to do anything +const int KMinVertexCount = 3; + + + +namespace EasyTracker +{ + + Tracker::Tracker() : + iSettings{ KModuleName }, + iPreview{ preview_width, preview_height } + { + opencv_init(); + + connect(&*iSettings.b, &bundle_::saving, this, &Tracker::CheckCamera, Qt::DirectConnection); + connect(&*iSettings.b, &bundle_::reloading, this, &Tracker::CheckCamera, Qt::DirectConnection); + + connect(&iSettings.fov, value_::value_changed<int>(), this, &Tracker::set_fov, Qt::DirectConnection); + set_fov(iSettings.fov); + // We could not get this working, nevermind + //connect(&iSettings.cam_fps, value_::value_changed<int>(), this, &Tracker::SetFps, Qt::DirectConnection); + + // Make sure deadzones are updated whenever the settings are changed + connect(&iSettings.DeadzoneRectHalfEdgeSize, value_::value_changed<int>(), this, &Tracker::UpdateSettings, Qt::DirectConnection); + + // Update point extractor whenever some of the settings it needs are changed + connect(&iSettings.iMinBlobSize, value_::value_changed<int>(), this, &Tracker::UpdateSettings, Qt::DirectConnection); + connect(&iSettings.iMaxBlobSize, value_::value_changed<int>(), this, &Tracker::UpdateSettings, Qt::DirectConnection); + + // Make sure solver is updated whenever the settings are changed + connect(&iSettings.PnpSolver, value_::value_changed<int>(), this, &Tracker::UpdateSettings, Qt::DirectConnection); + + // Debug + connect(&iSettings.debug, value_::value_changed<bool>(), this, &Tracker::UpdateSettings, Qt::DirectConnection); + + // Make sure model is updated whenever it is changed + connect(&iSettings.iCustomModelThree, value_::value_changed<bool>(), this, &Tracker::UpdateModel, Qt::DirectConnection); + connect(&iSettings.iCustomModelFour, value_::value_changed<bool>(), this, &Tracker::UpdateModel, Qt::DirectConnection); + connect(&iSettings.iCustomModelFive, value_::value_changed<bool>(), this, &Tracker::UpdateModel, Qt::DirectConnection); + + // Update model logic + #define UM(v) connect(&iSettings.v, value_::value_changed<int>(), this, &Tracker::UpdateModel, Qt::DirectConnection) + UM(iVertexTopX); UM(iVertexTopY); UM(iVertexTopZ); + UM(iVertexTopRightX); UM(iVertexTopRightY); UM(iVertexTopRightZ); + UM(iVertexTopLeftX); UM(iVertexTopLeftY); UM(iVertexTopLeftZ); + UM(iVertexRightX); UM(iVertexRightY); UM(iVertexRightZ); + UM(iVertexLeftX); UM(iVertexLeftY); UM(iVertexLeftZ); + UM(iVertexCenterX); UM(iVertexCenterY); UM(iVertexCenterZ); + + UpdateModel(); + + UpdateSettings(); + + cv::namedWindow("Preview"); + } + + Tracker::~Tracker() + { + iThread.exit(); + iThread.wait(); + + if (iDebug) + cv::destroyWindow("Preview"); + + if (camera) + { + QMutexLocker l(&camera_mtx); + camera->stop(); + } + } + + + // Compute Euler angles from rotation matrix + void getEulerAngles(cv::Mat &rotCamerMatrix, cv::Vec3d &eulerAngles) + { + cv::Mat cameraMatrix, rotMatrix, transVect, rotMatrixX, rotMatrixY, rotMatrixZ; + double* _r = rotCamerMatrix.ptr<double>(); + double projMatrix[12] = { _r[0],_r[1],_r[2],0, + _r[3],_r[4],_r[5],0, + _r[6],_r[7],_r[8],0 }; + + cv::decomposeProjectionMatrix(cv::Mat(3, 4, CV_64FC1, projMatrix), + cameraMatrix, + rotMatrix, + transVect, + rotMatrixX, + rotMatrixY, + rotMatrixZ, + eulerAngles); + } + + /// + void Tracker::CreateCameraIntrinsicsMatrices() + { + // Create our camera matrix + iCameraMatrix(0, 0) = iCameraInfo.fx; + iCameraMatrix(1, 1) = iCameraInfo.fy; + iCameraMatrix(0, 2) = iCameraInfo.P_x; + iCameraMatrix(1, 2) = iCameraInfo.P_y; + iCameraMatrix(2, 2) = 1; + + // Create distortion cooefficients + iDistCoeffsMatrix = cv::Matx<double, 8, 1>::zeros(); + // As per OpenCV docs they should be thus: k1, k2, p1, p2, k3, k4, k5, k6 + // 0 - Radial first order + // 1 - Radial second order + // 2 - Tangential first order + // 3 - Tangential second order + // 4 - Radial third order + // 5 - Radial fourth order + // 6 - Radial fifth order + // 7 - Radial sixth order + // + // SL: Using distortion coefficients in this way is breaking our face tracking output. + // Just disable them for now until we invest time and effort to work it out. + // For our face tracking use case not having proper distortion coefficients ain't a big deal anyway + // See issues #1141 and #1020 + //for (unsigned k = 0; k < 8; k++) + // iDistCoeffsMatrix(k) = (double)iCameraInfo.dist_c[k]; + } + + + void Tracker::MatchVertices(int& aTopIndex, int& aRightIndex, int& aLeftIndex, int& aCenterIndex, int& aTopRight, int& aTopLeft) + { + if (iModel.size() == 5) + { + MatchFiveVertices(aTopIndex, aRightIndex, aLeftIndex, aTopRight, aTopLeft); + } + else if (!iSettings.iClipModelThree) + { + MatchThreeOrFourVertices(aTopIndex, aRightIndex, aLeftIndex, aCenterIndex); + } + else + { + // Clip model + MatchClipVertices(aTopIndex, aRightIndex, aLeftIndex); + } + } + + + void Tracker::MatchFiveVertices(int& aTopIndex, int& aRightIndex, int& aLeftIndex, int& aTopRight, int& aTopLeft) + { + //Bitmap origin is top left + iTrackedPoints.clear(); + + int vertexIndices[] = { -1,-1,-1,-1,-1 }; + std::vector<int> indices = { 0,1,2,3,4 }; + + // Tracked points must match the order of the object model points. + // Find top most point, that's the one with min Y as we assume our guy's head is not up side down + int minY = std::numeric_limits<int>::max(); + for (int i = 0; i < (int)iPoints.size(); i++) + { + if (iPoints[i].y < minY) + { + minY = iPoints[i].y; + vertexIndices[VertexPosition::Top] = i; + } + } + indices.erase(std::find(indices.begin(), indices.end(), vertexIndices[VertexPosition::Top])); + + // Find right most point + int maxX = 0; + for (int i = 0; i < (int)iPoints.size(); i++) + { + // Excluding top most point + if (i != vertexIndices[VertexPosition::Top] && iPoints[i].x > maxX) + { + maxX = iPoints[i].x; + vertexIndices[VertexPosition::Right] = i; + } + } + indices.erase(std::find(indices.begin(), indices.end(), vertexIndices[VertexPosition::Right])); + + // Find left most point + int minX = std::numeric_limits<int>::max(); + for (int i = 0; i < (int)iPoints.size(); i++) + { + // Excluding top most point and right most point + if (i != vertexIndices[VertexPosition::Top] && i != vertexIndices[VertexPosition::Right] && iPoints[i].x < minX) + { + minX = iPoints[i].x; + vertexIndices[VertexPosition::Left] = i; + } + } + indices.erase(std::find(indices.begin(), indices.end(), vertexIndices[VertexPosition::Left])); + + // Check which of our two remaining points is on the left + int leftIndex = -1; + int rightIndex = -1; + if (iPoints[indices[0]].x > iPoints[indices[1]].x) + { + leftIndex = indices[1]; + rightIndex = indices[0]; + } + else + { + leftIndex = indices[0]; + rightIndex = indices[1]; + } + + // Check which of the left points is at the top + if (iPoints[vertexIndices[VertexPosition::Left]].y < iPoints[leftIndex].y) + { + vertexIndices[VertexPosition::TopLeft] = vertexIndices[VertexPosition::Left]; + vertexIndices[VertexPosition::Left] = leftIndex; + } + else + { + vertexIndices[VertexPosition::TopLeft] = leftIndex; + } + + // Check which of the right points is at the top + if (iPoints[vertexIndices[VertexPosition::Right]].y < iPoints[rightIndex].y) + { + vertexIndices[VertexPosition::TopRight] = vertexIndices[VertexPosition::Right]; + vertexIndices[VertexPosition::Right] = rightIndex; + } + else + { + vertexIndices[VertexPosition::TopRight] = rightIndex; + } + + + // Order matters, see UpdateModel function + iTrackedPoints.push_back(iPoints[vertexIndices[VertexPosition::Top]]); + iTrackedPoints.push_back(iPoints[vertexIndices[VertexPosition::Right]]); + iTrackedPoints.push_back(iPoints[vertexIndices[VertexPosition::Left]]); + iTrackedPoints.push_back(iPoints[vertexIndices[VertexPosition::TopRight]]); + iTrackedPoints.push_back(iPoints[vertexIndices[VertexPosition::TopLeft]]); + + // + aTopIndex = vertexIndices[VertexPosition::Top]; + aRightIndex = vertexIndices[VertexPosition::Right]; + aLeftIndex = vertexIndices[VertexPosition::Left]; + aTopRight = vertexIndices[VertexPosition::TopRight]; + aTopLeft = vertexIndices[VertexPosition::TopLeft]; + + + } + + + void Tracker::MatchThreeOrFourVertices(int& aTopIndex, int& aRightIndex, int& aLeftIndex, int& aCenterIndex) + { + //Bitmap origin is top left + iTrackedPoints.clear(); + // Tracked points must match the order of the object model points. + // Find top most point, that's the one with min Y as we assume our guy's head is not up side down + int minY = std::numeric_limits<int>::max(); + for (int i = 0; i < (int)iPoints.size(); i++) + { + if (iPoints[i].y < minY) + { + minY = iPoints[i].y; + aTopIndex = i; + } + } + + + int maxX = 0; + + // Find right most point + for (int i = 0; i < (int)iPoints.size(); i++) + { + // Excluding top most point + if (i != aTopIndex && iPoints[i].x > maxX) + { + maxX = iPoints[i].x; + aRightIndex = i; + } + } + + // Find left most point + int minX = std::numeric_limits<int>::max(); + for (int i = 0; i < (int)iPoints.size(); i++) + { + // Excluding top most point and right most point + if (i != aTopIndex && i != aRightIndex && iPoints[i].x < minX) + { + aLeftIndex = i; + minX = iPoints[i].x; + } + } + + // Find center point, the last one + for (int i = 0; i < (int)iPoints.size(); i++) + { + // Excluding the three points we already have + if (i != aTopIndex && i != aRightIndex && i != aLeftIndex) + { + aCenterIndex = i; + } + } + + // Order matters + iTrackedPoints.push_back(iPoints[aTopIndex]); + iTrackedPoints.push_back(iPoints[aRightIndex]); + iTrackedPoints.push_back(iPoints[aLeftIndex]); + if (iModel.size() > iTrackedPoints.size()) + { + // We are tracking more than 3 points + iTrackedPoints.push_back(iPoints[aCenterIndex]); + } + } + + /** + */ + void Tracker::MatchClipVertices(int& aTopIndex, int& aMiddleIndex, int& aBottomIndex) + { + //Bitmap origin is top left + iTrackedPoints.clear(); + // Tracked points must match the order of the object model points. + // Find top most point, that's the one with min Y as we assume our guy's head is not up side down + int minY = std::numeric_limits<int>::max(); + for (int i = 0; i < (int)iPoints.size(); i++) + { + if (iPoints[i].y < minY) + { + minY = iPoints[i].y; + aTopIndex = i; + } + } + + + int maxY = 0; + + // Find bottom most point + for (int i = 0; i < (int)iPoints.size(); i++) + { + // Excluding top most point + if (i != aTopIndex && iPoints[i].y > maxY) + { + maxY = iPoints[i].y; + aBottomIndex = i; + } + } + + + // Find center point, the last one + for (int i = 0; i < (int)iPoints.size(); i++) + { + // Excluding the three points we already have + if (i != aTopIndex && i != aBottomIndex) + { + aMiddleIndex = i; + } + } + + // Order matters + iTrackedPoints.push_back(iPoints[aTopIndex]); + iTrackedPoints.push_back(iPoints[aMiddleIndex]); + iTrackedPoints.push_back(iPoints[aBottomIndex]); + } + + + + /// + /// + /// + void Tracker::ProcessFrame() + { + //infout << "ProcessFrame - begin"; + QMutexLocker l(&iProcessLock); + + // Create OpenCV matrix from our frame + // TODO: Assert channel size is one or two + iMatFrame = cv::Mat(iFrame.height, iFrame.width, CV_MAKETYPE((iFrame.channel_size == 2 ? CV_16U : CV_8U), iFrame.channels), iFrame.data, iFrame.stride); + iFrameCount++; + + bool doPreview = check_is_visible(); + if (doPreview) + { + iPreview = iMatFrame; + } + + iPoints.clear(); + + // Do not attempt point extraction on a color buffer as it is running real slow and is useless anyway. + // If we are ever going to support color buffer we will need another implementation. + if (iFrame.channels == 1) + { + iPointExtractor.ExtractPoints(iMatFrame, (doPreview ? &iPreview.iFrameRgb : nullptr), (int)iModel.size(), iPoints); + } + + + const bool success = iPoints.size() >= iModel.size() && iModel.size() >= KMinVertexCount; + + int topPointIndex = -1; + int rightPointIndex = -1; + int leftPointIndex = -1; + int centerPointIndex = -1; + int topRightPointIndex = -1; + int topLeftPointIndex = -1; + + if (success) + { + // Lets match our 3D vertices with our image 2D points + MatchVertices(topPointIndex, rightPointIndex, leftPointIndex, centerPointIndex, topRightPointIndex, topLeftPointIndex); + + bool movedEnough = true; + // Check if we moved enough since last time we were here + // This is our deadzone management + if (iDeadzoneHalfEdge != 0 // Check if deazones are enabled + && iTrackedRects.size() == iTrackedPoints.size()) + { + movedEnough = false; + for (size_t i = 0; i < iTrackedPoints.size(); i++) + { + if (!iTrackedRects[i].contains(iTrackedPoints[i])) + { + movedEnough = true; + break; + } + } + } + + if (!movedEnough) + { + // We are in a dead zone + // However we still have tracking so make sure we don't auto center + QMutexLocker lock(&iDataLock); + iBestTime.start(); + } + else + { + // Build deadzone rectangles if needed + iTrackedRects.clear(); + if (iDeadzoneHalfEdge != 0) // Check if deazones are enabled + { + for (const cv::Point2f& pt : iTrackedPoints) + { + cv::Rect rect(pt - cv::Point2f(iDeadzoneHalfEdge, iDeadzoneHalfEdge), cv::Size(iDeadzoneEdge, iDeadzoneEdge)); + iTrackedRects.push_back(rect); + } + } + + dbgout << "Object: " << iModel << "\n"; + dbgout << "Points: " << iTrackedPoints << "\n"; + + iAngles.clear(); + iBestSolutionIndex = -1; + // Solve P3P problem with OpenCV + int solutionCount = 0; + if (iModel.size() == 3) + { + solutionCount = cv::solveP3P(iModel, iTrackedPoints, iCameraMatrix, iDistCoeffsMatrix, iRotations, iTranslations, iSolver); + } + else + { + //Guess extrinsic boolean is only for ITERATIVE method, it will be set to false for all other method + cv::Mat rotation, translation; + // Init only needed for iterative, it's also useless as it is + rotation = cv::Mat::zeros(3, 1, CV_64FC1); + translation = cv::Mat::zeros(3, 1, CV_64FC1); + rotation.setTo(cv::Scalar(0)); + translation.setTo(cv::Scalar(0)); + ///// + iRotations.clear(); + iTranslations.clear(); + bool solved = cv::solvePnP(iModel, iTrackedPoints, iCameraMatrix, iDistCoeffsMatrix, rotation, translation, true, iSolver ); + if (solved) + { + solutionCount = 1; + iRotations.push_back(rotation); + iTranslations.push_back(translation); + } + } + + // Reset best solution index + iBestSolutionIndex = -1; + + if (solutionCount > 0) + { + dbgout << "Solution count: " << solutionCount << "\n"; + int minPitch = std::numeric_limits<int>::max(); + // Find the solution we want amongst all possible ones + for (int i = 0; i < solutionCount; i++) + { + dbgout << "Translation:\n"; + dbgout << iTranslations.at(i); + dbgout << "\n"; + dbgout << "Rotation:\n"; + //dbgout << rvecs.at(i); + cv::Mat rotationCameraMatrix; + cv::Rodrigues(iRotations[i], rotationCameraMatrix); + cv::Vec3d angles; + getEulerAngles(rotationCameraMatrix, angles); + iAngles.push_back(angles); + + // Check if pitch is closest to zero + int absolutePitch = (int)std::abs(angles[0]); + if (minPitch > absolutePitch) + { + // The solution with pitch closest to zero is the one we want + minPitch = absolutePitch; + iBestSolutionIndex = i; + } + + dbgout << angles; + dbgout << "\n"; + } + + dbgout << "\n"; + } + + if (iBestSolutionIndex != -1) + { + // Best translation + cv::Vec3d translation = iTranslations[iBestSolutionIndex]; + // Best angles + cv::Vec3d angles = iAngles[iBestSolutionIndex]; + + // Pass solution through our kalman filter + iKf.Update(translation[0], translation[1], translation[2], angles[2], angles[0], angles[1]); + + // Check if our solution makes sense + // For now, just discard solutions with extrem pitch + if (std::abs(angles[0]) > 50) //TODO: Put that in settings + { + infout << "WARNING: discarding solution!"; + iBadSolutionCount++; + } + else + { + iGoodSolutionCount++; + // We succeded in finding a solution to our PNP problem + ever_success.store(true, std::memory_order_relaxed); + + // Send solution data back to main thread + QMutexLocker l2(&iDataLock); + iBestAngles = angles; + iBestTranslation = translation; + iBestTime.start(); + } + + } + } + } + + if (doPreview) + { + double qualityIndex = 1 - (iGoodSolutionCount!=0?(double)iBadSolutionCount / (double)iGoodSolutionCount:0); + std::ostringstream ss; + ss << "FPS: " << iFps << "/" << iSkippedFps << " QI: " << qualityIndex; + iPreview.DrawInfo(ss.str()); + + //Color is BGR + if (topPointIndex != -1) + { + // Render a cross to indicate which point is the head + static const cv::Scalar color(0, 255, 255); // Yellow + iPreview.DrawCross(iPoints[topPointIndex],color); + } + + if (rightPointIndex != -1) + { + static const cv::Scalar color(255, 0, 255); // Pink + iPreview.DrawCross(iPoints[rightPointIndex], color); + } + + if (leftPointIndex != -1) + { + static const cv::Scalar color(255, 0, 0); // Blue + iPreview.DrawCross(iPoints[leftPointIndex], color); + } + + if (centerPointIndex != -1) + { + static const cv::Scalar color(0, 255, 0); // Green + iPreview.DrawCross(iPoints[centerPointIndex], color); + } + + if (topRightPointIndex != -1) + { + static const cv::Scalar color(0, 0, 255); // Red + iPreview.DrawCross(iPoints[topRightPointIndex], color); + } + + if (topLeftPointIndex != -1) + { + static const cv::Scalar color(255, 255, 0); // Cyan + iPreview.DrawCross(iPoints[topLeftPointIndex], color); + } + + + // Render our deadzone rects + for (const cv::Rect& rect : iTrackedRects) + { + cv::rectangle(iPreview.iFrameRgb,rect,cv::Scalar(255,0,0)); + } + + // Show full size preview pop-up + if (iDebug) + { + cv::imshow("Preview", iPreview.iFrameRgb); + cv::waitKey(1); + } + + // Update preview widget + widget->update_image(iPreview.get_bitmap()); + + auto[w, h] = widget->preview_size(); + if (w != preview_width || h != preview_height) + { + // Resize preivew if widget size has changed + preview_width = w; preview_height = h; + iPreview = Preview(w, h); + } + } + else + { + // No preview, destroy preview pop-up + if (iDebug) + { + cv::destroyWindow("Preview"); + } + } + + dbgout << "Frame time:" << iTimer.elapsed_seconds(); + //infout << "ProcessFrame - end"; + } + + /// + /// + /// + void Tracker::Tick() + { + if (CheckCamera()) + { + // Camera was just started, skipping that frame as it was causing a deadlock on our process mutex in ProcessFrame + // In fact it looked like ProcessFrame was called twice without completing. + // That has something to do with the ticker interval being changed after the camera is started. + return; + } + + iTimer.start(); + + bool new_frame = false; + { + QMutexLocker l(&camera_mtx); + + if (camera) + { + std::tie(iFrame, new_frame) = camera->get_frame(); + } + + } + + if (new_frame) + { + ProcessFrame(); + } + else + { + iSkippedFrameCount++; + } + + // Compute FPS + double elapsed = iFpsTimer.elapsed_seconds(); + if (elapsed >= 1.0) + { + iFps = iFrameCount / elapsed; + iSkippedFps = iSkippedFrameCount / elapsed; + iFrameCount = 0; + iSkippedFrameCount = 0; + iFpsTimer.start(); + } + + } + + /// @return True if camera was just started, false otherwise. + bool Tracker::CheckCamera() + { + QMutexLocker l(&camera_mtx); + + if (camera->is_open()) + { + return false; + } + + iCameraInfo.fps = iSettings.cam_fps; + iCameraInfo.width = iSettings.cam_res_x; + iCameraInfo.height = iSettings.cam_res_y; + + bool res = camera->start(iCameraInfo); + //portable::sleep(5000); + + // We got our camera intrinsics, create corresponding matrices + CreateCameraIntrinsicsMatrices(); + + // If ever the camera implementation provided an FPS now is the time to apply it + DoSetFps(iCameraInfo.fps); + + return res; + } + + void Tracker::set_fov(int value) + { + (void)value; + //QMutexLocker l(&camera_mtx); + + } + + // Calling this from another thread than the one it belongs too after it's started somehow breaks our timer + void Tracker::SetFps(int aFps) + { + QMutexLocker l(&camera_mtx); + DoSetFps(aFps); + } + + void Tracker::DoSetFps(int aFps) + { + // Aplly FPS to timer + iTicker.setInterval(1000 / aFps + 1); + + // Reset Kalman filter + //int nStates = 18; // the number of states + //int nMeasurements = 6; // the number of measured states + //int nInputs = 0; // the number of control actions + //double dt = 0.125; // time between measurements (1/FPS) + double dt = 1000.0 / aFps; + iKf.Init(18, 6, 0, dt); + } + + + /// + /// Create our model from settings specifications + /// + void Tracker::UpdateModel() + { + infout << "Update model - begin"; + + QMutexLocker lock(&iProcessLock); + // Construct the points defining the object we want to detect based on settings. + // We are converting them from millimeters to centimeters. + // TODO: Need to support clip too. That's cap only for now. + iModel.clear(); + + if (!iSettings.iClipModelThree) + { + iModel.push_back(cv::Point3f(iSettings.iVertexTopX / 10.0, iSettings.iVertexTopY / 10.0, iSettings.iVertexTopZ / 10.0)); // Top + iModel.push_back(cv::Point3f(iSettings.iVertexRightX / 10.0, iSettings.iVertexRightY / 10.0, iSettings.iVertexRightZ / 10.0)); // Right + iModel.push_back(cv::Point3f(iSettings.iVertexLeftX / 10.0, iSettings.iVertexLeftY / 10.0, iSettings.iVertexLeftZ / 10.0)); // Left + + if (iSettings.iCustomModelFour) + { + iModel.push_back(cv::Point3f(iSettings.iVertexCenterX / 10.0, iSettings.iVertexCenterY / 10.0, iSettings.iVertexCenterZ / 10.0)); // Center + } + else if (iSettings.iCustomModelFive) + { + iModel.push_back(cv::Point3f(iSettings.iVertexTopRightX / 10.0, iSettings.iVertexTopRightY / 10.0, iSettings.iVertexTopRightZ / 10.0)); // Top Right + iModel.push_back(cv::Point3f(iSettings.iVertexTopLeftX / 10.0, iSettings.iVertexTopLeftY / 10.0, iSettings.iVertexTopLeftZ / 10.0)); // Top Left + } + } + else + { + // Clip model type + iModel.push_back(cv::Point3f(iSettings.iVertexClipTopX / 10.0, iSettings.iVertexClipTopY / 10.0, iSettings.iVertexClipTopZ / 10.0)); // Top + iModel.push_back(cv::Point3f(iSettings.iVertexClipMiddleX / 10.0, iSettings.iVertexClipMiddleY / 10.0, iSettings.iVertexClipMiddleZ / 10.0)); // Middle + iModel.push_back(cv::Point3f(iSettings.iVertexClipBottomX / 10.0, iSettings.iVertexClipBottomY / 10.0, iSettings.iVertexClipBottomZ / 10.0)); // Bottom + } + + infout << "Update model - end"; + } + + /// + /// Take a copy of the settings needed by our thread to avoid deadlocks + /// + void Tracker::UpdateSettings() + { + infout << "Update Setting - begin"; + QMutexLocker l(&iProcessLock); + iPointExtractor.UpdateSettings(); + iSolver = iSettings.PnpSolver; + iDeadzoneHalfEdge = iSettings.DeadzoneRectHalfEdgeSize; + iDeadzoneEdge = iDeadzoneHalfEdge * 2; + iTrackedRects.clear(); + iDebug = iSettings.debug; + infout << "Update Setting - end"; + } + + /// + module_status Tracker::start_tracker(QFrame* video_frame) + { + // Check that we support that solver + if (iSolver!=cv::SOLVEPNP_P3P && iSolver != cv::SOLVEPNP_AP3P && iModel.size()==3) + { + return module_status("Error: Solver not supported use either P3P or AP3P."); + } + + // Create our camera + camera = video::make_camera(iSettings.camera_name); + + if (!camera) + return error(QStringLiteral("Can't open camera %1").arg(iSettings.camera_name)); + + //video_frame->setAttribute(Qt::WA_NativeWindow); + widget = std::make_unique<video_widget>(video_frame); + layout = std::make_unique<QHBoxLayout>(video_frame); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(&*widget); + video_frame->setLayout(&*layout); + //video_widget->resize(video_frame->width(), video_frame->height()); + video_frame->show(); + + // Precise timer is needed otherwise the interval is not really respected + iTicker.setTimerType(Qt::PreciseTimer); + SetFps(iSettings.cam_fps); + iTicker.moveToThread(&iThread); + // Connect timer timeout signal to our tick slot + connect(&iTicker, SIGNAL(timeout()), SLOT(Tick()), Qt::DirectConnection); + // Start our timer once our thread is started + iTicker.connect(&iThread, SIGNAL(started()), SLOT(start())); + iFpsTimer.start(); // Kick off our FPS counter + iThread.setObjectName("EasyTrackerThread"); + iThread.setPriority(QThread::HighPriority); // Do we really want that? + iThread.start(); + + return {}; + } + + // + void FeedData(double* aData, const cv::Vec3d& aAngles, const cv::Vec3d& aTranslation) + { + aData[Yaw] = aAngles[1]; + aData[Pitch] = aAngles[0]; + aData[Roll] = aAngles[2]; + aData[TX] = aTranslation[0]; + aData[TY] = aTranslation[1]; + aData[TZ] = aTranslation[2]; + } + + // + // That's called around 250 times per second. + // Therefore we better not do anything here other than provide current data. + // + void Tracker::data(double* aData) + { + if (ever_success.load(std::memory_order_relaxed)) + { + // Get data back from tracker thread + QMutexLocker l(&iDataLock); + // If there was no new data recently then we provide center data. + // Basically, if our user remove her hat, we will go back to center position until she puts it back on. + if (iSettings.iAutoCenter && iBestTime.elapsed_ms() > iSettings.iAutoCenterTimeout) + { + // Reset to center until we get new data + FeedData(aData, iCenterAngles, iCenterTranslation); + } + else + { + // We got valid data, provide it + FeedData(aData, iBestAngles, iBestTranslation); + } + } + } + + bool Tracker::center() + { + QMutexLocker l(&iDataLock); + iCenterTranslation = iBestTranslation; + iCenterAngles = iBestAngles; + // Returning false tells the pipeline we want to use the default center behaviour + return false; + } + + +} diff --git a/tracker-easy/tracker-easy.h b/tracker-easy/tracker-easy.h new file mode 100644 index 00000000..4510fc7d --- /dev/null +++ b/tracker-easy/tracker-easy.h @@ -0,0 +1,168 @@ +/* Copyright (c) 2019 Stephane Lenclud + * + * 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 "api/plugin-api.hpp" +#include "cv/numeric.hpp" +#include "video/video-widget.hpp" +#include "video/camera.hpp" +#include "compat/timer.hpp" + + +#include "preview.h" +#include "settings.h" +#include "point-extractor.h" +#include "kalman-filter-pose.h" + +#include <atomic> +#include <memory> +#include <vector> + +#include <opencv2/core.hpp> +#include <opencv2/video/tracking.hpp> + +#include <QThread> +#include <QMutex> +#include <QLayout> + +namespace EasyTracker +{ + + namespace VertexPosition + { + enum Type + { + Top = 0, + Right, + Left, + TopRight, + TopLeft, + Center + }; + } + + static const QString KModuleName = "tracker-easy"; + + class Dialog; + + using namespace numeric_types; + + struct Tracker : public QObject, ITracker + { + Q_OBJECT + public: + friend class Dialog; + + explicit Tracker(); + ~Tracker() override; + + // From ITracker + module_status start_tracker(QFrame* parent_window) override; + void data(double* data) override; + bool center() override; + + private slots: + void Tick(); + + + private: + void UpdateModel(); + void CreateCameraIntrinsicsMatrices(); + void ProcessFrame(); + void MatchVertices(int& aTopIndex, int& aRightIndex, int& aLeftIndex, int& aCenterIndex, int& aTopRight, int& aTopLeft); + void MatchThreeOrFourVertices(int& aTopIndex, int& aRightIndex, int& aLeftIndex, int& aCenterIndex); + void MatchFiveVertices(int& aTopIndex, int& aRightIndex, int& aLeftIndex, int& aTopRight, int& aTopLeft); + void MatchClipVertices(int& aTopIndex, int& aMiddleIndex, int& aBottomIndex); + + + // + + bool CheckCamera(); + void set_fov(int value); + void SetFps(int aFps); + void DoSetFps(int aFps); + void UpdateSettings(); + + QMutex camera_mtx; + QThread iThread; + QTimer iTicker; + + Settings iSettings; + + std::unique_ptr<QLayout> layout; + std::vector<cv::Point> iPoints; + + int preview_width = 320, preview_height = 240; + + PointExtractor iPointExtractor; + + std::unique_ptr<video::impl::camera> camera; + video::impl::camera::info iCameraInfo; + std::unique_ptr<video_widget> widget; + + video::frame iFrame; + cv::Mat iMatFrame; + Preview iPreview; + + std::atomic<bool> ever_success = false; + mutable QMutex iProcessLock, iDataLock; + + //// Copy the settings need by our thread to avoid dead locks + // Deadzone + int iDeadzoneEdge=0; + int iDeadzoneHalfEdge=0; + // Solver + int iSolver = cv::SOLVEPNP_P3P; + bool iDebug = false; + //// + + // Statistics + Timer iTimer; + Timer iFpsTimer; + int iFrameCount = 0; + int iSkippedFrameCount = 0; + int iFps = 0; + int iSkippedFps = 0; + uint iBadSolutionCount = 0; + uint iGoodSolutionCount = 0; + + // + KalmanFilterPose iKf; + + // Vertices defining the model we are tracking + std::vector<cv::Point3f> iModel; + // Bitmap points corresponding to model vertices + std::vector<cv::Point2f> iTrackedPoints; + + std::vector<cv::Rect> iTrackedRects; + + // Intrinsics camera matrix + cv::Matx33d iCameraMatrix { cv::Matx33d::zeros() }; + // Intrinsics distortion coefficients as a matrix + cv::Matx<double, 8, 1> iDistCoeffsMatrix; + // Translation solutions + std::vector<cv::Mat> iTranslations; + // Rotation solutions + std::vector<cv::Mat> iRotations; + // Angle solutions, pitch, yaw, roll, in this order + std::vector<cv::Vec3d> iAngles; + // The index of our best solution in the above arrays + int iBestSolutionIndex = -1; + // Best translation + cv::Vec3d iBestTranslation; + // Best angles + cv::Vec3d iBestAngles; + // Time at which we found our last best solution + Timer iBestTime; + // Center translation + cv::Vec3d iCenterTranslation = {0,0,0}; + // Center angles + cv::Vec3d iCenterAngles = { 0,0,0 }; + }; + +} diff --git a/tracker-easy/tracker_easy.qrc b/tracker-easy/tracker_easy.qrc new file mode 100644 index 00000000..b7d1a896 --- /dev/null +++ b/tracker-easy/tracker_easy.qrc @@ -0,0 +1,9 @@ +<RCC> + <qresource prefix="/"> + <file>Resources/easy-tracker-logo.png</file> + <file>Resources/cap_front.png</file> + <file>Resources/cap_side.png</file> + <file>Resources/clip_front.png</file> + <file>Resources/clip_side.png</file> + </qresource> +</RCC> |