Tekkotsu Homepage
Demos
Overview
Downloads
Dev. Resources
Reference
Credits

AgentData.cc

Go to the documentation of this file.
00001 //-*-c++-*-
00002 #include <vector>
00003 #include <iostream>
00004 #include <math.h>
00005 
00006 #include "Point.h"       // Point data member
00007 #include "Shared/Measures.h"    // coordinate_t; AngPi data member
00008 #include "ShapeTypes.h"  // agentDataType
00009 
00010 #include "SketchSpace.h"
00011 #include "Sketch.h"
00012 #include "ShapeSpace.h"  // required by DATASTUFF_CC
00013 #include "ShapeRoot.h"   // required by DATASTUFF_CC
00014 
00015 #include "visops.h"
00016 #include "ShapeFuns.h"
00017 #include "ShapeEllipse.h"
00018 #include "Crew/MapBuilder.h"
00019 #include "VRmixin.h"
00020 #include "Motion/Kinematics.h"  // for kine
00021 
00022 #include "AgentData.h"
00023 #include "ShapeAgent.h"
00024 
00025 using namespace std;
00026 
00027 namespace DualCoding {
00028 
00029   DATASTUFF_CC(AgentData);
00030 
00031   AgentData::AgentData(ShapeSpace& _space, const Point &c, AngTwoPi orient) 
00032     : BaseData(_space,agentDataType), center_pt(c), orientation(orient), hostAddr(0)
00033   { mobile = true; }
00034   
00035   AgentData::AgentData(const AgentData& otherData)
00036     : BaseData(otherData), 
00037       center_pt(otherData.center_pt), 
00038       orientation(otherData.orientation),
00039       hostAddr(otherData.hostAddr)
00040   { mobile = true; }
00041 
00042   BoundingBox2D AgentData::getBoundingBox() const {
00043     BoundingBox2D b;
00044     fmat::Column<3> bl = -RobotInfo::AgentBoundingBoxHalfDims - RobotInfo::AgentBoundingBoxBaseFrameOffset;
00045     fmat::Column<3> tr =  RobotInfo::AgentBoundingBoxHalfDims - RobotInfo::AgentBoundingBoxBaseFrameOffset;
00046     fmat::Matrix<3,3> rot = fmat::rotationZN<3,float>(orientation);
00047     bl = rot*bl + RobotInfo::AgentBoundingBoxBaseFrameOffset + center_pt.getCoords();
00048     tr = rot*tr + RobotInfo::AgentBoundingBoxBaseFrameOffset + center_pt.getCoords();
00049     b.expand(bl);
00050     b.expand(tr);
00051     return b;
00052   }
00053 
00054   bool AgentData::isMatchFor(const ShapeRoot&other) const {
00055         //agent's are considered matching of they overlap in space
00056         if(!isSameTypeAs(other)) {
00057             return false;
00058         }
00059     const int MAX_CENTER_DIST = 100;
00060         float center_dist = (center_pt - other->getCentroid()).xyzNorm();
00061         return center_dist <= MAX_CENTER_DIST;
00062   }
00063 
00064   //! Print information about this shape. (Virtual in BaseData.)
00065   void AgentData::printParams() const {
00066     cout << "Type = " << getTypeName();
00067     cout << "Shape ID = " << getId() << endl;
00068     cout << "Parent ID = " << getParentId() << endl;
00069 
00070     // Print critical points.
00071     cout << endl;
00072     cout << "center{" << getCentroid().coords[0] << ", " << getCentroid().coords[1] << "}" << endl;
00073     
00074     cout << "orientation = " << orientation << endl;
00075     //cout << "color = " << getColor() << endl;
00076     printf("color = %d %d %d\n",getColor().red,getColor().green,getColor().blue);
00077 
00078     cout << "mobile = " << getMobile() << endl;
00079     cout << "viewable = " << isViewable() << endl;
00080   }
00081 
00082 
00083   //! Transformations. (Virtual in BaseData.)
00084   void AgentData::applyTransform(const fmat::Transform& Tmat, const ReferenceFrameType_t newref) {
00085     //  cout << "AgentData::applyTransform: " << getId() << endl;
00086     //  cout << Tmat << endl;
00087     center_pt.applyTransform(Tmat,newref);
00088     orientation = orientation - (AngTwoPi)atan2(Tmat(1,2),Tmat(1,1));
00089     //  updateOrientation();
00090   }
00091 
00092   //! Functions to set properties.
00093   void AgentData::setOrientation(AngTwoPi _orientation) {
00094     orientation = _orientation;
00095     deleteRendering();
00096   }
00097 
00098   void AgentData::projectToGround(const fmat::Transform& camToBase,
00099                                   const PlaneEquation& groundplane) {
00100     center_pt.projectToGround(camToBase,groundplane);
00101   }
00102 
00103   bool AgentData::updateParams(const ShapeRoot& other, bool force) {
00104     if (isMatchFor(other) || force) {
00105       const AgentData& other_agent = ShapeRootTypeConst(other,AgentData).getData();
00106       int other_conf = other_agent.getConfidence();
00107       if (other_conf <= 0)
00108         return true;
00109       center_pt = (center_pt*confidence + other_agent.getCentroid()*other_conf) / (confidence+other_conf);
00110       orientation = orientation*((orientation_t)confidence/(confidence+other_conf))
00111         + other_agent.getOrientation()*((orientation_t)other_conf/(confidence+other_conf));
00112       return true;
00113     }
00114     return false;
00115   }
00116 
00117   //! Render into a sketch space and return reference. (Private.)
00118   Sketch<bool>* AgentData::render() const {
00119     Sketch<bool>* draw_result =
00120       new Sketch<bool>(space->getDualSpace(), "render("+getName()+")");
00121     draw_result = 0;
00122 
00123     // JJW This does not take orientation into account.
00124     // should probably take principal axis into account later on
00125     int cx = int(getCentroid().coords[0]);
00126     int cy = int(getCentroid().coords[1]);
00127   
00128     // Sure the agent rendering is terribly inefficient, but it works
00129     const float a = 2.f;
00130     const float b = 2.f;
00131     const float x_skip = std::atan(1/(0.5f*a)); // minimum x-diff w/o gaps 
00132     for(float x = (cx-a); x<(cx+a); x+=x_skip) {
00133       float y_y0_sq = (b*b) * (1 - (x-cx)*(x-cx)/(a*a));
00134       if(y_y0_sq > 0) {
00135         int y_bot = cy + (int)(sqrt(y_y0_sq));
00136         int y_top = cy - (int)(sqrt(y_y0_sq));
00137         (*draw_result)((int)x,y_bot) = true;
00138         (*draw_result)((int)x,y_top) = true;
00139       }
00140     }
00141     (*draw_result)(cx-(int)a, cy) = true; // fill in "holes" at ends
00142     (*draw_result)(cx+(int)a, cy) = true;
00143     return draw_result;
00144   }
00145 
00146   // Extraction
00147 
00148   int AgentData::getColorIdMatch(yuv pixelColor) {
00149         // Finds the closest identification color to pixelColor.
00150         // id colors use line fits with u and v assumed linear wrt y
00151     int distances[NUM_COLOR_IDS];
00152     for (int i = 0; i < NUM_COLOR_IDS; ++i) {
00153             float u_dist = (pixelColor.u - color_ids[i].u_0 - color_ids[i].u_slope*pixelColor.y);
00154             float v_dist = (pixelColor.v - color_ids[i].v_0 - color_ids[i].v_slope*pixelColor.y);
00155       distances[i] =  u_dist*u_dist + v_dist*v_dist;
00156         }
00157 
00158     int min_val = distances[0];
00159     int min_index = 0;
00160     for (int i = 1; i < NUM_COLOR_IDS; ++i) {
00161       if (distances[i] < min_val) {
00162         if (color_ids[i].y_low >= 0 &&
00163             (pixelColor.y < color_ids[i].y_low || pixelColor.y > color_ids[i].y_high)) {
00164           continue;
00165         }
00166         min_val = distances[i];
00167         min_index = i;
00168       }
00169     }
00170 
00171     return color_ids[min_index].id;
00172   }
00173 
00174   int AgentData::getIdFromCenter(const Sketch<yuv> &camFrameYUV, const Point &a, const Point &b, const Point &c) {
00175         //find the id from the color at the center of the three points
00176     Point center = (a + b + c)/3;
00177     yuv pixelColor = camFrameYUV(center.coordX(), center.coordY());
00178     return getColorIdMatch(pixelColor);
00179   }
00180 
00181   int AgentData::getIdFromCorner(const Sketch<yuv> &camFrameYUV, const Point &vert_a, const Point &vert_b, const Point &loner) {
00182         //find the id from the color at the fourth corner of a rectangle
00183         //where vert_a, vert_b, and vert_c specify three corners of the rectangle
00184     float vert_dist = vert_b.coordY() - vert_a.coordY();
00185     yuv pixelColor;
00186         pixelColor = camFrameYUV(loner.coordX(), loner.coordY() - vert_dist);
00187     // cout << "pixel color: " << pixelColor << endl;
00188     return getColorIdMatch(pixelColor);
00189   }
00190 
00191   bool AgentData::circlesVerticallyAligned(const Shape<EllipseData> &a, const Shape<EllipseData> &b) {
00192         //tests whether the centers of two ellipses are vertically aligned with a small margin
00193     const float VLEVEL_THRESHOLD = 0.25f;
00194     Point a_center = a->getCentroid();
00195     Point b_center = b->getCentroid();
00196     float atanValue;
00197     if (a_center.coordX() >= b_center.coordX())
00198       atanValue = fabs((a_center - b_center).atanYX());
00199     else
00200       atanValue = fabs((b_center - a_center).atanYX());
00201     return atanValue < VLEVEL_THRESHOLD;
00202   }
00203 
00204   bool AgentData::circlesHorizontallyAligned(const Shape<EllipseData> &a, const Shape<EllipseData> &b) {
00205         //tests whether the centers of two ellipses are horizontally aligned with a small margin
00206     const float HLEVEL_THRESHOLD = 0.45f;
00207     Point a_center = a->getCentroid();
00208     Point b_center = b->getCentroid();
00209     float atanValue;
00210     if (a_center.coordY() >= b_center.coordY())
00211       atanValue = fabs((float)(a_center - b_center).atanYX() - M_PI_2);
00212     else
00213       atanValue = fabs((float)(b_center - a_center).atanYX() - M_PI_2);
00214     return atanValue < HLEVEL_THRESHOLD;
00215   }
00216 
00217   void AgentData::findCirclesManual(const Sketch<uchar> &camFrame, 
00218                                     std::vector<int> &neighbor_density,
00219                                     std::vector<Shape<EllipseData> > &agentIdCircles) {
00220     // Finds red ellipses in a camera frame.
00221     // It can find smaller ellipses than the normal MapBuilder method.
00222     NEW_SKETCH_N(red_stuff, bool, visops::colormask(camFrame, "red"));
00223     NEW_SKETCH_N(blobs, usint, visops::labelcc(red_stuff));
00224     NEW_SKETCH_N(labeli, bool, visops::zeros(camFrame));
00225     NEW_SHAPEVEC(circles, EllipseData, 0);
00226 
00227     // Given connected components in the camera frame, find a bounding
00228     // box for each component.  Filter out components by aspect ratio
00229     // (width/height) and actual area to bounding box area ratio.
00230     for (unsigned int i = 1; i <= blobs->max(); ++i) {
00231       labeli.bind(blobs == i);
00232       int count = 0;
00233       int x_sum = 0;
00234       int y_sum = 0;
00235       int min_index = labeli->findMinPlus();
00236       int xstart = min_index % labeli.width;
00237       int ystart = min_index / labeli.width;
00238       int lbound = labeli.width;
00239       int rbound = xstart;
00240       int tbound = ystart;
00241       int bbound = ystart;
00242       for (int y = ystart; y < labeli.height; ++y) {
00243         for (int x = 0; x < labeli.width; ++x) {
00244           if (labeli(x, y)) {
00245             if (x < lbound)
00246               lbound = x;
00247             else if (x > rbound)
00248               rbound = x;
00249             if (y > bbound) {
00250               bbound = y;
00251             }
00252             x_sum += x;
00253             y_sum += y;
00254             ++count;
00255           }
00256         }
00257       }
00258       if (count == 0) continue;
00259 
00260       float major,  minor;
00261       if (bbound - tbound > rbound - lbound) {
00262         major = bbound - tbound;
00263         minor = rbound - lbound;
00264       } else {
00265         major = rbound - lbound;
00266         minor = bbound - tbound;
00267       }
00268       ++major;
00269       ++minor;
00270 
00271       //aspect ratio filter
00272       if (major/minor > 3) {
00273         // cout << i << ": aspect ratio " << major/minor << endl;
00274         continue;
00275       }
00276 
00277       //area filter
00278       if ((major*minor)/count > 2) {
00279         // cout << i << ": area " << (major*minor)/count << endl;
00280         continue;
00281       }
00282 
00283       minor = major;
00284       float center_x = x_sum/((float)count);
00285       float center_y = y_sum/((float)count);
00286       NEW_SHAPE_N(circle, EllipseData,
00287                   new EllipseData(VRmixin::camShS,
00288                                   Point(center_x, center_y, 0),
00289                                   major/2, minor/2));
00290       circle->setColor("red");
00291       circles.push_back(circle);
00292       agentIdCircles.push_back(circle);
00293     }
00294 
00295     // Find number of neighbors to each ellipse in a neighborhood
00296     // of (-deltaX to deltaX, -deltaY to deltaY).  This is used to
00297     // rule out circles as id markers later to prevent noise from
00298     // looking like a small rectangular pattern.
00299     const int deltaX = 60;
00300     const int deltaY = 60;
00301 
00302     SHAPEVEC_ITERATE(circles, EllipseData, circle) {
00303       neighbor_density.push_back(0);
00304       Point centroid = circle->getCentroid();
00305       float center_x = centroid.coordX();
00306       float center_y = centroid.coordY();
00307       float left = max(center_x - deltaX, 0.0f);
00308       float right = min(center_x + deltaX, (float)blobs.width - 1);
00309       float top = max(center_y - deltaY, 0.0f);
00310       float bottom = min(center_y + deltaY, (float)blobs.height - 1);
00311 
00312       //for each circle, check all other circles to see if they are neighbors
00313       SHAPEVEC_ITERATE(circles, EllipseData, neighbor) {
00314         Point neighbor_centroid = neighbor->getCentroid();
00315         float neighbor_x = neighbor_centroid.coordX();
00316         float neighbor_y = neighbor_centroid.coordY();
00317         if (neighbor_x >= left && neighbor_x <= right && neighbor_y >= top && neighbor_y <= bottom) {
00318           ++neighbor_density.back();
00319         }
00320       } END_ITERATE;
00321     } END_ITERATE;
00322   }
00323 
00324   void AgentData::findAgentsBelt(const Sketch<yuv> &camFrameYUV,
00325                                  const std::vector<int> &neighbor_density,
00326                                  const std::vector<Shape<EllipseData> > &circles) {
00327         // Look for an agent identification marker around the base of
00328         // a robot.  The marker is four circles marking the corners of
00329         // a rectangle.  Three of the markers are red and the color of
00330         // the fourth marker is used to identify the specific robot.
00331         // The side of the robot the marker is on is given by which of
00332         // the four circles is not red.
00333     const float robot_radius = VRmixin::theAgent->getBoundingBoxHalfDims()[1];
00334     // cout << "num circles: " << circles.size() << endl;
00335 
00336     enum RobotSide robot_side;
00337     vector<Shape<AgentData> > possible_agents;
00338 
00339     // We project the circles to a plane slightly above the ground to
00340     // reduce the distance error, since the circles aren't actually in
00341     // a horizontal plane.
00342     PlaneEquation above_ground(0, 0, 1, 65); //when on ground
00343     //PlaneEquation above_ground(0, 0, 1, 65 - 737); //when on table
00344     const fmat::Transform cam_transform = kine->linkToBase(CameraFrameOffset);
00345 
00346     for (unsigned int i = 0; i < neighbor_density.size(); ++i) {
00347       // cout << "density: " << circles[i]->getId() << " - " << neighbor_density[i] << endl;
00348     }
00349 
00350     // For each unique combination of three circles, check whether
00351     // they can form a rectangle.  If so, compute the center of the
00352     // robot from the position of the two vertically aligned circles
00353     // and find its id from the fourth circle.
00354     const int DENSITY_THRESHOLD = 6;
00355     for (unsigned int i = 0; i < circles.size(); ++i) {
00356       if (neighbor_density[i] > DENSITY_THRESHOLD)
00357         continue;
00358       for (unsigned int j = i + 1; j < circles.size(); ++j) {
00359         if (!circlesVerticallyAligned(circles[i], circles[j])) {
00360           // cout << circles[i]->getId() << " and " << circles[j]->getId() << " not vertically aligned." << endl;
00361           continue;
00362         }
00363         if (!compareCircleSize(circles[i], circles[j])) {
00364           //cout << circles[i]->getId() << " and " << circles[j]->getId() <<
00365           //" not similar size." << endl;
00366           continue;
00367         }
00368         for (unsigned int k = 0; k < circles.size(); ++k) {
00369           if (k == i || k == j) continue;
00370           unsigned int v, h;
00371           if (circlesHorizontallyAligned(circles[i], circles[k])) {
00372             v = i;
00373             h = j;
00374           } else if (circlesHorizontallyAligned(circles[j], circles[k])) {
00375             v = j;
00376             h = i;
00377           } else {
00378             // cout << "neither " << circles[i]->getId() << " nor " << circles[j]->getId() <<
00379             //  " is horizontally aligned with " << circles[k]->getId() << "." << endl;
00380             continue;
00381           }
00382           if (!compareCircleSize(circles[i], circles[k]) &&
00383               !compareCircleSize(circles[j], circles[k]))
00384             continue;
00385           float radius = max(circles[i]->getSemimajor(),
00386                              max(circles[j]->getSemimajor(),
00387                                  circles[k]->getSemimajor()));
00388 
00389           if (fabs(circles[i]->getCentroid().coordX() - circles[j]->getCentroid().coordX()) >= 7*radius) {
00390             //cout << circles[i]->getId() << " too far in x from " << circles[j]->getId() << endl;
00391             continue;
00392           }
00393           if (fabs(circles[v]->getCentroid().coordY() - circles[k]->getCentroid().coordY()) >= 7*radius) {
00394             //cout << circles[v]->getId() << " too far in y from " << circles[k]->getId() << endl;
00395             continue;
00396           }
00397 
00398           Point pi = circles[i]->getCentroid();
00399           Point pj = circles[j]->getCentroid();
00400           Point pk = circles[k]->getCentroid();
00401 
00402           if (!pi.projectToGround(cam_transform, above_ground)) {
00403             cout << "project to ground failed." << endl;
00404             break;
00405           }
00406           if (!pj.projectToGround(cam_transform, above_ground)) {
00407             cout << "project to ground failed." << endl;
00408             break;
00409           }
00410           if (!pk.projectToGround(cam_transform, above_ground)) {
00411             cout << "project to ground failed." << endl;
00412             continue;
00413           }
00414           Point pv = circles[v]->getCentroid();
00415           Point ph = circles[h]->getCentroid();
00416 
00417           if (!compareCircleDist(pi, pj) || !compareCircleDist(pj, pk)) {
00418             // cout << circles[i]->getCentroid() << endl;;
00419             // cout << pi << endl;
00420             // cout << circles[j]->getCentroid() << endl;;
00421             // cout << pj << endl;
00422             continue;
00423           }
00424 
00425                     // cout << "red dot colors: " <<
00426                     //     camFrameYUV(circles[i]->getCentroid().coordX(), circles[i]->getCentroid().coordY()) << " " <<
00427                     //     camFrameYUV(circles[j]->getCentroid().coordX(), circles[j]->getCentroid().coordY()) << " " <<
00428                     //     camFrameYUV(circles[k]->getCentroid().coordX(), circles[k]->getCentroid().coordY()) << endl;
00429 
00430           // cout << "v: " << circles[v]->getId() << endl;
00431           // cout << "h: " << circles[h]->getId() << endl;
00432           // cout << "k: " << circles[k]->getId() << endl;
00433 
00434           int robot_id = getIdFromCorner(camFrameYUV,
00435                                          circles[k]->getCentroid(),
00436                                          circles[v]->getCentroid(),
00437                                          circles[h]->getCentroid());
00438           // cout << "robot id: " << robot_id << endl;
00439 
00440           if (circles[k]->getCentroid().coordY() < pv.coordY()) {
00441             if (pv.coordX() < ph.coordX()) // top right missing
00442               robot_side = AgentData::FRONT;
00443             else // top left missing
00444               robot_side = AgentData::LEFT;
00445           } else {
00446             if (pv.coordX() < ph.coordX()) // bottom right missing
00447               robot_side = AgentData::BACK;
00448             else // bottom left missing
00449               robot_side = AgentData::RIGHT;
00450           }
00451 
00452                     //pi and pj are the vertically aligned circles
00453                     //knowing the radius of the robot we can construct a perpendicular
00454                     //bisector of the line between pi and pj which has length equal to
00455                     //the robot's radius
00456                     //this should give a good estimate of the robot's position
00457           Point diff = (pi - pj);
00458           Point mid = pi + diff/2;
00459           fmat::Matrix<3,3,float> rotate = fmat::rotationZ(-copysign(1.0f,diff.coordY())*M_PI/2);
00460           fmat::Column<3,float> robot_center = robot_radius*rotate*diff.unitVector().getCoords();
00461           Point center(0, 0, 0, egocentric);
00462           center.setCoords(robot_center);
00463           AngTwoPi orient_to_dots((float)center.atanYX());
00464           center += mid;
00465 
00466           // The pattern of the three red circles gives us the side of
00467           // the robot we're looking at, so we can adjust the
00468           // orientation.
00469           AngTwoPi orient;
00470           cout << "Saw agent '" << agent_names[robot_id] << "' ";
00471           switch (robot_side) {
00472           case AgentData::FRONT:
00473             cout << "front";
00474             orient = (float)orient_to_dots + M_PI;
00475             break;
00476           case AgentData::BACK:
00477             cout << "back";
00478             orient = (float)orient_to_dots;
00479             break;
00480           case AgentData::LEFT:
00481             cout << "left";
00482             orient = (float)orient_to_dots + M_PI/2;
00483             break;
00484           case AgentData::RIGHT:
00485             cout << "right";
00486             orient = (float)orient_to_dots + 3*M_PI/2;
00487             break;
00488           }
00489           cout << ", orientation " << float(orient)*180/M_PI << " deg." << endl;
00490 
00491           // Set the name based on the id color and add it to
00492           // localShS.  These will be filtered by matchSrcToDst in the
00493           // MapBuilder which relies on isMatchFor() and
00494           // updateParams() of AgentData.
00495           Shape<AgentData> new_agent(VRmixin::localShS, center);
00496           new_agent->setOrientation(orient);
00497           new_agent->setName(agent_names[robot_id]);
00498         }
00499       }
00500     }
00501   }
00502 
00503   void AgentData::extractAgents(const Sketch<uchar> &camFrame, const Sketch<yuv> &camFrameYUV,
00504                                 const std::set<color_index> &objcolors) {
00505     std::vector<int> neighbor_density;
00506     std::vector<Shape<EllipseData> > agentIdCircles;
00507     findCirclesManual(camFrame, neighbor_density, agentIdCircles);
00508     findAgentsBelt(camFrameYUV, neighbor_density, agentIdCircles);
00509     VRmixin::camShS.deleteShapes(agentIdCircles);
00510   }
00511 
00512   const char* AgentData::agent_names[NUM_COLOR_IDS] = {
00513     "octopus", // blue
00514     "turtle", // green
00515     "cycle", // yellow
00516     "kodu", // black
00517   };
00518 
00519   AgentData::ColorIdTarget AgentData::color_ids[NUM_COLOR_IDS] = {
00520     { 0, -1, -1, 115, 1.086f, 135, -0.638f}, // blue
00521     { 1, -1, -1, 110, 0, 130, -0.186}, // green
00522     { 2, -1, -1, 90, 0, 152, -0.121f},  // yellow
00523     { 3, 0, 16, 128, 0, 128, 0},  // black
00524   };
00525 
00526 } // namespace

DualCoding 5.1CVS
Generated Mon May 9 04:56:25 2016 by Doxygen 1.6.3