00001 #include <algorithm>
00002 #include <cmath>
00003 #include <climits>
00004 #include <map>
00005 #include <vector>
00006
00007 #include "Vision/AprilTags/Edge.h"
00008 #include "Vision/AprilTags/FloatImage.h"
00009 #include "Vision/AprilTags/Gaussian.h"
00010 #include "Vision/AprilTags/GrayModel.h"
00011 #include "Vision/AprilTags/GLine2D.h"
00012 #include "Vision/AprilTags/GLineSegment2D.h"
00013 #include "Vision/AprilTags/Gridder.h"
00014 #include "Vision/AprilTags/Homography33.h"
00015 #include "Vision/AprilTags/MathUtil.h"
00016 #include "Vision/AprilTags/Quad.h"
00017 #include "Vision/AprilTags/Segment.h"
00018 #include "Vision/AprilTags/TagFamily.h"
00019 #include "Vision/AprilTags/UnionFindSimple.h"
00020 #include "Vision/AprilTags/XYWeight.h"
00021
00022 #include "Vision/AprilTags/TagDetector.h"
00023
00024 #include "DualCoding/Sketch.h"
00025
00026 using namespace std;
00027 using namespace DualCoding;
00028
00029 namespace AprilTags {
00030
00031 std::vector<TagDetection> TagDetector::extractTags(const DualCoding::Sketch<DualCoding::uchar> &rawY) {
00032 return extractTags(FloatImage(rawY));
00033 }
00034
00035 std::vector<TagDetection> TagDetector::extractTags(const FloatImage& fimOrig) {
00036
00037
00038
00039
00040 FloatImage fim = fimOrig;
00041
00042
00043
00044
00045
00046
00047
00048
00049
00050 float sigma = 0;
00051
00052
00053
00054
00055
00056
00057
00058
00059 float segSigma = 0.8f;
00060
00061 if (sigma > 0) {
00062 int filtsz = ((int) max(3.0f, 3*sigma)) | 1;
00063 std::vector<float> filt = Gaussian::makeGaussianFilter(sigma, filtsz);
00064 fim.filterFactoredCentered(filt, filt);
00065 }
00066
00067
00068
00069
00070
00071
00072
00073 FloatImage fimSeg;
00074 if (segSigma > 0) {
00075 if (segSigma == sigma) {
00076 fimSeg = fim;
00077 } else {
00078
00079 int filtsz = ((int) max(3.0f, 3*segSigma)) | 1;
00080 std::vector<float> filt = Gaussian::makeGaussianFilter(segSigma, filtsz);
00081 fimSeg = fimOrig;
00082 fimSeg.filterFactoredCentered(filt, filt);
00083 }
00084 } else {
00085 fimSeg = fimOrig;
00086 }
00087
00088 FloatImage fimTheta(fimSeg.getWidth(), fimSeg.getHeight());
00089 FloatImage fimMag(fimSeg.getWidth(), fimSeg.getHeight());
00090
00091 for (int y = 1; y+1 < fimSeg.getHeight(); y++) {
00092 for (int x = 1; x+1 < fimSeg.getWidth(); x++) {
00093 float Ix = fimSeg.get(x+1, y) - fimSeg.get(x-1, y);
00094 float Iy = fimSeg.get(x, y+1) - fimSeg.get(x, y-1);
00095
00096 float mag = Ix*Ix + Iy*Iy;
00097 float theta = atan2(Iy, Ix);
00098
00099 fimTheta.set(x, y, theta);
00100 fimMag.set(x, y, mag);
00101 }
00102 }
00103
00104
00105
00106
00107
00108
00109
00110
00111
00112
00113
00114
00115
00116
00117
00118
00119
00120
00121
00122
00123
00124
00125
00126
00127
00128
00129
00130
00131
00132
00133
00134
00135 UnionFindSimple uf(fimSeg.getWidth()*fimSeg.getHeight());
00136
00137 int width = fimSeg.getWidth();
00138 int height = fimSeg.getHeight();
00139
00140 vector<Edge> edges(width*height*4);
00141 size_t nEdges = 0;
00142
00143
00144
00145
00146 {
00147
00148
00149
00150 vector<float> storage(width*height*4);
00151 float * tmin = &storage[width*height*0];
00152 float * tmax = &storage[width*height*1];
00153 float * mmin = &storage[width*height*2];
00154 float * mmax = &storage[width*height*3];
00155
00156 for (int y = 0; y+1 < height; y++) {
00157 for (int x = 0; x+1 < width; x++) {
00158
00159 float mag0 = fimMag.get(x,y);
00160 if (mag0 < Edge::minMag)
00161 continue;
00162 mmax[y*width+x] = mag0;
00163 mmin[y*width+x] = mag0;
00164
00165 float theta0 = fimTheta.get(x,y);
00166 tmin[y*width+x] = theta0;
00167 tmax[y*width+x] = theta0;
00168
00169
00170 Edge::calcEdges(theta0, x, y, fimTheta, fimMag, edges, nEdges);
00171
00172
00173
00174 }
00175 }
00176
00177 edges.resize(nEdges);
00178 std::stable_sort(edges.begin(), edges.end());
00179 Edge::mergeEdges(edges,uf,tmin,tmax,mmin,mmax);
00180 }
00181
00182
00183
00184
00185
00186 map<int, vector<XYWeight> > clusters;
00187 for (int y = 0; y+1 < fimSeg.getHeight(); y++) {
00188 for (int x = 0; x+1 < fimSeg.getWidth(); x++) {
00189 if (uf.getSetSize(y*fimSeg.getWidth()+x) < Segment::minimumSegmentSize)
00190 continue;
00191
00192 int rep = (int) uf.getRepresentative(y*fimSeg.getWidth()+x);
00193
00194 map<int, vector<XYWeight> >::iterator it = clusters.find(rep);
00195 if ( it == clusters.end() ) {
00196 clusters[rep] = vector<XYWeight>();
00197 it = clusters.find(rep);
00198 }
00199 vector<XYWeight> &points = it->second;
00200 points.push_back(XYWeight(x,y,fimMag.get(x,y)));
00201 }
00202 }
00203
00204
00205
00206 std::vector<Segment> segments;
00207 std::map<int, std::vector<XYWeight> >::const_iterator clustersItr;
00208 for (clustersItr = clusters.begin(); clustersItr != clusters.end(); clustersItr++) {
00209 std::vector<XYWeight> points = clustersItr->second;
00210 GLineSegment2D gseg = GLineSegment2D::lsqFitXYW(points);
00211
00212
00213 float length = MathUtil::distance2D(gseg.getP0(), gseg.getP1());
00214 if (length < Segment::minimumLineLength)
00215 continue;
00216
00217 Segment seg;
00218 float dy = gseg.getP1().second - gseg.getP0().second;
00219 float dx = gseg.getP1().first - gseg.getP0().first;
00220
00221 float tmpTheta = std::atan2(dy,dx);
00222
00223 seg.setTheta(tmpTheta);
00224 seg.setLength(length);
00225
00226
00227
00228
00229
00230
00231
00232
00233 float flip = 0, noflip = 0;
00234 for (unsigned int i = 0; i < points.size(); i++) {
00235 XYWeight xyw = points[i];
00236
00237 float theta = fimTheta.get((int) xyw.x, (int) xyw.y);
00238 float mag = fimMag.get((int) xyw.x, (int) xyw.y);
00239
00240
00241
00242 float err = MathUtil::mod2pi(theta - seg.getTheta());
00243
00244 if (err < 0)
00245 noflip += mag;
00246 else
00247 flip += mag;
00248 }
00249
00250 if (flip > noflip) {
00251 float temp = seg.getTheta() + (float)M_PI;
00252 seg.setTheta(temp);
00253 }
00254
00255 float dot = dx*std::cos(seg.getTheta()) + dy*std::sin(seg.getTheta());
00256 if (dot > 0) {
00257 seg.setX0(gseg.getP1().first); seg.setY0(gseg.getP1().second);
00258 seg.setX1(gseg.getP0().first); seg.setY1(gseg.getP0().second);
00259 }
00260 else {
00261 seg.setX0(gseg.getP0().first); seg.setY0(gseg.getP0().second);
00262 seg.setX1(gseg.getP1().first); seg.setY1(gseg.getP1().second);
00263 }
00264
00265 segments.push_back(seg);
00266 }
00267
00268
00269
00270
00271
00272
00273
00274
00275
00276
00277
00278
00279
00280
00281
00282
00283
00284
00285
00286 Gridder<Segment> gridder(0,0,width,height,10);
00287
00288
00289
00290
00291 for (unsigned int i = 0; i < segments.size(); i++) {
00292 gridder.add(segments[i].getX0(), segments[i].getY0(), &segments[i]);
00293 }
00294
00295
00296 for (unsigned i = 0; i < segments.size(); i++) {
00297 Segment &parentseg = segments[i];
00298
00299
00300 GLine2D parentLine(std::pair<float,float>(parentseg.getX0(), parentseg.getY0()),
00301 std::pair<float,float>(parentseg.getX1(), parentseg.getY1()));
00302
00303 Gridder<Segment>::iterator iter = gridder.find(parentseg.getX1(), parentseg.getY1(), 0.5f*parentseg.getLength());
00304 while(iter.hasNext()) {
00305 Segment &child = iter.next();
00306 if (MathUtil::mod2pi(child.getTheta() - parentseg.getTheta()) > 0) {
00307 continue;
00308 }
00309
00310
00311 GLine2D childLine(std::pair<float,float>(child.getX0(), child.getY0()),
00312 std::pair<float,float>(child.getX1(), child.getY1()));
00313
00314 std::pair<float,float> p = parentLine.intersectionWith(childLine);
00315 if (p.first == -1) {
00316 continue;
00317 }
00318
00319 float parentDist = MathUtil::distance2D(p, std::pair<float,float>(parentseg.getX1(),parentseg.getY1()));
00320 float childDist = MathUtil::distance2D(p, std::pair<float,float>(child.getX0(),child.getY0()));
00321
00322 if (max(parentDist,childDist) > parentseg.getLength()) {
00323
00324 continue;
00325 }
00326
00327
00328 parentseg.children.push_back(&child);
00329 }
00330 }
00331
00332
00333
00334
00335
00336
00337
00338
00339
00340
00341
00342
00343
00344
00345
00346
00347
00348 vector<Quad> quads;
00349
00350 vector<Segment*> tmp(5);
00351 for (unsigned int i = 0; i < segments.size(); i++) {
00352 tmp[0] = &segments[i];
00353 Quad::search(fimOrig, tmp, segments[i], 0, quads);
00354 }
00355
00356
00357
00358
00359
00360
00361
00362
00363
00364
00365
00366
00367
00368
00369
00370
00371
00372
00373
00374
00375
00376
00377
00378
00379
00380
00381
00382
00383
00384
00385
00386
00387
00388
00389
00390
00391
00392
00393
00394
00395 std::vector<TagDetection> detections;
00396
00397 for (unsigned int qi = 0; qi < quads.size(); qi++ ) {
00398 Quad &quad = quads[qi];
00399
00400
00401 GrayModel blackModel, whiteModel;
00402 const int dd = 2 * thisTagFamily.blackBorder + thisTagFamily.dimension;
00403
00404 for (int iy = -1; iy <= dd; iy++) {
00405 float y = (iy + 0.5f) / dd;
00406 for (int ix = -1; ix <= dd; ix++) {
00407 float x = (ix + 0.5f) / dd;
00408 std::pair<float,float> pxy = quad.interpolate01(x, y);
00409 int irx = (int) (pxy.first + 0.5);
00410 int iry = (int) (pxy.second + 0.5);
00411 if (irx < 0 || irx >= width || iry < 0 || iry >= height)
00412 continue;
00413 float v = fim.get(irx, iry);
00414 if (iy == -1 || iy == dd || ix == -1 || ix == dd)
00415 whiteModel.addObservation(x, y, v);
00416 else if (iy == 0 || iy == (dd-1) || ix == 0 || ix == (dd-1))
00417 blackModel.addObservation(x, y, v);
00418 }
00419 }
00420
00421 bool bad = false;
00422 unsigned long long tagCode = 0;
00423 for ( int iy = thisTagFamily.dimension-1; iy >= 0; iy-- ) {
00424 float y = (thisTagFamily.blackBorder + iy + 0.5f) / dd;
00425 for (int ix = 0; ix < thisTagFamily.dimension; ix++ ) {
00426 float x = (thisTagFamily.blackBorder + ix + 0.5f) / dd;
00427 std::pair<float,float> pxy = quad.interpolate01(x, y);
00428 int irx = (int) (pxy.first + 0.5);
00429 int iry = (int) (pxy.second + 0.5);
00430 if (irx < 0 || irx >= width || iry < 0 || iry >= height) {
00431 cout << "*** bad: irx=" << irx << " iry=" << iry << endl;
00432 bad = true;
00433 continue;
00434 }
00435 float threshold = (blackModel.interpolate(x,y) + whiteModel.interpolate(x,y)) * 0.5f;
00436 float v = fim.get(irx, iry);
00437
00438
00439
00440
00441
00442
00443
00444
00445
00446 tagCode = tagCode << 1;
00447 if ( v > threshold)
00448 tagCode |= 1;
00449 }
00450 }
00451
00452 if ( !bad ) {
00453 TagDetection thisTagDetection;
00454 thisTagFamily.decode(thisTagDetection, tagCode);
00455
00456
00457
00458
00459 thisTagDetection.homography = quad.homography.getH();
00460 thisTagDetection.hxy = quad.homography.getCXY();
00461
00462 float c = std::cos(thisTagDetection.rotation*(float)M_PI/2);
00463 float s = std::sin(thisTagDetection.rotation*(float)M_PI/2);
00464 fmat::Matrix<3,3> R;
00465 R(0,0) = R(1,1) = c;
00466 R(0,1) = -s;
00467 R(1,0) = s;
00468 R(2,2) = 1;
00469 thisTagDetection.homography *= R;
00470
00471
00472
00473
00474
00475 std::pair<float,float> bottomLeft = thisTagDetection.interpolate(-1,-1);
00476 int bestRot = -1;
00477 float bestDist = FLT_MAX;
00478 bool debugging = false;
00479 if ( debugging )
00480 std::cout << " tag id=" << thisTagDetection.id << " rot=" << thisTagDetection.rotation;
00481 for ( int i=0; i<4; i++ ) {
00482 float const dist = AprilTags::MathUtil::distance2D(bottomLeft, quad.quadPoints[i]);
00483 if ( debugging )
00484 std::cout << std::setw(12) << dist;
00485 if ( dist < bestDist ) {
00486 bestDist = dist;
00487 bestRot = i;
00488 }
00489 }
00490 for (int i=0; i < 4; i++)
00491 thisTagDetection.p[i] = quad.quadPoints[(i+bestRot) % 4];
00492 if ( debugging ) {
00493 const TagDetection &d = thisTagDetection;
00494 std::cout << " best=" << bestRot << endl;
00495 std::cout << " " << d.p[0].first << " " << d.p[1].first
00496 << " " << d.p[2].first << " " << d.p[3].first << std::endl;
00497 std::cout << R.fmt("%8.5f") << std::endl << thisTagDetection.homography.fmt("%8.5f") << std::endl;
00498 }
00499
00500 if (thisTagDetection.good) {
00501 thisTagDetection.cxy = quad.interpolate01(0.5f, 0.5f);
00502 thisTagDetection.observedPerimeter = quad.observedPerimeter;
00503 detections.push_back(thisTagDetection);
00504 }
00505 }
00506 }
00507
00508
00509
00510
00511
00512
00513
00514
00515 std::vector<TagDetection> goodDetections;
00516
00517
00518
00519 for ( vector<TagDetection>::const_iterator it = detections.begin();
00520 it != detections.end(); it++ ) {
00521 const TagDetection &thisTagDetection = *it;
00522
00523 bool newFeature = true;
00524
00525 for ( unsigned int odidx = 0; odidx < goodDetections.size(); odidx++) {
00526 TagDetection &otherTagDetection = goodDetections[odidx];
00527
00528 if ( thisTagDetection.id != otherTagDetection.id ||
00529 ! thisTagDetection.overlapsTooMuch(otherTagDetection) )
00530 continue;
00531
00532
00533 newFeature = false;
00534
00535
00536 if ( thisTagDetection.hammingDistance > otherTagDetection.hammingDistance )
00537 continue;
00538
00539
00540 if ( thisTagDetection.hammingDistance < otherTagDetection.hammingDistance ||
00541 thisTagDetection.observedPerimeter > otherTagDetection.observedPerimeter )
00542 goodDetections[odidx] = thisTagDetection;
00543 }
00544
00545 if ( newFeature )
00546 goodDetections.push_back(thisTagDetection);
00547
00548
00549
00550
00551
00552
00553
00554
00555
00556
00557
00558
00559
00560
00561 }
00562
00563
00564
00565
00566
00567
00568
00569
00570
00571
00572 return goodDetections;
00573 }
00574
00575 }