// The polygon approximation stage is used to decrease the number of points // describing the contour shape. // This is a good quality check to filter out areas without markers // because they can always be represented with a polygon that contains 4 vertices. // If the approximated polygon has more than (or fewer than) 4 vertices, // it is definitely not what we are looking for... void MarkerDetector::findMarkerCandidates(const std::vector<std::vector<cv::Point> >& contours, std::vector<Marker>& detectedMarkers) { std::vector<cv::Point> approxCurve; std::vector<Marker> possibleMarkers; // For each contour, analyze if it is a paralelepiped likely to be the marker. for (size_t i = 0; i < contours.size(); i++) { // Approximate a polygonal curve with the specified precision. // Approximate a curve or a polygon with another curve-polygon with less vertices // so that the distance between them is less or equal to the specified precision. // It uses the Douglas-Peucker algorithm http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm // See: http://docs.opencv.org/modules/imgproc/doc/structural_analysis_and_shape_descriptors.html#approxpolydp cv::approxPolyDP(contours[i], approxCurve, double(contours[i].size()) * 0.05, true); // We interested only in polygons that contains only four vertices. if (approxCurve.size() != 4) { continue; } // And they have to be convex. // See: http://docs.opencv.org/modules/imgproc/doc/structural_analysis_and_shape_descriptors.html#iscontourconvex if (!cv::isContourConvex(approxCurve)) { continue; } // Ensure that the distace between consecutive points is large enough. float minDist = std::numeric_limits<float>::max(); for (int i = 0; i < 4; i++) { cv::Point side = approxCurve[i] - approxCurve[(i + 1) % 4]; float squaredSideLength = float(side.dot(side)); minDist = std::min(minDist, squaredSideLength); } // Check that distance is not very small. if (minDist < minContourLengthAllowed) { continue; } // ... Marker m; for (int i = 0; i < 4; i++) { m.points.push_back(cv::Point2f(float(approxCurve[i].x), float(approxCurve[i].y))); } // Sort the points in anti-clockwise order: // trace a line between the first and second point and // if the third point is at the right side, then the points are anti-clockwise. cv::Point v1 = m.points[1] - m.points[0]; cv::Point v2 = m.points[2] - m.points[0]; double o = (v1.x * v2.y) - (v1.y * v2.x); // If the third point is in the left side, then sort in anti-clockwise order. if (o < 0.0) { std::swap(m.points[1], m.points[3]); } possibleMarkers.push_back(m); } // Remove these elements which corners are too close to each other. // First detect candidates for removal: std::vector< std::pair<int, int> > tooNearCandidates; for (size_t i = 0; i < possibleMarkers.size(); i++) { const Marker& m1 = possibleMarkers[i]; // Calculate the average distance of each corner to the nearest corner of the other marker candidate. for (size_t j = i + 1; j < possibleMarkers.size(); j++) { const Marker& m2 = possibleMarkers[j]; float distSquared = 0.0f; for (int c = 0; c < 4; c++) { cv::Point v = m1.points[c] - m2.points[c]; distSquared += v.dot(v); } distSquared /= 4.0f; if (distSquared < 100.0f) { tooNearCandidates.push_back(std::pair<int, int>(i, j)); } } } // Mark for removal the element of the pair with smaller perimeter. std::vector<bool> removalMask(possibleMarkers.size(), false); for (size_t i = 0; i < tooNearCandidates.size(); i++) { float p1 = MarkerDetector::perimeter(possibleMarkers[tooNearCandidates[i].first].points); float p2 = MarkerDetector::perimeter(possibleMarkers[tooNearCandidates[i].second].points); size_t removalIndex; if (p1 > p2) { removalIndex = tooNearCandidates[i].second; } else { removalIndex = tooNearCandidates[i].first; } removalMask[removalIndex] = true; } // Return candidates. detectedMarkers.clear(); for (size_t i = 0; i < possibleMarkers.size(); i++) { if (!removalMask[i]) { detectedMarkers.push_back(possibleMarkers[i]); } } }
void MarkerDetector::findCandidates ( const ContoursVector& contours, std::vector<Marker>& detectedMarkers ) { std::vector<cv::Point> approxCurve; std::vector<Marker> possibleMarkers; // For each contour, analyze if it is a parallelepiped likely to be the marker for (size_t i=0; i<contours.size(); i++) { // Approximate to a polygon double eps = contours[i].size() * 0.05; cv::approxPolyDP(contours[i], approxCurve, eps, true); // We interested only in polygons that contains only four points if (approxCurve.size() != 4) continue; // And they have to be convex if (!cv::isContourConvex(approxCurve)) continue; // Ensure that the distance between consecutive points is large enough float minDist = std::numeric_limits<float>::max(); for (int i = 0; i < 4; i++) { cv::Point side = approxCurve[i] - approxCurve[(i+1)%4]; float squaredSideLength = side.dot(side); minDist = std::min(minDist, squaredSideLength); } // Check that distance is not very small if (minDist < m_minContourLengthAllowed) continue; // All tests are passed. Save marker candidate: Marker m; for (int i = 0; i<4; i++) m.points.push_back( cv::Point2f(approxCurve[i].x,approxCurve[i].y) ); // Sort the points in anti-clockwise order // Trace a line between the first and second point. // If the third point is at the right side, then the points are anti-clockwise cv::Point v1 = m.points[1] - m.points[0]; cv::Point v2 = m.points[2] - m.points[0]; double o = (v1.x * v2.y) - (v1.y * v2.x); if (o < 0.0) //if the third point is in the left side, then sort in anti-clockwise order std::swap(m.points[1], m.points[3]); possibleMarkers.push_back(m); } // Remove these elements which corners are too close to each other. // First detect candidates for removal: std::vector< std::pair<int,int> > tooNearCandidates; for (size_t i=0;i<possibleMarkers.size();i++) { const Marker& m1 = possibleMarkers[i]; //calculate the average distance of each corner to the nearest corner of the other marker candidate for (size_t j=i+1;j<possibleMarkers.size();j++) { const Marker& m2 = possibleMarkers[j]; float distSquared = 0; for (int c = 0; c < 4; c++) { cv::Point v = m1.points[c] - m2.points[c]; distSquared += v.dot(v); } distSquared /= 4; if (distSquared < 100) { tooNearCandidates.push_back(std::pair<int,int>(i,j)); } } } // Mark for removal the element of the pair with smaller perimeter std::vector<bool> removalMask (possibleMarkers.size(), false); for (size_t i=0; i<tooNearCandidates.size(); i++) { float p1 = perimeter(possibleMarkers[tooNearCandidates[i].first ].points); float p2 = perimeter(possibleMarkers[tooNearCandidates[i].second].points); size_t removalIndex; if (p1 > p2) removalIndex = tooNearCandidates[i].second; else removalIndex = tooNearCandidates[i].first; removalMask[removalIndex] = true; } // Return candidates detectedMarkers.clear(); for (size_t i=0;i<possibleMarkers.size();i++) { if (!removalMask[i]) detectedMarkers.push_back(possibleMarkers[i]); } }
void MarkerDetector::findCandidates(cv::Mat src, std::vector<Marker>& detectedMarkers, int minPoints, int maxPoints) { cv::Mat thres2; src.copyTo ( thres2 ); std::vector<cv::Vec4i> hierarchy2; ContoursVector allContours; std::vector<cv::Point> approxCurve; std::vector<Marker> possibleMarkers; cv::findContours(thres2, allContours,hierarchy2, CV_RETR_LIST, CV_CHAIN_APPROX_NONE); for (size_t i=0; i<allContours.size(); i++) { int contourSize = allContours[i].size(); if ( minPoints< contourSize && contourSize<maxPoints ) { //drawContours( src, allContours, i, cv::Scalar(0,0,255), CV_FILLED, 8); cv::approxPolyDP(allContours[i], approxCurve, double(contourSize * 0.05), true); // Selecting only those with 4 points (corners) if (approxCurve.size() != 4) continue; // Selecting only convex polygons if (!cv::isContourConvex(cv::Mat(approxCurve))) continue; // Ensure that the distance between consecutive points is large enough float minDist = std::numeric_limits<float>::max(); for (int i = 0; i < 4; i++) { cv::Point side = approxCurve[i] - approxCurve[(i+1)%4]; //float squaredSideLength = side.dot(side); float squaredSideLength= std::sqrt ( ( float ) ( approxCurve[i].x-approxCurve[ ( i+1 ) %4].x ) * ( approxCurve[i].x-approxCurve[ ( i+1 ) %4].x ) + ( approxCurve[i].y-approxCurve[ ( i+1 ) %4].y ) * ( approxCurve[i].y-approxCurve[ ( i+1 ) %4].y ) ); //std::cout<<squaredSideLength<< " " <<test<<std::endl; //minDist = std::min(minDist, squaredSideLength); if ( squaredSideLength<minDist ) minDist=squaredSideLength; } // Selecting those whose minimum side length is still greater than the limit if (minDist <= 10) continue; // All tests are passed. Save marker candidate: Marker m; for (int i = 0; i<4; i++) m.points.push_back( cv::Point2f(approxCurve[i].x,approxCurve[i].y) ); // Sort the points in anti-clockwise order // Trace a line between the first and second point. // If the third point is at the right side, then the points are anti-clockwise cv::Point v1 = m.points[1] - m.points[0]; cv::Point v2 = m.points[2] - m.points[0]; double o = (v1.x * v2.y) - (v1.y * v2.x); if (o < 0.0) //if the third point is in the left side, then sort in anti-clockwise order std::swap(m.points[1], m.points[3]); possibleMarkers.push_back(m); } } // Remove these elements which corners are too close to each other. // First detect candidates for removal: std::vector< std::pair<int,int> > tooNearCandidates; for (size_t i=0;i<possibleMarkers.size();i++) { const Marker& m1 = possibleMarkers[i]; //calculate the average distance of each corner to the nearest corner of the other marker candidate for (size_t j=i+1;j<possibleMarkers.size();j++) { const Marker& m2 = possibleMarkers[j]; float distSquared = 0; for (int c = 0; c < 4; c++) { /* distSquared+= sqrt ( ( m1.points[c].x-m2.points[c].x ) * ( m1.points[c].x-m2.points[c].x ) + ( m1.points[c].y-m2.points[c].y ) * ( m1.points[c].y-m2.points[c].y ) ); */ distSquared+= sqrt ( ( possibleMarkers[i].points[c].x-possibleMarkers[j].points[c].x ) * ( possibleMarkers[i].points[c].x-possibleMarkers[j].points[c].x ) + ( possibleMarkers[i].points[c].y-possibleMarkers[j].points[c].y ) * ( possibleMarkers[i].points[c].y-possibleMarkers[j].points[c].y ) ); //cv::Point v = m1.points[c] - m2.points[c]; //distSquared2 += v.dot(v); } distSquared /= 4; if (distSquared < 10) { tooNearCandidates.push_back(std::pair<int,int>(i,j)); } } } // Mark for removal the element of the pair with smaller perimeter std::vector<bool> removalMask (possibleMarkers.size(), false); for (size_t i=0; i<tooNearCandidates.size(); i++) { float p1 = getPerimeter(possibleMarkers[tooNearCandidates[i].first ].points); float p2 = getPerimeter(possibleMarkers[tooNearCandidates[i].second].points); size_t removalIndex; if (p1 > p2) removalIndex = tooNearCandidates[i].second; else removalIndex = tooNearCandidates[i].first; removalMask[removalIndex] = true; } // Return candidates detectedMarkers.clear(); for (size_t i=0;i<possibleMarkers.size();i++) { if (!removalMask[i]) detectedMarkers.push_back(possibleMarkers[i]); } }