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
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 tagCode = tagCode << 1;
00438 if ( v > threshold)
00439 tagCode |= 1;
00440 }
00441 }
00442
00443 if ( !bad ) {
00444 TagDetection thisTagDetection;
00445 thisTagFamily.decode(thisTagDetection, tagCode);
00446
00447
00448 thisTagDetection.homography = quad.homography.getH();
00449 thisTagDetection.hxy = quad.homography.getCXY();
00450
00451 float c = std::cos(thisTagDetection.rotation*(float)M_PI/2);
00452 float s = std::sin(thisTagDetection.rotation*(float)M_PI/2);
00453 fmat::Matrix<3,3> R;
00454 R(0,0) = R(1,1) = c;
00455 R(0,1) = -s;
00456 R(1,0) = s;
00457 R(2,2) = 1;
00458 thisTagDetection.homography *= R;
00459
00460
00461
00462
00463
00464 std::pair<float,float> bottomLeft = thisTagDetection.interpolate(-1,-1);
00465 int bestRot = -1;
00466 float bestDist = FLT_MAX;
00467 for ( int i=0; i<4; i++ ) {
00468 float const dist = AprilTags::MathUtil::distance2D(bottomLeft, quad.quadPoints[i]);
00469 if ( dist < bestDist ) {
00470 bestDist = dist;
00471 bestRot = i;
00472 }
00473 }
00474 for (int i=0; i< 4; i++)
00475 thisTagDetection.p[i] = quad.quadPoints[(i+bestRot) % 4];
00476
00477 if (thisTagDetection.good) {
00478 thisTagDetection.cxy = quad.interpolate01(0.5f, 0.5f);
00479 thisTagDetection.observedPerimeter = quad.observedPerimeter;
00480 detections.push_back(thisTagDetection);
00481 }
00482 }
00483 }
00484
00485
00486
00487
00488
00489
00490
00491
00492 std::vector<TagDetection> goodDetections;
00493
00494
00495
00496 for ( vector<TagDetection>::const_iterator it = detections.begin();
00497 it != detections.end(); it++ ) {
00498 const TagDetection &thisTagDetection = *it;
00499
00500 bool newFeature = true;
00501
00502 for ( unsigned int odidx = 0; odidx < goodDetections.size(); odidx++) {
00503 TagDetection &otherTagDetection = goodDetections[odidx];
00504
00505 if ( thisTagDetection.id != otherTagDetection.id ||
00506 ! thisTagDetection.overlapsTooMuch(otherTagDetection) )
00507 continue;
00508
00509
00510 newFeature = false;
00511
00512
00513 if ( thisTagDetection.hammingDistance > otherTagDetection.hammingDistance )
00514 continue;
00515
00516
00517 if ( thisTagDetection.hammingDistance < otherTagDetection.hammingDistance ||
00518 thisTagDetection.observedPerimeter > otherTagDetection.observedPerimeter )
00519 goodDetections[odidx] = thisTagDetection;
00520 }
00521
00522 if ( newFeature )
00523 goodDetections.push_back(thisTagDetection);
00524
00525
00526
00527
00528
00529
00530
00531
00532
00533
00534
00535
00536
00537
00538 }
00539
00540 cout << "AprilTags: edges=" << nEdges << " clusters=" << clusters.size() << " segments=" << segments.size()
00541 << " quads=" << quads.size() << " detections=" << detections.size() << " unique tags=" << goodDetections.size() << endl;
00542
00543 return goodDetections;
00544 }
00545
00546 }