00001 /*!@file Robots2/Beobot2/Navigation/GistSal_Navigation/GistSal_Navigation.C */ 00002 // //////////////////////////////////////////////////////////////////// // 00003 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2001 by the // 00004 // University of Southern California (USC) and the iLab at USC. // 00005 // See http://iLab.usc.edu for information about this project. // 00006 // //////////////////////////////////////////////////////////////////// // 00007 // Major portions of the iLab Neuromorphic Vision Toolkit are protected // 00008 // under the U.S. patent ``Computation of Intrinsic Perceptual Saliency // 00009 // in Visual Environments, and Applications'' by Christof Koch and // 00010 // Laurent Itti, California Institute of Technology, 2001 (patent // 00011 // pending; application number 09/912,225 filed July 23, 2001; see // 00012 // http://pair.uspto.gov/cgi-bin/final/home.pl for current status). // 00013 // //////////////////////////////////////////////////////////////////// // 00014 // This file is part of the iLab Neuromorphic Vision C++ Toolkit. // 00015 // // 00016 // The iLab Neuromorphic Vision C++ Toolkit is free software; you can // 00017 // redistribute it and/or modify it under the terms of the GNU General // 00018 // Public License as published by the Free Software Foundation; either // 00019 // version 2 of the License, or (at your option) any later version. // 00020 // // 00021 // The iLab Neuromorphic Vision C++ Toolkit is distributed in the hope // 00022 // that it will be useful, but WITHOUT ANY WARRANTY; without even the // 00023 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // 00024 // PURPOSE. See the GNU General Public License for more details. // 00025 // // 00026 // You should have received a copy of the GNU General Public License // 00027 // along with the iLab Neuromorphic Vision C++ Toolkit; if not, write // 00028 // to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, // 00029 // Boston, MA 02111-1307 USA. // 00030 // //////////////////////////////////////////////////////////////////// // 00031 // 00032 // Primary maintainer for this file: Chin-Kai Chang <chinkaic@usc.edu> 00033 // $HeadURL: svn://ilab.usc.edu/trunk/saliency/src/Robots/Beobot2/GistSal_Navigation.C 00034 // $ $Id: GistSal_Navigation.C 13126 2010-04-02 03:42:30Z beobot $ 00035 // 00036 ////////////////////////////////////////////////////////////////////////// 00037 00038 #include "Robots/Beobot2/Navigation/GistSal_Navigation/GistSal_Navigation.H" 00039 #include "Ice/BeobotEvents.ice.H" 00040 00041 #include "Raster/Raster.H" 00042 #include "Util/sformat.H" 00043 #include "Image/Image.H" 00044 #include "Image/DrawOps.H" 00045 #include "Image/ColorOps.H" // for luminance() 00046 #include "Image/ShapeOps.H" // for rescale() 00047 #include "Image/MathOps.H" // for stdev() 00048 #include "Image/MatrixOps.H" // for matrixMult() 00049 #include "Image/CutPaste.H" // for inplacePaste() 00050 00051 #include "Util/Timer.H" 00052 00053 #include "SIFT/Keypoint.H" 00054 #include "SIFT/VisualObject.H" 00055 #include "SIFT/VisualObjectMatch.H" 00056 #include "Transport/FrameInfo.H" 00057 00058 #include "Ice/IceImageUtils.H" 00059 00060 #include "Component/ModelParam.H" 00061 #include "Component/ModelOptionDef.H" 00062 00063 #define IMAX 1.0 00064 #define IMIN 0.0 00065 #define LOST_TIME_LIMIT 6500000 00066 #define ROT_TIME_LIMIT 3000000 00067 #define JUNCTION_START_DIST 8.0 00068 #define REVERSE_COUNT_MAX 10 00069 #define INVALID_ANGLE -100.0 00070 #define INVALID_DISTANCE -200.0 00071 00072 #define C_PROCEDURE_INACTIVE 0 00073 #define C_PROCEDURE_ACTIVE 1 00074 #define C_PROCEDURE_CONCLUDING 2 00075 00076 // ###################################################################### 00077 GistSal_Navigation::GistSal_Navigation(OptionManager& mgr, 00078 const std::string& descrName, 00079 const std::string& tagName) 00080 : 00081 RobotBrainComponent(mgr, descrName, tagName), 00082 itsOfs(new OutputFrameSeries(mgr)), 00083 itsTimer(1000000), 00084 itsCurrImgID(-1), 00085 itsPrevProcImgID(-1), 00086 itsTransSpeed(0.0), 00087 itsRotSpeed(0.0), 00088 itsDir(0), 00089 itsIState(0.0), 00090 itsDState(0.0), 00091 itsPGain(1.0), 00092 itsIGain(0.01), 00093 itsDGain(0.0), 00094 itsReverseCount(0), 00095 itsReverseDir(false), 00096 itsDistToGoal(-1.0), 00097 itsLastFoundTimer(1000000), 00098 itsSearchTracker(new SalientRegionTracker(mgr)), 00099 itsNewMovementTracker(new SalientRegionTracker(mgr)), 00100 itsCurrMovementTracker(new SalientRegionTracker(mgr)) 00101 { 00102 addSubComponent(itsOfs); 00103 00104 // tracker 00105 addSubComponent(itsSearchTracker); 00106 addSubComponent(itsNewMovementTracker); 00107 addSubComponent(itsCurrMovementTracker); 00108 itsResetSearchTracker = true; 00109 00110 // assume the robot is not moving 00111 // until after a landmark is recognized 00112 itsTurnAngle = 0.0; 00113 itsJunctionDist = -1.0; 00114 itsLastFoundTimer.reset(); 00115 00116 // FIX: HACK IT FIRST TO JUST GO HOME 00117 itsDesiredSegmentLocation = 3; 00118 itsDesiredSegmentLengthTraveled = 0.95; 00119 itsInGoal = false; 00120 00121 // this initialization is meaningless 00122 itsCurrentSegmentLocation = 0; 00123 itsCurrentSegmentLengthTraveled = 0.0; 00124 00125 itsCornerProcStatus = C_PROCEDURE_INACTIVE; 00126 } 00127 00128 // ###################################################################### 00129 void GistSal_Navigation::setEnvironment 00130 (rutz::shared_ptr<Environment> env) 00131 { 00132 itsEnvironment = env; 00133 00134 // from its environment: topological map 00135 itsTopologicalMap = env->getTopologicalMap(); 00136 00137 // from its environment: visual landmark database 00138 itsLandmarkDB = env->getLandmarkDB(); 00139 } 00140 00141 // ###################################################################### 00142 GistSal_Navigation::~GistSal_Navigation() 00143 { } 00144 00145 // ###################################################################### 00146 void GistSal_Navigation::start1() 00147 { } 00148 00149 // ###################################################################### 00150 void GistSal_Navigation::registerTopics() 00151 { 00152 // subscribe to gist, saliency, localizer result, and motor topics 00153 this->registerSubscription("LandmarkTrackMessageTopic"); 00154 this->registerSubscription("LandmarkDBSearchResultMessageTopic"); 00155 this->registerSubscription("CurrentLocationMessageTopic"); 00156 this->registerSubscription("MotorMessageTopic"); 00157 this->registerSubscription("CornerMotorRequestTopic"); 00158 00159 this->registerPublisher("CornerLocationMessageTopic"); 00160 this->registerPublisher("MotorRequestTopic"); 00161 } 00162 00163 // ###################################################################### 00164 void GistSal_Navigation::updateMessage 00165 (const RobotSimEvents::EventMessagePtr& eMsg, const Ice::Current&) 00166 { 00167 Timer timer(1000000); 00168 00169 // input image, saliency, and gist features message 00170 if(eMsg->ice_isA("::BeobotEvents::LandmarkTrackMessage")) 00171 { 00172 // store the image 00173 BeobotEvents::LandmarkTrackMessagePtr lTrackMsg = 00174 BeobotEvents::LandmarkTrackMessagePtr::dynamicCast(eMsg); 00175 00176 int currRequestID = lTrackMsg->RequestID; 00177 Image<PixRGB<byte> > img = Ice2Image<PixRGB<byte> >(lTrackMsg->currIma); 00178 00179 LINFO("Got an lTrackMessage with Request ID = %d", currRequestID); 00180 00181 // get the recieved message 00182 its_input_mutex.lock(); 00183 itsCurrImg = img; 00184 itsCurrImgID = currRequestID; 00185 00186 // get the salient region information 00187 uint inputSize = lTrackMsg->salientRegions.size(); 00188 itsCurrSalPoints.clear(); 00189 itsCurrSalRects.clear(); 00190 for(uint i = 0; i < inputSize; i++) 00191 { 00192 BeobotEvents::SalientRegion salReg = lTrackMsg->salientRegions[i]; 00193 LINFO("M[%4d] sp[%4d,%4d] rect[%4d,%4d,%4d,%4d]", 00194 i, salReg.salpt.i, salReg.salpt.j, 00195 salReg.objRect.tl.i, salReg.objRect.tl.j, 00196 salReg.objRect.br.i, salReg.objRect.br.j); 00197 00198 Point2D<int> salpt(salReg.salpt.i, salReg.salpt.j); 00199 itsCurrSalPoints.push_back(salpt); 00200 00201 Rectangle rect = Rectangle::tlbrO 00202 (salReg.objRect.tl.j, salReg.objRect.tl.i, 00203 salReg.objRect.br.j, salReg.objRect.br.i); 00204 itsCurrSalRects.push_back(rect); 00205 } 00206 00207 // get the conspicuity maps 00208 itsCurrCmap.clear(); 00209 itsCurrCmap.reset(NUM_CHANNELS); 00210 for(uint i = 0; i < NUM_CHANNELS; i++) 00211 itsCurrCmap[i] = Ice2Image<float>(lTrackMsg->conspicuityMaps[i]); 00212 00213 its_input_mutex.unlock(); 00214 00215 LINFO("[input time: %f ms]\n\n", timer.get()/1000.0f); 00216 } 00217 00218 // landmark database match found to be tracked 00219 else if(eMsg->ice_isA("::BeobotEvents::LandmarkDBSearchResultMessage")) 00220 { 00221 BeobotEvents::LandmarkDBSearchResultMessagePtr dbrMsg = 00222 BeobotEvents::LandmarkDBSearchResultMessagePtr::dynamicCast(eMsg); 00223 00224 int currRequestID = dbrMsg->RequestID; 00225 00226 LINFO("Got a LandmarkDBSearchResultMessage with Request ID = %d", 00227 currRequestID); 00228 00229 its_tracker_mutex.lock(); 00230 its_Curr_Dbr_mutex.lock(); 00231 itsCurrImgID = currRequestID; 00232 00233 // make sure there is at least 1 to be tracked 00234 uint numRecognized = dbrMsg->matches.size(); 00235 00236 // check for asynchrony 00237 if(numRecognized > 0) 00238 { 00239 uint mIndex = dbrMsg->matches[numRecognized - 1].inputSalRegID; 00240 if(mIndex >= itsSearchTracker->getNumTrackedPoints()) 00241 { 00242 LINFO("\n\n\nASYNCHRONIZATION HAS OCCURED: %d %d\n\n", 00243 mIndex, itsSearchTracker->getNumTrackedPoints()); 00244 numRecognized = 0; 00245 00246 // NOTE: need better solution to fix this problem 00247 } 00248 } 00249 00250 // if we have a new recognized salient region 00251 if(numRecognized > 0) 00252 { 00253 itsNewMovementTracker->clear(); 00254 00255 itsNewMovementTracker->reset(numRecognized); 00256 itsNewMovementTracker->setCurrInputImage 00257 (itsSearchTracker->getCurrInputImage()); 00258 itsNewMovementTracker->setOriginalInputImage 00259 (itsSearchTracker->getOriginalInputImage()); 00260 00261 // MOVE found Landmark from SearchTracker to NewTracker 00262 itsNewLandmarkDBMatches.clear(); 00263 for(uint i = 0; i < dbrMsg->matches.size(); i++) 00264 { 00265 uint index = dbrMsg->matches[i].inputSalRegID; 00266 itsNewMovementTracker->move(itsSearchTracker, index); 00267 00268 // get the salient region of the landmark 00269 // that are just identified 00270 rutz::shared_ptr<LandmarkDBMatch> ldbm(new LandmarkDBMatch()); 00271 GSlocJobData dbmi(index, 00272 dbrMsg->matches[i].dbSegNum, 00273 dbrMsg->matches[i].dbLmkNum, 00274 dbrMsg->matches[i].dbVOStart, 00275 dbrMsg->matches[i].dbVOEnd); 00276 ldbm->dbi = dbmi; 00277 itsNewLandmarkDBMatches.push_back(ldbm); 00278 00279 LINFO("DB Match sent: %d: (%d %d - %d %d)", 00280 itsNewLandmarkDBMatches[i]->dbi.objNum, 00281 itsNewLandmarkDBMatches[i]->dbi.segNum, 00282 itsNewLandmarkDBMatches[i]->dbi.lmkNum, 00283 itsNewLandmarkDBMatches[i]->dbi.voStartNum, 00284 itsNewLandmarkDBMatches[i]->dbi.voEndNum); 00285 } 00286 } 00287 00288 // we can now reset the search tracker 00289 itsResetSearchTracker = true; 00290 00291 its_Curr_Dbr_mutex.unlock(); 00292 its_tracker_mutex.unlock(); 00293 LINFO("Found %d new landmarks in %f ms \n\n", 00294 numRecognized, timer.get()/1000.0f); 00295 } 00296 00297 // current location message 00298 else if(eMsg->ice_isA("::BeobotEvents::CurrentLocationMessage")) 00299 { 00300 BeobotEvents::CurrentLocationMessagePtr clMsg = 00301 BeobotEvents::CurrentLocationMessagePtr::dynamicCast(eMsg); 00302 LDEBUG("Got a CurrentLocationMessage with Request ID = %d" 00303 ": loc(%d, %f)", 00304 clMsg->RequestID, clMsg->segNum, clMsg->lenTrav); 00305 its_Curr_Loc_mutex.lock(); 00306 itsCurrentSegmentLocation = clMsg->segNum; 00307 itsCurrentSegmentLengthTraveled = clMsg->lenTrav; 00308 its_Curr_Loc_mutex.unlock(); 00309 } 00310 00311 // motor message 00312 else if(eMsg->ice_isA("::BeobotEvents::MotorMessage")) 00313 { 00314 BeobotEvents::MotorMessagePtr mtrMsg = 00315 BeobotEvents::MotorMessagePtr::dynamicCast(eMsg); 00316 its_Curr_Mtr_mutex.lock(); 00317 itsRemoteMode = mtrMsg->rcMode; 00318 itsRcTransSpeed = mtrMsg->rcTransVel; 00319 itsRcRotSpeed = mtrMsg->rcRotVel; 00320 its_Curr_Mtr_mutex.unlock(); 00321 00322 LDEBUG("Got a MotorMessage with Request ID = %d: RC Trans %f, Rot %f", 00323 mtrMsg->RequestID, itsRcTransSpeed, itsRcRotSpeed); 00324 } 00325 00326 // corner message 00327 else if(eMsg->ice_isA("::BeobotEvents::CornerMotorRequest")) 00328 { 00329 BeobotEvents::CornerMotorRequestPtr cmtrMsg = 00330 BeobotEvents::CornerMotorRequestPtr::dynamicCast(eMsg); 00331 00332 its_Curr_CornerMtr_mutex.lock(); 00333 itsCornerTransSpeed = cmtrMsg->transVel; 00334 itsCornerRotSpeed = cmtrMsg->rotVel; 00335 00336 if(cmtrMsg->status == C_PROCEDURE_INACTIVE) 00337 itsCornerProcStatus = C_PROCEDURE_INACTIVE; 00338 else if(cmtrMsg->status == C_PROCEDURE_ACTIVE) 00339 itsCornerProcStatus = C_PROCEDURE_ACTIVE; 00340 else if(cmtrMsg->status == C_PROCEDURE_CONCLUDING) 00341 itsCornerProcStatus = C_PROCEDURE_CONCLUDING; 00342 00343 its_Curr_CornerMtr_mutex.unlock(); 00344 LINFO("Got a CornerMotorRequest Trans %f, Rot %f", 00345 itsCornerTransSpeed, itsCornerRotSpeed); 00346 } 00347 } 00348 00349 // ###################################################################### 00350 void GistSal_Navigation::evolve() 00351 { 00352 // check if the current image is updated 00353 its_input_mutex.lock(); 00354 bool newInputFlag = (itsPrevProcImgID < itsCurrImgID); 00355 its_input_mutex.unlock(); 00356 00357 // if so, process 00358 if(newInputFlag) 00359 { 00360 itsTimer.reset(); 00361 00362 its_tracker_mutex.lock(); 00363 its_Curr_Dbr_mutex.lock(); 00364 its_Curr_CornerMtr_mutex.lock(); 00365 00366 its_input_mutex.lock(); 00367 itsProcImg = itsCurrImg; 00368 itsPrevProcImgID = itsCurrImgID; 00369 std::vector<Point2D<int> > salPoints = itsCurrSalPoints; 00370 ImageSet<float> cmap = itsCurrCmap; 00371 std::vector<Rectangle> rects = itsCurrSalRects; 00372 its_input_mutex.unlock(); 00373 00374 // feed conspicuity maps to the trackers 00375 LINFO("\n search tracker: reset: %d ", itsResetSearchTracker); 00376 // FIX !!! 00377 std::vector<rutz::shared_ptr<VisualObject> > fakeVO; 00378 itsSearchTracker->input 00379 (itsProcImg, cmap, itsResetSearchTracker, 00380 salPoints, rects, fakeVO); 00381 LINFO("\n movement tracker"); 00382 itsCurrMovementTracker->input 00383 (itsProcImg, cmap, false, 00384 salPoints, rects, fakeVO); 00385 itsNewMovementTracker->input 00386 (itsProcImg, cmap, false, 00387 salPoints, rects, fakeVO); 00388 00389 // so that we only reset once per request 00390 itsResetSearchTracker = false; 00391 00392 // FOR MOVEMENT 00393 00394 // check for nearness to junctions/intersections 00395 bool nearJunction = estimateProgressToDestination(); 00396 00397 // NOTE: BIAS IGNORED 00398 00399 // if near junction && not in corner procedure 00400 if(nearJunction && 00401 itsCornerProcStatus == C_PROCEDURE_INACTIVE) 00402 { 00403 // start the turning procedure 00404 // send out which corner to cue from 00405 BeobotEvents::CornerLocationMessagePtr msg = 00406 new BeobotEvents::CornerLocationMessage; 00407 msg->cornerLocation = itsCurrentSegmentLocation; 00408 this->publish("CornerLocationMessageTopic", msg); 00409 LINFO("Publishing corner location %d", msg->cornerLocation); 00410 } 00411 00412 // if in the middle of corner procedure 00413 if(itsCornerProcStatus == C_PROCEDURE_ACTIVE) 00414 { 00415 itsTransSpeed = itsCornerTransSpeed; 00416 itsRotSpeed = itsCornerRotSpeed; 00417 00418 itsLastFoundTimer.reset(); 00419 } 00420 00421 if(itsCornerProcStatus == C_PROCEDURE_CONCLUDING || 00422 itsCornerProcStatus == C_PROCEDURE_INACTIVE ) 00423 { 00424 // get the recognized salient regions 00425 uint64 kpst = itsTimer.get(); 00426 bool foundSomething = (getKeypointMatches() != 0); 00427 //if(foundSomething) itsLastFoundTimer.reset(); 00428 00429 LINFO("[KP time: %f]\n\n", (itsTimer.get() - kpst)/1000.0f); 00430 if(itsCurrLandmarkDBMatches.size() != 0) 00431 { 00432 // extract the error difference 00433 computeErrorDifference(); 00434 00435 // last found timer can be reset 00436 itsLastFoundTimer.reset(); 00437 itsReverseCount = 0; 00438 //itsReverseDir = false; 00439 00440 itsCornerProcStatus = C_PROCEDURE_INACTIVE; 00441 } 00442 else 00443 { 00444 if(itsCornerProcStatus == C_PROCEDURE_CONCLUDING) 00445 { 00446 itsTransSpeed = itsCornerTransSpeed; 00447 itsRotSpeed = itsCornerRotSpeed; 00448 00449 itsLastFoundTimer.reset(); 00450 } 00451 else 00452 { 00453 // slow down - hope we find new regions 00454 itsTransSpeed = 0.6; 00455 00456 //if(itsLastFoundTimer.get() > ROT_TIME_LIMIT 00457 // && !itsReverseDir) 00458 // { 00459 // itsRotSpeed = -1.0 * itsRotSpeed; 00460 // itsReverseDir = true; 00461 // } 00462 //itsDistToGoal = -1.0; 00463 } 00464 } 00465 00466 // if we have not found anything for some time 00467 if(itsLastFoundTimer.get() > LOST_TIME_LIMIT) 00468 recover(foundSomething); 00469 } 00470 00471 // use the error difference to drive 00472 navigation(); 00473 00474 its_Curr_CornerMtr_mutex.unlock(); 00475 its_Curr_Dbr_mutex.unlock(); 00476 its_tracker_mutex.unlock(); 00477 00478 // draw the state of the surroundings 00479 drawState(); 00480 itsOfs->writeRGB(itsDispImg, " ><\" GistSal_nav", 00481 FrameInfo("GistSal_nav",SRC_POS)); 00482 00483 LINFO("[time: %f]\n\n\n\n", itsTimer.get()/1000.0f); 00484 } 00485 } 00486 00487 // ###################################################################### 00488 // computing visual error difference 00489 uint GistSal_Navigation::getKeypointMatches() 00490 { 00491 Timer tim(1000000); 00492 uint nCount, cCount; 00493 00494 // check if there are new DBmatches 00495 //uint oldNcount = itsNewLandmarkDBMatches.size(); 00496 //uint oldCcount = itsCurrLandmarkDBMatches.size(); 00497 00498 // check if the new regions can be projected forward 00499 nCount = projectForward(itsNewLandmarkDBMatches, 00500 itsNewMovementTracker); 00501 if(nCount == 0) 00502 { 00503 // No 00504 00505 // stick with the old ones 00506 cCount = projectForward(itsCurrLandmarkDBMatches, 00507 itsCurrMovementTracker); 00508 } 00509 else 00510 { 00511 // project forward succeed 00512 // move new regions to become current ones 00513 itsCurrLandmarkDBMatches = itsNewLandmarkDBMatches; 00514 00515 itsCurrMovementTracker->clear(); 00516 itsCurrMovementTracker->reset(nCount); 00517 itsCurrMovementTracker->setCurrInputImage 00518 (itsNewMovementTracker->getCurrInputImage()); 00519 itsCurrMovementTracker->setOriginalInputImage 00520 (itsNewMovementTracker->getOriginalInputImage()); 00521 for(uint i = 0; i < nCount; i++) 00522 itsCurrMovementTracker->move(itsNewMovementTracker, i); 00523 } 00524 cCount = itsCurrLandmarkDBMatches.size(); 00525 LINFO("\n\n\n Project forward: %d time: %f\n\n\n", 00526 cCount, tim.get()/1000.0f); 00527 00528 // DON'T STOP ON THE WRONG SCALE 00529 00530 // check if there are new DBmatches 00531 uint count = itsNewLandmarkDBMatches.size(); 00532 00533 00534 // if not keep the old DBmatches 00535 if (count == 0) 00536 { 00537 count = itsCurrLandmarkDBMatches.size(); 00538 // for(uint i = 0; i < itsCurrLandmarkDBMatches.size(); i++) 00539 // { 00540 00541 // int index = itsLandmarkDB->getLandmark 00542 // (itsCurrLandmarkDBMatches[i]->dbi.segNum, 00543 // itsCurrLandmarkDBMatches[i]->dbi.lmkNum)->match 00544 // (vo, cmatch, 00545 // itsCurrLandmarkDBMatches[i]->dbi.voStartNum, 00546 // -1, 10.0F, .75F, 2.5F, 4, M_PI/4, 1.25F); 00547 00548 00549 // SIFTaffine aff = 00550 // (itsCurrLandmarkDBMatches[i])->matchRes->getSIFTaffine(); 00551 // float theta, sx, sy, str; 00552 // aff.decompose(theta, sx, sy, str); 00553 // if(sx > 1.25F || sx < 0.8F) 00554 // if(count > 0) count--; 00555 // } 00556 } 00557 00558 // int index = itsLandmarkDB->getLandmark 00559 // (landmarkDBMatches[i]->dbi.segNum, 00560 // landmarkDBMatches[i]->dbi.lmkNum)->match 00561 // (vo, cmatch, landmarkDBMatches[i]->dbi.voStartNum, 00562 // -1, 10.0F, .75F, 2.5F, 4, M_PI/4, 1.25F); 00563 00564 00565 00566 00567 00568 return count; 00569 } 00570 00571 // ###################################################################### 00572 uint GistSal_Navigation::projectForward 00573 (std::vector<rutz::shared_ptr<LandmarkDBMatch> > &landmarkDBMatches, 00574 nub::soft_ref<SalientRegionTracker> tracker) 00575 { 00576 // get the found database matches 00577 std::vector<rutz::shared_ptr<LandmarkDBMatch> > tempLandmarkDBMatches; 00578 00579 LINFO("start: %"ZU, landmarkDBMatches.size()); 00580 00581 for(uint i = 0; i < landmarkDBMatches.size(); i++) 00582 { 00583 // get the tracked salient region rectangle 00584 Rectangle rect = tracker->getCurrTrackedROI(i); 00585 00586 // create a Visual Object 00587 Point2D<int> offset= rect.topLeft(); 00588 Image<PixRGB<byte> > objImg = crop(itsCurrImg, rect); 00589 std::string iName("iName"); 00590 std::string ifName = iName + std::string(".png"); 00591 rutz::shared_ptr<VisualObject> 00592 vo(new VisualObject(iName, ifName, objImg)); 00593 //vo(new VisualObject(iName, ifName, objImg, salpt - offset)); 00594 00595 // project forward with the database found 00596 LINFO("DB Match sent: %d: (%d %d - %d %d)", 00597 landmarkDBMatches[i]->dbi.objNum, 00598 landmarkDBMatches[i]->dbi.segNum, 00599 landmarkDBMatches[i]->dbi.lmkNum, 00600 landmarkDBMatches[i]->dbi.voStartNum, 00601 landmarkDBMatches[i]->dbi.voEndNum); 00602 00603 rutz::shared_ptr<VisualObjectMatch> cmatch; 00604 int index = itsLandmarkDB->getLandmark 00605 (landmarkDBMatches[i]->dbi.segNum, 00606 landmarkDBMatches[i]->dbi.lmkNum)->match 00607 (vo, cmatch, landmarkDBMatches[i]->dbi.voStartNum, 00608 -1, 10.0F, .75F, 2.5F, 4, M_PI/4, 1.25F); 00609 landmarkDBMatches[i]->dbi.voStartNum = index; 00610 landmarkDBMatches[i]->dbi.voEndNum = index; 00611 00612 // set successful projected forward landmarks 00613 if(index != -1) 00614 { 00615 LINFO("\nProject Forward succeed: %d\n\n", index); 00616 00617 landmarkDBMatches[i]->vo = vo; 00618 00619 // need to store this to release mutex during drawState() 00620 Point2D<int> pt = 00621 itsLandmarkDB->getLandmark 00622 (landmarkDBMatches[i]->dbi.segNum, 00623 landmarkDBMatches[i]->dbi.lmkNum) 00624 ->getOffsetCoords 00625 (landmarkDBMatches[i]->dbi.voStartNum); 00626 00627 landmarkDBMatches[i]->voOffset = offset; 00628 landmarkDBMatches[i]->dbOffset = pt; 00629 landmarkDBMatches[i]->matchRes = cmatch; 00630 00631 tempLandmarkDBMatches.push_back(landmarkDBMatches[i]); 00632 00633 LINFO("project success %d", i); 00634 } 00635 else 00636 LINFO("\nProject Forward fail: %d\n\n", index); 00637 } 00638 00639 // only switch when there is a new 00640 //if(tempFoundDBmatches.size() != 0) 00641 landmarkDBMatches = tempLandmarkDBMatches; 00642 00643 return uint(landmarkDBMatches.size()); 00644 } 00645 00646 // ###################################################################### 00647 // computing visual error difference 00648 void GistSal_Navigation::computeErrorDifference() 00649 { 00650 // boundary condition 00651 //if((teachID-5) < TEACH_START_NUMBER) teachID = TEACH_START_NUMBER+2; 00652 00653 // looking 10 frame ahead to find best match frame 00654 //for(int id = teachID-2; id < teachID+10; id++) 00655 // ========================= 00656 // find the best Keyframe 00657 // float theta = 2.0, sx = 2.0, sy = 2.0, str = 0; 00658 // match->getSIFTaffine().decompose(theta, sx, sy, str); 00659 00660 int dirF = 0,dirL = 0,dirR = 0; 00661 00662 uint w = itsProcImg.getWidth(); 00663 uint h = itsProcImg.getHeight(); 00664 00665 // mean and standard error 00666 //double stdx = 0.0, stdy = 0.0; 00667 double avgdx = 0.0, avgdy = 0.0; 00668 00669 uint nLDBMatch = itsCurrLandmarkDBMatches.size(); 00670 uint totalMatchSize = 0; 00671 00672 for(uint i = 0; i < nLDBMatch; i++) 00673 totalMatchSize += itsCurrLandmarkDBMatches[i]->matchRes->size(); 00674 00675 // go through each identified landmark 00676 for(uint i = 0; i < nLDBMatch; i++) 00677 { 00678 int dirGain = 1; 00679 00680 rutz::shared_ptr<LandmarkDBMatch> ldbm = 00681 itsCurrLandmarkDBMatches[i]; 00682 rutz::shared_ptr<VisualObjectMatch> matchRes = 00683 ldbm->matchRes; 00684 00685 Point2D<int> obOffset = ldbm->voOffset; 00686 Point2D<int> dbOffset = ldbm->dbOffset; 00687 00688 bool isODmatch = 00689 (ldbm->vo == matchRes->getVoRef()); 00690 00691 // compute the direction and find mean of error 00692 for (uint j = 0; j < matchRes->size(); j++) 00693 { 00694 rutz::shared_ptr<Keypoint> obkp; // input visual object 00695 rutz::shared_ptr<Keypoint> dbkp; // database visual object 00696 if(isODmatch) 00697 { 00698 obkp = matchRes->getKeypointMatch(j).refkp; 00699 dbkp = matchRes->getKeypointMatch(j).tstkp; 00700 } 00701 else 00702 { 00703 obkp = matchRes->getKeypointMatch(j).tstkp; 00704 dbkp = matchRes->getKeypointMatch(j).refkp; 00705 } 00706 00707 // to shift origin to the middle of the image 00708 double ud = dbkp->getX() + dbOffset.i - w/2; 00709 double ut = obkp->getX() + obOffset.i - w/2; 00710 00711 double vd = dbkp->getY() + dbOffset.j - h/2; 00712 double vt = obkp->getY() + obOffset.j - h/2; 00713 00714 double dx = fabs(ut - ud); 00715 double dy = fabs(vt - vd); 00716 00717 // calculate standard deviation and average 00718 //double stdx = 0.0; 00719 //double stdy = 0.0; 00720 avgdx += dx; 00721 avgdy += dy; 00722 00723 // calculate direction 00724 if(ut > 0.0 && ud < 0.0) 00725 { 00726 dirR += dirGain; 00727 } 00728 else if(ut < 0.0 && ud > 0.0) 00729 { 00730 dirL += dirGain; 00731 } 00732 else if(ut > 0.0 && ut > ud) 00733 { 00734 dirR += dirGain; 00735 } 00736 else if(ut < 0.0 && ut < ud) 00737 { 00738 dirL += dirGain; 00739 } 00740 else 00741 { 00742 dirF +=dirGain; 00743 } 00744 } 00745 } 00746 LINFO("dirL: %4d dirF: %4d dirR: %4d", dirL, dirF, dirR); 00747 avgdx /= totalMatchSize; 00748 avgdy /= totalMatchSize; 00749 LINFO("avgdx %f avgdy %f", avgdx, avgdy); 00750 00751 // calculating standard deviation 00752 // in the loop above-> stdx += dx * dx; 00753 // after loop -> stdx -= (avgdx*avgdx); 00754 // stdx /= bestMatchSize; 00755 // stdx = sqrt(stdx); 00756 00757 itsXError = avgdx; 00758 itsYError = avgdy; 00759 itsError = sqrt(avgdx*avgdx + avgdy*avgdy); 00760 00761 // compute the forward speed based on scale 00762 // if it's much smaller than the one on DB: drive fast 00763 // bestMatch->getSIFTaffine().decompose(theta, sx, sy, str); 00764 // 00765 // //if the size difference is large(sx/sy << 1.0),we can speed up 00766 // double rms = 1.0 -(sqrt(sx*sx+sy*sy)/sqrt(2)); 00767 00768 // double gain = 1.0; 00769 // if(rms > 0.0) 00770 // itsTransSpeed = 0.8 + rms * gain; 00771 // if(itsTransSpeed > 1.0)itsTransSpeed = 1.0; 00772 // if(itsTransSpeed < 0.0)itsTransSpeed = 0.0; 00773 00774 // just use the same speed, for now 00775 itsTransSpeed = 0.8; 00776 00777 // compute turning speed 00778 if(dirF >= abs(dirR + dirL)) 00779 { itsRotSpeed = 0.0; itsDir = 0; } 00780 else if(dirR > dirL) 00781 { 00782 //itsXError -= 55.0; 00783 //if(itsXError < 0.0) itsXError = 0; 00784 00785 if(itsXError > 50.0) 00786 itsRotSpeed = -0.40; 00787 else 00788 itsRotSpeed = -0.20 + itsXError/50.0 * -0.20; 00789 00790 itsDir = 1.0; 00791 } 00792 else 00793 { 00794 //itsXError += 25.0; 00795 //if(itsXError < 0.0) itsXError = 0; 00796 00797 if(itsXError > 50.0) 00798 itsRotSpeed = 0.60; 00799 else 00800 itsRotSpeed = 0.30 + itsXError/50.0 * 0.30; 00801 00802 itsDir = -1.0; 00803 } 00804 00805 LINFO("Speed: [trans: %7.3f, rot: %7.3f] Error: %f", 00806 itsTransSpeed, itsRotSpeed, itsError); 00807 } 00808 00809 // ###################################################################### 00810 bool GistSal_Navigation::estimateProgressToDestination() 00811 { 00812 bool nearJunction = false; 00813 00814 its_Curr_Loc_mutex.lock(); 00815 uint currSegNum = itsCurrentSegmentLocation; 00816 float currLenTrav = itsCurrentSegmentLengthTraveled; 00817 uint desSegNum = itsDesiredSegmentLocation; 00818 float desLenTrav = itsDesiredSegmentLengthTraveled; 00819 its_Curr_Loc_mutex.unlock(); 00820 00821 // check the moves for the robot to go to the goal location 00822 float dist = 00823 itsTopologicalMap->getPath 00824 (currSegNum, currLenTrav, desSegNum, desLenTrav, itsMoves); 00825 // real distance in ft. 00826 dist *= itsTopologicalMap->getMapScale(); 00827 itsDistToGoal = dist; 00828 00829 // have a turn around command (if necessary) as well 00830 uint msize = itsMoves.size(); 00831 for(uint i = 0; i < msize; i++) 00832 LINFO("Moves[%d]: %d", i, itsMoves[i]); 00833 00834 itsJunctionDist = INVALID_DISTANCE; 00835 00836 // if we are at goal location 00837 if(dist < 3.5 || msize == 0) 00838 { 00839 // tell the robot to stop 00840 itsInGoal = true; 00841 itsTransSpeed = 0.0; 00842 itsRotSpeed = 0.0; 00843 return false; 00844 } 00845 else itsInGoal = false; 00846 00847 // we actually only need to look one step ahead 00848 if(itsMoves[0] == TOPOMAP_TURN_AROUND) 00849 { 00850 // turn around 00851 itsTurnAngle = M_PI; 00852 LINFO("1 Move: TURN AROUND"); 00853 00854 // this is probably a blocking open-loop operation 00855 00856 // but also need to be careful about 00857 // not overcommitting too quickly 00858 00859 } 00860 else if(itsMoves[0] == TOPOMAP_MOVE_FORWARD) 00861 { 00862 // if the goal is in the current and the next edge 00863 if(msize == 1) 00864 { 00865 LINFO("1 Move: almost there just MOVE FORWARD"); 00866 00867 // calculate speed according to distance left 00868 double speed = 0.8; 00869 if(dist < 10.0 && dist > 0.0) speed *= dist/10.0; 00870 itsTransSpeed = speed; 00871 00872 itsTurnAngle = 0.0; 00873 itsJunctionDist = -1111.0; 00874 } 00875 // else we need to figure out how far the next node is 00876 else 00877 { 00878 // get the current edge 00879 float ds, de; 00880 rutz::shared_ptr<Edge> e1 = 00881 itsTopologicalMap->getEdge(currSegNum, currLenTrav, ds, de); 00882 00883 // distance to junction in ft. 00884 float jdist = de * itsTopologicalMap->getMapScale(); 00885 itsJunctionDist = jdist; 00886 LINFO("JDIST: %f", jdist ); 00887 00888 // if we are far from the junction 00889 if(jdist > JUNCTION_START_DIST) 00890 { 00891 // move with speed according to how far from junction 00892 // calculate speed according to distance left 00893 //float speed = 0.8; 00894 //if(jdist > 5.0 && jdist > 0.0) 00895 // speed = speed - .6*(5.0 - jdist)/5.0; 00896 // itsTransSpeed = speed; 00897 00898 itsTurnAngle = 0.0; 00899 } 00900 else 00901 { 00902 // get the next edge 00903 rutz::shared_ptr<Edge> e2 = 00904 itsTopologicalMap->getEdge(itsMoves[1]); 00905 00906 // calculate the degree amount for the turn as well 00907 float angle = itsTopologicalMap->getAngle(e1,e2); 00908 float dangle = angle*180.0/M_PI; 00909 LINFO("ANGLE: %f or %f", angle, dangle); 00910 00911 itsTurnAngle = angle; 00912 double addRot = 0.0; 00913 double fdangle = fabs(dangle); 00914 00915 // don't add rotation if the angle is less than 30 degrees 00916 // keep going with the visual feedback 00917 if(fdangle < 30.0) 00918 addRot = 0.0; 00919 // else if within 30 to 150 (to the right) 00920 else if(dangle > 30.0 && dangle < 150.0) 00921 addRot = -0.3; 00922 // else if going hard right 00923 else if(dangle >= 150.0) 00924 addRot = -0.6; 00925 // else if within -30 to -150 (to the left) 00926 else if(dangle < -30.0 && dangle > -150.0) 00927 addRot = 0.3; 00928 // else if going hard left 00929 else if(dangle <= -150.0) 00930 addRot = 0.6; 00931 00932 LINFO("\n\n\naddRot: %f dangle: %f\n\n\n\n", addRot, dangle); 00933 00934 // IMPORTANT: need more visual feedback here!!! 00935 // check with the inputs that are matched 00936 // see if they are from the next segment 00937 00938 // MOTOR COMMAND 00939 itsTransSpeed = 0.8; 00940 itsRotSpeed += addRot; 00941 00942 nearJunction = true; 00943 } 00944 } 00945 } 00946 00947 return nearJunction; 00948 00949 // QUESTION: how do we infuse this with other motor command 00950 00951 // train gist for distance to junction 00952 // -> recognizing intersection 00953 00954 // NOTE: put all these information to the display 00955 } 00956 00957 // ###################################################################### 00958 // The main function for recovery 00959 void GistSal_Navigation::recover(bool foundSomething) 00960 { 00961 itsTurnAngle = INVALID_ANGLE; 00962 itsJunctionDist = INVALID_DISTANCE; 00963 00964 // pan around 00965 itsTransSpeed = 0.0; 00966 00967 //uint64 time = itsLastFoundTimer.get(); 00968 //if(((time/8000000)%2) == 1) 00969 //itsRotSpeed = -0.45; 00970 //else 00971 00972 if(foundSomething && itsReverseCount != 0) 00973 { 00974 itsRotSpeed = 0.0; 00975 if(itsReverseCount > 0) itsReverseCount--; 00976 } 00977 else if(foundSomething || itsReverseCount > 0) 00978 { 00979 itsRotSpeed = -0.6; 00980 00981 itsReverseCount++; 00982 if(itsReverseCount > REVERSE_COUNT_MAX) 00983 itsReverseCount = 0; 00984 } 00985 else 00986 itsRotSpeed = 0.6; // spin LEFT 00987 } 00988 00989 // ###################################################################### 00990 // The main function of navigation 00991 void GistSal_Navigation::navigation() 00992 { 00993 // update motor command 00994 //updateMotorPID(itsTransSpeed, itsRotSpeed); 00995 00996 // FIX !!!!!!!!!!!!!!!!!! BYPASS FOR NOW !!!!!!!!!!!!!!!!!!!!!!!! 00997 //updateMotor(itsCornerTransSpeed, itsCornerRotSpeed); 00998 updateMotor(itsTransSpeed, itsRotSpeed); 00999 } 01000 01001 // ###################################################################### 01002 void GistSal_Navigation::drawState() 01003 { 01004 uint w = itsProcImg.getWidth(); 01005 uint h = itsProcImg.getHeight(); 01006 01007 itsDispImg.resize(5*w, 3*h, NO_INIT); 01008 01009 for(uint i = 0; i < itsCurrLandmarkDBMatches.size(); i++) 01010 { 01011 //Image< PixRGB<byte> > mImg = itsMatchRes[i]->getMatchImage(1.0F); 01012 //inplacePaste(itsDispImg, mImg, Point2D<int>(w*i, 0)); 01013 01014 rutz::shared_ptr<LandmarkDBMatch> ldbm = 01015 itsCurrLandmarkDBMatches[i]; 01016 01017 rutz::shared_ptr<VisualObjectMatch> matchRes = 01018 ldbm->matchRes; 01019 01020 Dims d(w,h); 01021 Point2D<int> objOffset1 = ldbm->voOffset; 01022 Point2D<int> objOffset2 = ldbm->dbOffset; 01023 01024 bool isODmatch = (ldbm->vo == matchRes->getVoRef()); 01025 bool isDOmatch = (ldbm->vo == matchRes->getVoTest()); 01026 01027 Image< PixRGB<byte> > result; 01028 if(isODmatch) 01029 { 01030 // LINFO("[%d %d] O[%d %d wh: %d %d] D[%d %d wh: %d %d]", 01031 // d.w(), d.h(), 01032 // objOffset1.i, objOffset1.j, 01033 // itsMatchRes[i]->getVoRef()->getImage().getWidth(), 01034 // itsMatchRes[i]->getVoRef()->getImage().getHeight(), 01035 // objOffset2.i, objOffset2.j, 01036 // itsMatchRes[i]->getVoTest()->getImage().getWidth(), 01037 // itsMatchRes[i]->getVoTest()->getImage().getHeight() ); 01038 01039 result = matchRes->getMatchImage(d, objOffset1, objOffset2); 01040 } 01041 else if(isDOmatch) 01042 { 01043 // LINFO("[%d %d]2 O[%d %d wh: %d %d] D[%d %d wh: %d %d]", 01044 // d.w(), d.h(), 01045 // objOffset1.i, objOffset1.j, 01046 // itsMatchRes[i]->getVoTest()->getImage().getWidth(), 01047 // itsMatchRes[i]->getVoTest()->getImage().getHeight(), 01048 // objOffset2.i, objOffset2.j, 01049 // itsMatchRes[i]->getVoRef()->getImage().getWidth(), 01050 // itsMatchRes[i]->getVoRef()->getImage().getHeight() ); 01051 01052 result = matchRes->getMatchImage(d, objOffset2, objOffset1); 01053 } 01054 else{LFATAL("boom. Object neither ref nor tst."); } 01055 01056 std::string ntext(sformat("[%d]", i)); 01057 if(isODmatch) ntext += std::string("OD"); 01058 else ntext += std::string("OD"); 01059 inplacePaste(itsDispImg, result, Point2D<int>(i*w,0)); 01060 writeText(itsDispImg, Point2D<int>(w*i,0), ntext.c_str()); 01061 01062 SIFTaffine aff = matchRes->getSIFTaffine(); 01063 float theta, sx, sy, str; 01064 aff.decompose(theta, sx, sy, str); 01065 std::string ntext2(sformat("[%6.3f, %6.3f", sx, sy)); 01066 writeText(itsDispImg, Point2D<int>(w*i,20), ntext2.c_str()); 01067 } 01068 01069 inplacePaste(itsDispImg, drawInfoImg(), Point2D<int>(0, 2*h)); 01070 } 01071 01072 // ###################################################################### 01073 // display current heading information 01074 Image<PixRGB<byte> > GistSal_Navigation::drawInfoImg() 01075 { 01076 uint w = itsProcImg.getWidth(); 01077 uint h = itsProcImg.getHeight(); 01078 01079 Image<PixRGB<byte> > dirImg(5*w, h,ZEROS); 01080 01081 std::string text; 01082 if(itsDir == 0) { text = sformat("Dir: ||| "); } 01083 else if(itsDir > 0) { text = sformat("Dir: --> "); } 01084 else { text = sformat("Dir: <-- "); } 01085 01086 uint totalMatchSize = 0; 01087 uint nLDBMatch = itsCurrLandmarkDBMatches.size(); 01088 for(uint i = 0; i < nLDBMatch; i++) 01089 totalMatchSize += itsCurrLandmarkDBMatches[i]->matchRes->size(); 01090 text += sformat("#kp[%4d]", totalMatchSize); 01091 01092 text += sformat("lDiff:[%8.3f] X:[%8.3f] Y:[%8.3f] ", 01093 itsError, itsXError, itsYError); 01094 01095 text += sformat("Trans[%8.3f] Rot[%8.3f]", 01096 itsTransSpeed, itsRotSpeed); 01097 01098 writeText(dirImg, Point2D<int>(0,0), text.c_str(), 01099 PixRGB<byte>(255,255,255), PixRGB<byte>(0,0,0), 01100 SimpleFont::FIXED(8)); 01101 01102 std::string text2; 01103 its_Curr_Loc_mutex.lock(); 01104 uint currSegNum = itsCurrentSegmentLocation; 01105 float currLenTrav = itsCurrentSegmentLengthTraveled; 01106 uint desSegNum = itsDesiredSegmentLocation; 01107 float desLenTrav = itsDesiredSegmentLengthTraveled; 01108 its_Curr_Loc_mutex.unlock(); 01109 01110 text2 += sformat("C[%3d, %6.3f] -> ", currSegNum, currLenTrav); 01111 text2 += sformat("D[%3d, %6.3f]: ", desSegNum, desLenTrav); 01112 text2 += sformat("dist:[%8.3f]: ", itsDistToGoal); 01113 text2 += sformat("M:[%"ZU"]: [", itsMoves.size()); 01114 for(uint i = 0; i < itsMoves.size(); i++) 01115 text2 += sformat(" %d", itsMoves[i]); 01116 01117 if(itsTurnAngle == INVALID_ANGLE) 01118 text2 += sformat("] tAng: \">< "); 01119 else 01120 text2 += sformat("] tAng: %7.3f ", itsTurnAngle*180.0/M_PI); 01121 text2 += sformat("jDist: %8.3f", itsJunctionDist); 01122 01123 writeText(dirImg, Point2D<int>(0,20), text2.c_str(), 01124 PixRGB<byte>(255,255,255), PixRGB<byte>(0,0,0), 01125 SimpleFont::FIXED(8)); 01126 01127 std::string text3; 01128 if(itsInGoal) text3 += sformat("IN GOAL "); 01129 else text3 += sformat("NOT IN GOAL "); 01130 uint64 tim = itsLastFoundTimer.get(); 01131 float lftime = tim/1000.0; 01132 text3 += sformat("%f ", lftime); 01133 if(tim > LOST_TIME_LIMIT) 01134 text3 += sformat("- OVER LOST TIME LIMIT. "); 01135 text3 += sformat("STATUS: %3d", itsCornerProcStatus); 01136 01137 writeText(dirImg, Point2D<int>(0,40), text3.c_str(), 01138 PixRGB<byte>(255,255,255), PixRGB<byte>(0,0,0), 01139 SimpleFont::FIXED(8)); 01140 01141 return dirImg; 01142 } 01143 01144 // ###################################################################### 01145 void GistSal_Navigation::updateMotorPID(double tran, double rot,double error) 01146 { 01147 error/=100.0; 01148 if(itsDir == 1) 01149 error *= -1.0; 01150 double pTerm,iTerm,dTerm; 01151 pTerm = itsPGain * error; 01152 itsIState += error; 01153 if(itsIState > IMAX) 01154 itsIState = IMAX; 01155 else if(itsIState < IMIN) 01156 itsIState = IMIN; 01157 iTerm = itsIGain * itsIState; 01158 dTerm = itsDGain * (rot - itsDState); 01159 itsDState = rot; 01160 double pid = pTerm + iTerm - dTerm; 01161 01162 LINFO("P[%1.2f] I[%1.2f] D[%1.2f], Istate[%1.2f] DState[%1.2f]", 01163 pTerm,iTerm,dTerm,itsIState,itsDState); 01164 LINFO("pid[%1.2f],rot[%1.2f]",pid,rot); 01165 updateMotor(tran,pid); 01166 } 01167 01168 // ###################################################################### 01169 void GistSal_Navigation::updateMotor(double tran, double rot) 01170 { 01171 BeobotEvents::MotorRequestPtr msg = new BeobotEvents::MotorRequest; 01172 msg->transVel = tran; 01173 msg->rotVel = rot; 01174 this->publish("MotorRequestTopic", msg); 01175 LINFO("[%d] Publish motor request Trans %f Rotation %f", 01176 itsPrevProcImgID,tran,rot); 01177 } 01178 01179 // ###################################################################### 01180 /* So things look consistent in everyone's emacs... */ 01181 /* Local Variables: */ 01182 /* indent-tabs-mode: nil */ 01183 /* End: */