// // 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 #include #include #include #include #include #include #include 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(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(0,0,0,0)). applyInPlace(m_pBitmaps[TRACKER_IMG_FINGERS]); break; default: m_pBitmaps[i] = BitmapPtr(new Bitmap(area.size(), I8)); FilterFill(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 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 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 matchedNewBlobs; set 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 TrackerInputDevice::pollEvents() { lock_guard lock(*m_pMutex); vector pTouchEvents; vector 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& 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 pTouchEvents, vector pTrackEvents) { // Copy related blobs to related events. // Yuck. vector::iterator it; for (it = pTouchEvents.begin(); it != pTouchEvents.end(); ++it) { TouchEventPtr pTouchEvent = boost::dynamic_pointer_cast(*it); BlobPtr pTouchBlob = pTouchEvent->getBlob(); BlobPtr pRelatedBlob = pTouchBlob->getFirstRelated(); if (pRelatedBlob) { vector::iterator it2; TouchEventPtr pTrackEvent; BlobPtr pTrackBlob; for (it2 = pTrackEvents.begin(); pTrackBlob != pRelatedBlob && it2 != pTrackEvents.end(); ++it2) { pTrackEvent = boost::dynamic_pointer_cast(*it2); pTrackBlob = pTrackEvent->getBlob(); } if (pTrackBlob == pRelatedBlob) { pTouchEvent->addRelatedEvent(pTrackEvent); pTrackEvent->addRelatedEvent(pTouchEvent); } } } } void TrackerInputDevice::findFingertips(std::vector& pTouchEvents) { vector::iterator it; for (it = pTouchEvents.begin(); it != pTouchEvents.end(); ++it) { TouchEventPtr pTouchEvent = boost::dynamic_pointer_cast(*it); vector 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); } } } }