summaryrefslogtreecommitdiffhomepage
path: root/tracker-easy/tracker-easy.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tracker-easy/tracker-easy.cpp')
-rw-r--r--tracker-easy/tracker-easy.cpp912
1 files changed, 912 insertions, 0 deletions
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;
+ }
+
+
+}