Homepage Demos Overview Downloads Tutorials Reference
Credits

BlobData.cc

Go to the documentation of this file.
00001 //-*-c++-*-
00002 #include <iostream>
00003 #include <vector>
00004 
00005 #include "Vision/cmvision.h"
00006 
00007 #include "SketchSpace.h"
00008 #include "Sketch.h"
00009 #include "ShapeSpace.h"
00010 #include "ShapeRoot.h"
00011 
00012 #include "BlobData.h"
00013 #include "LineData.h"  // for drawline2d
00014 #include "ShapeBlob.h"
00015 #include "visops.h"
00016 #include "Region.h"
00017 #include "ShapeLine.h"
00018 #include "ShapePoint.h"
00019 
00020 #include "BrickOps.h" 
00021 
00022 namespace DualCoding {
00023 
00024 BlobData::BlobData(ShapeSpace& _space,
00025        const Point &_topLeft, const Point &_topRight,
00026        const Point &_bottomLeft, const Point &_bottomRight,
00027        const float _area, const std::vector<run> &_runvec,
00028        const BlobOrientation_t _orientation, rgb _rgbvalue) :
00029   BaseData(_space, getStaticType()),
00030   topLeft(_topLeft), topRight(_topRight),
00031   bottomLeft(_bottomLeft), bottomRight(_bottomRight),
00032   area(_area), runvec(_runvec), orientation(_orientation)
00033 {
00034   setColor(_rgbvalue);
00035 }
00036       
00037 DATASTUFF_CC(BlobData);
00038 
00039 //! return the centroid of the shape in point format
00040 Point BlobData::getCentroid() const {
00041   return Point((topLeft.coords + topRight.coords + bottomLeft.coords + bottomRight.coords) / 4,
00042          getRefFrameType());
00043 }
00044   
00045 void BlobData::printParams() const {
00046   cout << "Blob"
00047        << " tl=" << topLeft.coords
00048        << " tr=" << topRight.coords
00049        << " bl=" << bottomLeft.coords
00050        << " br=" << bottomRight.coords
00051        << " area=" << area
00052        << endl;
00053 }
00054 
00055 Sketch<bool>* BlobData::render() const {
00056   SketchSpace &SkS = space->getDualSpace();
00057   Sketch<bool>* result = new Sketch<bool>(SkS, "render("+getName()+")");
00058   *result = false;
00059   switch ( orientation ) {
00060   case groundplane:
00061     if ( space->getRefFrameType() == camcentric ) {
00062       for ( std::vector<BlobData::run>::const_iterator it = runvec.begin();
00063       it != runvec.end(); it++ ) {
00064   const BlobData::run &r = *it;
00065   int const xstop = r.x + r.width;
00066   for ( int xi=r.x; xi<xstop; xi++ )
00067     (*result)(xi, r.y) = true;
00068       }
00069     } else {
00070       NEWMAT::ColumnVector tl(topLeft.getCoords()), tr(topRight.getCoords()),
00071   bl(bottomLeft.getCoords()), br(bottomRight.getCoords());
00072       SkS.applyTmat(tl); SkS.applyTmat(tr); SkS.applyTmat(bl); SkS.applyTmat(br);
00073       LineData::drawline2d(*result, (int)tl(1), (int)tl(2), (int)tr(1), (int)tr(2));
00074       LineData::drawline2d(*result, (int)tr(1), (int)tr(2), (int)br(1), (int)br(2));
00075       LineData::drawline2d(*result, (int)br(1), (int)br(2), (int)bl(1), (int)bl(2));
00076       LineData::drawline2d(*result, (int)bl(1), (int)bl(2), (int)tl(1), (int)tl(2));
00077     }
00078     break;
00079   case pillar:
00080   case pinata:
00081     cout << "BlobData::render() : Can't yet render blobs in pillar/pinata orientations" << endl;
00082     break;
00083   }
00084   return result;
00085 }
00086 
00087 //! Transformations. (Virtual in BaseData.)
00088 void BlobData::applyTransform(const NEWMAT::Matrix& Tmat) {
00089   switch ( orientation ) {
00090 
00091   case groundplane:
00092     topLeft.applyTransform(Tmat);
00093     topRight.applyTransform(Tmat);
00094     bottomLeft.applyTransform(Tmat);
00095     bottomRight.applyTransform(Tmat);
00096     break;
00097 
00098   case pillar:
00099   case pinata:
00100     // *** HACK: THIS IS NOT THE RIGHT WAY TO CALCULATE HEIGHT
00101     Point height_incr((topLeft-bottomLeft + topRight-bottomRight) / 2);
00102     height_incr.coords << 0 << 0;
00103     bottomLeft.applyTransform(Tmat);
00104     bottomRight.applyTransform(Tmat);
00105     topLeft = bottomLeft + height_incr;
00106     topRight = bottomRight + height_incr;
00107     break;
00108   };
00109 }
00110 
00111 void BlobData::projectToGround(const NEWMAT::Matrix& camToBase,
00112              const NEWMAT::ColumnVector& gndplane) {
00113   switch ( orientation ) {
00114   case groundplane:
00115     topLeft.projectToGround(camToBase,gndplane);
00116     topRight.projectToGround(camToBase,gndplane);
00117     bottomLeft.projectToGround(camToBase,gndplane);
00118     bottomRight.projectToGround(camToBase,gndplane);
00119     break;
00120   case pillar:
00121   case pinata:
00122     bottomLeft.projectToGround(camToBase,gndplane);
00123     bottomRight.projectToGround(camToBase,gndplane);
00124     // DST HACK -- hard coded pillar height 100 mm
00125     topLeft = bottomLeft;
00126     topRight = bottomRight;
00127     topLeft.coords(3) += 100;
00128     topRight.coords(3) += 100;
00129   }
00130   update_derived_properties();
00131 }
00132 
00133   /*
00134 void BlobData::projectToGround(int xres, int yres, const NEWMAT::ColumnVector& gndplane) {
00135   switch ( orientation ) {
00136   case groundplane:
00137     topLeft.projectToGround(xres,yres,gndplane);
00138     topRight.projectToGround(xres,yres,gndplane);
00139     bottomLeft.projectToGround(xres,yres,gndplane);
00140     bottomRight.projectToGround(xres,yres,gndplane);
00141     break;
00142   case pillar:
00143   case pinata:
00144     bottomLeft.projectToGround(xres,yres,gndplane);
00145     bottomRight.projectToGround(xres,yres,gndplane);
00146     // DST HACK -- hard coded pillar height 100 mm
00147     topLeft = bottomLeft;
00148     topRight = bottomRight;
00149     topLeft.coords(3) += 100;
00150     topRight.coords(3) += 100;
00151   }
00152   update_derived_properties();
00153 }
00154   */
00155 
00156 void BlobData::update_derived_properties() {
00157   switch ( orientation ) {
00158 
00159   case groundplane:
00160     // DST HACK -- should use the formula for area of a quadrilaterl
00161     area = (topRight+bottomRight-topLeft-bottomLeft).coords(1) *
00162            (bottomLeft+bottomRight-topLeft-topRight).coords(2) / 4;
00163     break;
00164 
00165   case pillar:
00166   case pinata:
00167     // fill this in later
00168     break;
00169   }
00170 }
00171 
00172 bool BlobData::isMatchFor(const ShapeRoot& other) const {
00173   if (!(isSameTypeAs(other) && isSameColorAs(other)))
00174     return false;
00175   else {
00176     const Shape<BlobData>& other_blob = ShapeRootTypeConst(other,BlobData);
00177     float dist = getCentroid().distanceFrom(other_blob->getCentroid());
00178     return dist < 2*sqrt(area);
00179   }
00180 }
00181 
00182 bool BlobData::updateParams(const ShapeRoot& other, bool forceUpdate) {
00183   const Shape<BlobData>& other_blob = ShapeRootTypeConst(other,BlobData);
00184   int other_conf = other_blob->confidence;
00185   if (other_conf <= 0) {
00186     if (forceUpdate)
00187       other_conf = 1;
00188     else
00189       return false;
00190   }
00191   confidence += other_conf;
00192   const int sumconf = confidence + other_conf;
00193   topLeft = (topLeft*confidence + other_blob->topLeft*other_conf) / sumconf;
00194   topRight = (topRight*confidence + other_blob->topRight*other_conf) / sumconf;
00195   bottomLeft = (bottomLeft*confidence + other_blob->bottomLeft*other_conf) / sumconf;
00196   bottomRight = (bottomRight*confidence + other_blob->bottomRight*other_conf) / sumconf;
00197   update_derived_properties();
00198   return true;
00199 }
00200 
00201 std::vector<Shape<BlobData> >
00202 BlobData::extractBlobs(const Sketch<bool> &sketch, 
00203            const set<int>& colors, int minarea,
00204            BlobData::BlobOrientation_t orient, 
00205            int maxblobs) {
00206   // We could multiply sketch*color_index to convert to uchar and
00207   // spare ourselves the for loop that follows for setting blob
00208   // colors, but this way is actually much faster because we skip all
00209   // the pixel-wise multiplications.  Casting Sketch<bool> to
00210   // Sketch<uchar> is done with a simple reinterpret_cast; no copying.
00211   std::vector<Shape<BlobData> > result(extractBlobs((Sketch<uchar>&)sketch,colors,minarea,orient,maxblobs));
00212   rgb rgbvalue(sketch->getColor());
00213   for ( std::vector<Shape<BlobData> >::iterator it = result.begin();
00214   it != result.end(); it++ )
00215     (*it)->setColor(rgbvalue);
00216   return result;
00217 }
00218 
00219 std::vector<Shape<BlobData> > 
00220 BlobData::extractBlobs(const Sketch<bool> &sketch, int minarea,
00221        BlobData::BlobOrientation_t orient, int maxblobs) {
00222   const int numColors = ProjectInterface::getNumColors();
00223   set<int> colors;
00224   for (int i = 1; i < numColors; i++) colors.insert(i);
00225   return extractBlobs(sketch, colors, minarea, orient, maxblobs);
00226 }
00227   
00228 
00229 std::vector<Shape<BlobData> >
00230 BlobData::extractBlobs(const Sketch<uchar> &sketch, 
00231            const set<int>& colors, int minarea,
00232            BlobData::BlobOrientation_t orient, int maxblobs) {
00233   int parent = sketch->getId();
00234   uchar *pixels = &((*sketch.pixels)[0]);
00235 
00236   // convert pixel array to RLE
00237   int const maxRuns = (sketch.width * sketch.height) / 8;
00238   CMVision::run<uchar> *rle_buffer = new CMVision::run<uchar>[maxRuns];
00239   unsigned int const numRuns = CMVision::EncodeRuns(rle_buffer, pixels, sketch.width, sketch.height, maxRuns);
00240 
00241   // convert RLE to region list
00242   CMVision::ConnectComponents(rle_buffer,numRuns);
00243   int const maxRegions = (sketch.width * sketch.height) / 16;   // formula from RegionGenerator.h
00244   CMVision::region *regions = new CMVision::region[maxRegions];
00245   unsigned int numRegions = CMVision::ExtractRegions(regions, maxRegions, rle_buffer, numRuns);
00246   unsigned int const numColors = ProjectInterface::getNumColors();
00247   CMVision::color_class_state *ccs = new CMVision::color_class_state[numColors];
00248   unsigned int const maxArea = CMVision::SeparateRegions(ccs, numColors, regions, numRegions);
00249   CMVision::SortRegions(ccs, numColors, maxArea);
00250   CMVision::MergeRegions(ccs, int(numColors), rle_buffer);
00251 
00252   // extract blobs from region list
00253   std::vector<Shape<BlobData> > result(20);
00254   result.clear();
00255   ShapeSpace &ShS = sketch->getSpace().getDualSpace();
00256   //  for ( size_t color=1; color < numColors; color++ ) {
00257   for (set<int>::const_iterator it = colors.begin();
00258        it != colors.end(); it++) {
00259     //    const CMVision::region* list_head = ccs[color].list;
00260     const CMVision::region* list_head = ccs[*it].list;
00261     if ( list_head != NULL ) {
00262       //      const rgb rgbvalue = ProjectInterface::getColorRGB(color);
00263       const rgb rgbvalue = ProjectInterface::getColorRGB(*it);
00264       for (int i=0; list_head!=NULL && i<maxblobs && list_head->area >= minarea;
00265      list_head = list_head->next, i++) {
00266   BlobData* blobdat = new_blob(ShS,*list_head, rle_buffer, orient, rgbvalue);
00267   blobdat->setParentId(parent);
00268   result.push_back(Shape<BlobData>(blobdat));
00269       }
00270     }
00271   }
00272   delete[] ccs;
00273   delete[] regions;
00274   delete[] rle_buffer;
00275   return result;
00276 }
00277 
00278 std::vector<Shape<BlobData> > 
00279 BlobData::extractBlobs(const Sketch<uchar> &sketch, int minarea,
00280        BlobData::BlobOrientation_t orient, int maxblobs) {
00281   const int numColors = ProjectInterface::getNumColors();
00282   set<int> colors;
00283   for (int i = 1; i < numColors; i++) colors.insert(i);
00284   return extractBlobs(sketch, colors, minarea, orient, maxblobs);
00285 }
00286 
00287 
00288 BlobData* BlobData::new_blob(ShapeSpace& space,
00289            const CMVision::region &reg,
00290            const CMVision::run<uchar> *rle_buff, 
00291            const BlobData::BlobOrientation_t orient,
00292            const rgb rgbvalue) {
00293   int const x1 = reg.x1;
00294   int const y1 = reg.y1;
00295   int const x2 = reg.x2;
00296   int const y2 = reg.y2;
00297   // Count the number of runs so we can allocate a vector of the right size.
00298   // The first run might be numbered 0, so we must use -1 as end of list indicator.
00299   int numruns = 0;
00300   for (int runidx = reg.run_start; runidx != -1; 
00301        runidx = rle_buff[runidx].next ? rle_buff[runidx].next : -1)
00302     ++numruns;
00303   std::vector<BlobData::run> runvec(numruns);
00304   runvec.clear();
00305   // now fill in the run vector
00306   for (int runidx = reg.run_start; runidx != -1; 
00307        runidx = rle_buff[runidx].next ? rle_buff[runidx].next : -1) {
00308     const CMVision::run<uchar> &this_run = rle_buff[runidx];
00309     runvec.push_back(BlobData::run(this_run.x, this_run.y, this_run.width));
00310   }
00311   if ( space.getRefFrameType() == camcentric )
00312     return new BlobData(space,
00313       Point(x1,y1),Point(x2,y1),
00314       Point(x1,y2),Point(x2,y2),
00315       reg.area, runvec, orient, rgbvalue);
00316   else
00317     return new BlobData(space,
00318       Point(x2,y2),Point(x1,y2),
00319       Point(x2,y1),Point(x1,y1),
00320           reg.area, runvec, orient, rgbvalue);
00321 }
00322 
00323 
00324 
00325 
00326 
00327 
00328   // General call to find the corners of a blob.
00329   // Used in brick / pyramid extraction
00330   //
00331   //
00332   // Requires the number of corners you expect to find.
00333   // Currently just uses shape fitting to find the corners
00334   // Originally used either the derivative or diagonal approaches to finding corners
00335   //
00336   // Shape fitting works very well, but is very slow.
00337   // (no attempt was made to optimize it though)
00338   //
00339   // The old diagonal/derivative approach is at the bottom
00340   // It is much faster, but less robust.
00341   std::vector<Point> BlobData::findCorners(unsigned int nExpected, std::vector<Point>& candidates, float &bestValue)
00342   {
00343     std::vector<Point> fitCorners = findCornersShapeFit(nExpected, candidates, bestValue);
00344 
00345     // debug output
00346     for (unsigned int i=0; i<fitCorners.size(); i++){
00347       NEW_SHAPE(fitline, LineData, LineData(*space, fitCorners[i], fitCorners[(i+1)%nExpected]));
00348       fitline->setParentId(getViewableId());
00349     }
00350 
00351     // Handling off-screen bricks:
00352     // If our fit of corners is close to the edge of the image,
00353     // re-try fitting 3 or 5 corners to the brick
00354     bool onEdge = false;
00355     int width = space->getDualSpace().getWidth();
00356     int height = space->getDualSpace().getHeight();
00357     for (unsigned int i=0; i<nExpected; i++) {
00358       if (fitCorners[i].coordX() < 5 || fitCorners[i].coordX() > width - 5 ||
00359     fitCorners[i].coordY() < 5 || fitCorners[i].coordY() > height - 5) {
00360   onEdge = true;
00361   break;
00362       }
00363     }
00364     if (onEdge && nExpected == 4) {
00365       std::vector<int> outsideCandidates;
00366       for (unsigned int i=0; i<nExpected; i++) {
00367   if (candidates[i].coordX() < 5 || candidates[i].coordX() > width - 5 ||
00368       candidates[i].coordY() < 5 || candidates[i].coordY() > height - 5) {
00369     outsideCandidates.push_back(i);
00370   }
00371       }
00372 
00373 
00374       std::vector<Point> candidates3(candidates), candidates5;
00375 
00376       if (outsideCandidates.size() == 0) {
00377   std::cout<<"Err? final points are near the edge, but the candidates aren't?"<<std::endl;
00378       }
00379       
00380       // If just one candidate is outside the scene, 
00381       // try removing it for 3 points
00382       // and adding the points where it crosses the border of the image for 5 points
00383       if (outsideCandidates.size() == 1) {
00384   int outC = outsideCandidates[0];
00385   candidates3.erase(candidates3.begin() + outC);
00386       
00387   Point p1, p2;
00388   if (candidates[outC].coordX() < 5) {
00389     p1.setCoords(0,0);
00390     p2.setCoords(0,height);
00391   }
00392   else if (candidates[outC].coordX() > width - 5) {
00393     p1.setCoords(width,0);
00394     p2.setCoords(width,height);
00395   }
00396   else if (candidates[outC].coordY() < 5) {
00397     p1.setCoords(0,0);
00398     p2.setCoords(width,0);
00399   }
00400   else {
00401     p1.setCoords(0,height);
00402     p2.setCoords(width,height);
00403   }
00404   LineData edgeLine(*space, p1,p2);
00405   LineData l1(*space, candidates[(outC+3)%4], candidates[outC]);
00406   LineData l2(*space, candidates[(outC+1)%4], candidates[outC]);
00407   candidates5.push_back(candidates[(outC+3)%4]);
00408   candidates5.push_back(l1.intersectionWithLine(edgeLine));
00409   candidates5.push_back(l2.intersectionWithLine(edgeLine));
00410   candidates5.push_back(candidates[(outC+1)%4]);
00411   candidates5.push_back(candidates[(outC+2)%4]);
00412       }
00413       
00414       if (outsideCandidates.size() == 2) {
00415   Point betweenOutside = (candidates[outsideCandidates[0]] + candidates[outsideCandidates[1]])/2;
00416   candidates3[outsideCandidates[0]].setCoords(betweenOutside);
00417   candidates3.erase(candidates3.begin() + outsideCandidates[1]);
00418   
00419   int dC = outsideCandidates[1] - outsideCandidates[0];
00420   int c1 = outsideCandidates[1];
00421   candidates5.push_back(candidates[outsideCandidates[0]]);
00422   candidates5.push_back(betweenOutside);
00423   candidates5.push_back(candidates[outsideCandidates[1]]);
00424   candidates5.push_back(candidates[(c1+dC)%4]);
00425   candidates5.push_back(candidates[(c1+2*dC)%4]);
00426       }
00427       
00428       if (outsideCandidates.size() > 2) {
00429   // Not gonna get very good points out of this, probably
00430       }
00431     
00432       float value3, value5;
00433       for (unsigned int i=0; i<candidates3.size(); i++) {
00434   NEW_SHAPE(candidate3, PointData, PointData(*space, candidates3[i]));
00435   candidate3->setParentId(getViewableId());
00436       }
00437       for (unsigned int i=0; i<candidates5.size(); i++) {
00438   NEW_SHAPE(candidate5, PointData, PointData(*space, candidates5[i]));
00439   candidate5->setParentId(getViewableId());
00440       }
00441       std::vector<Point> fitcorners3 = findCornersShapeFit(3, candidates3, value3);
00442       std::vector<Point> fitcorners5 = findCornersShapeFit(5, candidates5, value5);
00443       for (unsigned int i=0; i<fitcorners3.size(); i++) {
00444   NEW_SHAPE(fit3, PointData, PointData(*space, fitcorners3[i]));
00445   fit3->setParentId(getViewableId());
00446       }
00447       for (unsigned int i=0; i<fitcorners5.size(); i++) {
00448   NEW_SHAPE(fit5, PointData, PointData(*space, fitcorners5[i]));
00449   fit5->setParentId(getViewableId());
00450       }
00451     }
00452 
00453     // right now just take the corners from quadrilateral fitting
00454     return fitCorners;
00455 
00456 
00457 
00458 
00459     // Old method, used the diagonal and derivative approaches and took the better results
00460     // Was generally much faster, but broke down in some special cases
00461     /* 
00462     const float MIN_BOUNDING_SCORE = .75;
00463 
00464     float derivScore = -1, diagScore = -1;
00465 
00466     std::vector<Point> derivCorners = findCornersDerivative();
00467 
00468     if (derivCorners.size() == nExpected) {
00469 
00470       derivScore = getBoundingQuadrilateralInteriorPointRatio(derivCorners);
00471       std::cout<<"Derivative score for ("<<getViewableId()<<") = "<<derivScore<<std::endl;
00472       if (derivScore > MIN_BOUNDING_SCORE) {
00473   return derivCorners;
00474       }
00475     }
00476     
00477     std::vector<Point> diagCorners = findCornersDiagonal();
00478 
00479     if (diagCorners.size() == nExpected) {
00480       diagScore = getBoundingQuadrilateralInteriorPointRatio(diagCorners);
00481       std::cout<<"Diagonal score for ("<<getViewableId()<<") = "<<diagScore<<std::endl;
00482       if (diagScore > derivScore) {
00483   return diagCorners;
00484       }
00485       else if (derivScore > .5) {
00486   return derivCorners;
00487       }
00488     }
00489 
00490     // can we integrate sets of incomplete / overcomplete points?
00491 
00492     std::vector<Point> result;
00493 
00494     return result;
00495     */
00496   }
00497 
00498 
00499   /*
00500    * Derivative approach to finding the corners of the blobs. 
00501    * Computes the distance from the center to the edge in a circle around the blob
00502    * Takes a couple derivatives, and looks for peaks.
00503    *
00504    * Works well on big shapes, poorly on small ones
00505    * Doesn't make any guarantees as to how many points are returned. 
00506    */
00507   std::vector<Point> BlobData::findCornersDerivative()
00508   {
00509     std::vector<Point> corners;
00510 
00511     float radius = sqrt((topRight.coordX()-topLeft.coordX())*(topRight.coordX()-topLeft.coordX()) + 
00512       (bottomLeft.coordY()-topLeft.coordY())*(bottomLeft.coordY()-topLeft.coordY()))/2 + 1;
00513     int len = (int)(2*M_PI*radius + 1);
00514     float distances[len];
00515     Point points[len];
00516     Point centroid = getCentroid();
00517     NEW_SKETCH(rendering, bool, getRendering());
00518     
00519     int i=0, maxi = 0;
00520 
00521     maxi = findRadialDistancesFromPoint(centroid, radius, rendering, distances, points);
00522     float gdist[len], ddist[len], d2dist[len], d3dist[len];
00523     applyGaussian(distances, gdist, len);
00524     takeDerivative(gdist, ddist, len);
00525     takeDerivative(ddist, d2dist, len);
00526     takeDerivative(d2dist, d3dist, len);
00527 
00528     drawHist(gdist, len, rendering);
00529     drawHist(ddist, len, rendering);
00530     drawHist(d2dist, len, rendering);
00531     
00532 
00533     // Corners are negative peaks in the second derivative of the distance from the center
00534     // Zero-crossings in the first derivative should work, but we want to capture contour changes that 
00535     // don't necessarily cross zero (steep increase -> shallow increase could be a corner)
00536 
00537     const float MIN_D2 = 5.0;
00538 
00539     float curmin = -MIN_D2;
00540     int curi = -1;
00541     int curonpeak = 0;
00542     for (i=0; i<len; i++) {
00543       if (d2dist[i]  < curmin) {
00544   curmin = d2dist[i];
00545   curi = i;
00546   curonpeak = 1;
00547       }
00548       else if (curonpeak) {
00549   if (d2dist[i] > -MIN_D2 || (d3dist[i-1] > 0 && d3dist[i] <= 0)) {
00550     curonpeak = 0;
00551     curmin = -MIN_D2;
00552     corners.push_back(points[curi]);
00553     NEW_SHAPE(cornerpoint, PointData, Shape<PointData>(*space, points[curi])); 
00554     cornerpoint->setParentId(rendering->getViewableId());
00555   } 
00556       }
00557     }
00558 
00559 
00560     // Normalize returned corners to counter-clock-wise order;
00561     vector<Point> reversedCorners;
00562     for (i=corners.size()-1; i>=0; i--){
00563       reversedCorners.push_back(corners[i]);
00564     }
00565 
00566     return reversedCorners;
00567   }
00568 
00569 
00570   /*
00571    * Diagonal approach to finding corners
00572    * ad-hoc heuristic works well for normal parallelograms 
00573    *
00574    * fails on trapezoids and triangles, and where nCorners != 4. 
00575    *
00576    *
00577    * Computes radial distance from the center like the derivative method
00578    * Finds the peak in distance (furthest point is once corner)
00579    * Finds the peak in the opposite region (another corner)
00580    * 
00581    * At this point it can draw the diagonal. The breakdown occurs when 
00582    * it thinks it has a diagonal at this point but it isn't an actual diagonal
00583    *
00584    * Then split the quadrilateral into two triangles, and the furthest points from the 
00585    * diagonal are the two remaining corners. 
00586    */
00587   std::vector<Point> BlobData::findCornersDiagonal()
00588   {
00589     std::vector<Point> corners;
00590 
00591     float radius = sqrt((topRight.coordX()-topLeft.coordX())*(topRight.coordX()-topLeft.coordX()) + 
00592       (bottomLeft.coordY()-topLeft.coordY())*(bottomLeft.coordY()-topLeft.coordY()))/2 + 1;
00593     int len = (int)(2*M_PI*radius + 1);
00594     float distances[len];
00595     Point points[len];
00596     Point centroid = getCentroid();
00597     NEW_SKETCH(rendering, bool, getRendering());
00598     
00599     int i=0;
00600     int maxi = 0, origmaxi = 0;
00601     bool stillmax = false;
00602 
00603     maxi = findRadialDistancesFromPoint(centroid, radius, rendering, distances, points);
00604 
00605     // Find second max
00606     int maxi2 = 0;
00607     float max2 = 0;
00608     stillmax = false;
00609     origmaxi = -1;
00610     for (i=0; i<len; i++) {
00611       if (distances[i] >= max2 && 
00612     abs(i-maxi) > len*3/8 && 
00613     abs(i-maxi) < len*5/8) {
00614   if (distances[i] > max2) {
00615     maxi2 = i;
00616     max2 = distances[i];
00617     origmaxi = maxi2;
00618     stillmax = true;
00619   }
00620   else if (stillmax){
00621     maxi2 = (origmaxi+i)/2;
00622   }
00623       }
00624       else {
00625   stillmax = false;
00626       }
00627     }
00628     
00629     corners.push_back(points[maxi]);
00630     corners.push_back(points[maxi2]);
00631     std::cout<<"Corners: ("<<corners[0].coordX()<<","<<corners[0].coordY()<<")  ("<<
00632       corners[1].coordX()<<","<<corners[1].coordY()<<")\n";
00633 
00634     // Get the regions on either side of the line made by the two corners
00635     // The most distant points in those regions are the other two corners
00636     NEW_SHAPE(diag, LineData, Shape<LineData>(*space, corners[0], corners[1]));
00637     diag->firstPt().setActive(false);
00638     diag->secondPt().setActive(false);
00639     diag->setParentId(rendering->getViewableId());
00640 
00641     NEW_SKETCH_N(filled, bool, visops::topHalfPlane(diag));
00642     NEW_SKETCH(side1, bool, filled & rendering);
00643     NEW_SKETCH(side2, bool, !filled & rendering);
00644     
00645     const float MIN_PT_DIST = 3.0;
00646 
00647     Point pt3 = (Region::extractRegion(side1)).mostDistantPtFrom(diag.getData());
00648     Point pt4 = (Region::extractRegion(side2)).mostDistantPtFrom(diag.getData());
00649     if (diag->perpendicularDistanceFrom(pt3) > MIN_PT_DIST)
00650       corners.push_back(pt3);
00651     if (diag->perpendicularDistanceFrom(pt4) > MIN_PT_DIST)
00652       corners.push_back(pt4);
00653     
00654 
00655     // Sort the corners into order going around the brick
00656     std::vector<Point> resultCorners;
00657     std::vector<float> angles;
00658 
00659     float ta;
00660     Point tp;
00661     for (i=0; i<(int)corners.size(); i++) {
00662       Point di = corners[i] - centroid;
00663       angles.push_back(atan2(di.coordY(), di.coordX()));
00664       resultCorners.push_back(corners[i]);
00665       for (int j=i-1; j>=0; j--) {
00666   if (angles[j+1] > angles[j]) {
00667     ta = angles[j];
00668     angles[j] = angles[j+1];
00669     angles[j+1] = ta;
00670     tp = resultCorners[j];
00671     resultCorners[j] = resultCorners[j+1];
00672     resultCorners[j+1] = tp;
00673   }
00674   else{
00675     break;
00676   }
00677       }
00678     }
00679 
00680     NEW_SHAPE(cornerline1, LineData, Shape<LineData>(*space, resultCorners[0], resultCorners[1])); 
00681     cornerline1->setParentId(rendering->getViewableId());
00682     if (resultCorners.size() > 3) {
00683       NEW_SHAPE(cornerline2, LineData, Shape<LineData>(*space, resultCorners[2], resultCorners[3]));
00684       cornerline2->setParentId(rendering->getViewableId());
00685     }
00686     
00687      return resultCorners;
00688   }
00689 
00690 
00691 
00692 
00693 
00694   // find corners by fitting a quadrilateral to the blob
00695   //
00696   // Its expecting a set of candidate points that are roughly aligned with one set of edges and are
00697   // significantly wider than the blob itself. 
00698   // It contracts the candidate points to form a rough bounding box, 
00699   // then does simulated annealing on random perturbations of the end points to get a good fit
00700   //
00701   // Potential quadrilateral fits are scored by maximizing the number of edge points of the blob that 
00702   // lie under one of the lines, and minimizing the total area. 
00703   // A fixed minimum edge length constraint is also enforced. 
00704   std::vector<Point> BlobData::findCornersShapeFit(unsigned int ncorners, std::vector<Point>& candidates, 
00705                float &bestValue)
00706   { 
00707     NEW_SKETCH(rendering, bool, getRendering());
00708 
00709     std::vector<Point> bestPoints(ncorners), curTest(ncorners);
00710     float bestScore;
00711     int bestEdgeCount;
00712     std::vector<std::vector<Point> > testPoints;
00713     std::vector<float> testScores;
00714     std::vector<int> testEdgeCounts;
00715 
00716     if (candidates.size() == ncorners) {
00717       for (unsigned int i=0; i<ncorners; i++) {
00718   bestPoints[i].setCoords(candidates[i]);
00719   curTest[i].setCoords(candidates[i]);
00720       }
00721     }
00722     else {
00723       std::cout<<"Warning: incorrect number of candidates provided"<<std::endl;
00724       return bestPoints;
00725     }
00726 
00727     NEW_SKETCH_N(nsum, uchar, visops::neighborSum(getRendering()));
00728     NEW_SKETCH(borderPixels, bool, nsum > 0 & nsum < 8 & getRendering());
00729     int edgeTotal = 0;
00730     for (unsigned int x=0; x<borderPixels->getWidth(); x++) {
00731       borderPixels(x,0) = 0;
00732       borderPixels(x,borderPixels->getHeight() - 1) = 0;
00733     }
00734     for (unsigned int y=0; y<borderPixels->getHeight(); y++) {
00735       borderPixels(0,y) = 0;
00736       borderPixels(borderPixels->getWidth() - 1, y) = 0;
00737     }
00738     for (unsigned int i=0; i<borderPixels->getNumPixels(); i++) {
00739       if (borderPixels->at(i))
00740   edgeTotal++;
00741     }
00742 
00743     bestScore = getBoundingQuadrilateralScore(*this, bestPoints, borderPixels, bestEdgeCount, *space);
00744 
00745     int testCount = 0, testEdgeCount;
00746     Point dp;
00747     float dpDist, testRatio;
00748     bool hasMoved;
00749 
00750     float annealingScalar = 1.0;
00751     bool doingRandomMovement = false;
00752     const float ANNEALING_CAP = 25.0;
00753     const float WEIGHT_SCALAR = .2;
00754     
00755     const float MIN_DISTANCE = 10.0;
00756     const float MIN_BOUNDING_RATIO = 0.8;
00757 
00758     int iterationCount = 0, annealingStart = 0;
00759     // Right now it just keeps going until the annealing weight gets to a set threshold
00760     // Should probably be improved by checking the quality of the fit at intervals
00761     // especially if the points have stopped moving
00762     while (annealingScalar < ANNEALING_CAP) {
00763 
00764       hasMoved = false;
00765       
00766       // Test each corner in succession
00767       for (unsigned int i=0; i<ncorners; i++) {
00768 
00769   testScores.clear();
00770   testPoints.clear();
00771   testEdgeCounts.clear();
00772   testCount = 0;
00773   // Try moving the corner toward or away from either adjacent corner by 1 pixel
00774   // Only look at moving toward adjacent corners until we start doing random movement
00775 
00776   for (unsigned int j=0; j<ncorners; j++)
00777     curTest[j].setCoords(bestPoints[j]);
00778   dp.setCoords(curTest[(i+1)%ncorners] - curTest[i]);
00779   dpDist = curTest[i].distanceFrom(curTest[(i+1)%ncorners]);
00780   // Don't allow corners to get too close to each other
00781   if (dpDist > MIN_DISTANCE) {
00782 
00783     dp/=dpDist;
00784     curTest[i]+=dp;
00785     testRatio = getBoundingQuadrilateralInteriorPointRatio(*this, curTest, *space);
00786     if (testRatio > MIN_BOUNDING_RATIO) {
00787       testScores.push_back(getBoundingQuadrilateralScore(*this, curTest, borderPixels, 
00788                      testEdgeCount, *space));
00789       testPoints.push_back(curTest);
00790       testEdgeCounts.push_back(testEdgeCount);
00791       testCount++;
00792     }
00793 
00794     // Look outward too if we're in the annealing stage
00795     if (doingRandomMovement) {
00796       curTest[i].setCoords(bestPoints[i]);
00797       curTest[i]-=dp;
00798       testRatio = getBoundingQuadrilateralInteriorPointRatio(*this, curTest, *space);
00799       if (testRatio > MIN_BOUNDING_RATIO) {
00800         testScores.push_back(getBoundingQuadrilateralScore(*this, curTest, borderPixels, 
00801                  testEdgeCount, *space));
00802         testPoints.push_back(curTest);
00803         testEdgeCounts.push_back(testEdgeCount);
00804         testCount++;
00805       }
00806 
00807     }
00808     
00809   }
00810 
00811   curTest[i].setCoords(bestPoints[i]);
00812   dp.setCoords(curTest[(i+ncorners-1)%ncorners] - curTest[i]);
00813   dpDist = curTest[i].distanceFrom(curTest[(i+ncorners-1)%ncorners]);
00814   // Don't allow corners to get too close to each other
00815   if (dpDist > MIN_DISTANCE) {
00816 
00817     dp/=dpDist;
00818     curTest[i]+=dp;
00819     testRatio = getBoundingQuadrilateralInteriorPointRatio(*this, curTest, *space);
00820     if (testRatio > MIN_BOUNDING_RATIO) {
00821       testScores.push_back(getBoundingQuadrilateralScore(*this, curTest, borderPixels, 
00822                      testEdgeCount, *space));
00823       testPoints.push_back(curTest);
00824       testEdgeCounts.push_back(testEdgeCount);
00825       testCount++;
00826     }
00827 
00828     // Look outward too if we're in the annealing stage
00829     if (doingRandomMovement) {
00830       curTest[i].setCoords(bestPoints[i]);
00831       curTest[i]-=dp;
00832       testRatio = getBoundingQuadrilateralInteriorPointRatio(*this, curTest, *space);
00833       if (testRatio > MIN_BOUNDING_RATIO) {
00834         testScores.push_back(getBoundingQuadrilateralScore(*this, curTest, borderPixels, 
00835                  testEdgeCount, *space));
00836         testPoints.push_back(curTest);
00837         testEdgeCounts.push_back(testEdgeCount);
00838         testCount++;
00839       }
00840     }
00841     
00842   }
00843   
00844 
00845   testScores.push_back(bestScore);
00846   testPoints.push_back(bestPoints);
00847   testEdgeCounts.push_back(bestEdgeCount);
00848   testCount++;
00849 
00850   int move = -1;
00851   if (doingRandomMovement) {
00852     move = pickMove(testScores, annealingScalar);
00853   }
00854   else {
00855     move = 0;
00856     for (int j=0; j<testCount; j++) {
00857       if (testScores[j] < testScores[move])
00858         move = j;
00859     }
00860     if (move != testCount-1) {
00861       hasMoved = true;
00862     }
00863   }
00864 
00865   if (move < 0 || move >= testCount)
00866     std::cout<<"Hmm, picked a bad move somewhere ("<<move<<")\n";
00867   else {
00868     bestPoints[i].setCoords(testPoints[move][i]);
00869     bestScore = testScores[move];
00870     bestEdgeCount = testEdgeCounts[move];
00871   }
00872 
00873       }
00874 
00875       // Increase the weight proportional to how much of the edges are covered
00876       // Only increase it if we're at the annealing stage though. 
00877       if (doingRandomMovement) {
00878   annealingScalar += bestEdgeCount*WEIGHT_SCALAR/edgeTotal;
00879       }
00880       else if (!hasMoved) {
00881   doingRandomMovement = true;
00882 
00883   // debug output
00884   annealingStart = iterationCount;
00885   for (unsigned int z=0; z<ncorners; z++) {
00886     NEW_SHAPE(preannealing, PointData, PointData(*space, bestPoints[z]));
00887     preannealing->setParentId(borderPixels->getViewableId());
00888   }
00889       }
00890 
00891       iterationCount++;
00892       if (iterationCount > 500) {
00893   std::cout<<"Warning, annealing stopped by max iteration count\n"<<std::endl;
00894   break;
00895       }
00896       
00897     }
00898 
00899     std::cout<<"Shape fit took "<<iterationCount<<" iterations ("<<iterationCount - annealingStart<<" of annealing)\n";
00900     bestValue = bestScore;
00901     return bestPoints;
00902 
00903   }
00904 
00905 
00906 // Comparison predicates
00907 
00908 bool BlobData::areaLessThan::operator() (const Shape<BlobData> &b1, const Shape<BlobData> &b2) const {
00909       return b1->getArea() < b2->getArea();
00910 }
00911 
00912 } // namespace

DualCoding 3.0beta
Generated Wed Oct 4 00:01:53 2006 by Doxygen 1.4.7