summaryrefslogtreecommitdiff
path: root/src/player/TrackerInputDevice.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/player/TrackerInputDevice.cpp')
-rw-r--r--src/player/TrackerInputDevice.cpp493
1 files changed, 493 insertions, 0 deletions
diff --git a/src/player/TrackerInputDevice.cpp b/src/player/TrackerInputDevice.cpp
new file mode 100644
index 0000000..88e6baa
--- /dev/null
+++ b/src/player/TrackerInputDevice.cpp
@@ -0,0 +1,493 @@
+//
+// libavg - Media Playback Engine.
+// Copyright (C) 2003-2014 Ulrich von Zadow
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+// Current versions can be found at www.libavg.de
+//
+// Original author of this file is igor@c-base.org
+//
+
+#include "TrackerInputDevice.h"
+#include "TouchEvent.h"
+#include "TrackerTouchStatus.h"
+
+#include "../base/Logger.h"
+#include "../base/ObjectCounter.h"
+#include "../base/ScopeTimer.h"
+#include "../base/Exception.h"
+
+#include "../graphics/HistoryPreProcessor.h"
+#include "../graphics/Filterfill.h"
+#include "../graphics/Pixel8.h"
+
+#include "../imaging/DeDistort.h"
+#include "../imaging/CoordTransformer.h"
+
+#include "../glm/gtx/norm.hpp"
+
+#include "Player.h"
+#include "AVGNode.h"
+
+#include <boost/thread/thread.hpp>
+#include <boost/bind.hpp>
+
+#include <map>
+#include <list>
+#include <vector>
+#include <queue>
+#include <set>
+#include <iostream>
+
+using namespace std;
+
+namespace avg {
+
+TrackerInputDevice::TrackerInputDevice()
+ : IInputDevice(EXTRACT_INPUTDEVICE_CLASSNAME(TrackerInputDevice)),
+ m_pTrackerThread(0),
+ m_bSubtractHistory(true),
+ m_pCalibrator(0)
+{
+ ObjectCounter::get()->incRef(&typeid(*this));
+
+ m_TrackerConfig.load();
+
+ string sDriver = m_TrackerConfig.getParam("/camera/driver/@value");
+ string sDevice = m_TrackerConfig.getParam("/camera/device/@value");
+ bool bFW800 = m_TrackerConfig.getBoolParam("/camera/fw800/@value");
+ IntPoint captureSize(m_TrackerConfig.getPointParam("/camera/size/"));
+ string sCaptureFormat = m_TrackerConfig.getParam("/camera/format/@value");
+ float frameRate = m_TrackerConfig.getFloatParam("/camera/framerate/@value");
+
+ PixelFormat camPF = stringToPixelFormat(sCaptureFormat);
+ if (camPF == NO_PIXELFORMAT) {
+ throw Exception(AVG_ERR_INVALID_ARGS,
+ "Unknown camera pixel format "+sCaptureFormat+".");
+ }
+
+ AVG_TRACE(Logger::category::CONFIG, Logger::severity::INFO,
+ "Trying to create a Tracker for " << sDriver
+ << " Camera: " << sDevice << " Size: " << captureSize << "format: "
+ << sCaptureFormat);
+ m_pCamera = createCamera(sDriver, sDevice, -1, bFW800, captureSize, camPF, I8,
+ frameRate);
+ AVG_TRACE(Logger::category::CONFIG, Logger::severity::INFO,
+ "Got Camera " << m_pCamera->getDevice() << " from driver: "
+ << m_pCamera->getDriverName());
+
+ IntPoint imgSize = m_pCamera->getImgSize();
+ m_pBitmaps[0] = BitmapPtr(new Bitmap(imgSize, I8));
+ m_pMutex = MutexPtr(new boost::mutex);
+ m_pCmdQueue = TrackerThread::CQueuePtr(new TrackerThread::CQueue);
+ m_pDeDistort = m_TrackerConfig.getTransform();
+ try {
+ m_ActiveDisplaySize = IntPoint(
+ m_TrackerConfig.getPointParam("/transform/activedisplaysize/"));
+ } catch (Exception) {
+ m_ActiveDisplaySize = IntPoint(Player::get()->getRootNode()->getSize());
+ }
+ try {
+ m_DisplayROI = m_TrackerConfig.getRectParam("/transform/displayroi/");
+ } catch (Exception) {
+ m_DisplayROI = FRect(glm::vec2(0,0), glm::vec2(m_ActiveDisplaySize));
+ }
+
+ IntRect roi = m_pDeDistort->getActiveBlobArea(m_DisplayROI);
+ if (roi.tl.x < 0 || roi.tl.y < 0 ||
+ roi.br.x > imgSize.x || roi.br.y > imgSize.y)
+ {
+ AVG_LOG_ERROR("Impossible tracker configuration: Region of interest is "
+ << roi << ", camera image size is " << imgSize << ". Aborting.");
+ exit(5);
+ }
+ m_InitialROI = roi;
+ createBitmaps(roi);
+ setDebugImages(false, false);
+
+ try {
+ m_bFindFingertips = m_TrackerConfig.getBoolParam(
+ "/tracker/findfingertips/@value");
+ } catch(Exception) {
+ m_bFindFingertips = false;
+ }
+
+}
+
+TrackerInputDevice::~TrackerInputDevice()
+{
+ m_pCmdQueue->pushCmd(boost::bind(&TrackerThread::stop, _1));
+ if (m_pTrackerThread) {
+ m_pTrackerThread->join();
+ delete m_pTrackerThread;
+ }
+ ObjectCounter::get()->decRef(&typeid(*this));
+}
+
+void TrackerInputDevice::start()
+{
+ m_pTrackerThread = new boost::thread(
+ TrackerThread(
+ m_InitialROI,
+ m_pCamera,
+ m_pBitmaps,
+ m_pMutex,
+ *m_pCmdQueue,
+ this,
+ m_bSubtractHistory,
+ m_TrackerConfig
+ )
+ );
+ setConfig();
+}
+
+void TrackerInputDevice::setParam(const string& sElement, const string& sValue)
+{
+ string sOldParamVal = m_TrackerConfig.getParam(sElement);
+ m_TrackerConfig.setParam(sElement, sValue);
+
+ // Test if active area is outside camera.
+ FRect area = m_pDeDistort->getActiveBlobArea(m_DisplayROI);
+ glm::vec2 size = m_TrackerConfig.getPointParam("/camera/size/");
+ int prescale = m_TrackerConfig.getIntParam("/tracker/prescale/@value");
+ if (area.br.x > size.x/prescale || area.br.y > size.y/prescale ||
+ area.tl.x < 0 || area.tl.y < 0)
+ {
+ m_TrackerConfig.setParam(sElement, sOldParamVal);
+ } else {
+ setConfig();
+ }
+// m_TrackerConfig.dump();
+}
+
+string TrackerInputDevice::getParam(const string& sElement)
+{
+ return m_TrackerConfig.getParam(sElement);
+}
+
+void TrackerInputDevice::setDebugImages(bool bImg, bool bFinger)
+{
+ m_pCmdQueue->pushCmd(boost::bind(&TrackerThread::setDebugImages, _1, bImg,
+ bFinger));
+}
+
+void TrackerInputDevice::resetHistory()
+{
+ m_pCmdQueue->pushCmd(boost::bind(&TrackerThread::resetHistory, _1));
+}
+
+void TrackerInputDevice::saveConfig()
+{
+ m_TrackerConfig.save();
+}
+
+void TrackerInputDevice::setConfig()
+{
+ m_pDeDistort = m_TrackerConfig.getTransform();
+ FRect area = m_pDeDistort->getActiveBlobArea(m_DisplayROI);
+ createBitmaps(area);
+ m_pCmdQueue->pushCmd(boost::bind(&TrackerThread::setConfig, _1, m_TrackerConfig,
+ area, m_pBitmaps));
+}
+
+void TrackerInputDevice::createBitmaps(const IntRect& area)
+{
+ lock_guard lock(*m_pMutex);
+ for (int i=1; i<NUM_TRACKER_IMAGES; i++) {
+ switch (i) {
+ case TRACKER_IMG_HISTOGRAM:
+ m_pBitmaps[TRACKER_IMG_HISTOGRAM] =
+ BitmapPtr(new Bitmap(IntPoint(256, 256), I8));
+ FilterFill<Pixel8>(Pixel8(0)).
+ applyInPlace(m_pBitmaps[TRACKER_IMG_HISTOGRAM]);
+ break;
+ case TRACKER_IMG_FINGERS:
+ m_pBitmaps[TRACKER_IMG_FINGERS] =
+ BitmapPtr(new Bitmap(area.size(), B8G8R8A8));
+ FilterFill<Pixel32>(Pixel32(0,0,0,0)).
+ applyInPlace(m_pBitmaps[TRACKER_IMG_FINGERS]);
+ break;
+ default:
+ m_pBitmaps[i] = BitmapPtr(new Bitmap(area.size(), I8));
+ FilterFill<Pixel8>(Pixel8(0)).applyInPlace(m_pBitmaps[i]);
+ }
+ }
+}
+
+Bitmap * TrackerInputDevice::getImage(TrackerImageID imageID) const
+{
+ lock_guard lock(*m_pMutex);
+ return new Bitmap(*m_pBitmaps[imageID]);
+}
+
+glm::vec2 TrackerInputDevice::getDisplayROIPos() const
+{
+ return m_DisplayROI.tl;
+}
+
+glm::vec2 TrackerInputDevice::getDisplayROISize() const
+{
+ return m_DisplayROI.size();
+}
+
+static ProfilingZoneID ProfilingZoneCalcTrack("trackBlobIDs(track)");
+static ProfilingZoneID ProfilingZoneCalcTouch("trackBlobIDs(touch)");
+
+void TrackerInputDevice::update(BlobVectorPtr pTrackBlobs,
+ BlobVectorPtr pTouchBlobs, long long time)
+{
+ if (pTrackBlobs) {
+ ScopeTimer Timer(ProfilingZoneCalcTrack);
+ trackBlobIDs(pTrackBlobs, time, false);
+ }
+ if (pTouchBlobs) {
+ ScopeTimer Timer(ProfilingZoneCalcTouch);
+ trackBlobIDs(pTouchBlobs, time, true);
+ }
+}
+
+// Temporary structure to be put into heap of blob distances. Used only in
+// trackBlobIDs.
+struct BlobDistEntry {
+ BlobDistEntry(float dist, BlobPtr pNewBlob, BlobPtr pOldBlob)
+ : m_Dist(dist),
+ m_pNewBlob(pNewBlob),
+ m_pOldBlob(pOldBlob)
+ {
+ }
+
+ float m_Dist;
+ BlobPtr m_pNewBlob;
+ BlobPtr m_pOldBlob;
+};
+typedef boost::shared_ptr<struct BlobDistEntry> BlobDistEntryPtr;
+
+// The heap is sorted by least distance, so this operator does the
+// _opposite_ of what is expected!
+bool operator < (const BlobDistEntryPtr& e1, const BlobDistEntryPtr& e2)
+{
+ return e1->m_Dist > e2->m_Dist;
+}
+
+void TrackerInputDevice::trackBlobIDs(BlobVectorPtr pNewBlobs, long long time,
+ bool bTouch)
+{
+ TouchStatusMap * pEvents;
+ string sConfigPath;
+ Event::Source source;
+ if (bTouch) {
+ sConfigPath = "/tracker/touch/";
+ pEvents = &m_TouchEvents;
+ source = Event::TOUCH;
+ } else {
+ sConfigPath = "/tracker/track/";
+ pEvents = &m_TrackEvents;
+ source = Event::TRACK;
+ }
+ BlobVector oldBlobs;
+ for (TouchStatusMap::iterator it = pEvents->begin(); it != pEvents->end(); ++it) {
+ (*it).second->setStale();
+ oldBlobs.push_back((*it).first);
+ }
+ // Create a heap that contains all distances of old to new blobs < MaxDist
+ float MaxDist = m_TrackerConfig.getFloatParam(sConfigPath+"similarity/@value");
+ float MaxDistSquared = MaxDist*MaxDist;
+ priority_queue<BlobDistEntryPtr> distHeap;
+ for (BlobVector::iterator it = pNewBlobs->begin(); it != pNewBlobs->end(); ++it) {
+ BlobPtr pNewBlob = *it;
+ for(BlobVector::iterator it2 = oldBlobs.begin(); it2 != oldBlobs.end(); ++it2) {
+ BlobPtr pOldBlob = *it2;
+ float distSquared = glm::distance2(pNewBlob->getCenter(),
+ pOldBlob->getEstimatedNextCenter());
+ if (distSquared <= MaxDistSquared) {
+ BlobDistEntryPtr pEntry = BlobDistEntryPtr(
+ new BlobDistEntry(distSquared, pNewBlob, pOldBlob));
+ distHeap.push(pEntry);
+ }
+ }
+ }
+ // Match up the closest blobs.
+ set<BlobPtr> matchedNewBlobs;
+ set<BlobPtr> matchedOldBlobs;
+ int numMatchedBlobs = 0;
+ while (!distHeap.empty()) {
+ BlobDistEntryPtr pEntry = distHeap.top();
+ distHeap.pop();
+ if (matchedNewBlobs.find(pEntry->m_pNewBlob) == matchedNewBlobs.end() &&
+ matchedOldBlobs.find(pEntry->m_pOldBlob) == matchedOldBlobs.end())
+ {
+ // Found a pair of matched blobs.
+ numMatchedBlobs++;
+ BlobPtr pNewBlob = pEntry->m_pNewBlob;
+ BlobPtr pOldBlob = pEntry->m_pOldBlob;
+ matchedNewBlobs.insert(pNewBlob);
+ matchedOldBlobs.insert(pOldBlob);
+ AVG_ASSERT (pEvents->find(pOldBlob) != pEvents->end());
+ TrackerTouchStatusPtr pTouchStatus;
+ pTouchStatus = pEvents->find(pOldBlob)->second;
+ // Make sure we don't discard any events that have related info.
+ bool bKeepAllEvents = pNewBlob->getFirstRelated() && !bTouch;
+ pTouchStatus->blobChanged(pNewBlob, time, bKeepAllEvents);
+ pNewBlob->calcNextCenter(pOldBlob->getCenter());
+ // Update the mapping.
+ (*pEvents)[pNewBlob] = pTouchStatus;
+ pEvents->erase(pOldBlob);
+ }
+ }
+ // Blobs have been matched. Left-overs are new blobs.
+ for (BlobVector::iterator it = pNewBlobs->begin(); it != pNewBlobs->end(); ++it) {
+ if (matchedNewBlobs.find(*it) == matchedNewBlobs.end()) {
+ TrackerTouchStatusPtr pTouchStatus = TrackerTouchStatusPtr(
+ new TrackerTouchStatus(*it, time, m_pDeDistort, m_DisplayROI,
+ source));
+ (*pEvents)[(*it)] = pTouchStatus;
+ }
+ }
+
+ // All event streams that are still stale haven't been updated: blob is gone,
+ // set the sentinel for this.
+ for(TouchStatusMap::iterator it=pEvents->begin(); it!=pEvents->end(); ++it) {
+ if ((*it).second->isStale()) {
+ (*it).second->blobGone();
+ }
+ }
+};
+
+TrackerCalibrator* TrackerInputDevice::startCalibration()
+{
+ AVG_ASSERT(!m_pCalibrator);
+ m_pOldTransformer = m_TrackerConfig.getTransform();
+ m_OldDisplayROI = m_DisplayROI;
+ m_DisplayROI = FRect(glm::vec2(0,0), glm::vec2(m_ActiveDisplaySize));
+ m_TrackerConfig.setTransform(DeDistortPtr(new DeDistort(
+ glm::vec2(m_pBitmaps[0]->getSize()), glm::vec2(m_ActiveDisplaySize))));
+ setConfig();
+ m_pCalibrator = new TrackerCalibrator(m_pBitmaps[0]->getSize(),
+ m_ActiveDisplaySize);
+ return m_pCalibrator;
+}
+
+void TrackerInputDevice::endCalibration()
+{
+ AVG_ASSERT(m_pCalibrator);
+ m_TrackerConfig.setTransform(m_pCalibrator->makeTransformer());
+ m_DisplayROI = m_OldDisplayROI;
+ FRect area = m_TrackerConfig.getTransform()->getActiveBlobArea(m_DisplayROI);
+ if (area.size().x*area.size().y > 1024*1024*8) {
+ AVG_LOG_WARNING("Ignoring calibration - resulting area would be " << area);
+ m_TrackerConfig.setTransform(m_pOldTransformer);
+ }
+ setConfig();
+ delete m_pCalibrator;
+ m_pCalibrator = 0;
+ m_pOldTransformer = DeDistortPtr();
+}
+
+void TrackerInputDevice::abortCalibration()
+{
+ AVG_ASSERT(m_pCalibrator);
+ m_TrackerConfig.setTransform(m_pOldTransformer);
+ setConfig();
+ m_pOldTransformer = DeDistortPtr();
+ delete m_pCalibrator;
+ m_pCalibrator = 0;
+}
+
+vector<EventPtr> TrackerInputDevice::pollEvents()
+{
+ lock_guard lock(*m_pMutex);
+ vector<EventPtr> pTouchEvents;
+ vector<EventPtr> pTrackEvents;
+ pollEventType(pTouchEvents, m_TouchEvents, CursorEvent::TOUCH);
+ pollEventType(pTrackEvents, m_TrackEvents, CursorEvent::TRACK);
+ copyRelatedInfo(pTouchEvents, pTrackEvents);
+ if (m_bFindFingertips) {
+ findFingertips(pTouchEvents);
+ }
+ pTouchEvents.insert(pTouchEvents.end(),
+ pTrackEvents.begin(), pTrackEvents.end());
+ return pTouchEvents;
+}
+
+void TrackerInputDevice::pollEventType(vector<EventPtr>& res, TouchStatusMap& Events,
+ CursorEvent::Source source)
+{
+ EventPtr pEvent;
+ for (TouchStatusMap::iterator it = Events.begin(); it!= Events.end();) {
+ TrackerTouchStatusPtr pTouchStatus = (*it).second;
+ pEvent = pTouchStatus->pollEvent();
+ if (pEvent) {
+ res.push_back(pEvent);
+ if (pEvent->getType() == Event::CURSOR_UP) {
+ Events.erase(it++);
+ } else {
+ ++it;
+ }
+ } else {
+ ++it;
+ }
+ }
+}
+
+void TrackerInputDevice::copyRelatedInfo(vector<EventPtr> pTouchEvents,
+ vector<EventPtr> pTrackEvents)
+{
+ // Copy related blobs to related events.
+ // Yuck.
+ vector<EventPtr>::iterator it;
+ for (it = pTouchEvents.begin(); it != pTouchEvents.end(); ++it) {
+ TouchEventPtr pTouchEvent = boost::dynamic_pointer_cast<TouchEvent>(*it);
+ BlobPtr pTouchBlob = pTouchEvent->getBlob();
+ BlobPtr pRelatedBlob = pTouchBlob->getFirstRelated();
+ if (pRelatedBlob) {
+ vector<EventPtr>::iterator it2;
+ TouchEventPtr pTrackEvent;
+ BlobPtr pTrackBlob;
+ for (it2 = pTrackEvents.begin();
+ pTrackBlob != pRelatedBlob && it2 != pTrackEvents.end();
+ ++it2)
+ {
+ pTrackEvent = boost::dynamic_pointer_cast<TouchEvent>(*it2);
+ pTrackBlob = pTrackEvent->getBlob();
+ }
+ if (pTrackBlob == pRelatedBlob) {
+ pTouchEvent->addRelatedEvent(pTrackEvent);
+ pTrackEvent->addRelatedEvent(pTouchEvent);
+ }
+ }
+ }
+}
+
+void TrackerInputDevice::findFingertips(std::vector<EventPtr>& pTouchEvents)
+{
+ vector<EventPtr>::iterator it;
+ for (it = pTouchEvents.begin(); it != pTouchEvents.end(); ++it) {
+ TouchEventPtr pTouchEvent = boost::dynamic_pointer_cast<TouchEvent>(*it);
+ vector<TouchEventPtr> pTrackEvents = pTouchEvent->getRelatedEvents();
+ if (pTrackEvents.size() > 0) {
+ float handAngle = pTouchEvent->getHandOrientation();
+ float dist = glm::length(pTouchEvent->getMajorAxis())*2;
+ glm::vec2 tweakVec = fromPolar(handAngle, dist);
+ glm::vec2 newPos = pTouchEvent->getPos()-tweakVec;
+ newPos.x = max(0.0f, min(newPos.x, float(m_ActiveDisplaySize.x)));
+ newPos.y = max(0.0f, min(newPos.y, float(m_ActiveDisplaySize.y)));
+ pTouchEvent->setPos(newPos);
+ }
+ }
+}
+
+}
+