|
|
|
@ -43,9 +43,259 @@ |
|
|
|
|
#include "circlesgrid.hpp" |
|
|
|
|
//#define DEBUG_CIRCLES
|
|
|
|
|
|
|
|
|
|
#ifdef DEBUG_CIRCLES |
|
|
|
|
#include <opencv2/highgui/highgui.hpp> |
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
using namespace cv; |
|
|
|
|
using namespace std; |
|
|
|
|
|
|
|
|
|
void CirclesGridClusterFinder::hierarchicalClustering(const vector<Point2f> points, const Size &patternSize, vector<Point2f> &patternPoints) |
|
|
|
|
{ |
|
|
|
|
Mat dists(points.size(), points.size(), CV_32FC1, Scalar(0)); |
|
|
|
|
Mat distsMask(dists.size(), CV_8UC1, Scalar(0)); |
|
|
|
|
for(size_t i=0; i<points.size(); i++) |
|
|
|
|
{ |
|
|
|
|
for(size_t j=i+1; j<points.size(); j++) |
|
|
|
|
{ |
|
|
|
|
dists.at<float>(i, j) = norm(points[i] - points[j]); |
|
|
|
|
distsMask.at<uchar>(i, j) = 255; |
|
|
|
|
//TODO: use symmetry
|
|
|
|
|
distsMask.at<uchar>(j, i) = distsMask.at<uchar>(i, j); |
|
|
|
|
dists.at<float>(j, i) = dists.at<float>(i, j); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
vector<std::list<size_t> > clusters(points.size()); |
|
|
|
|
for(size_t i=0; i<points.size(); i++) |
|
|
|
|
{ |
|
|
|
|
clusters[i].push_back(i); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
int patternClusterIdx = 0; |
|
|
|
|
while(clusters[patternClusterIdx].size() < patternSize.area() && countNonZero(distsMask == 255) > 0) |
|
|
|
|
{ |
|
|
|
|
Point minLoc; |
|
|
|
|
minMaxLoc(dists, 0, 0, &minLoc, 0, distsMask); |
|
|
|
|
int minIdx = std::min(minLoc.x, minLoc.y); |
|
|
|
|
int maxIdx = std::max(minLoc.x, minLoc.y); |
|
|
|
|
|
|
|
|
|
distsMask.row(maxIdx).setTo(0); |
|
|
|
|
distsMask.col(maxIdx).setTo(0); |
|
|
|
|
Mat newDists = cv::min(dists.row(minLoc.x), dists.row(minLoc.y)); |
|
|
|
|
Mat tmpLine = dists.row(minIdx); |
|
|
|
|
newDists.copyTo(tmpLine); |
|
|
|
|
tmpLine = dists.col(minIdx); |
|
|
|
|
newDists.copyTo(tmpLine); |
|
|
|
|
|
|
|
|
|
clusters[minIdx].splice(clusters[minIdx].end(), clusters[maxIdx]); |
|
|
|
|
patternClusterIdx = minIdx; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
patternPoints.clear(); |
|
|
|
|
if(clusters[patternClusterIdx].size() != patternSize.area()) |
|
|
|
|
{ |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
patternPoints.reserve(clusters[patternClusterIdx].size()); |
|
|
|
|
for(std::list<size_t>::iterator it = clusters[patternClusterIdx].begin(); it != clusters[patternClusterIdx].end(); it++) |
|
|
|
|
{ |
|
|
|
|
patternPoints.push_back(points[*it]); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void CirclesGridClusterFinder::findGrid(const std::vector<cv::Point2f> points, cv::Size patternSize, vector<Point2f>& centers) |
|
|
|
|
{ |
|
|
|
|
centers.clear(); |
|
|
|
|
|
|
|
|
|
vector<Point2f> patternPoints; |
|
|
|
|
hierarchicalClustering(points, patternSize, patternPoints); |
|
|
|
|
if(patternPoints.empty()) |
|
|
|
|
{ |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
vector<Point2f> hull2f; |
|
|
|
|
convexHull(Mat(patternPoints), hull2f, false); |
|
|
|
|
|
|
|
|
|
vector<Point2f> corners; |
|
|
|
|
findCorners(hull2f, corners); |
|
|
|
|
|
|
|
|
|
vector<Point2f> outsideCorners; |
|
|
|
|
findOutsideCorners(corners, outsideCorners); |
|
|
|
|
|
|
|
|
|
vector<Point2f> sortedCorners; |
|
|
|
|
getSortedCorners(hull2f, corners, outsideCorners, sortedCorners); |
|
|
|
|
|
|
|
|
|
vector<Point2f> rectifiedPatternPoints; |
|
|
|
|
rectifyPatternPoints(patternSize, patternPoints, sortedCorners, rectifiedPatternPoints); |
|
|
|
|
|
|
|
|
|
parsePatternPoints(patternSize, patternPoints, rectifiedPatternPoints, centers); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void CirclesGridClusterFinder::findCorners(const std::vector<cv::Point2f> &hull2f, std::vector<cv::Point2f> &corners) |
|
|
|
|
{ |
|
|
|
|
//find angles (cosines) of vertices in convex hull
|
|
|
|
|
vector<float> angles; |
|
|
|
|
for(size_t i=0; i<hull2f.size(); i++) |
|
|
|
|
{ |
|
|
|
|
Point2f vec1 = hull2f[(i+1) % hull2f.size()] - hull2f[i % hull2f.size()]; |
|
|
|
|
Point2f vec2 = hull2f[(i-1 + static_cast<int>(hull2f.size())) % hull2f.size()] - hull2f[i % hull2f.size()]; |
|
|
|
|
float angle = vec1.ddot(vec2) / (norm(vec1) * norm(vec2)); |
|
|
|
|
angles.push_back(angle); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//sort angles by cosine
|
|
|
|
|
//corners are the most sharp angles (6)
|
|
|
|
|
Mat anglesMat = Mat(angles); |
|
|
|
|
Mat sortedIndices; |
|
|
|
|
sortIdx(anglesMat, sortedIndices, CV_SORT_EVERY_COLUMN + CV_SORT_DESCENDING); |
|
|
|
|
CV_Assert(sortedIndices.type() == CV_32SC1); |
|
|
|
|
const int cornersCount = 6; |
|
|
|
|
corners.clear(); |
|
|
|
|
for(int i=0; i<cornersCount; i++) |
|
|
|
|
{ |
|
|
|
|
corners.push_back(hull2f[sortedIndices.at<int>(i, 0)]); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void CirclesGridClusterFinder::findOutsideCorners(const std::vector<cv::Point2f> &corners, std::vector<cv::Point2f> &outsideCorners) |
|
|
|
|
{ |
|
|
|
|
//find two pairs of the most nearest corners
|
|
|
|
|
double min1 = std::numeric_limits<double>::max(); |
|
|
|
|
double min2 = std::numeric_limits<double>::max(); |
|
|
|
|
Point minLoc1, minLoc2; |
|
|
|
|
|
|
|
|
|
for(size_t i=0; i<corners.size(); i++) |
|
|
|
|
{ |
|
|
|
|
for(size_t j=i+1; j<corners.size(); j++) |
|
|
|
|
{ |
|
|
|
|
double dist = norm(corners[i] - corners[j]); |
|
|
|
|
Point loc(j, i); |
|
|
|
|
if(dist < min1) |
|
|
|
|
{ |
|
|
|
|
min2 = min1; |
|
|
|
|
minLoc2 = minLoc1; |
|
|
|
|
min1 = dist; |
|
|
|
|
minLoc1 = loc; |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
if(dist < min2) |
|
|
|
|
{ |
|
|
|
|
min2 = dist; |
|
|
|
|
minLoc2 = loc; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
std::set<int> outsideCornersIndices; |
|
|
|
|
for(size_t i=0; i<corners.size(); i++) |
|
|
|
|
{ |
|
|
|
|
outsideCornersIndices.insert(i); |
|
|
|
|
} |
|
|
|
|
outsideCornersIndices.erase(minLoc1.x); |
|
|
|
|
outsideCornersIndices.erase(minLoc1.y); |
|
|
|
|
outsideCornersIndices.erase(minLoc2.x); |
|
|
|
|
outsideCornersIndices.erase(minLoc2.y); |
|
|
|
|
|
|
|
|
|
outsideCorners.clear(); |
|
|
|
|
for(std::set<int>::iterator it = outsideCornersIndices.begin(); it != outsideCornersIndices.end(); it++) |
|
|
|
|
{ |
|
|
|
|
outsideCorners.push_back(corners[*it]); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
{ |
|
|
|
|
Point2f center = std::accumulate(corners.begin(), corners.end(), Point2f(0.0f, 0.0f)); |
|
|
|
|
center *= 1.0 / corners.size(); |
|
|
|
|
|
|
|
|
|
vector<Point2f> centerToCorners; |
|
|
|
|
for(size_t i=0; i<outsideCorners.size(); i++) |
|
|
|
|
{ |
|
|
|
|
centerToCorners.push_back(outsideCorners[i] - center); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//TODO: use CirclesGridFinder::getDirection
|
|
|
|
|
float crossProduct = centerToCorners[0].x * centerToCorners[1].y - centerToCorners[0].y * centerToCorners[1].x; |
|
|
|
|
//y axis is inverted in computer vision so we check > 0
|
|
|
|
|
bool isClockwise = crossProduct > 0; |
|
|
|
|
Point2f firstCorner = isClockwise ? outsideCorners[1] : outsideCorners[0]; |
|
|
|
|
|
|
|
|
|
std::vector<Point2f>::const_iterator firstCornerIterator = std::find(hull2f.begin(), hull2f.end(), firstCorner); |
|
|
|
|
sortedCorners.clear(); |
|
|
|
|
for(vector<Point2f>::const_iterator it = firstCornerIterator; it != hull2f.end(); it++) |
|
|
|
|
{ |
|
|
|
|
vector<Point2f>::const_iterator itCorners = std::find(corners.begin(), corners.end(), *it); |
|
|
|
|
if(itCorners != corners.end()) |
|
|
|
|
{ |
|
|
|
|
sortedCorners.push_back(*it); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
for(vector<Point2f>::const_iterator it = hull2f.begin(); it != firstCornerIterator; it++) |
|
|
|
|
{ |
|
|
|
|
vector<Point2f>::const_iterator itCorners = std::find(corners.begin(), corners.end(), *it); |
|
|
|
|
if(itCorners != corners.end()) |
|
|
|
|
{ |
|
|
|
|
sortedCorners.push_back(*it); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void CirclesGridClusterFinder::rectifyPatternPoints(const cv::Size &patternSize, const std::vector<cv::Point2f> &patternPoints, const std::vector<cv::Point2f> &sortedCorners, std::vector<cv::Point2f> &rectifiedPatternPoints) |
|
|
|
|
{ |
|
|
|
|
//indices of corner points in pattern
|
|
|
|
|
vector<Point> trueIndices; |
|
|
|
|
trueIndices.push_back(Point(0, 0)); |
|
|
|
|
trueIndices.push_back(Point(patternSize.width - 1, 0)); |
|
|
|
|
trueIndices.push_back(Point(patternSize.width - 1, 1)); |
|
|
|
|
trueIndices.push_back(Point(patternSize.width - 1, patternSize.height - 2)); |
|
|
|
|
trueIndices.push_back(Point(patternSize.width - 1, patternSize.height - 1)); |
|
|
|
|
trueIndices.push_back(Point(0, patternSize.height - 1)); |
|
|
|
|
|
|
|
|
|
vector<Point2f> idealPoints; |
|
|
|
|
for(size_t idx=0; idx<trueIndices.size(); idx++) |
|
|
|
|
{ |
|
|
|
|
int i = trueIndices[idx].y; |
|
|
|
|
int j = trueIndices[idx].x; |
|
|
|
|
idealPoints.push_back(Point2f((2*j + i % 2)*squareSize, i*squareSize)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Mat homography = findHomography(Mat(sortedCorners), Mat(idealPoints), 0); |
|
|
|
|
Mat rectifiedPointsMat; |
|
|
|
|
transform(Mat(patternPoints), rectifiedPointsMat, homography); |
|
|
|
|
rectifiedPatternPoints.clear(); |
|
|
|
|
convertPointsHomogeneous(rectifiedPointsMat, rectifiedPatternPoints); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void CirclesGridClusterFinder::parsePatternPoints(const cv::Size &patternSize, const std::vector<cv::Point2f> &patternPoints, const std::vector<cv::Point2f> &rectifiedPatternPoints, std::vector<cv::Point2f> ¢ers) |
|
|
|
|
{ |
|
|
|
|
flann::LinearIndexParams flannIndexParams; |
|
|
|
|
flann::Index flannIndex(Mat(rectifiedPatternPoints).reshape(1), flannIndexParams); |
|
|
|
|
|
|
|
|
|
centers.clear(); |
|
|
|
|
for( int i = 0; i < patternSize.height; i++ ) |
|
|
|
|
{ |
|
|
|
|
for( int j = 0; j < patternSize.width; j++ ) |
|
|
|
|
{ |
|
|
|
|
Point2f idealPt((2*j + i % 2)*squareSize, i*squareSize); |
|
|
|
|
vector<float> query = Mat(idealPt); |
|
|
|
|
int knn = 1; |
|
|
|
|
vector<int> indices(knn); |
|
|
|
|
vector<float> dists(knn); |
|
|
|
|
flannIndex.knnSearch(query, indices, dists, knn, flann::SearchParams()); |
|
|
|
|
centers.push_back(patternPoints.at(indices[0])); |
|
|
|
|
|
|
|
|
|
if(dists[0] > maxRectifiedDistance) |
|
|
|
|
{ |
|
|
|
|
centers.clear(); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Graph::Graph(size_t n) |
|
|
|
|
{ |
|
|
|
|
for (size_t i = 0; i < n; i++) |
|
|
|
|