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

DualCoding 5.1CVS
Generated Fri Mar 16 05:23:45 2012 by Doxygen 1.6.3