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