diff options
Diffstat (limited to 'tracker-easy/point-extractor.cpp')
-rw-r--r-- | tracker-easy/point-extractor.cpp | 168 |
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); + } + + } + } + +} + |