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> ¢ers)
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