00001 // File: HawkSlammer.C 00002 // Author: Josh Villbrandt <josh.villbrandt@usc.edu> 00003 // Date: April 2010 00004 00005 #include "Robots/BeoHawk/computer/HawkSlammer.H" 00006 00007 // ###################################################################### 00008 HawkSlammer::HawkSlammer(std::string myName, int argc, char* argv[]) 00009 : HawkAgent(myName, argc, argv), 00010 window(Dims(320, 240)) { 00011 // Help section 00012 helpTitle = "HawkSlammer"; 00013 helpDescription = "Runs an implementation of DP-SLAM for the BeoHawk."; 00014 helpOptions.push_back("\t--pause\t\t(false) Wait for keyboard input after each iteration."); 00015 helpOptions.push_back("\t--mode\t\t(simulation) Try realTimeDrop, realTimeCombine, or simulation."); 00016 helpOptions.push_back("\t--particles\t(100) Number of particles in the simulation."); 00017 helpOptions.push_back("\t--resolution\t(30) Size of each grid cell in mm."); 00018 helpOptions.push_back("\t--maxLaserDistance\t(6000) Largest distance that the laser scanner can detect."); 00019 00020 // Parameters 00021 pauseEachStep = loadBoolParameter("pause", false); 00022 numberOfParticles = loadIntParameter("particles", 100); 00023 mapResolution = loadIntParameter("resolution", 30); // mm per grid cell 00024 maxLaserDistance = loadIntParameter("maxLaserDistance", 6000); 00025 robotXStdDev = loadDoubleParameter("robotXStdDev", 50); // 50 mm 00026 robotYStdDev = loadDoubleParameter("robotYStdDev", 50); // 50 mm 00027 robotThetaStdDev = loadDoubleParameter("robotThetaStdDev", 0.1); // ~6 deg 00028 scannerVariance = loadDoubleParameter("scannerVariance", 20); // 20 mm 00029 std::string modeIn = loadStringParameter("mode", "simulation"); 00030 if(modeIn == "realTimeDrop") mode = REALTIME_DROP; 00031 else if(modeIn == "realTimeCombine") mode = REALTIME_COMBINE; 00032 else mode = SIMULATION; 00033 00034 // Initialize SLAM 00035 initializeSlam(); 00036 } 00037 00038 // ###################################################################### 00039 void HawkSlammer::registerTopics() { 00040 registerPublisher("SlamDataMessage"); 00041 registerSubscription("SensorDataMessage"); 00042 } 00043 00044 // ###################################################################### 00045 bool HawkSlammer::scheduler() { 00046 if(sensorDataMessages.size() > 0) { 00047 slam(); 00048 return true; 00049 } 00050 00051 return false; 00052 } 00053 00054 // ###################################################################### 00055 void HawkSlammer::catchMessage(const HawkMessages::MessagePtr& hawkMessage, const Ice::Current&) { 00056 if(hawkMessage->ice_isA("::HawkMessages::SensorDataMessage")) 00057 { 00058 // Save message to storage queue 00059 HawkMessages::SensorDataMessagePtr msg = HawkMessages::SensorDataMessagePtr::dynamicCast(hawkMessage); 00060 sensorDataMessages.push_back(msg); 00061 wakeUp(); 00062 } 00063 } 00064 00065 // ###################################################################### 00066 void HawkSlammer::initializeSlam() { 00067 // Initialize grid 00068 int initialPixels = (int)(2 * maxLaserDistance / mapResolution); 00069 occupancyGrid = occupancyMap_t(initialPixels, initialPixels, NO_INIT); 00070 00071 // Initialize tree head 00072 boost::shared_ptr<Particle> head(new Particle()); 00073 head->parent.reset(); 00074 head->robotPose.x = initialPixels/2; 00075 head->robotPose.y = initialPixels/2; 00076 head->robotPose.z = 0; 00077 head->robotPose.theta = 0; 00078 head->probability = 1; 00079 ancestryTree.head = head; 00080 00081 // Initialize tree leaves 00082 ancestryTree.leaves.push_back(head); 00083 } 00084 00085 // ###################################################################### 00086 void HawkSlammer::slam() { 00087 std::cout << "slam(): " << sensorDataMessages.size() << " messages in queue" << std::endl; 00088 00089 // Select correct message 00090 SensorDataMessagePtr msg; 00091 if(mode == REALTIME_COMBINE) { 00092 // create a pseudo message by adding all attemptedMoves 00093 msg = sensorDataMessages.back(); 00094 for(size_t i = 0; i < (sensorDataMessages.size()-1); i++) { 00095 // fill this in 00096 // is not simply x += x if non-zero rotation 00097 } 00098 sensorDataMessages.clear(); 00099 } 00100 else if(mode == REALTIME_DROP) { 00101 // just take the last message received and drop the rest 00102 msg = sensorDataMessages.front(); 00103 sensorDataMessages.clear(); 00104 } 00105 else { 00106 // just take the last message received and save the rest 00107 msg = sensorDataMessages.front(); 00108 sensorDataMessages.erase(sensorDataMessages.begin()); 00109 } 00110 00111 // Algorithm core (SLAM) 00112 updateParticles(msg); 00113 sendSlamDataMessage(); // we can send this immediately and then worry about cleaning up 00114 resampleParticles(); 00115 std::cout << "active particles: " << 1+countDescendants(ancestryTree.head) << std::endl; 00116 if(pauseEachStep) { 00117 std::cout << "Please press enter to continue..."; 00118 std::cin.get(); 00119 std::cout << std::endl; 00120 } 00121 pruneParticles(); 00122 00123 // Debug items 00124 std::cout << "active particles: " << 1+countDescendants(ancestryTree.head) << std::endl; 00125 if(pauseEachStep) { 00126 std::cout << "Please press enter to continue..."; 00127 std::cin.get(); 00128 std::cout << std::endl; 00129 } 00130 } 00131 00132 // ###################################################################### 00133 void HawkSlammer::updateParticles(SensorDataMessagePtr msg) { 00134 std::cout << "updateParticles()" << std::endl; 00135 00136 // Prep 00137 ancestryTree.mostLikely.reset(); 00138 double totalProbability = 0; 00139 00140 // Apply new laser scan to every particle 00141 for(size_t i = 0; i < ancestryTree.leaves.size(); i++) { 00142 std::cout << "Working with leaf #" << i << ". "; 00143 // Add gaussian noise to attempted move to make our particle's move 00144 Pose move = msg->attemptedMove; 00145 move.x = (move.x + gaussian(robotXStdDev)) / mapResolution; 00146 move.y = (move.y + gaussian(robotYStdDev)) / mapResolution; 00147 move.theta += gaussian(robotThetaStdDev); 00148 00149 // Add movement to absolute position while converting from robot frame of reference to world FoR 00150 double worldTheta = cleanAngle(ancestryTree.leaves.at(i)->robotPose.theta + move.theta); 00151 ancestryTree.leaves.at(i)->robotPose.theta = worldTheta; // theta = 0 along x axis 00152 ancestryTree.leaves.at(i)->robotPose.x += move.x*cos(worldTheta) + move.y*sin(worldTheta); 00153 ancestryTree.leaves.at(i)->robotPose.y += move.y*cos(worldTheta) - move.x*sin(worldTheta); 00154 00155 // Apply each laser cast of the laser scan set 00156 int offset = msg->scannerData.size()/2; 00157 std::cout << "Applying laser scan... "; 00158 for(int j = 0; j < (int)msg->scannerData.size(); j++) { 00159 addLaserCast(ancestryTree.leaves.at(i), (j-offset)*msg->angularResolution, msg->scannerData.at(j) / mapResolution); 00160 } 00161 00162 // Quick score (only look around the endpoint of the scan) 00163 00164 // Compute probability of entire laser scan (unnormalized) 00165 double probabilityOfLaserScan = 0; 00166 std::cout << "Scoring laser scan... "; 00167 for(int j = 0; j < (int)msg->scannerData.size(); j++) { 00168 probabilityOfLaserScan += scoreLaserCast(ancestryTree.leaves.at(i), (j-offset)*msg->angularResolution, msg->scannerData.at(j) / mapResolution); 00169 } 00170 00171 // Affect this particle's probability 00172 std::cout << probabilityOfLaserScan << std::endl; 00173 ancestryTree.leaves.at(i)->probability *= probabilityOfLaserScan; // DP-SLAM uses max(, th) here to provide a min-prob threshold 00174 totalProbability += ancestryTree.leaves.at(i)->probability; 00175 00176 // Search for most likely particle 00177 if(!ancestryTree.mostLikely || ancestryTree.leaves.at(i)->probability > ancestryTree.mostLikely->probability) { 00178 ancestryTree.mostLikely = ancestryTree.leaves.at(i); 00179 } 00180 } 00181 00182 // Normalize particles 00183 for(size_t i = 0; i < ancestryTree.leaves.size(); i++) { 00184 if(totalProbability == 0) ancestryTree.leaves.at(i)->probability = 1 / ancestryTree.leaves.size(); 00185 else ancestryTree.leaves.at(i)->probability = ancestryTree.leaves.at(i)->probability / totalProbability; 00186 } 00187 } 00188 00189 // ###################################################################### 00190 double HawkSlammer::randomDouble() { 00191 return double(rand()) / (double(RAND_MAX) + 1.0); 00192 } 00193 00194 // ###################################################################### 00195 double HawkSlammer::gaussian(double stdDev) { 00196 double sum = 0; 00197 for(int i = 0; i < 12; i++) { 00198 sum += randomDouble() * 2 * stdDev - stdDev; 00199 } 00200 return sum / 2; 00201 } 00202 00203 // ###################################################################### 00204 double HawkSlammer::cleanAngle(double angle) { 00205 while(angle > M_PI) angle -= 2*M_PI; 00206 while(angle < -M_PI) angle += 2*M_PI; 00207 return angle; 00208 } 00209 00210 // ###################################################################### 00211 void HawkSlammer::addLaserCast(boost::shared_ptr<Particle> treeLeaf, double scanAngle, double scanDistance) { 00212 if(scanDistance == 0) return; 00213 00214 double startx = treeLeaf->robotPose.x; 00215 double starty = treeLeaf->robotPose.y; 00216 double theta = cleanAngle(treeLeaf->robotPose.theta + scanAngle); 00217 00218 double overflow, slope; // Used for actually tracing the line 00219 int x, y, incX, incY, endx, endy; 00220 int xedge, yedge; // Used in computing the midpoint. Recompensates for which edge of the square the line entered from 00221 double dx, dy; 00222 double distance = scanDistance, error; 00223 00224 // Precompute these for speed 00225 double secant = 1.0/fabs(cos(theta)); 00226 double cosecant = 1.0/fabs(sin(theta)); 00227 00228 // Mark the final endpoint of the line trace, so that we know when to stop. 00229 // We keep the endpoint as both float and int. 00230 dx = (startx + (cos(theta) * distance)); 00231 dy = (starty + (sin(theta) * distance)); 00232 endx = (int) (dx); 00233 endy = (int) (dy); 00234 00235 // Decide which x and y directions the line is travelling. 00236 // inc tells us which way to increment x and y. edge indicates whether we are computing 00237 // distance from the near or far edge. 00238 if (startx > dx) { 00239 incX = -1; 00240 xedge = 1; 00241 } 00242 else { 00243 incX = 1; 00244 xedge = 0; 00245 } 00246 00247 if (starty > dy) { 00248 incY = -1; 00249 yedge = 1; 00250 } 00251 else { 00252 incY = 1; 00253 yedge = 0; 00254 } 00255 00256 // Figure out whether primary motion is in the x or y direction. 00257 // The two sections of code look nearly identical, with x and y reversed. 00258 if (fabs(startx - dx) > fabs(starty - dy)) { 00259 // The given starting point is non-integer. The line therefore starts at some point partially set in to the starting 00260 // square. Overflow starts at this offcenter amount, in order to make steps in the y direction at the right places. 00261 y = (int)(starty); 00262 overflow = starty - y; 00263 00264 // We always use overflow as a decreasing number- therefore positive y motion needs to 00265 // adjust the overflow value accordingly. 00266 if(incY == 1) overflow = 1.0 - overflow; 00267 00268 // Compute the effective slope of the line. 00269 slope = fabs(tan(theta)); 00270 00271 // The first square is a delicate thing, as we aren't doing a full square traversal in 00272 // either direction. So we figure out this strange portion of a step so that we can then 00273 // work off of the axes later. 00274 // NOTE: we aren't computing the probability of this first step. Its a technical issue for 00275 // simplicity, and the odds of the sensor sitting on top of a solid object are sufficiently 00276 // close to zero to ignore this tiny portion of a step. 00277 error = fabs(((int)(startx)+incX+xedge)-startx); 00278 overflow = overflow - (slope*error); 00279 // The first step is actually in the y direction, due to the proximity of starty to the y axis. 00280 if (overflow < 0.0) { 00281 y = y + incY; 00282 overflow = overflow + 1.0; 00283 } 00284 00285 // Now we can start the actual line trace. 00286 for(x = (int)(startx) + incX; x != endx; x = x + incX) { 00287 overflow = overflow - slope; 00288 00289 // Compute the distance travelled in this square 00290 if(overflow < 0.0) distance = (overflow+slope)*cosecant; 00291 else distance = fabs(slope)*cosecant; 00292 00293 // Update every grid square we cross as empty... 00294 addObservation(treeLeaf, x, y, distance, 0); 00295 00296 // ...including the overlap in the minor direction 00297 if(overflow < 0) { 00298 y = y + incY; 00299 distance = -overflow*cosecant; 00300 overflow = overflow + 1.0; 00301 addObservation(treeLeaf, x, y, distance, 0); 00302 } 00303 } 00304 00305 // Update the last grid square seen as having a hit. 00306 // make sure the laser just didn't go the full distance and not hit something... 00307 //if(addEnd) { 00308 if(incX < 0) distance = fabs((x+1) - dx)*secant; 00309 else distance = fabs(dx - x)*secant; 00310 addObservation(treeLeaf, endx, endy, distance, 1); 00311 //} 00312 } 00313 // This is the same as the previous block of code, with x and y reversed. 00314 else { 00315 x = (int)(startx); 00316 overflow = startx - x; 00317 if(incX == 1) overflow = 1.0 - overflow; 00318 slope = 1.0/fabs(tan(theta)); 00319 00320 error = fabs(((int)(starty)+incY+yedge)-starty); 00321 overflow = overflow - (error*slope); 00322 if(overflow < 0.0) { 00323 x = x + incX; 00324 overflow = overflow + 1.0; 00325 } 00326 00327 for(y = (int)(starty) + incY; y != endy; y = y + incY) { 00328 overflow = overflow - slope; 00329 if (overflow < 0) distance = (overflow+slope)*secant; 00330 else distance = fabs(slope)*secant; 00331 00332 addObservation(treeLeaf, x, y, distance, 0); 00333 00334 if(overflow < 0.0) { 00335 x = x + incX; 00336 distance = -overflow*secant; 00337 overflow = overflow + 1.0; 00338 addObservation(treeLeaf, x, y, distance, 0); 00339 } 00340 } 00341 00342 //if(addEnd) { 00343 if(incY < 0) distance = fabs(((y+1) - dy)/sin(theta)); 00344 else distance = fabs((dy - y)/sin(theta)); 00345 00346 addObservation(treeLeaf, endx, endy, distance, 1); 00347 //} 00348 } 00349 } 00350 00351 // ###################################################################### 00352 void HawkSlammer::addObservation(boost::shared_ptr<Particle> treeLeaf, int x, int y, double distanceTraveled, int hit) { 00353 // Make new observation 00354 boost::shared_ptr<Observation> o(new Observation()); 00355 o->source = treeLeaf; 00356 o->x = x; 00357 o->y = y; 00358 00359 // Add empty map if needed 00360 occupancyMapPix_t treeMap; 00361 if(!occupancyGrid.getVal(x, y)) { 00362 treeMap = occupancyMapPix_t( 00363 new std::map<boost::shared_ptr<Particle>, boost::shared_ptr<Observation> >); 00364 occupancyGrid.setVal(x, y, treeMap); 00365 } 00366 else treeMap = occupancyGrid.getVal(x, y); 00367 00368 // Search for an ancestor observation 00369 boost::shared_ptr<Particle> ancestor = treeLeaf->parent; 00370 bool ancestorObservation = false; 00371 std::map< boost::shared_ptr<Particle>, boost::shared_ptr<Observation> >::const_iterator it; 00372 while(!ancestorObservation && ancestor) { 00373 it = treeMap->find(ancestor); 00374 if(it != treeMap->end()) ancestorObservation = true; 00375 else ancestor = ancestor->parent; 00376 } 00377 00378 // Set new observation's parameters accordingly 00379 if(ancestorObservation) { 00380 o->laserTravel = it->second->laserTravel + distanceTraveled; 00381 o->laserTerminations = it->second->laserTerminations + hit; 00382 o->ancestor = it->second; 00383 } 00384 else { 00385 o->laserTravel = distanceTraveled; 00386 o->laserTerminations = hit; 00387 o->ancestor.reset(); 00388 } 00389 00390 // Add new observation to treeLeaf's observation list and the occupancyGrid 00391 treeLeaf->observations.push_back(o); 00392 treeMap->insert( std::pair<boost::shared_ptr<Particle>, boost::shared_ptr<Observation> >(treeLeaf, o) ); 00393 } 00394 00395 // ###################################################################### 00396 double HawkSlammer::scoreLaserCast(boost::shared_ptr<Particle> treeLeaf, double scanAngle, double scanDistance) { 00397 if(scanDistance == 0) return scoreObservation(treeLeaf, treeLeaf->robotPose.x, treeLeaf->robotPose.y, scanDistance); 00398 00399 double startx = treeLeaf->robotPose.x; 00400 double starty = treeLeaf->robotPose.y; 00401 double theta = cleanAngle(treeLeaf->robotPose.theta + scanAngle); 00402 double MeasuredDist = scanDistance; 00403 00404 double overflow, slope; // Used for actually tracing the line 00405 int x, y, incX, incY, endx, endy; 00406 double dx, dy; 00407 double totalProb; // Total probability that the line trace should have stopped before this step in the trace 00408 double eval; // Total raw probability for the observation given this line trace through the map 00409 double prob, distance, error; 00410 double secant, cosecant; // precomputed for speed 00411 double xblock, yblock; 00412 double xMotion, yMotion; 00413 double standardDist; 00414 00415 // eval is the total probability for this line trace. Since this is a summation, eval starts at 0 00416 eval = 0.0; 00417 // totalProb is the total probability that the laser scan could travel this far through the occupancyGrid 00418 // This starts at 1, and decreases as possible objects are passed. 00419 totalProb = 1.0; 00420 // a couple of variables are precomuted for speed. 00421 secant = 1.0/fabs(cos(theta)); 00422 cosecant = 1.0/fabs(sin(theta)); 00423 00424 // If you look at Localize funtion in low.c, you can see that there are two different line traces 00425 // performed. The second trace is the full evaluation of the scan. The first trace is a kind of 00426 // approximate scan, only covering a small section near the percieved endpoint of the scan. The 00427 // first trace is used as a way of culling out obviously bad particles without having to do too 00428 // much on them. For this "culling" trace, we specify directly how much further past the endpoint 00429 // we want to trace. When that culling number is set to zero, we know that it is the full trace 00430 // we are looking at, and we can as far out as 20 grid squares beyond the endpoint (anything further 00431 // has essentially zero probability to add to the scan. 00432 /*if (culling) 00433 distance = MeasuredDist+culling; 00434 else 00435 distance = MIN(MeasuredDist+20.0, MAX_SENSE_RANGE);*/ 00436 distance = MeasuredDist; 00437 00438 // The endpoint of the scan, in both float and int. 00439 dx = (startx + (cos(theta) * distance)); 00440 dy = (starty + (sin(theta) * distance)); 00441 endx = (int) (dx); 00442 endy = (int) (dy); 00443 00444 // Decide which x and y directions the line is travelling. 00445 if(startx > dx) { 00446 incX = -1; 00447 xblock = -startx; 00448 } 00449 else { 00450 incX = 1; 00451 xblock = 1.0-startx; 00452 } 00453 00454 if(starty > dy) { 00455 incY = -1; 00456 yblock = -starty; 00457 } 00458 else { 00459 incY = 1; 00460 yblock = 1.0-starty; 00461 } 00462 00463 // Two copies of the same basic code, swapping the roles of x and y, depending on which one is the primary 00464 // direction of motion in the line trace. 00465 if(fabs(startx - dx) > fabs(starty - dy)) { 00466 y = (int) (starty); 00467 00468 // The given starting point is non-integer. The line therefore starts at some point partially set in to the starting 00469 // square. Overflow starts at this off-center amount, in order to make steps in the y direction at the right places. 00470 overflow = starty - y; 00471 // Code is simpler if overflow is always decreasing towards zero. Note that slope is forced to be postive 00472 if(incY == 1) overflow = 1.0 - overflow; 00473 00474 slope = fabs(tan(theta)); 00475 if(slope > 1.0) slope = fabs((starty - dy) / (startx - dx)); 00476 00477 // The first square is a delicate thing, as we aren't doing a full square traversal in 00478 // either direction. So we figure out this strange portion of a step so that we can then 00479 // work off of the axes later. 00480 // NOTE: we aren't computing the probability of this first step. Its a technical issue for 00481 // simplicity, and the odds of the sensor sitting on top of a solid object are sufficiently 00482 // close to zero to ignore this tiny portion of a step. 00483 dx = fabs((int)(startx)+xblock); 00484 dy = fabs(tan(theta)*dx); 00485 // The first step is actually in the y direction, due to the proximity of starty 00486 // to the y axis. 00487 if (overflow - dy < 0.0) { 00488 y = y + incY; 00489 overflow = overflow - dy + 1.0; 00490 } 00491 // Our first step is in fact in the x direction in this case. Set up for the overflow to 00492 // be our starting offset plus this little extra we travel in the y direction. 00493 else overflow = overflow - dy; 00494 00495 // Most of the scans will be the same length across a grid square, entering and exiting on opposite 00496 // sides. Precompute this amount to save a little time. 00497 standardDist = slope*cosecant; 00498 00499 // These two numbers help determine just how far away the endpoint of the scan is from the current 00500 // position in the scan. xMotion keeps track of the distance from the endpoint of the scan to next 00501 // point where the line trace will cross the x-axis. Since each step in this loop moves us across 00502 // exactly one grid square, this number will change by 1/cosine(theta) (ie secant) each iteration. 00503 // yMotion obviously does the same for the y axis. 00504 xMotion = -fabs(fabs(( ((int) (startx)) +xblock) * secant) - MeasuredDist); 00505 yMotion = -fabs(fabs((y+yblock) * cosecant) - MeasuredDist); 00506 00507 for(x = (int) (startx) + incX; x != endx; x = x + incX) { 00508 // Update our two running counts. 00509 xMotion = xMotion + secant; 00510 overflow = overflow - slope; 00511 00512 // Establish the distance travelled by the laser through this square. Note that this amount is 00513 // less than normal if the slope has overflowed, implying that the y-axis has been crossed. 00514 if (overflow < 0.0) distance = (overflow+slope)*cosecant; 00515 else distance = standardDist; 00516 00517 // Compute the probability of the laser stopping in the square, given the particle's unique map. 00518 // Keep in mind that the probability of even getting this far in the trace is likely less than 1. 00519 prob = totalProb * scoreObservation(treeLeaf, x, y, distance); 00520 if(prob > 0) { 00521 // If the scan had actually been stopped by an object in the map at this square, 00522 // how much error would there be in the laser? Determine which axis will be crossed 00523 // next, and compute from there. (This value is actually kept as a running total now). 00524 if(overflow < 0.0) error = fabs(yMotion); 00525 else error = fabs(xMotion); 00526 00527 // Increase the probability of the scan by the probability of stopping here, multiplied by the 00528 // probability that a scan which stopped here could produce the error observed. 00529 // If the error is too large, the net effect on probability of the scan is essentially zero. 00530 // We can save some time by not computing this exponential. 00531 if(error < 20.0) eval = eval + (prob * exp(-(error*error)/(2*scannerVariance))); 00532 00533 // Correspondingly decrease the probability that laser has continued. 00534 totalProb = totalProb - prob; 00535 } 00536 00537 // If the overflow has dipped below zero, then the trace has crossed the y-axis, and we need to compute 00538 // everything for a single step in the y direction. 00539 if(overflow < 0.0) { 00540 y += incY; 00541 yMotion = yMotion + cosecant; 00542 00543 distance = -overflow*cosecant; 00544 overflow = overflow + 1.0; 00545 00546 prob = totalProb * scoreObservation(treeLeaf, x, y, distance); 00547 if(prob > 0) { 00548 // There is no question about which axis will be the next crossed, since we just crossed the y-axis, 00549 // and x motion is dominant 00550 error = fabs(xMotion); 00551 if(error < 20.0) eval = eval + (prob * exp(-(error*error)/(2*scannerVariance))); 00552 } 00553 totalProb = totalProb - prob; 00554 } 00555 00556 } 00557 } 00558 00559 // ...second verse, same as the first... 00560 // Pretty much a direct copy of the previous block of code, with x and y reversed. 00561 else { 00562 x = (int) (startx); 00563 overflow = startx - x; 00564 if(incX == 1) overflow = 1.0 - overflow; 00565 slope = 1.0/fabs(tan(theta)); 00566 00567 // (See corresponding comments in the previous half of this function) 00568 dy = fabs((int)(starty)+yblock); 00569 dx = fabs(dy/tan(theta)); 00570 if(overflow - dx < 0) { 00571 x = x + incX; 00572 overflow = overflow - dx + 1.0; 00573 } 00574 else overflow = overflow - dx; 00575 00576 standardDist = slope*secant; 00577 xMotion = -fabs(fabs((x+xblock) * secant) - MeasuredDist); 00578 yMotion = -fabs(fabs(( ((int) (starty)) +yblock) * cosecant) - MeasuredDist); 00579 00580 for(y = (int) (starty) + incY; y != endy; y = y + incY) { 00581 yMotion = yMotion + cosecant; 00582 overflow = overflow - slope; 00583 00584 if(overflow < 0.0) distance = (overflow+slope)*secant; 00585 else distance = standardDist; 00586 00587 prob = totalProb * scoreObservation(treeLeaf, x, y, distance); 00588 if (prob > 0) { 00589 if(overflow < 0.0) error = fabs(xMotion); 00590 else error = fabs(yMotion); 00591 if(error < 20.0) eval = eval + (prob * exp(-(error*error)/(2*scannerVariance))); 00592 } 00593 totalProb = totalProb - prob; 00594 00595 if(overflow < 0.0) { 00596 x += incX; 00597 xMotion = xMotion + secant; 00598 00599 distance = -overflow*secant; 00600 overflow = overflow + 1.0; 00601 00602 prob = totalProb * scoreObservation(treeLeaf, x, y, distance); 00603 if(prob > 0) { 00604 error = fabs(yMotion); 00605 if(error < 20.0) eval = eval + (prob * exp(-(error*error)/(2*scannerVariance))); 00606 } 00607 totalProb = totalProb - prob; 00608 } 00609 00610 } 00611 } 00612 00613 // If the laser reported a range beyond the maximum range allowed, any left-over probability that 00614 // the laser has not yet been stopped all has a probability of 1 to get the measured reading. We 00615 // therefore just add this remaining probability to the evaluation. 00616 if(MeasuredDist >= maxLaserDistance) return (eval + totalProb); 00617 00618 // Otherwise, we know that the total probability of the laser being stopped at some point during 00619 // the scan is 1. Normalize the evaluation to enforce this. 00620 if(totalProb == 1) return 0; 00621 return (eval / (1.0 - totalProb)); 00622 } 00623 00624 // ###################################################################### 00625 double HawkSlammer::scoreObservation(boost::shared_ptr<Particle> treeLeaf, int x, int y, double distanceTraveled) { 00626 // This is easy, no particle has observed this, return unkown probability 00627 if(occupancyGrid.getVal(x, y)) { 00628 std::map< boost::shared_ptr<Particle>, boost::shared_ptr<Observation> >::const_iterator it = occupancyGrid.getVal(x, y)->find(treeLeaf); 00629 if(it == occupancyGrid.getVal(x, y)->end()) 00630 return (1.0 - exp(-1.0/(8000 * mapResolution) * distanceTraveled)); 00631 else return (1.0 - exp(-(it->second->laserTerminations / it->second->laserTravel) * distanceTraveled)); 00632 } 00633 else return (1.0 - exp(-1.0/(8000 * mapResolution) * distanceTraveled)); 00634 } 00635 00636 // ###################################################################### 00637 void HawkSlammer::sendSlamDataMessage() { 00638 std::cout << "sendSlamDataMessage()" << std::endl; 00639 00640 // Create Message 00641 HawkMessages::SlamDataMessagePtr msg = new HawkMessages::SlamDataMessage; 00642 msg->hawkPose = ancestryTree.mostLikely->robotPose; 00643 Image< PixRGB<byte> > mapImage = makeMapImage(ancestryTree.mostLikely); 00644 if(ancestryTree.leaves.size() >= 3) { 00645 Image< PixRGB<byte> > topImage = concatX(mapImage, makeMapImage(ancestryTree.leaves.at(0))); 00646 Image< PixRGB<byte> > bottomImage = concatX(makeMapImage(ancestryTree.leaves.at(1)), makeMapImage(ancestryTree.leaves.at(2))); 00647 mapImage = concatY(topImage, bottomImage); 00648 } 00649 00650 // Debug print to screen 00651 std::cout << "\trobotPose: (" << msg->hawkPose.x << ", " << msg->hawkPose.y << ", " << msg->hawkPose.z << ", " << msg->hawkPose.theta << ")" << std::endl; 00652 window.drawImage(mapImage, 0, 0, true); 00653 00654 // Send Message 00655 publish("SlamDataMessage", msg); 00656 } 00657 00658 // ###################################################################### 00659 void HawkSlammer::resampleParticles() { 00660 std::cout << "resampleParticles()" << std::endl; 00661 00662 // Calculate a Cumulative Distribution Function for our particle weights 00663 std::vector<float> CDF; 00664 CDF.resize(ancestryTree.leaves.size()); 00665 CDF.at(0) = ancestryTree.leaves.at(0)->probability; 00666 for(int i = 1; i < (int)CDF.size(); i++) { 00667 CDF.at(i) = CDF.at(i-1) + ancestryTree.leaves.at(i)->probability; 00668 } 00669 00670 // Roulette wheel through particles 00671 std::vector<boost::shared_ptr<Particle> > lonelyLeaves = ancestryTree.leaves; 00672 std::vector<boost::shared_ptr<Particle> > newLeaves; 00673 int i = 0; 00674 float newProb = 1.0/float(numberOfParticles); 00675 float u = randomDouble()* newProb; 00676 for(int j = 0; j < numberOfParticles; j++) { 00677 // Keep the wheel moving 00678 while(i < (int)CDF.size() && u > CDF.at(i)) i++; 00679 u += newProb; 00680 00681 // Make new particle 00682 boost::shared_ptr<Particle> p(new Particle()); 00683 p->parent = ancestryTree.leaves.at(i); 00684 p->robotPose = ancestryTree.leaves.at(i)->robotPose; 00685 p->probability = newProb; 00686 00687 // Tell everyone! 00688 ancestryTree.leaves.at(i)->children.push_back(p); 00689 newLeaves.push_back(p); 00690 00691 // Remove from lonelyLeaves vector 00692 std::vector<boost::shared_ptr<Particle> >::iterator it; 00693 it = std::find(lonelyLeaves.begin(), lonelyLeaves.end(), ancestryTree.leaves.at(i)); 00694 if(it != lonelyLeaves.end()) lonelyLeaves.erase(it); 00695 } 00696 00697 // Set new particles as tree leaves 00698 ancestryTree.leaves = newLeaves; 00699 00700 // Delete Un-resampled leaves 00701 std::cout << "before resampling, active particles: " << 1+countDescendants(ancestryTree.head) << std::endl; 00702 std::cout << "we have " << lonelyLeaves.size() << " lonely leaves to be deleted" << std::endl; 00703 for(size_t i = 0; i < lonelyLeaves.size(); i++) { 00704 std::vector<boost::shared_ptr<Particle> >::iterator it; 00705 it = find(lonelyLeaves.at(i)->parent->children.begin(), lonelyLeaves.at(i)->parent->children.end(), lonelyLeaves.at(i)); 00706 lonelyLeaves.at(i)->parent->children.erase(it); 00707 // with no references left, particle should now get deleted by boost 00708 } 00709 std::cout << "after resampling, active particles: " << 1+countDescendants(ancestryTree.head) << std::endl; 00710 } 00711 00712 // ###################################################################### 00713 void HawkSlammer::pruneParticles() { 00714 std::cout << "pruneParticles()" << std::endl; 00715 00716 // Start a recursive call through the tree 00717 collapseParticle(ancestryTree.head); 00718 } 00719 00720 // ###################################################################### 00721 void HawkSlammer::collapseParticle(boost::shared_ptr<Particle> particle) { 00722 // Check to see if we have an only child 00723 if(particle->children.size() == 1) { 00724 std::cout << "fouund a particle to squash!" << std::endl; 00725 // Particle's only child 00726 boost::shared_ptr<Particle> child = particle->children.at(0); 00727 00728 std::cout << "Child has " << child->observations.size() << " observations" << std::endl; 00729 // Delete particle's observations that that have been replaced by the child 00730 for(size_t j = 0; j < child->observations.size(); j++) { 00731 // Does child have observation in the same place as particle? 00732 if(child->observations.at(j)->ancestor && child->observations.at(j)->ancestor->source == particle) { 00733 // He does! So copy over the particle's observation ancestor 00734 child->observations.at(j)->ancestor = child->observations.at(j)->ancestor->ancestor; 00735 00736 // And remove reference of particle's observation in occupancyGrid 00737 std::cout << "Erasing observation with " << (*occupancyGrid.getVal(child->observations.at(j)->x, child->observations.at(j)->y))[particle].use_count() << " references" << std::endl; 00738 occupancyGrid.getVal(child->observations.at(j)->x, child->observations.at(j)->y)->erase(particle); 00739 } 00740 } 00741 std::cout << "Child now has " << child->observations.size() << " observations" << std::endl; 00742 std::cin.get(); 00743 00744 // Particle's remaining observations should become the child's 00745 for(int j = 0; j < (int)particle->observations.size(); j++) { 00746 particle->observations.at(j)->source = child; 00747 child->observations.push_back(particle->observations.at(j)); 00748 } 00749 00750 // Update child's parent reference 00751 child->parent = particle->parent; 00752 00753 // Update parent's child reference 00754 if(!particle->parent) { 00755 // This is the head of the tree, replace the head 00756 ancestryTree.head = child; 00757 } 00758 else { 00759 // This is a normal node, replace the pointer in the children vector 00760 std::vector<boost::shared_ptr<Particle> >::iterator it; 00761 it = find(particle->parent->children.begin(), particle->parent->children.end(), particle); 00762 *it = child; 00763 } 00764 00765 // Delete particle (automatic with boost) 00766 particle->parent.reset(); 00767 particle->children.clear(); 00768 particle->observations.clear(); 00769 00770 // Now re-run the algorithm on the child 00771 collapseParticle(child); 00772 } 00773 else { 00774 // Run this algorithm recursively for each child 00775 for(size_t i = 0; i < particle->children.size(); i++) { 00776 collapseParticle(particle->children.at(i)); 00777 } 00778 } 00779 } 00780 00781 // ###################################################################### 00782 int HawkSlammer::countDescendants(boost::shared_ptr<Particle> particle) { 00783 int particles = particle->children.size(); 00784 00785 for(size_t i = 0; i < particle->children.size(); i++) { 00786 particles += countDescendants(particle->children.at(i)); 00787 } 00788 00789 return particles; 00790 } 00791 00792 // ###################################################################### 00793 Image< PixRGB<byte> > HawkSlammer::makeMapImage(boost::shared_ptr<Particle> primaryParticle) { 00794 Image< PixRGB<byte> > mapImage(occupancyGrid.getWidth(), occupancyGrid.getHeight(), NO_INIT); 00795 PixRGB<byte> hitDot, nohitDot, notscannedDot; 00796 hitDot.set(0, 0, 0); //black 00797 nohitDot.set(195, 195, 195); //light grey 00798 notscannedDot.set(255, 255, 255); //white 00799 00800 // Make the image 00801 for(int i = 0; i < mapImage.getWidth(); i++) { 00802 for(int j = 0; j < mapImage.getHeight(); j++) { 00803 // Find an observation from the particle or his ancestors 00804 boost::shared_ptr<Particle> particle = primaryParticle; 00805 boost::shared_ptr<Observation> observation; 00806 std::map<boost::shared_ptr<Particle>, boost::shared_ptr<Observation> >::iterator it; 00807 while(!observation && particle) { 00808 if(occupancyGrid.getVal(i, j)) { 00809 it = occupancyGrid.getVal(i, j)->find(particle); 00810 if(it != occupancyGrid.getVal(i, j)->end()) observation = it->second; 00811 else particle = particle->parent; 00812 } 00813 else particle = particle->parent; 00814 } 00815 00816 // Color map accordingly 00817 if(observation) { 00818 if(observation->laserTerminations / observation->laserTravel > 0.5) 00819 mapImage.setVal(i, j, hitDot); 00820 else mapImage.setVal(i, j, nohitDot); 00821 } 00822 else mapImage.setVal(i, j, notscannedDot); 00823 } 00824 } 00825 00826 return mapImage; 00827 } 00828 00829 // ###################################################################### 00830 Image< PixRGB<byte> > HawkSlammer::makeScannerImage(SensorDataMessagePtr msg) { 00831 double angularResolution = msg->angularResolution; 00832 LongSeq scannerData = msg->scannerData; 00833 Image< PixRGB<byte> > scannerImage(2*HALF_WINDOW_SIZE,2*HALF_WINDOW_SIZE,ZEROS); 00834 00835 // Plot scanner points 00836 long min = maxLaserDistance, max = 0; 00837 int offset = scannerData.size()/2; 00838 for(size_t i = 0; i < scannerData.size(); i++) { 00839 if(scannerData.at(i) > max) max = scannerData.at(i); 00840 if(scannerData.at(i) < min) min = scannerData.at(i); 00841 // Calculate distance 00842 float distance = scannerData.at(i); 00843 float angle = (i-offset)*angularResolution; 00844 distance = distance/maxLaserDistance*HALF_WINDOW_SIZE; //((distance - min)/(max-min))*256; 00845 if (distance < 0) distance = 1.0; 00846 00847 // Draw point 00848 Point2D<int> pt; 00849 pt.i = HALF_WINDOW_SIZE - int(distance*sin(angle)); 00850 pt.j = HALF_WINDOW_SIZE - int(distance*cos(angle)); 00851 drawCircle(scannerImage, pt, 1, PixRGB<byte>(255,0,0)); 00852 00853 // Draw range lines 00854 if(i == 0 || i == (scannerData.size() - 1)) { 00855 pt.i = HALF_WINDOW_SIZE - int(HALF_WINDOW_SIZE*sin(angle)); 00856 pt.j = HALF_WINDOW_SIZE - int(HALF_WINDOW_SIZE*cos(angle)); 00857 drawLine(scannerImage, Point2D<int>(HALF_WINDOW_SIZE,HALF_WINDOW_SIZE),pt,PixRGB<byte>(0,0,255)); 00858 } 00859 00860 /*if((i >= (-50 + 141)) && (i <= (49 + 141))) 00861 drawLine(scannerImage, Point2D<int>(HALF_WINDOW_SIZE,HALF_WINDOW_SIZE),pt,PixRGB<byte>(0,0,255)); 00862 else 00863 drawLine(scannerImage, Point2D<int>(HALF_WINDOW_SIZE,HALF_WINDOW_SIZE),pt,PixRGB<byte>(0,255,0));*/ 00864 } 00865 std::cout << "min, max: " << min << ", " << max << std::endl; 00866 00867 return scannerImage; 00868 } 00869 00870 // ###################################################################### 00871 int main (int argc, char* argv[]) { 00872 std::cout << "HawkSlammer: starting..." << std::endl; 00873 00874 HawkSlammer agent("HawkSlammer", argc, argv); 00875 agent.start(); 00876 00877 std::cout << "HawkSlammer: all done!" << std::endl; 00878 }