• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*M///////////////////////////////////////////////////////////////////////////////////////
2  //
3  //  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
4  //
5  //  By downloading, copying, installing or using the software you agree to this license.
6  //  If you do not agree to this license, do not download, install,
7  //  copy or use the software.
8  //
9  //
10  //                           License Agreement
11  //                For Open Source Computer Vision Library
12  //
13  // Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
14  // Copyright (C) 2009, Willow Garage Inc., all rights reserved.
15  // Third party copyrights are property of their respective owners.
16  //
17  // Redistribution and use in source and binary forms, with or without modification,
18  // are permitted provided that the following conditions are met:
19  //
20  //   * Redistribution's of source code must retain the above copyright notice,
21  //     this list of conditions and the following disclaimer.
22  //
23  //   * Redistribution's in binary form must reproduce the above copyright notice,
24  //     this list of conditions and the following disclaimer in the documentation
25  //     and/or other materials provided with the distribution.
26  //
27  //   * The name of the copyright holders may not be used to endorse or promote products
28  //     derived from this software without specific prior written permission.
29  //
30  // This software is provided by the copyright holders and contributors "as is" and
31  // any express or implied warranties, including, but not limited to, the implied
32  // warranties of merchantability and fitness for a particular purpose are disclaimed.
33  // In no event shall the Intel Corporation or contributors be liable for any direct,
34  // indirect, incidental, special, exemplary, or consequential damages
35  // (including, but not limited to, procurement of substitute goods or services;
36  // loss of use, data, or profits; or business interruption) however caused
37  // and on any theory of liability, whether in contract, strict liability,
38  // or tort (including negligence or otherwise) arising in any way out of
39  // the use of this software, even if advised of the possibility of such damage.
40  //
41  //M*/
42 
43 #include "precomp.hpp"
44 #include "circlesgrid.hpp"
45 #include <limits>
46 
47  // Requires CMake flag: DEBUG_opencv_calib3d=ON
48 //#define DEBUG_CIRCLES
49 
50 #ifdef DEBUG_CIRCLES
51 #  include <iostream>
52 #  include "opencv2/opencv_modules.hpp"
53 #  ifdef HAVE_OPENCV_HIGHGUI
54 #    include "opencv2/highgui.hpp"
55 #  else
56 #    undef DEBUG_CIRCLES
57 #  endif
58 #endif
59 
60 using namespace cv;
61 
62 #ifdef DEBUG_CIRCLES
drawPoints(const std::vector<Point2f> & points,Mat & outImage,int radius=2,Scalar color=Scalar::all (255),int thickness=-1)63 void drawPoints(const std::vector<Point2f> &points, Mat &outImage, int radius = 2,  Scalar color = Scalar::all(255), int thickness = -1)
64 {
65   for(size_t i=0; i<points.size(); i++)
66   {
67     circle(outImage, points[i], radius, color, thickness);
68   }
69 }
70 #endif
71 
hierarchicalClustering(const std::vector<Point2f> & points,const Size & patternSz,std::vector<Point2f> & patternPoints)72 void CirclesGridClusterFinder::hierarchicalClustering(const std::vector<Point2f> &points, const Size &patternSz, std::vector<Point2f> &patternPoints)
73 {
74 #ifdef HAVE_TEGRA_OPTIMIZATION
75     if(tegra::useTegra() && tegra::hierarchicalClustering(points, patternSz, patternPoints))
76         return;
77 #endif
78     int j, n = (int)points.size();
79     size_t pn = static_cast<size_t>(patternSz.area());
80 
81     patternPoints.clear();
82     if (pn >= points.size())
83     {
84         if (pn == points.size())
85             patternPoints = points;
86         return;
87     }
88 
89     Mat dists(n, n, CV_32FC1, Scalar(0));
90     Mat distsMask(dists.size(), CV_8UC1, Scalar(0));
91     for(int i = 0; i < n; i++)
92     {
93         for(j = i+1; j < n; j++)
94         {
95             dists.at<float>(i, j) = (float)norm(points[i] - points[j]);
96             distsMask.at<uchar>(i, j) = 255;
97             //TODO: use symmetry
98             distsMask.at<uchar>(j, i) = 255;//distsMask.at<uchar>(i, j);
99             dists.at<float>(j, i) = dists.at<float>(i, j);
100         }
101     }
102 
103     std::vector<std::list<size_t> > clusters(points.size());
104     for(size_t i=0; i<points.size(); i++)
105     {
106         clusters[i].push_back(i);
107     }
108 
109     int patternClusterIdx = 0;
110     while(clusters[patternClusterIdx].size() < pn)
111     {
112         Point minLoc;
113         minMaxLoc(dists, 0, 0, &minLoc, 0, distsMask);
114         int minIdx = std::min(minLoc.x, minLoc.y);
115         int maxIdx = std::max(minLoc.x, minLoc.y);
116 
117         distsMask.row(maxIdx).setTo(0);
118         distsMask.col(maxIdx).setTo(0);
119         Mat tmpRow = dists.row(minIdx);
120         Mat tmpCol = dists.col(minIdx);
121         cv::min(dists.row(minLoc.x), dists.row(minLoc.y), tmpRow);
122         tmpRow = tmpRow.t();
123         tmpRow.copyTo(tmpCol);
124 
125         clusters[minIdx].splice(clusters[minIdx].end(), clusters[maxIdx]);
126         patternClusterIdx = minIdx;
127     }
128 
129     //the largest cluster can have more than pn points -- we need to filter out such situations
130     if(clusters[patternClusterIdx].size() != static_cast<size_t>(patternSz.area()))
131     {
132       return;
133     }
134 
135     patternPoints.reserve(clusters[patternClusterIdx].size());
136     for(std::list<size_t>::iterator it = clusters[patternClusterIdx].begin(); it != clusters[patternClusterIdx].end();++it)
137     {
138         patternPoints.push_back(points[*it]);
139     }
140 }
141 
findGrid(const std::vector<cv::Point2f> & points,cv::Size _patternSize,std::vector<Point2f> & centers)142 void CirclesGridClusterFinder::findGrid(const std::vector<cv::Point2f> &points, cv::Size _patternSize, std::vector<Point2f>& centers)
143 {
144   patternSize = _patternSize;
145   centers.clear();
146   if(points.empty())
147   {
148     return;
149   }
150 
151   std::vector<Point2f> patternPoints;
152   hierarchicalClustering(points, patternSize, patternPoints);
153   if(patternPoints.empty())
154   {
155     return;
156   }
157 
158 #ifdef DEBUG_CIRCLES
159   Mat patternPointsImage(1024, 1248, CV_8UC1, Scalar(0));
160   drawPoints(patternPoints, patternPointsImage);
161   imshow("pattern points", patternPointsImage);
162 #endif
163 
164   std::vector<Point2f> hull2f;
165   convexHull(patternPoints, hull2f, false);
166   const size_t cornersCount = isAsymmetricGrid ? 6 : 4;
167   if(hull2f.size() < cornersCount)
168     return;
169 
170   std::vector<Point2f> corners;
171   findCorners(hull2f, corners);
172   if(corners.size() != cornersCount)
173     return;
174 
175   std::vector<Point2f> outsideCorners, sortedCorners;
176   if(isAsymmetricGrid)
177   {
178     findOutsideCorners(corners, outsideCorners);
179     const size_t outsideCornersCount = 2;
180     if(outsideCorners.size() != outsideCornersCount)
181       return;
182   }
183   getSortedCorners(hull2f, patternPoints, corners, outsideCorners, sortedCorners);
184   if(sortedCorners.size() != cornersCount)
185     return;
186 
187   std::vector<Point2f> rectifiedPatternPoints;
188   rectifyPatternPoints(patternPoints, sortedCorners, rectifiedPatternPoints);
189   if(patternPoints.size() != rectifiedPatternPoints.size())
190     return;
191 
192   parsePatternPoints(patternPoints, rectifiedPatternPoints, centers);
193 }
194 
findCorners(const std::vector<cv::Point2f> & hull2f,std::vector<cv::Point2f> & corners)195 void CirclesGridClusterFinder::findCorners(const std::vector<cv::Point2f> &hull2f, std::vector<cv::Point2f> &corners)
196 {
197   //find angles (cosines) of vertices in convex hull
198   std::vector<float> angles;
199   for(size_t i=0; i<hull2f.size(); i++)
200   {
201     Point2f vec1 = hull2f[(i+1) % hull2f.size()] - hull2f[i % hull2f.size()];
202     Point2f vec2 = hull2f[(i-1 + static_cast<int>(hull2f.size())) % hull2f.size()] - hull2f[i % hull2f.size()];
203     float angle = (float)(vec1.ddot(vec2) / (norm(vec1) * norm(vec2)));
204     angles.push_back(angle);
205   }
206 
207   //sort angles by cosine
208   //corners are the most sharp angles (6)
209   Mat anglesMat = Mat(angles);
210   Mat sortedIndices;
211   sortIdx(anglesMat, sortedIndices, SORT_EVERY_COLUMN + SORT_DESCENDING);
212   CV_Assert(sortedIndices.type() == CV_32SC1);
213   CV_Assert(sortedIndices.cols == 1);
214   const int cornersCount = isAsymmetricGrid ? 6 : 4;
215   Mat cornersIndices;
216   cv::sort(sortedIndices.rowRange(0, cornersCount), cornersIndices, SORT_EVERY_COLUMN + SORT_ASCENDING);
217   corners.clear();
218   for(int i=0; i<cornersCount; i++)
219   {
220     corners.push_back(hull2f[cornersIndices.at<int>(i, 0)]);
221   }
222 }
223 
findOutsideCorners(const std::vector<cv::Point2f> & corners,std::vector<cv::Point2f> & outsideCorners)224 void CirclesGridClusterFinder::findOutsideCorners(const std::vector<cv::Point2f> &corners, std::vector<cv::Point2f> &outsideCorners)
225 {
226   CV_Assert(!corners.empty());
227   outsideCorners.clear();
228   //find two pairs of the most nearest corners
229   const size_t n = corners.size();
230 
231 #ifdef DEBUG_CIRCLES
232   Mat cornersImage(1024, 1248, CV_8UC1, Scalar(0));
233   drawPoints(corners, cornersImage);
234   imshow("corners", cornersImage);
235 #endif
236 
237   std::vector<Point2f> tangentVectors(n);
238   for(size_t k=0; k < n; k++)
239   {
240     Point2f diff = corners[(k + 1) % n] - corners[k];
241     tangentVectors[k] = diff * (1.0f / norm(diff));
242   }
243 
244   //compute angles between all sides
245   Mat cosAngles((int)n, (int)n, CV_32FC1, 0.0f);
246   for(size_t i = 0; i < n; i++)
247   {
248     for(size_t j = i + 1; j < n; j++)
249     {
250       float val = fabs(tangentVectors[i].dot(tangentVectors[j]));
251       cosAngles.at<float>((int)i, (int)j) = val;
252       cosAngles.at<float>((int)j, (int)i) = val;
253     }
254   }
255 
256   //find two parallel sides to which outside corners belong
257   Point maxLoc;
258   minMaxLoc(cosAngles, 0, 0, 0, &maxLoc);
259   const int diffBetweenFalseLines = 3;
260   if(abs(maxLoc.x - maxLoc.y) == diffBetweenFalseLines)
261   {
262     cosAngles.row(maxLoc.x).setTo(0.0f);
263     cosAngles.col(maxLoc.x).setTo(0.0f);
264     cosAngles.row(maxLoc.y).setTo(0.0f);
265     cosAngles.col(maxLoc.y).setTo(0.0f);
266     minMaxLoc(cosAngles, 0, 0, 0, &maxLoc);
267   }
268 
269 #ifdef DEBUG_CIRCLES
270   Mat linesImage(1024, 1248, CV_8UC1, Scalar(0));
271   line(linesImage, corners[maxLoc.y], corners[(maxLoc.y + 1) % n], Scalar(255));
272   line(linesImage, corners[maxLoc.x], corners[(maxLoc.x + 1) % n], Scalar(255));
273   imshow("lines", linesImage);
274 #endif
275 
276   int maxIdx = std::max(maxLoc.x, maxLoc.y);
277   int minIdx = std::min(maxLoc.x, maxLoc.y);
278   const int bigDiff = 4;
279   if(maxIdx - minIdx == bigDiff)
280   {
281     minIdx += (int)n;
282     std::swap(maxIdx, minIdx);
283   }
284   if(maxIdx - minIdx != (int)n - bigDiff)
285   {
286     return;
287   }
288 
289   int outsidersSegmentIdx = (minIdx + maxIdx) / 2;
290 
291   outsideCorners.push_back(corners[outsidersSegmentIdx % n]);
292   outsideCorners.push_back(corners[(outsidersSegmentIdx + 1) % n]);
293 
294 #ifdef DEBUG_CIRCLES
295   drawPoints(outsideCorners, cornersImage, 2, Scalar(128));
296   imshow("corners", cornersImage);
297 #endif
298 }
299 
300 namespace {
pointLineDistance(const cv::Point2f & p,const cv::Vec4f & line)301 double pointLineDistance(const cv::Point2f &p, const cv::Vec4f &line)
302 {
303   Vec3f pa( line[0], line[1], 1 );
304   Vec3f pb( line[2], line[3], 1 );
305   Vec3f l = pa.cross(pb);
306   return std::abs((p.x * l[0] + p.y * l[1] + l[2])) * 1.0 /
307          std::sqrt(double(l[0] * l[0] + l[1] * l[1]));
308 }
309 }
310 
getSortedCorners(const std::vector<cv::Point2f> & hull2f,const std::vector<cv::Point2f> & patternPoints,const std::vector<cv::Point2f> & corners,const std::vector<cv::Point2f> & outsideCorners,std::vector<cv::Point2f> & sortedCorners)311 void CirclesGridClusterFinder::getSortedCorners(const std::vector<cv::Point2f> &hull2f, const std::vector<cv::Point2f> &patternPoints, const std::vector<cv::Point2f> &corners, const std::vector<cv::Point2f> &outsideCorners, std::vector<cv::Point2f> &sortedCorners)
312 {
313   Point2f firstCorner;
314   if(isAsymmetricGrid)
315   {
316     Point2f center = std::accumulate(corners.begin(), corners.end(), Point2f(0.0f, 0.0f));
317     center *= 1.0 / corners.size();
318 
319     std::vector<Point2f> centerToCorners;
320     for(size_t i=0; i<outsideCorners.size(); i++)
321     {
322       centerToCorners.push_back(outsideCorners[i] - center);
323     }
324 
325     //TODO: use CirclesGridFinder::getDirection
326     float crossProduct = centerToCorners[0].x * centerToCorners[1].y - centerToCorners[0].y * centerToCorners[1].x;
327     //y axis is inverted in computer vision so we check > 0
328     bool isClockwise = crossProduct > 0;
329     firstCorner  = isClockwise ? outsideCorners[1] : outsideCorners[0];
330   }
331   else
332   {
333     firstCorner = corners[0];
334   }
335 
336   std::vector<Point2f>::const_iterator firstCornerIterator = std::find(hull2f.begin(), hull2f.end(), firstCorner);
337   sortedCorners.clear();
338   for(std::vector<Point2f>::const_iterator it = firstCornerIterator; it != hull2f.end();++it)
339   {
340     std::vector<Point2f>::const_iterator itCorners = std::find(corners.begin(), corners.end(), *it);
341     if(itCorners != corners.end())
342     {
343       sortedCorners.push_back(*it);
344     }
345   }
346   for(std::vector<Point2f>::const_iterator it = hull2f.begin(); it != firstCornerIterator;++it)
347   {
348     std::vector<Point2f>::const_iterator itCorners = std::find(corners.begin(), corners.end(), *it);
349     if(itCorners != corners.end())
350     {
351       sortedCorners.push_back(*it);
352     }
353   }
354 
355   if(!isAsymmetricGrid)
356   {
357     double dist01 = norm(sortedCorners[0] - sortedCorners[1]);
358     double dist12 = norm(sortedCorners[1] - sortedCorners[2]);
359     // Use half the average distance between circles on the shorter side as threshold for determining whether a point lies on an edge.
360     double thresh = min(dist01, dist12) / min(patternSize.width, patternSize.height) / 2;
361 
362     size_t circleCount01 = 0;
363     size_t circleCount12 = 0;
364     Vec4f line01( sortedCorners[0].x, sortedCorners[0].y, sortedCorners[1].x, sortedCorners[1].y );
365     Vec4f line12( sortedCorners[1].x, sortedCorners[1].y, sortedCorners[2].x, sortedCorners[2].y );
366     // Count the circles along both edges.
367     for (size_t i = 0; i < patternPoints.size(); i++)
368     {
369       if (pointLineDistance(patternPoints[i], line01) < thresh)
370         circleCount01++;
371       if (pointLineDistance(patternPoints[i], line12) < thresh)
372         circleCount12++;
373     }
374 
375     // Ensure that the edge from sortedCorners[0] to sortedCorners[1] is the one with more circles (i.e. it is interpreted as the pattern's width).
376     if ((circleCount01 > circleCount12 && patternSize.height > patternSize.width) || (circleCount01 < circleCount12 && patternSize.height < patternSize.width))
377     {
378       for(size_t i=0; i<sortedCorners.size()-1; i++)
379       {
380         sortedCorners[i] = sortedCorners[i+1];
381       }
382       sortedCorners[sortedCorners.size() - 1] = firstCorner;
383     }
384   }
385 }
386 
rectifyPatternPoints(const std::vector<cv::Point2f> & patternPoints,const std::vector<cv::Point2f> & sortedCorners,std::vector<cv::Point2f> & rectifiedPatternPoints)387 void CirclesGridClusterFinder::rectifyPatternPoints(const std::vector<cv::Point2f> &patternPoints, const std::vector<cv::Point2f> &sortedCorners, std::vector<cv::Point2f> &rectifiedPatternPoints)
388 {
389   //indices of corner points in pattern
390   std::vector<Point> trueIndices;
391   trueIndices.push_back(Point(0, 0));
392   trueIndices.push_back(Point(patternSize.width - 1, 0));
393   if(isAsymmetricGrid)
394   {
395     trueIndices.push_back(Point(patternSize.width - 1, 1));
396     trueIndices.push_back(Point(patternSize.width - 1, patternSize.height - 2));
397   }
398   trueIndices.push_back(Point(patternSize.width - 1, patternSize.height - 1));
399   trueIndices.push_back(Point(0, patternSize.height - 1));
400 
401   std::vector<Point2f> idealPoints;
402   for(size_t idx=0; idx<trueIndices.size(); idx++)
403   {
404     int i = trueIndices[idx].y;
405     int j = trueIndices[idx].x;
406     if(isAsymmetricGrid)
407     {
408       idealPoints.push_back(Point2f((2*j + i % 2)*squareSize, i*squareSize));
409     }
410     else
411     {
412       idealPoints.push_back(Point2f(j*squareSize, i*squareSize));
413     }
414   }
415 
416   Mat homography = findHomography(sortedCorners, idealPoints, 0);
417   Mat rectifiedPointsMat;
418   transform(patternPoints, rectifiedPointsMat, homography);
419   rectifiedPatternPoints.clear();
420   convertPointsFromHomogeneous(rectifiedPointsMat, rectifiedPatternPoints);
421 }
422 
parsePatternPoints(const std::vector<cv::Point2f> & patternPoints,const std::vector<cv::Point2f> & rectifiedPatternPoints,std::vector<cv::Point2f> & centers)423 void CirclesGridClusterFinder::parsePatternPoints(const std::vector<cv::Point2f> &patternPoints, const std::vector<cv::Point2f> &rectifiedPatternPoints, std::vector<cv::Point2f> &centers)
424 {
425 #ifndef HAVE_OPENCV_FLANN
426   CV_UNUSED(patternPoints);
427   CV_UNUSED(rectifiedPatternPoints);
428   CV_UNUSED(centers);
429   CV_Error(Error::StsNotImplemented, "The desired functionality requires flann module, which was disabled.");
430 #else
431   flann::LinearIndexParams flannIndexParams;
432   flann::Index flannIndex(Mat(rectifiedPatternPoints).reshape(1), flannIndexParams);
433 
434   centers.clear();
435   for( int i = 0; i < patternSize.height; i++ )
436   {
437     for( int j = 0; j < patternSize.width; j++ )
438     {
439       Point2f idealPt;
440       if(isAsymmetricGrid)
441         idealPt = Point2f((2*j + i % 2)*squareSize, i*squareSize);
442       else
443         idealPt = Point2f(j*squareSize, i*squareSize);
444 
445       Mat query(1, 2, CV_32F, &idealPt);
446       const int knn = 1;
447       int indicesbuf[knn] = {0};
448       float distsbuf[knn] = {0.f};
449       Mat indices(1, knn, CV_32S, &indicesbuf);
450       Mat dists(1, knn, CV_32F, &distsbuf);
451       flannIndex.knnSearch(query, indices, dists, knn, flann::SearchParams());
452       centers.push_back(patternPoints.at(indicesbuf[0]));
453 
454       if(distsbuf[0] > maxRectifiedDistance)
455       {
456 #ifdef DEBUG_CIRCLES
457         std::cout << "Pattern not detected: too large rectified distance" << std::endl;
458 #endif
459         centers.clear();
460         return;
461       }
462     }
463   }
464 #endif
465 }
466 
Graph(size_t n)467 Graph::Graph(size_t n)
468 {
469   for (size_t i = 0; i < n; i++)
470   {
471     addVertex(i);
472   }
473 }
474 
doesVertexExist(size_t id) const475 bool Graph::doesVertexExist(size_t id) const
476 {
477   return (vertices.find(id) != vertices.end());
478 }
479 
addVertex(size_t id)480 void Graph::addVertex(size_t id)
481 {
482   CV_Assert( !doesVertexExist( id ) );
483 
484   vertices.insert(std::pair<size_t, Vertex> (id, Vertex()));
485 }
486 
addEdge(size_t id1,size_t id2)487 void Graph::addEdge(size_t id1, size_t id2)
488 {
489   CV_Assert( doesVertexExist( id1 ) );
490   CV_Assert( doesVertexExist( id2 ) );
491 
492   vertices[id1].neighbors.insert(id2);
493   vertices[id2].neighbors.insert(id1);
494 }
495 
removeEdge(size_t id1,size_t id2)496 void Graph::removeEdge(size_t id1, size_t id2)
497 {
498   CV_Assert( doesVertexExist( id1 ) );
499   CV_Assert( doesVertexExist( id2 ) );
500 
501   vertices[id1].neighbors.erase(id2);
502   vertices[id2].neighbors.erase(id1);
503 }
504 
areVerticesAdjacent(size_t id1,size_t id2) const505 bool Graph::areVerticesAdjacent(size_t id1, size_t id2) const
506 {
507   Vertices::const_iterator it = vertices.find(id1);
508   CV_Assert(it != vertices.end());
509   const Neighbors & neighbors = it->second.neighbors;
510   return neighbors.find(id2) != neighbors.end();
511 }
512 
getVerticesCount() const513 size_t Graph::getVerticesCount() const
514 {
515   return vertices.size();
516 }
517 
getDegree(size_t id) const518 size_t Graph::getDegree(size_t id) const
519 {
520   Vertices::const_iterator it = vertices.find(id);
521   CV_Assert( it != vertices.end() );
522   return it->second.neighbors.size();
523 }
524 
floydWarshall(cv::Mat & distanceMatrix,int infinity) const525 void Graph::floydWarshall(cv::Mat &distanceMatrix, int infinity) const
526 {
527   const int edgeWeight = 1;
528 
529   const int n = (int)getVerticesCount();
530   distanceMatrix.create(n, n, CV_32SC1);
531   distanceMatrix.setTo(infinity);
532   for (Vertices::const_iterator it1 = vertices.begin(); it1 != vertices.end();++it1)
533   {
534     distanceMatrix.at<int> ((int)it1->first, (int)it1->first) = 0;
535     for (Neighbors::const_iterator it2 = it1->second.neighbors.begin(); it2 != it1->second.neighbors.end();++it2)
536     {
537       CV_Assert( it1->first != *it2 );
538       distanceMatrix.at<int> ((int)it1->first, (int)*it2) = edgeWeight;
539     }
540   }
541 
542   for (Vertices::const_iterator it1 = vertices.begin(); it1 != vertices.end();++it1)
543   {
544     for (Vertices::const_iterator it2 = vertices.begin(); it2 != vertices.end();++it2)
545     {
546       for (Vertices::const_iterator it3 = vertices.begin(); it3 != vertices.end();++it3)
547       {
548       int i1 = (int)it1->first, i2 = (int)it2->first, i3 = (int)it3->first;
549         int val1 = distanceMatrix.at<int> (i2, i3);
550         int val2;
551         if (distanceMatrix.at<int> (i2, i1) == infinity ||
552       distanceMatrix.at<int> (i1, i3) == infinity)
553           val2 = val1;
554         else
555         {
556           val2 = distanceMatrix.at<int> (i2, i1) + distanceMatrix.at<int> (i1, i3);
557         }
558         distanceMatrix.at<int> (i2, i3) = (val1 == infinity) ? val2 : std::min(val1, val2);
559       }
560     }
561   }
562 }
563 
getNeighbors(size_t id) const564 const Graph::Neighbors& Graph::getNeighbors(size_t id) const
565 {
566   Vertices::const_iterator it = vertices.find(id);
567   CV_Assert( it != vertices.end() );
568   return it->second.neighbors;
569 }
570 
Segment(cv::Point2f _s,cv::Point2f _e)571 CirclesGridFinder::Segment::Segment(cv::Point2f _s, cv::Point2f _e) :
572   s(_s), e(_e)
573 {
574 }
575 
576 void computeShortestPath(Mat &predecessorMatrix, int v1, int v2, std::vector<int> &path);
577 void computePredecessorMatrix(const Mat &dm, int verticesCount, Mat &predecessorMatrix);
578 
CirclesGridFinderParameters()579 CirclesGridFinderParameters::CirclesGridFinderParameters()
580 {
581   minDensity = 10;
582   densityNeighborhoodSize = Size2f(16, 16);
583   minDistanceToAddKeypoint = 20;
584   kmeansAttempts = 100;
585   convexHullFactor = 1.1f;
586   keypointScale = 1;
587 
588   minGraphConfidence = 9;
589   vertexGain = 1;
590   vertexPenalty = -0.6f;
591   edgeGain = 1;
592   edgePenalty = -0.6f;
593   existingVertexGain = 10000;
594 
595   minRNGEdgeSwitchDist = 5.f;
596   gridType = SYMMETRIC_GRID;
597 }
598 
CirclesGridFinderParameters2()599 CirclesGridFinderParameters2::CirclesGridFinderParameters2()
600 : CirclesGridFinderParameters()
601 {
602     squareSize = 1.0f;
603     maxRectifiedDistance = squareSize/2.0f;
604 }
605 
CirclesGridFinder(Size _patternSize,const std::vector<Point2f> & testKeypoints,const CirclesGridFinderParameters & _parameters)606 CirclesGridFinder::CirclesGridFinder(Size _patternSize, const std::vector<Point2f> &testKeypoints,
607                                      const CirclesGridFinderParameters &_parameters) :
608   patternSize(static_cast<size_t> (_patternSize.width), static_cast<size_t> (_patternSize.height))
609 {
610   CV_Assert(_patternSize.height >= 0 && _patternSize.width >= 0);
611 
612   keypoints = testKeypoints;
613   parameters = _parameters;
614   largeHoles = 0;
615   smallHoles = 0;
616 }
617 
findHoles()618 bool CirclesGridFinder::findHoles()
619 {
620   switch (parameters.gridType)
621   {
622     case CirclesGridFinderParameters::SYMMETRIC_GRID:
623     {
624       std::vector<Point2f> vectors, filteredVectors, basis;
625       Graph rng(0);
626       computeRNG(rng, vectors);
627       filterOutliersByDensity(vectors, filteredVectors);
628       std::vector<Graph> basisGraphs;
629       findBasis(filteredVectors, basis, basisGraphs);
630       findMCS(basis, basisGraphs);
631       break;
632     }
633 
634     case CirclesGridFinderParameters::ASYMMETRIC_GRID:
635     {
636       std::vector<Point2f> vectors, tmpVectors, filteredVectors, basis;
637       Graph rng(0);
638       computeRNG(rng, tmpVectors);
639       rng2gridGraph(rng, vectors);
640       filterOutliersByDensity(vectors, filteredVectors);
641       std::vector<Graph> basisGraphs;
642       findBasis(filteredVectors, basis, basisGraphs);
643       findMCS(basis, basisGraphs);
644       eraseUsedGraph(basisGraphs);
645       holes2 = holes;
646       holes.clear();
647       findMCS(basis, basisGraphs);
648       break;
649     }
650 
651     default:
652       CV_Error(Error::StsBadArg, "Unknown pattern type");
653   }
654   return (isDetectionCorrect());
655   //CV_Error( 0, "Detection is not correct" );
656 }
657 
rng2gridGraph(Graph & rng,std::vector<cv::Point2f> & vectors) const658 void CirclesGridFinder::rng2gridGraph(Graph &rng, std::vector<cv::Point2f> &vectors) const
659 {
660   for (size_t i = 0; i < rng.getVerticesCount(); i++)
661   {
662     Graph::Neighbors neighbors1 = rng.getNeighbors(i);
663     for (Graph::Neighbors::iterator it1 = neighbors1.begin(); it1 != neighbors1.end(); ++it1)
664     {
665       Graph::Neighbors neighbors2 = rng.getNeighbors(*it1);
666       for (Graph::Neighbors::iterator it2 = neighbors2.begin(); it2 != neighbors2.end(); ++it2)
667       {
668         if (i < *it2)
669         {
670           Point2f vec1 = keypoints[i] - keypoints[*it1];
671           Point2f vec2 = keypoints[*it1] - keypoints[*it2];
672           if (norm(vec1 - vec2) < parameters.minRNGEdgeSwitchDist || norm(vec1 + vec2)
673               < parameters.minRNGEdgeSwitchDist)
674             continue;
675 
676           vectors.push_back(keypoints[i] - keypoints[*it2]);
677           vectors.push_back(keypoints[*it2] - keypoints[i]);
678         }
679       }
680     }
681   }
682 }
683 
eraseUsedGraph(std::vector<Graph> & basisGraphs) const684 void CirclesGridFinder::eraseUsedGraph(std::vector<Graph> &basisGraphs) const
685 {
686   for (size_t i = 0; i < holes.size(); i++)
687   {
688     for (size_t j = 0; j < holes[i].size(); j++)
689     {
690       for (size_t k = 0; k < basisGraphs.size(); k++)
691       {
692         if (i != holes.size() - 1 && basisGraphs[k].areVerticesAdjacent(holes[i][j], holes[i + 1][j]))
693         {
694           basisGraphs[k].removeEdge(holes[i][j], holes[i + 1][j]);
695         }
696 
697         if (j != holes[i].size() - 1 && basisGraphs[k].areVerticesAdjacent(holes[i][j], holes[i][j + 1]))
698         {
699           basisGraphs[k].removeEdge(holes[i][j], holes[i][j + 1]);
700         }
701       }
702     }
703   }
704 }
705 
isDetectionCorrect()706 bool CirclesGridFinder::isDetectionCorrect()
707 {
708   switch (parameters.gridType)
709   {
710     case CirclesGridFinderParameters::SYMMETRIC_GRID:
711     {
712       if (holes.size() != patternSize.height)
713         return false;
714 
715       std::set<size_t> vertices;
716       for (size_t i = 0; i < holes.size(); i++)
717       {
718         if (holes[i].size() != patternSize.width)
719           return false;
720 
721         for (size_t j = 0; j < holes[i].size(); j++)
722         {
723           vertices.insert(holes[i][j]);
724         }
725       }
726 
727       return vertices.size() == patternSize.area();
728     }
729 
730     case CirclesGridFinderParameters::ASYMMETRIC_GRID:
731     {
732       if (holes.size() < holes2.size() || holes[0].size() < holes2[0].size())
733       {
734         largeHoles = &holes2;
735         smallHoles = &holes;
736       }
737       else
738       {
739         largeHoles = &holes;
740         smallHoles = &holes2;
741       }
742 
743       size_t largeWidth = patternSize.width;
744       size_t largeHeight = (size_t)ceil(patternSize.height / 2.);
745       size_t smallWidth = patternSize.width;
746       size_t smallHeight = (size_t)floor(patternSize.height / 2.);
747 
748       size_t sw = smallWidth, sh = smallHeight, lw = largeWidth, lh = largeHeight;
749       if (largeHoles->size() != largeHeight)
750       {
751         std::swap(lh, lw);
752       }
753       if (smallHoles->size() != smallHeight)
754       {
755         std::swap(sh, sw);
756       }
757 
758       if (largeHoles->size() != lh || smallHoles->size() != sh)
759       {
760         return false;
761       }
762 
763       std::set<size_t> vertices;
764       for (size_t i = 0; i < largeHoles->size(); i++)
765       {
766         if (largeHoles->at(i).size() != lw)
767         {
768           return false;
769         }
770 
771         for (size_t j = 0; j < largeHoles->at(i).size(); j++)
772         {
773           vertices.insert(largeHoles->at(i)[j]);
774         }
775 
776         if (i < smallHoles->size())
777         {
778           if (smallHoles->at(i).size() != sw)
779           {
780             return false;
781           }
782 
783           for (size_t j = 0; j < smallHoles->at(i).size(); j++)
784           {
785             vertices.insert(smallHoles->at(i)[j]);
786           }
787         }
788       }
789       return (vertices.size() == largeHeight * largeWidth + smallHeight * smallWidth);
790     }
791   }
792   CV_Error(Error::StsBadArg, "Unknown pattern type");
793 }
794 
findMCS(const std::vector<Point2f> & basis,std::vector<Graph> & basisGraphs)795 void CirclesGridFinder::findMCS(const std::vector<Point2f> &basis, std::vector<Graph> &basisGraphs)
796 {
797   holes.clear();
798   Path longestPath;
799   size_t bestGraphIdx = findLongestPath(basisGraphs, longestPath);
800   std::vector<size_t> holesRow = longestPath.vertices;
801 
802   while (holesRow.size() > std::max(patternSize.width, patternSize.height))
803   {
804     holesRow.pop_back();
805     holesRow.erase(holesRow.begin());
806   }
807 
808   if (bestGraphIdx == 0)
809   {
810     holes.push_back(holesRow);
811     size_t w = holes[0].size();
812     size_t h = holes.size();
813 
814     //parameters.minGraphConfidence = holes[0].size() * parameters.vertexGain + (holes[0].size() - 1) * parameters.edgeGain;
815     //parameters.minGraphConfidence = holes[0].size() * parameters.vertexGain + (holes[0].size() / 2) * parameters.edgeGain;
816     //parameters.minGraphConfidence = holes[0].size() * parameters.existingVertexGain + (holes[0].size() / 2) * parameters.edgeGain;
817     parameters.minGraphConfidence = holes[0].size() * parameters.existingVertexGain;
818     for (size_t i = h; i < patternSize.height; i++)
819     {
820       addHolesByGraph(basisGraphs, true, basis[1]);
821     }
822 
823     //parameters.minGraphConfidence = holes.size() * parameters.existingVertexGain + (holes.size() / 2) * parameters.edgeGain;
824     parameters.minGraphConfidence = holes.size() * parameters.existingVertexGain;
825 
826     for (size_t i = w; i < patternSize.width; i++)
827     {
828       addHolesByGraph(basisGraphs, false, basis[0]);
829     }
830   }
831   else
832   {
833     holes.resize(holesRow.size());
834     for (size_t i = 0; i < holesRow.size(); i++)
835       holes[i].push_back(holesRow[i]);
836 
837     size_t w = holes[0].size();
838     size_t h = holes.size();
839 
840     parameters.minGraphConfidence = holes.size() * parameters.existingVertexGain;
841     for (size_t i = w; i < patternSize.width; i++)
842     {
843       addHolesByGraph(basisGraphs, false, basis[0]);
844     }
845 
846     parameters.minGraphConfidence = holes[0].size() * parameters.existingVertexGain;
847     for (size_t i = h; i < patternSize.height; i++)
848     {
849       addHolesByGraph(basisGraphs, true, basis[1]);
850     }
851   }
852 }
853 
rectifyGrid(Size detectedGridSize,const std::vector<Point2f> & centers,const std::vector<Point2f> & keypoints,std::vector<Point2f> & warpedKeypoints)854 Mat CirclesGridFinder::rectifyGrid(Size detectedGridSize, const std::vector<Point2f>& centers,
855                                    const std::vector<Point2f> &keypoints, std::vector<Point2f> &warpedKeypoints)
856 {
857   CV_Assert( !centers.empty() );
858   const float edgeLength = 30;
859   const Point2f offset(150, 150);
860 
861   std::vector<Point2f> dstPoints;
862   bool isClockwiseBefore =
863       getDirection(centers[0], centers[detectedGridSize.width - 1], centers[centers.size() - 1]) < 0;
864 
865   int iStart = isClockwiseBefore ? 0 : detectedGridSize.height - 1;
866   int iEnd = isClockwiseBefore ? detectedGridSize.height : -1;
867   int iStep = isClockwiseBefore ? 1 : -1;
868   for (int i = iStart; i != iEnd; i += iStep)
869   {
870     for (int j = 0; j < detectedGridSize.width; j++)
871     {
872       dstPoints.push_back(offset + Point2f(edgeLength * j, edgeLength * i));
873     }
874   }
875 
876   Mat H = findHomography(centers, dstPoints, RANSAC);
877   //Mat H = findHomography(corners, dstPoints);
878 
879   if (H.empty())
880   {
881       H = Mat::zeros(3, 3, CV_64FC1);
882       warpedKeypoints.clear();
883       return H;
884   }
885 
886   std::vector<Point2f> srcKeypoints;
887   for (size_t i = 0; i < keypoints.size(); i++)
888   {
889     srcKeypoints.push_back(keypoints[i]);
890   }
891 
892   Mat dstKeypointsMat;
893   transform(srcKeypoints, dstKeypointsMat, H);
894   std::vector<Point2f> dstKeypoints;
895   convertPointsFromHomogeneous(dstKeypointsMat, dstKeypoints);
896 
897   warpedKeypoints.clear();
898   for (size_t i = 0; i < dstKeypoints.size(); i++)
899   {
900     Point2f pt = dstKeypoints[i];
901     warpedKeypoints.push_back(pt);
902   }
903 
904   return H;
905 }
906 
findNearestKeypoint(Point2f pt) const907 size_t CirclesGridFinder::findNearestKeypoint(Point2f pt) const
908 {
909   size_t bestIdx = 0;
910   double minDist = std::numeric_limits<double>::max();
911   for (size_t i = 0; i < keypoints.size(); i++)
912   {
913     double dist = norm(pt - keypoints[i]);
914     if (dist < minDist)
915     {
916       minDist = dist;
917       bestIdx = i;
918     }
919   }
920   return bestIdx;
921 }
922 
addPoint(Point2f pt,std::vector<size_t> & points)923 void CirclesGridFinder::addPoint(Point2f pt, std::vector<size_t> &points)
924 {
925   size_t ptIdx = findNearestKeypoint(pt);
926   if (norm(keypoints[ptIdx] - pt) > parameters.minDistanceToAddKeypoint)
927   {
928     Point2f kpt = Point2f(pt);
929     keypoints.push_back(kpt);
930     points.push_back(keypoints.size() - 1);
931   }
932   else
933   {
934     points.push_back(ptIdx);
935   }
936 }
937 
findCandidateLine(std::vector<size_t> & line,size_t seedLineIdx,bool addRow,Point2f basisVec,std::vector<size_t> & seeds)938 void CirclesGridFinder::findCandidateLine(std::vector<size_t> &line, size_t seedLineIdx, bool addRow, Point2f basisVec,
939                                           std::vector<size_t> &seeds)
940 {
941   line.clear();
942   seeds.clear();
943 
944   if (addRow)
945   {
946     for (size_t i = 0; i < holes[seedLineIdx].size(); i++)
947     {
948       Point2f pt = keypoints[holes[seedLineIdx][i]] + basisVec;
949       addPoint(pt, line);
950       seeds.push_back(holes[seedLineIdx][i]);
951     }
952   }
953   else
954   {
955     for (size_t i = 0; i < holes.size(); i++)
956     {
957       Point2f pt = keypoints[holes[i][seedLineIdx]] + basisVec;
958       addPoint(pt, line);
959       seeds.push_back(holes[i][seedLineIdx]);
960     }
961   }
962 
963   CV_Assert( line.size() == seeds.size() );
964 }
965 
findCandidateHoles(std::vector<size_t> & above,std::vector<size_t> & below,bool addRow,Point2f basisVec,std::vector<size_t> & aboveSeeds,std::vector<size_t> & belowSeeds)966 void CirclesGridFinder::findCandidateHoles(std::vector<size_t> &above, std::vector<size_t> &below, bool addRow, Point2f basisVec,
967                                            std::vector<size_t> &aboveSeeds, std::vector<size_t> &belowSeeds)
968 {
969   above.clear();
970   below.clear();
971   aboveSeeds.clear();
972   belowSeeds.clear();
973 
974   findCandidateLine(above, 0, addRow, -basisVec, aboveSeeds);
975   size_t lastIdx = addRow ? holes.size() - 1 : holes[0].size() - 1;
976   findCandidateLine(below, lastIdx, addRow, basisVec, belowSeeds);
977 
978   CV_Assert( below.size() == above.size() );
979   CV_Assert( belowSeeds.size() == aboveSeeds.size() );
980   CV_Assert( below.size() == belowSeeds.size() );
981 }
982 
areCentersNew(const std::vector<size_t> & newCenters,const std::vector<std::vector<size_t>> & holes)983 bool CirclesGridFinder::areCentersNew(const std::vector<size_t> &newCenters, const std::vector<std::vector<size_t> > &holes)
984 {
985   for (size_t i = 0; i < newCenters.size(); i++)
986   {
987     for (size_t j = 0; j < holes.size(); j++)
988     {
989       if (holes[j].end() != std::find(holes[j].begin(), holes[j].end(), newCenters[i]))
990       {
991         return false;
992       }
993     }
994   }
995 
996   return true;
997 }
998 
insertWinner(float aboveConfidence,float belowConfidence,float minConfidence,bool addRow,const std::vector<size_t> & above,const std::vector<size_t> & below,std::vector<std::vector<size_t>> & holes)999 void CirclesGridFinder::insertWinner(float aboveConfidence, float belowConfidence, float minConfidence, bool addRow,
1000                                      const std::vector<size_t> &above, const std::vector<size_t> &below,
1001                                      std::vector<std::vector<size_t> > &holes)
1002 {
1003   if (aboveConfidence < minConfidence && belowConfidence < minConfidence)
1004     return;
1005 
1006   if (addRow)
1007   {
1008     if (aboveConfidence >= belowConfidence)
1009     {
1010       if (!areCentersNew(above, holes))
1011         CV_Error( 0, "Centers are not new" );
1012 
1013       holes.insert(holes.begin(), above);
1014     }
1015     else
1016     {
1017       if (!areCentersNew(below, holes))
1018         CV_Error( 0, "Centers are not new" );
1019 
1020       holes.insert(holes.end(), below);
1021     }
1022   }
1023   else
1024   {
1025     if (aboveConfidence >= belowConfidence)
1026     {
1027       if (!areCentersNew(above, holes))
1028         CV_Error( 0, "Centers are not new" );
1029 
1030       for (size_t i = 0; i < holes.size(); i++)
1031       {
1032         holes[i].insert(holes[i].begin(), above[i]);
1033       }
1034     }
1035     else
1036     {
1037       if (!areCentersNew(below, holes))
1038         CV_Error( 0, "Centers are not new" );
1039 
1040       for (size_t i = 0; i < holes.size(); i++)
1041       {
1042         holes[i].insert(holes[i].end(), below[i]);
1043       }
1044     }
1045   }
1046 }
1047 
computeGraphConfidence(const std::vector<Graph> & basisGraphs,bool addRow,const std::vector<size_t> & points,const std::vector<size_t> & seeds)1048 float CirclesGridFinder::computeGraphConfidence(const std::vector<Graph> &basisGraphs, bool addRow,
1049                                                 const std::vector<size_t> &points, const std::vector<size_t> &seeds)
1050 {
1051   CV_Assert( points.size() == seeds.size() );
1052   float confidence = 0;
1053   const size_t vCount = basisGraphs[0].getVerticesCount();
1054   CV_Assert( basisGraphs[0].getVerticesCount() == basisGraphs[1].getVerticesCount() );
1055 
1056   for (size_t i = 0; i < seeds.size(); i++)
1057   {
1058     if (seeds[i] < vCount && points[i] < vCount)
1059     {
1060       if (!basisGraphs[addRow].areVerticesAdjacent(seeds[i], points[i]))
1061       {
1062         confidence += parameters.vertexPenalty;
1063       }
1064       else
1065       {
1066         confidence += parameters.vertexGain;
1067       }
1068     }
1069 
1070     if (points[i] < vCount)
1071     {
1072       confidence += parameters.existingVertexGain;
1073     }
1074   }
1075 
1076   for (size_t i = 1; i < points.size(); i++)
1077   {
1078     if (points[i - 1] < vCount && points[i] < vCount)
1079     {
1080       if (!basisGraphs[!addRow].areVerticesAdjacent(points[i - 1], points[i]))
1081       {
1082         confidence += parameters.edgePenalty;
1083       }
1084       else
1085       {
1086         confidence += parameters.edgeGain;
1087       }
1088     }
1089   }
1090   return confidence;
1091 
1092 }
1093 
addHolesByGraph(const std::vector<Graph> & basisGraphs,bool addRow,Point2f basisVec)1094 void CirclesGridFinder::addHolesByGraph(const std::vector<Graph> &basisGraphs, bool addRow, Point2f basisVec)
1095 {
1096   std::vector<size_t> above, below, aboveSeeds, belowSeeds;
1097   findCandidateHoles(above, below, addRow, basisVec, aboveSeeds, belowSeeds);
1098   float aboveConfidence = computeGraphConfidence(basisGraphs, addRow, above, aboveSeeds);
1099   float belowConfidence = computeGraphConfidence(basisGraphs, addRow, below, belowSeeds);
1100 
1101   insertWinner(aboveConfidence, belowConfidence, parameters.minGraphConfidence, addRow, above, below, holes);
1102 }
1103 
filterOutliersByDensity(const std::vector<Point2f> & samples,std::vector<Point2f> & filteredSamples)1104 void CirclesGridFinder::filterOutliersByDensity(const std::vector<Point2f> &samples, std::vector<Point2f> &filteredSamples)
1105 {
1106   if (samples.empty())
1107     CV_Error( 0, "samples is empty" );
1108 
1109   filteredSamples.clear();
1110 
1111   for (size_t i = 0; i < samples.size(); i++)
1112   {
1113     Rect_<float> rect(samples[i] - Point2f(parameters.densityNeighborhoodSize) * 0.5,
1114                       parameters.densityNeighborhoodSize);
1115     int neighborsCount = 0;
1116     for (size_t j = 0; j < samples.size(); j++)
1117     {
1118       if (rect.contains(samples[j]))
1119         neighborsCount++;
1120     }
1121     if (neighborsCount >= parameters.minDensity)
1122       filteredSamples.push_back(samples[i]);
1123   }
1124 
1125   if (filteredSamples.empty())
1126     CV_Error( 0, "filteredSamples is empty" );
1127 }
1128 
findBasis(const std::vector<Point2f> & samples,std::vector<Point2f> & basis,std::vector<Graph> & basisGraphs)1129 void CirclesGridFinder::findBasis(const std::vector<Point2f> &samples, std::vector<Point2f> &basis, std::vector<Graph> &basisGraphs)
1130 {
1131   basis.clear();
1132   Mat bestLabels;
1133   TermCriteria termCriteria;
1134   Mat centers;
1135   const int clustersCount = 4;
1136   kmeans(Mat(samples).reshape(1, 0), clustersCount, bestLabels, termCriteria, parameters.kmeansAttempts,
1137          KMEANS_RANDOM_CENTERS, centers);
1138   CV_Assert( centers.type() == CV_32FC1 );
1139 
1140   std::vector<int> basisIndices;
1141   //TODO: only remove duplicate
1142   for (int i = 0; i < clustersCount; i++)
1143   {
1144     int maxIdx = (fabs(centers.at<float> (i, 0)) < fabs(centers.at<float> (i, 1)));
1145     if (centers.at<float> (i, maxIdx) > 0)
1146     {
1147       Point2f vec(centers.at<float> (i, 0), centers.at<float> (i, 1));
1148       basis.push_back(vec);
1149       basisIndices.push_back(i);
1150     }
1151   }
1152   if (basis.size() != 2)
1153     CV_Error(0, "Basis size is not 2");
1154 
1155   if (basis[1].x > basis[0].x)
1156   {
1157     std::swap(basis[0], basis[1]);
1158     std::swap(basisIndices[0], basisIndices[1]);
1159   }
1160 
1161   const float minBasisDif = 2;
1162   if (norm(basis[0] - basis[1]) < minBasisDif)
1163     CV_Error(0, "degenerate basis" );
1164 
1165   std::vector<std::vector<Point2f> > clusters(2), hulls(2);
1166   for (int k = 0; k < (int)samples.size(); k++)
1167   {
1168     int label = bestLabels.at<int> (k, 0);
1169     int idx = -1;
1170     if (label == basisIndices[0])
1171       idx = 0;
1172     if (label == basisIndices[1])
1173       idx = 1;
1174     if (idx >= 0)
1175     {
1176       clusters[idx].push_back(basis[idx] + parameters.convexHullFactor * (samples[k] - basis[idx]));
1177     }
1178   }
1179   for (size_t i = 0; i < basis.size(); i++)
1180   {
1181     convexHull(clusters[i], hulls[i]);
1182   }
1183 
1184   basisGraphs.resize(basis.size(), Graph(keypoints.size()));
1185   for (size_t i = 0; i < keypoints.size(); i++)
1186   {
1187     for (size_t j = 0; j < keypoints.size(); j++)
1188     {
1189       if (i == j)
1190         continue;
1191 
1192       Point2f vec = keypoints[i] - keypoints[j];
1193 
1194       for (size_t k = 0; k < hulls.size(); k++)
1195       {
1196         if (pointPolygonTest(hulls[k], vec, false) >= 0)
1197         {
1198           basisGraphs[k].addEdge(i, j);
1199         }
1200       }
1201     }
1202   }
1203   if (basisGraphs.size() != 2)
1204     CV_Error(0, "Number of basis graphs is not 2");
1205 }
1206 
computeRNG(Graph & rng,std::vector<cv::Point2f> & vectors,Mat * drawImage) const1207 void CirclesGridFinder::computeRNG(Graph &rng, std::vector<cv::Point2f> &vectors, Mat *drawImage) const
1208 {
1209   rng = Graph(keypoints.size());
1210   vectors.clear();
1211 
1212   //TODO: use more fast algorithm instead of naive N^3
1213   for (size_t i = 0; i < keypoints.size(); i++)
1214   {
1215     for (size_t j = 0; j < keypoints.size(); j++)
1216     {
1217       if (i == j)
1218         continue;
1219 
1220       Point2f vec = keypoints[i] - keypoints[j];
1221       double dist = norm(vec);
1222 
1223       bool isNeighbors = true;
1224       for (size_t k = 0; k < keypoints.size(); k++)
1225       {
1226         if (k == i || k == j)
1227           continue;
1228 
1229         double dist1 = norm(keypoints[i] - keypoints[k]);
1230         double dist2 = norm(keypoints[j] - keypoints[k]);
1231         if (dist1 < dist && dist2 < dist)
1232         {
1233           isNeighbors = false;
1234           break;
1235         }
1236       }
1237 
1238       if (isNeighbors)
1239       {
1240         rng.addEdge(i, j);
1241         vectors.push_back(keypoints[i] - keypoints[j]);
1242         if (drawImage != 0)
1243         {
1244           line(*drawImage, keypoints[i], keypoints[j], Scalar(255, 0, 0), 2);
1245           circle(*drawImage, keypoints[i], 3, Scalar(0, 0, 255), -1);
1246           circle(*drawImage, keypoints[j], 3, Scalar(0, 0, 255), -1);
1247         }
1248       }
1249     }
1250   }
1251 }
1252 
computePredecessorMatrix(const Mat & dm,int verticesCount,Mat & predecessorMatrix)1253 void computePredecessorMatrix(const Mat &dm, int verticesCount, Mat &predecessorMatrix)
1254 {
1255   CV_Assert( dm.type() == CV_32SC1 );
1256   predecessorMatrix.create(verticesCount, verticesCount, CV_32SC1);
1257   predecessorMatrix = -1;
1258   for (int i = 0; i < predecessorMatrix.rows; i++)
1259   {
1260     for (int j = 0; j < predecessorMatrix.cols; j++)
1261     {
1262       int dist = dm.at<int> (i, j);
1263       for (int k = 0; k < verticesCount; k++)
1264       {
1265         if (dm.at<int> (i, k) == dist - 1 && dm.at<int> (k, j) == 1)
1266         {
1267           predecessorMatrix.at<int> (i, j) = k;
1268           break;
1269         }
1270       }
1271     }
1272   }
1273 }
1274 
computeShortestPath(Mat & predecessorMatrix,size_t v1,size_t v2,std::vector<size_t> & path)1275 static void computeShortestPath(Mat &predecessorMatrix, size_t v1, size_t v2, std::vector<size_t> &path)
1276 {
1277   if (predecessorMatrix.at<int> ((int)v1, (int)v2) < 0)
1278   {
1279     path.push_back(v1);
1280     return;
1281   }
1282 
1283   computeShortestPath(predecessorMatrix, v1, predecessorMatrix.at<int> ((int)v1, (int)v2), path);
1284   path.push_back(v2);
1285 }
1286 
findLongestPath(std::vector<Graph> & basisGraphs,Path & bestPath)1287 size_t CirclesGridFinder::findLongestPath(std::vector<Graph> &basisGraphs, Path &bestPath)
1288 {
1289   std::vector<Path> longestPaths(1);
1290   std::vector<int> confidences;
1291 
1292   size_t bestGraphIdx = 0;
1293   const int infinity = -1;
1294   for (size_t graphIdx = 0; graphIdx < basisGraphs.size(); graphIdx++)
1295   {
1296     const Graph &g = basisGraphs[graphIdx];
1297     Mat distanceMatrix;
1298     g.floydWarshall(distanceMatrix, infinity);
1299     Mat predecessorMatrix;
1300     computePredecessorMatrix(distanceMatrix, (int)g.getVerticesCount(), predecessorMatrix);
1301 
1302     double maxVal;
1303     Point maxLoc;
1304     minMaxLoc(distanceMatrix, 0, &maxVal, 0, &maxLoc);
1305 
1306     if (maxVal > longestPaths[0].length)
1307     {
1308       longestPaths.clear();
1309       confidences.clear();
1310       bestGraphIdx = graphIdx;
1311     }
1312     if (longestPaths.empty() || (maxVal == longestPaths[0].length && graphIdx == bestGraphIdx))
1313     {
1314       Path path = Path(maxLoc.x, maxLoc.y, cvRound(maxVal));
1315       CV_Assert(maxLoc.x >= 0 && maxLoc.y >= 0)
1316         ;
1317       size_t id1 = static_cast<size_t> (maxLoc.x);
1318       size_t id2 = static_cast<size_t> (maxLoc.y);
1319       computeShortestPath(predecessorMatrix, id1, id2, path.vertices);
1320       longestPaths.push_back(path);
1321 
1322       int conf = 0;
1323       for (int v2 = 0; v2 < (int)path.vertices.size(); v2++)
1324       {
1325         conf += (int)basisGraphs[1 - (int)graphIdx].getDegree(v2);
1326       }
1327       confidences.push_back(conf);
1328     }
1329   }
1330   //if( bestGraphIdx != 0 )
1331   //CV_Error( 0, "" );
1332 
1333   int maxConf = -1;
1334   int bestPathIdx = -1;
1335   for (int i = 0; i < (int)confidences.size(); i++)
1336   {
1337     if (confidences[i] > maxConf)
1338     {
1339       maxConf = confidences[i];
1340       bestPathIdx = i;
1341     }
1342   }
1343 
1344   //int bestPathIdx = rand() % longestPaths.size();
1345   bestPath = longestPaths.at(bestPathIdx);
1346   bool needReverse = (bestGraphIdx == 0 && keypoints[bestPath.lastVertex].x < keypoints[bestPath.firstVertex].x)
1347       || (bestGraphIdx == 1 && keypoints[bestPath.lastVertex].y < keypoints[bestPath.firstVertex].y);
1348   if (needReverse)
1349   {
1350     std::swap(bestPath.lastVertex, bestPath.firstVertex);
1351     std::reverse(bestPath.vertices.begin(), bestPath.vertices.end());
1352   }
1353   return bestGraphIdx;
1354 }
1355 
drawBasis(const std::vector<Point2f> & basis,Point2f origin,Mat & drawImg) const1356 void CirclesGridFinder::drawBasis(const std::vector<Point2f> &basis, Point2f origin, Mat &drawImg) const
1357 {
1358   for (size_t i = 0; i < basis.size(); i++)
1359   {
1360     Point2f pt(basis[i]);
1361     line(drawImg, origin, origin + pt, Scalar(0, (double)(i * 255), 0), 2);
1362   }
1363 }
1364 
drawBasisGraphs(const std::vector<Graph> & basisGraphs,Mat & drawImage,bool drawEdges,bool drawVertices) const1365 void CirclesGridFinder::drawBasisGraphs(const std::vector<Graph> &basisGraphs, Mat &drawImage, bool drawEdges,
1366                                         bool drawVertices) const
1367 {
1368   //const int vertexRadius = 1;
1369   const int vertexRadius = 3;
1370   const Scalar vertexColor = Scalar(0, 0, 255);
1371   const int vertexThickness = -1;
1372 
1373   const Scalar edgeColor = Scalar(255, 0, 0);
1374   //const int edgeThickness = 1;
1375   const int edgeThickness = 2;
1376 
1377   if (drawEdges)
1378   {
1379     for (size_t i = 0; i < basisGraphs.size(); i++)
1380     {
1381       for (size_t v1 = 0; v1 < basisGraphs[i].getVerticesCount(); v1++)
1382       {
1383         for (size_t v2 = 0; v2 < basisGraphs[i].getVerticesCount(); v2++)
1384         {
1385           if (basisGraphs[i].areVerticesAdjacent(v1, v2))
1386           {
1387             line(drawImage, keypoints[v1], keypoints[v2], edgeColor, edgeThickness);
1388           }
1389         }
1390       }
1391     }
1392   }
1393   if (drawVertices)
1394   {
1395     for (size_t v = 0; v < basisGraphs[0].getVerticesCount(); v++)
1396     {
1397       circle(drawImage, keypoints[v], vertexRadius, vertexColor, vertexThickness);
1398     }
1399   }
1400 }
1401 
drawHoles(const Mat & srcImage,Mat & drawImage) const1402 void CirclesGridFinder::drawHoles(const Mat &srcImage, Mat &drawImage) const
1403 {
1404   //const int holeRadius = 4;
1405   //const int holeRadius = 2;
1406   //const int holeThickness = 1;
1407   const int holeRadius = 3;
1408   const int holeThickness = -1;
1409 
1410   //const Scalar holeColor = Scalar(0, 0, 255);
1411   const Scalar holeColor = Scalar(0, 255, 0);
1412 
1413   if (srcImage.channels() == 1)
1414     cvtColor(srcImage, drawImage, COLOR_GRAY2RGB);
1415   else
1416     srcImage.copyTo(drawImage);
1417 
1418   for (size_t i = 0; i < holes.size(); i++)
1419   {
1420     for (size_t j = 0; j < holes[i].size(); j++)
1421     {
1422       if (j != holes[i].size() - 1)
1423         line(drawImage, keypoints[holes[i][j]], keypoints[holes[i][j + 1]], Scalar(255, 0, 0), 2);
1424       if (i != holes.size() - 1)
1425         line(drawImage, keypoints[holes[i][j]], keypoints[holes[i + 1][j]], Scalar(255, 0, 0), 2);
1426 
1427       circle(drawImage, keypoints[holes[i][j]], holeRadius, holeColor, holeThickness);
1428     }
1429   }
1430 }
1431 
getDetectedGridSize() const1432 Size CirclesGridFinder::getDetectedGridSize() const
1433 {
1434   if (holes.size() == 0)
1435     return Size(0, 0);
1436 
1437   return Size((int)holes[0].size(), (int)holes.size());
1438 }
1439 
getHoles(std::vector<Point2f> & outHoles) const1440 void CirclesGridFinder::getHoles(std::vector<Point2f> &outHoles) const
1441 {
1442   outHoles.clear();
1443 
1444   for (size_t i = 0; i < holes.size(); i++)
1445   {
1446     for (size_t j = 0; j < holes[i].size(); j++)
1447     {
1448       outHoles.push_back(keypoints[holes[i][j]]);
1449     }
1450   }
1451 }
1452 
areIndicesCorrect(Point pos,std::vector<std::vector<size_t>> * points)1453 static bool areIndicesCorrect(Point pos, std::vector<std::vector<size_t> > *points)
1454 {
1455   if (pos.y < 0 || pos.x < 0)
1456     return false;
1457   return (static_cast<size_t> (pos.y) < points->size() && static_cast<size_t> (pos.x) < points->at(pos.y).size());
1458 }
1459 
getAsymmetricHoles(std::vector<cv::Point2f> & outHoles) const1460 void CirclesGridFinder::getAsymmetricHoles(std::vector<cv::Point2f> &outHoles) const
1461 {
1462   outHoles.clear();
1463 
1464   std::vector<Point> largeCornerIndices, smallCornerIndices;
1465   std::vector<Point> firstSteps, secondSteps;
1466   size_t cornerIdx = getFirstCorner(largeCornerIndices, smallCornerIndices, firstSteps, secondSteps);
1467   CV_Assert(largeHoles != 0 && smallHoles != 0)
1468     ;
1469 
1470   Point srcLargePos = largeCornerIndices[cornerIdx];
1471   Point srcSmallPos = smallCornerIndices[cornerIdx];
1472 
1473   while (areIndicesCorrect(srcLargePos, largeHoles) || areIndicesCorrect(srcSmallPos, smallHoles))
1474   {
1475     Point largePos = srcLargePos;
1476     while (areIndicesCorrect(largePos, largeHoles))
1477     {
1478       outHoles.push_back(keypoints[largeHoles->at(largePos.y)[largePos.x]]);
1479       largePos += firstSteps[cornerIdx];
1480     }
1481     srcLargePos += secondSteps[cornerIdx];
1482 
1483     Point smallPos = srcSmallPos;
1484     while (areIndicesCorrect(smallPos, smallHoles))
1485     {
1486       outHoles.push_back(keypoints[smallHoles->at(smallPos.y)[smallPos.x]]);
1487       smallPos += firstSteps[cornerIdx];
1488     }
1489     srcSmallPos += secondSteps[cornerIdx];
1490   }
1491 }
1492 
getDirection(Point2f p1,Point2f p2,Point2f p3)1493 double CirclesGridFinder::getDirection(Point2f p1, Point2f p2, Point2f p3)
1494 {
1495   Point2f a = p3 - p1;
1496   Point2f b = p2 - p1;
1497   return a.x * b.y - a.y * b.x;
1498 }
1499 
areSegmentsIntersecting(Segment seg1,Segment seg2)1500 bool CirclesGridFinder::areSegmentsIntersecting(Segment seg1, Segment seg2)
1501 {
1502   bool doesStraddle1 = (getDirection(seg2.s, seg2.e, seg1.s) * getDirection(seg2.s, seg2.e, seg1.e)) < 0;
1503   bool doesStraddle2 = (getDirection(seg1.s, seg1.e, seg2.s) * getDirection(seg1.s, seg1.e, seg2.e)) < 0;
1504   return doesStraddle1 && doesStraddle2;
1505 
1506   /*
1507    Point2f t1 = e1-s1;
1508    Point2f n1(t1.y, -t1.x);
1509    double c1 = -n1.ddot(s1);
1510 
1511    Point2f t2 = e2-s2;
1512    Point2f n2(t2.y, -t2.x);
1513    double c2 = -n2.ddot(s2);
1514 
1515    bool seg1 = ((n1.ddot(s2) + c1) * (n1.ddot(e2) + c1)) <= 0;
1516    bool seg1 = ((n2.ddot(s1) + c2) * (n2.ddot(e1) + c2)) <= 0;
1517 
1518    return seg1 && seg2;
1519    */
1520 }
1521 
getCornerSegments(const std::vector<std::vector<size_t>> & points,std::vector<std::vector<Segment>> & segments,std::vector<Point> & cornerIndices,std::vector<Point> & firstSteps,std::vector<Point> & secondSteps) const1522 void CirclesGridFinder::getCornerSegments(const std::vector<std::vector<size_t> > &points, std::vector<std::vector<Segment> > &segments,
1523                                           std::vector<Point> &cornerIndices, std::vector<Point> &firstSteps,
1524                                           std::vector<Point> &secondSteps) const
1525 {
1526   segments.clear();
1527   cornerIndices.clear();
1528   firstSteps.clear();
1529   secondSteps.clear();
1530   int h = (int)points.size();
1531   int w = (int)points[0].size();
1532   CV_Assert(h >= 2 && w >= 2)
1533     ;
1534 
1535   //all 8 segments with one end in a corner
1536   std::vector<Segment> corner;
1537   corner.push_back(Segment(keypoints[points[1][0]], keypoints[points[0][0]]));
1538   corner.push_back(Segment(keypoints[points[0][0]], keypoints[points[0][1]]));
1539   segments.push_back(corner);
1540   cornerIndices.push_back(Point(0, 0));
1541   firstSteps.push_back(Point(1, 0));
1542   secondSteps.push_back(Point(0, 1));
1543   corner.clear();
1544 
1545   corner.push_back(Segment(keypoints[points[0][w - 2]], keypoints[points[0][w - 1]]));
1546   corner.push_back(Segment(keypoints[points[0][w - 1]], keypoints[points[1][w - 1]]));
1547   segments.push_back(corner);
1548   cornerIndices.push_back(Point(w - 1, 0));
1549   firstSteps.push_back(Point(0, 1));
1550   secondSteps.push_back(Point(-1, 0));
1551   corner.clear();
1552 
1553   corner.push_back(Segment(keypoints[points[h - 2][w - 1]], keypoints[points[h - 1][w - 1]]));
1554   corner.push_back(Segment(keypoints[points[h - 1][w - 1]], keypoints[points[h - 1][w - 2]]));
1555   segments.push_back(corner);
1556   cornerIndices.push_back(Point(w - 1, h - 1));
1557   firstSteps.push_back(Point(-1, 0));
1558   secondSteps.push_back(Point(0, -1));
1559   corner.clear();
1560 
1561   corner.push_back(Segment(keypoints[points[h - 1][1]], keypoints[points[h - 1][0]]));
1562   corner.push_back(Segment(keypoints[points[h - 1][0]], keypoints[points[h - 2][0]]));
1563   cornerIndices.push_back(Point(0, h - 1));
1564   firstSteps.push_back(Point(0, -1));
1565   secondSteps.push_back(Point(1, 0));
1566   segments.push_back(corner);
1567   corner.clear();
1568 
1569   //y axis is inverted in computer vision so we check < 0
1570   bool isClockwise =
1571       getDirection(keypoints[points[0][0]], keypoints[points[0][w - 1]], keypoints[points[h - 1][w - 1]]) < 0;
1572   if (!isClockwise)
1573   {
1574 #ifdef DEBUG_CIRCLES
1575     std::cout << "Corners are counterclockwise" << std::endl;
1576 #endif
1577     std::reverse(segments.begin(), segments.end());
1578     std::reverse(cornerIndices.begin(), cornerIndices.end());
1579     std::reverse(firstSteps.begin(), firstSteps.end());
1580     std::reverse(secondSteps.begin(), secondSteps.end());
1581     std::swap(firstSteps, secondSteps);
1582   }
1583 }
1584 
doesIntersectionExist(const std::vector<Segment> & corner,const std::vector<std::vector<Segment>> & segments)1585 bool CirclesGridFinder::doesIntersectionExist(const std::vector<Segment> &corner, const std::vector<std::vector<Segment> > &segments)
1586 {
1587   for (size_t i = 0; i < corner.size(); i++)
1588   {
1589     for (size_t j = 0; j < segments.size(); j++)
1590     {
1591       for (size_t k = 0; k < segments[j].size(); k++)
1592       {
1593         if (areSegmentsIntersecting(corner[i], segments[j][k]))
1594           return true;
1595       }
1596     }
1597   }
1598 
1599   return false;
1600 }
1601 
getFirstCorner(std::vector<Point> & largeCornerIndices,std::vector<Point> & smallCornerIndices,std::vector<Point> & firstSteps,std::vector<Point> & secondSteps) const1602 size_t CirclesGridFinder::getFirstCorner(std::vector<Point> &largeCornerIndices, std::vector<Point> &smallCornerIndices, std::vector<
1603     Point> &firstSteps, std::vector<Point> &secondSteps) const
1604 {
1605   std::vector<std::vector<Segment> > largeSegments;
1606   std::vector<std::vector<Segment> > smallSegments;
1607 
1608   getCornerSegments(*largeHoles, largeSegments, largeCornerIndices, firstSteps, secondSteps);
1609   getCornerSegments(*smallHoles, smallSegments, smallCornerIndices, firstSteps, secondSteps);
1610 
1611   const size_t cornersCount = 4;
1612   CV_Assert(largeSegments.size() == cornersCount)
1613     ;
1614 
1615   bool isInsider[cornersCount];
1616 
1617   for (size_t i = 0; i < cornersCount; i++)
1618   {
1619     isInsider[i] = doesIntersectionExist(largeSegments[i], smallSegments);
1620   }
1621 
1622   int cornerIdx = 0;
1623   bool waitOutsider = true;
1624 
1625   for(;;)
1626   {
1627     if (waitOutsider)
1628     {
1629       if (!isInsider[(cornerIdx + 1) % cornersCount])
1630         waitOutsider = false;
1631     }
1632     else
1633     {
1634       if (isInsider[(cornerIdx + 1) % cornersCount])
1635         break;
1636     }
1637 
1638     cornerIdx = (cornerIdx + 1) % cornersCount;
1639   }
1640 
1641   return cornerIdx;
1642 }
1643