Homography.cc
Go to the documentation of this file.
00001 
00008 #include <blort/Recognizer3D/Homography.hh>
00009 
00010 
00011 #ifndef M_SQRT2
00012 #define M_SQRT2  1.41421356237309504880  // sqrt(2) 
00013 #endif
00014 
00015 //#define DEBUG
00016 #define HOM_RANSAC_ETA0 0.01 //0.01
00017 #define HOM_MAX_RANSAC_TRIALS 10000
00018 
00019 namespace P 
00020 {
00021 
00022 Homography::Homography()
00023  : lastPt(0), lastLn(0), T1(0), invT(0)
00024 {
00025 }
00026 
00027 Homography::Homography(unsigned nbPoints, unsigned nbLines)
00028  : lastPt(0), lastLn(0), T1(0), invT(0)
00029 {
00030   if (nbPoints>0)
00031   {
00032     pts1.Resize(nbPoints);
00033     pts2.Resize(nbPoints);
00034   }
00035   if (nbLines>0)
00036   {
00037     lns1.Resize(nbLines);
00038     lns2.Resize(nbLines);
00039   }
00040 }
00041 
00042 Homography::~Homography()
00043 {
00044   if (T1!=0) delete[] T1;
00045   if (invT!=0) delete[] invT;
00046 }
00047 
00048 /************************************* PRIVATE ***************************************/
00052 void Homography::GetRandIdx(unsigned size, unsigned num, P::Array<unsigned> &idx)
00053 {
00054   unsigned temp;
00055   idx.Clear();
00056   for (unsigned i=0; i<num; i++)
00057   {
00058     do{
00059       temp = rand()%size;
00060     }while(idx.Contains(temp));
00061     idx.PushBack(temp);
00062   }
00063 }
00064 
00068 void Homography::CountInlierPts(double H[9], double thr, int &inl)
00069 {
00070   Vector2 p;
00071  
00072   for (unsigned i=0; i<lastPt; i++)
00073   {
00074     MapPoint(&pts1[i].x, H, &p.x);
00075 
00076     if (Distance(p,pts2[i]) < thr)
00077       inl++;
00078   }
00079 }
00080 
00084 void Homography::CountInlierLns(double H[9], double thr, double thrAngle, int &inl)
00085 {
00086   double HT[9];
00087   Transpose33(H, HT);
00088 
00089   double thrAng = cos(thrAngle * M_PI/180.);         //for lines: inl = alpha [°]
00090 
00091   double k1, d1, k2, d2, dist1, dist2;
00092   Vector2 l2, dir1, dir2;
00093   Vector2 p1, p2, p0 = Vector2(0.,0.);
00094 
00095   for (unsigned i=0; i<lastLn; i++)
00096   {
00097     MapPoint(&lns2[i].x, HT, &l2.x);
00098 
00099     k1 = -lns1[i].x/lns1[i].y;
00100     d1 = -1./lns1[i].y;
00101     dir1.y = d1;
00102     dir1.x = 1.;
00103     dir1.Normalise();
00104 
00105     k2 = -l2.x/l2.y;
00106     d2 = -1./l2.y;
00107     dir2.y = d2;
00108     dir2.x = 1.;
00109     dir2.Normalise();
00110 
00111     p1.x = 1.;
00112     p1.y = k1+d1;
00113     p2.x = 1.;
00114     p2.y = k2+d2;
00115 
00116     dist1 = AbsDistPointToLine(p0, p1, dir1);
00117     dist2 = AbsDistPointToLine(p0, p2, dir2);
00118 
00119     if (fabs(dist1-dist2) < thr)
00120     {
00121       if (fabs(Dot(dir1,dir2)) >  thrAng)
00122       {
00123         inl++;
00124       }
00125     }
00126   }
00127 
00128 }
00129 
00133 void Homography::SetDataMatrix(double *d, double H[9], double thr, double thrAngle, int &numInl)
00134 {
00135   numInl=0;
00136 
00137   //set inlier points
00138   Vector2 p;
00139 
00140   outl.Resize(lastPt);
00141   outl.Set(0);
00142    
00143   for (unsigned i=0; i<lastPt; i++)
00144   {
00145     MapPoint(&pts1[i].x, H, &p.x);
00146 
00147     if (Distance(p,pts2[i]) < thr)
00148     {
00149       InsertPointCoeff(pts1[i], pts2[i], &d[numInl*18]);
00150       numInl++;
00151     }
00152     else
00153     {
00154        outl[i] = 1;
00155     }
00156   }
00157 
00158   //set inlier lines
00159   double HT[9];
00160   Transpose33(H, HT);
00161 
00162   double thrAng = cos(thrAngle * M_PI/180.);         //for lines: inl = alpha [°]
00163 
00164   double k1, d1, k2, d2, dist1, dist2;
00165   Vector2 l2, dir1, dir2;
00166   Vector2 p1, p2, p0 = Vector2(0.,0.);
00167 
00168   outlLns.Resize(lastLn);
00169   outlLns.Set(0);
00170   for (unsigned i=0; i<lastLn; i++)
00171   {
00172     MapPoint(&lns2[i].x, HT, &l2.x);
00173 
00174     k1 = -lns1[i].x/lns1[i].y;
00175     d1 = -1./lns1[i].y;
00176     dir1.y = d1;
00177     dir1.x = 1.;
00178     dir1.Normalise();
00179 
00180     k2 = -l2.x/l2.y;
00181     d2 = -1./l2.y;
00182     dir2.y = d1;
00183     dir2.x = 1.;
00184     dir2.Normalise();
00185 
00186     p1.x = 1.;
00187     p1.y = k1+d1;
00188     p2.x = 1.;
00189     p2.y = k2+d2;
00190 
00191     dist1 = AbsDistPointToLine(p0, p1, dir1);
00192     dist2 = AbsDistPointToLine(p0, p2, dir2);
00193 
00194     if (fabs(dist1-dist2) < thr)
00195     {
00196       if (fabs(Dot(dir1, dir2)) >  thrAng)
00197       {
00198         InsertLineCoeff(lns1[i], lns2[i], &d[numInl*18]);
00199         numInl++;
00200       }
00201       else
00202         outlLns[i] = 1;
00203     }
00204     else
00205       outlLns[i] = 1;
00206   }
00207 
00208 }
00209 
00213 bool Homography::SolveHomography(CvMat *A, CvMat *W, CvMat *Ut, CvMat *Vt, double H[9])
00214 {
00215   cvSVD( A, W, Ut, Vt, CV_SVD_MODIFY_A);
00216 
00217   // determine A's rank
00218   unsigned z;
00219   for(z=0; z<9; z++)
00220     if(IsZero(cvmGet(W,z,z))) break; // remaining singular values are smaller than this 
00221 
00222   if(z<8)
00223     return false; //A should have rank n-1
00224 
00225   // last is smallest singular value
00226   for (unsigned i=0; i<9; i++)
00227     H[i] = cvmGet(Vt,i,8);
00228 
00229   return true;
00230 }
00231 
00235 void Homography::NormalizePts(Array<Vector2> &in, unsigned numPts, Array<Vector2> &out, double T[9])
00236 {
00237   if (numPts==0)
00238     return;
00239 
00240   double centx=0., centy=0.;
00241   double dist, scale;
00242 
00243   for(unsigned i=0; i<numPts; i++)
00244   {
00245     centx+=in[i].x;
00246     centy+=in[i].y;
00247   }
00248 
00249   centx/=(double)(numPts);
00250   centy/=(double)(numPts);
00251 
00252   out.Resize(numPts);
00253   for(unsigned i=0; i<numPts; i++)
00254   {
00255     out[i].x = in[i].x-centx;
00256     out[i].y = in[i].y-centy;
00257   }
00258 
00259   dist=0.0;
00260   for(unsigned i=0; i<numPts; i++)
00261     dist += Length(out[i]);
00262 
00263   dist/=(double)(numPts);
00264   scale = M_SQRT2/dist;
00265 
00266   for (unsigned i=0; i<numPts; i++)
00267     out[i]*=scale;
00268 
00269   T[0]=scale; T[1]=0.0;   T[2]=-centx*scale;
00270   T[3]=0.0;   T[4]=scale; T[5]=-centy*scale;
00271   T[6]=0.0;   T[7]=0.0;   T[8]=1.0;
00272 }
00273 
00274 
00278 void Homography::NormalizeLns(double T[9], Array<Vector2> &in, unsigned numLns, Array<Vector2> &out)
00279 {
00280   if (numLns==0)
00281     return;
00282 
00283   double t;
00284   out.Resize(numLns);
00285  
00286   for (unsigned i=0; i<numLns; i++)
00287   {
00288     t = 1./(T[0] + T[2]*in[i].x + T[5]*in[i].y);
00289     out[i].x = in[i].x*t;
00290     out[i].y = in[i].y*t;
00291   }
00292 }
00293 
00297 void Homography::SetDataMatrixPts(Array<Vector2> &pts1, Array<Vector2> &pts2, unsigned numPts, unsigned start, CvMat *A)
00298 {
00299   if (numPts==0)
00300     return;
00301 
00302   double t1, t2, t3, t4;
00303   double *d = A->data.db + (start*2)*A->cols;  
00304 
00305   for (unsigned i=0; i<numPts; i++,d+=18)
00306   {
00307     t1 = pts1[i].x;
00308     t2 = pts1[i].y;
00309     t3 = pts2[i].x;
00310     t4 = pts2[i].y;
00311 
00312     d[0] = -t1;
00313     d[1] = -t2;
00314     d[2] = -1.0;
00315     d[3] = 0.0;
00316     d[4] = 0.0;
00317     d[5] = 0.0;
00318     d[6] = t3*t1;
00319     d[7] = t2*t3;
00320     d[8] = t3;
00321     d[9] = 0.0;
00322     d[10] = 0.0;
00323     d[11] = 0.0;
00324     d[12] = d[0];
00325     d[13] = d[1];
00326     d[14] = -1.0;
00327     d[15] = t4*t1;
00328     d[16] = t4*t2;
00329     d[17] = t4;
00330   }
00331 }
00332 
00336 void Homography::SetDataMatrixLns(Array<Vector2> &lns1, Array<Vector2> &lns2, unsigned numLns, unsigned start, CvMat *A)
00337 {
00338   if (numLns==0)
00339     return;
00340 
00341   double t1, t2, t3, t4;
00342   double *d = A->data.db + (start*2)*A->cols;  
00343 
00344   for (unsigned i=0; i<numLns; i++,d+=18)
00345   {
00346     t1 = lns1[i].x;  //x
00347     t2 = lns1[i].y;  //y
00348     t3 = lns2[i].x;  //u
00349     t4 = lns2[i].y;  //v
00350 
00351     d[0] = -t3;
00352     d[1] = 0.;
00353     d[2] = t3*t1;
00354     d[3] = -t4;
00355     d[4] = 0.;
00356     d[5] = t4*t1;
00357     d[6] = -1;
00358     d[7] = 0.;
00359     d[8] = t1;
00360     d[9] = 0.;
00361     d[10] = -t3;
00362     d[11] = t3*t2;
00363     d[12] = 0.;
00364     d[13] = -t4;
00365     d[14] = t4*t2;
00366     d[15] = 0.;
00367     d[16] = -1;
00368     d[17] = t2;
00369   }
00370 }
00371 
00376 void Homography::NormalizeH(double H[9], double T1[9], double T2[9], double nH[9])
00377 {
00378   double T1_1[9], tmp[9];
00379 
00380   Inv33(T1, T1_1);
00381   Mul33(T2, H, tmp);     // tmp = T2 * H
00382   Mul33(tmp, T1_1, nH);  // nH = T2 * H * T1^-1
00383 }
00384 
00389 void Homography::DenormalizeH(double nH[9], double T1[9], double T2[9], double H[9])
00390 {
00391   double T2_1[9], tmp[9];
00392 
00393   Inv33(T2, T2_1);
00394   Mul33(T2_1, nH, tmp);    // tmp = T2^-1 * nH 
00395   Mul33(tmp, T1, H);       // H = T2^-1 * nH * T1 
00396 }
00397 
00398 
00399 
00400 
00401 
00402 
00403 
00404 
00405 
00406 /************************************* PUBLIC ****************************************/
00407 
00408 
00414 bool Homography::EstimateHom(double H[9], int &nbOutliers, double inlPcent, bool returnOutl, int normalize, int NLrefine, int verbose)
00415 {
00416   int err;
00417 
00418   if (!returnOutl)
00419   {
00420     err = homest((double (*)[2])(&pts1[0]), (double (*)[2])(&pts2[0]), lastPt, inlPcent, H,
00421               normalize, NLrefine, 0, &nbOutliers, verbose);
00422   }
00423   else
00424   {
00425     Array<int> tmpOutl(pts1.Size());
00426     err = homest((double (*)[2])(&pts1[0]), (double (*)[2])(&pts2[0]), lastPt, inlPcent, H,
00427               normalize, NLrefine, &tmpOutl[0], &nbOutliers, verbose);
00428 
00429     outl.Resize(pts1.Size());
00430     outl.Set(0);
00431     for (int i=0; i<nbOutliers; i++)
00432     {
00433       outl[tmpOutl[i]] = 1;
00434     }
00435   }
00436 
00437   if (err==HOMEST_ERR)
00438     return false;
00439   return true;
00440 }
00441 
00446 bool Homography::EstimateAff(double H[9], int &nbOutliers, double inlPcent, bool returnOutl,  int normalize, int verbose)
00447 {
00448   int err;
00449 
00450   if (!returnOutl)
00451   {
00452     err = homestaff((double (*)[2])(&pts1[0]), (double (*)[2])(&pts2[0]), lastPt, inlPcent, H,
00453                     normalize, 0, &nbOutliers, verbose);
00454   }
00455   else
00456   {
00457     outl.Resize(pts1.Size());
00458     err = homestaff((double (*)[2])(&pts1[0]), (double (*)[2])(&pts2[0]), lastPt, inlPcent, H,
00459                         normalize, &outl[0], &nbOutliers, verbose);
00460   }
00461 
00462   if (err==HOMEST_ERR)
00463     return false;
00464   return true;
00465 }
00466 
00470 bool Homography::ComputeHom(double a1[2], double a2[2], double a3[2], double a4[2], 
00471                             double b1[2], double b2[2], double b3[2], double b4[2],
00472                             double H[9])
00473 {
00474   //double T1[9], invT[9];
00475   
00476   if (T1==0) T1 = new double[9];
00477   if (invT==0) invT = new double[9];
00478 
00479   // compute mapping of four (a) points to a unit square
00480   double s = (a2[0]-a3[0]) * (a4[1]-a3[1]) - (a4[0]-a3[0]) * (a2[1]-a3[1]);
00481 
00482   if (IsZero(s)) return false;
00483 
00484   T1[6] = ((a1[0]-a2[0]+a3[0]-a4[0])*(a4[1]-a3[1]) - 
00485            (a1[1]-a2[1]+a3[1]-a4[1])*(a4[0]-a3[0])) / s;
00486   T1[7] = ((a1[1]-a2[1]+a3[1]-a4[1])*(a2[0]-a3[0]) - 
00487            (a1[0]-a2[0]+a3[0]-a4[0])*(a2[1]-a3[1])) / s;
00488   T1[8] = 1.;
00489 
00490   T1[0] = a2[0]-a1[0] + T1[6]*a2[0];  T1[1] = a4[0]-a1[0] + T1[7]*a4[0];  T1[2] = a1[0];
00491   T1[3] = a2[1]-a1[1] + T1[6]*a2[1];  T1[4] = a4[1]-a1[1] + T1[7]*a4[1];  T1[5] = a1[1];
00492 
00493   if (!Inv33(T1, invT)) return false;
00494 
00495   // compute mapping of a unit square to four points
00496   s = (b2[0]-b3[0]) * (b4[1]-b3[1]) - (b4[0]-b3[0]) * (b2[1]-b3[1]);
00497   
00498   if (IsZero(s)) return false;
00499 
00500   T1[6] = ((b1[0]-b2[0]+b3[0]-b4[0])*(b4[1]-b3[1]) -
00501            (b1[1]-b2[1]+b3[1]-b4[1])*(b4[0]-b3[0])) / s;
00502   T1[7] = ((b1[1]-b2[1]+b3[1]-b4[1])*(b2[0]-b3[0]) -
00503            (b1[0]-b2[0]+b3[0]-b4[0])*(b2[1]-b3[1])) / s;
00504   T1[8] = 1.;
00505 
00506   T1[0] = b2[0]-b1[0] + T1[6]*b2[0];  T1[1] = b4[0]-b1[0] + T1[7]*b4[0];  T1[2] = b1[0];
00507   T1[3] = b2[1]-b1[1] + T1[6]*b2[1];  T1[4] = b4[1]-b1[1] + T1[7]*b4[1];  T1[5] = b1[1];
00508   
00509   // compute mapping four points (a) to four points (b)
00510   Mul33(T1,invT,H); 
00511 
00512   if (IsZero(H[8]))
00513     return false;
00514 
00515   Mul33(H, 1./H[8], H);
00516 
00517   return true;
00518 }
00519 
00523 bool Homography::ComputeAff(double a1[2], double a2[2], double a3[2], 
00524                             double b1[2], double b2[2], double b3[2], 
00525                             double H[9])
00526 {
00527     double S;
00528 
00529     S = a1[0]*(a3[1]-a2[1]) + 
00530         a2[0]*(a1[1]-a3[1]) +
00531         a3[0]*(a2[1]-a1[1]);
00532 
00533     if (IsZero(S)) return false;
00534 
00535       S=1/S;
00536 
00537       //calculation of the affine parameter
00538       H[0] = S * (a1[1]*(b2[0]-b3[0]) + a2[1]*(b3[0]-b1[0])+ a3[1]*(b1[0]-b2[0]));
00539       H[1] = S * (a1[0]*(b3[0]-b2[0]) + a2[0]*(b1[0]-b3[0])+ a3[0]*(b2[0]-b1[0]));
00540       H[3] = S * (a1[1]*(b2[1]-b3[1]) + a2[1]*(b3[1]-b1[1])+ a3[1]*(b1[1]-b2[1]));
00541       H[4] = S * (a1[0]*(b3[1]-b2[1]) + a2[0]*(b1[1]-b3[1])+ a3[0]*(b2[1]-b1[1]));
00542       H[2] = S * (a1[0]*(a3[1]*b2[0]-a2[1]*b3[0])+
00543                   a2[0]*(a1[1]*b3[0]-a3[1]*b1[0])+
00544                   a3[0]*(a2[1]*b1[0]-a1[1]*b2[0]));
00545       H[5] = S * (a1[0]*(a3[1]*b2[1]-a2[1]*b3[1])+
00546                   a2[0]*(a1[1]*b3[1]-a3[1]*b1[1])+
00547                   a3[0]*(a2[1]*b1[1]-a1[1]*b2[1]));
00548       H[6] = 0;
00549       H[7] = 0;
00550       H[8] = 1;
00551 
00552   return true;
00553 }
00554 
00555 
00560 bool Homography::ComputeHomLS(double H[9], bool normalize)
00561 {
00562   if ((normalize && lastPt<2) || lastPt+lastLn < 5)
00563     return false;
00564 
00565   bool ok;
00566   unsigned size = 2*(lastPt+lastLn);
00567 
00568   CvMat *A = cvCreateMat(size, 9, CV_64F);
00569   CvMat *W = cvCreateMat(9, 9, CV_64F);
00570   CvMat *Ut = cvCreateMat(size,9, CV_64F);
00571   CvMat *Vt = cvCreateMat(9, 9, CV_64F);
00572   double T1[9], T2[9];
00573 
00574   if (normalize)
00575   {
00576     Array<Vector2> npts1, npts2;
00577     Array<Vector2> nlns1, nlns2;
00578 
00579     NormalizePts(pts1, lastPt, npts1, T1);
00580     NormalizePts(pts2, lastPt, npts2, T2);
00581     NormalizeLns(T1, lns1, lastLn, nlns1);
00582     NormalizeLns(T2, lns2, lastLn, nlns2);
00583 
00584     SetDataMatrixPts(npts1,npts2, lastPt, 0, A);
00585     SetDataMatrixLns(nlns1,nlns2, lastLn, lastPt, A);
00586   }
00587   else
00588   {
00589     SetDataMatrixPts(pts1, pts2, lastPt, 0, A);
00590     SetDataMatrixLns(lns1, lns2, lastLn, lastPt, A);
00591   }
00592 
00593   ok = SolveHomography(A, W, Ut, Vt, H);
00594 
00595   if (ok)
00596   {
00597     if (normalize)
00598       DenormalizeH(H, T1, T2, H);
00599 
00600     Mul33(H,1./H[8],H);
00601   }
00602 
00603   cvReleaseMat(&A);
00604   cvReleaseMat(&W);
00605   cvReleaseMat(&Ut);
00606   cvReleaseMat(&Vt);
00607 
00608   return ok;
00609 }
00610 
00615 bool Homography::ComputeHomRobustLS(double H[9], bool normalize, double thrInlier, double thrAngle)
00616 {
00617   if ((normalize && lastPt<2) || lastPt+lastLn < 5)
00618     return false;
00619 
00620   //init
00621   bool ok;
00622   unsigned size = 2*(lastPt+lastLn);
00623 
00624   double dA[size*9];
00625   double dW[9*9];
00626   double dUt[size*9];
00627   double dVt[9*9];
00628   double T1[9], T2[9];
00629 
00630   double thr;
00631 
00632   CvMat A, W, Ut, Vt;
00633   cvInitMatHeader(&A, 10, 9, CV_64F, &dA);
00634   cvInitMatHeader(&W, 9, 9, CV_64F, &dW);
00635   cvInitMatHeader(&Ut, 10, 9, CV_64F, &dUt);
00636   cvInitMatHeader(&Vt, 9, 9, CV_64F, &dVt);
00637 
00638   if (normalize)
00639   {
00640     NormalizePts(pts1, lastPt, pts1, T1);
00641     NormalizePts(pts2, lastPt, pts2, T2);
00642     NormalizeLns(T1, lns1, lastLn, lns1);
00643     NormalizeLns(T2, lns2, lastLn, lns2);
00644     thr = T2[0]; 
00645   }
00646   else
00647   {
00648     thr = thrInlier;
00649   }
00650 
00651   //ransac
00652   int numLines, k=0, num = lastPt+lastLn;
00653   double eps = 4./(double)num;
00654   int inl, inls = 0;
00655   Array<unsigned> idx;
00656   double Ht[9];
00657   srand(time(NULL));
00658 
00659   while (pow(1. - pow(eps,4),k) >= HOM_RANSAC_ETA0 && k<HOM_MAX_RANSAC_TRIALS)
00660   {
00661     do{
00662       GetRandIdx(num, 5, idx);
00663 
00664       numLines=0;
00665       for (unsigned i=0; i<5; i++)
00666         if (idx[i]<lastPt)
00667           numLines++;
00668 
00669     }while(numLines < 2);
00670 
00671     for (unsigned i=0; i<5; i++)
00672     {
00673       if (idx[i]<lastPt)
00674         InsertPointCoeff(pts1[idx[i]], pts2[idx[i]], &dA[i*18]);
00675       else
00676         InsertLineCoeff(lns1[idx[i]-lastPt], lns2[idx[i]-lastPt], &dA[i*18]);
00677     }
00678 
00679     if (SolveHomography(&A, &W, &Ut, &Vt, Ht))
00680     {
00681       inl=0;
00682       CountInlierPts(Ht, thr, inl);
00683       CountInlierLns(Ht, thr,thrAngle, inl);
00684 
00685       if (inl > inls)
00686       {
00687         inls = inl;
00688         eps = (double)inls / (double)num;
00689         for (unsigned i=0; i<9; i++)
00690           H[i] = Ht[i];
00691       }
00692     }
00693 
00694     k++;
00695   }
00696   #ifdef DEBUG
00697   cout<<"ComputeHomRobustLS: Number of RANSAC trials: "<<k
00698       <<", Number of inlier: "<<inls<<"("<<lastLn+lastPt<<")"<<endl;
00699   #endif
00700 
00701   //solve least squares
00702   int numInl;
00703   SetDataMatrix(dA, H, thr,thrAngle, numInl);
00704 
00705   if (numInl<5)
00706     return false;
00707 
00708   cvInitMatHeader(&A, numInl*2, 9, CV_64F, &dA);
00709   cvInitMatHeader(&Ut, numInl*2, 9, CV_64F, &dUt);
00710 
00711   ok = SolveHomography(&A, &W, &Ut, &Vt, H);
00712 
00713   if (ok && normalize)
00714   {
00715     DenormalizeH(H, T1, T2, H);
00716   }
00717 
00718   if (!ok || pow(1. - pow(eps,4),k) >= HOM_RANSAC_ETA0)
00719     return false;
00720 
00721   return true;
00722 }
00723 
00724 
00725 
00726 
00727 
00728 }
00729 


blort
Author(s): Thomas Mörwald , Michael Zillich , Andreas Richtsfeld , Johann Prankl , Markus Vincze , Bence Magyar
autogenerated on Wed Aug 26 2015 15:24:12