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