Implemented new algorithm for asymmetric circles pattern detection. Use flag CALIB_CB_CLUSTERING.
This commit is contained in:
parent
2c8af20bd0
commit
37cd2b6f25
@ -560,7 +560,8 @@ CV_EXPORTS void drawChessboardCorners( Mat& image, Size patternSize,
|
|||||||
const vector<Point2f>& corners,
|
const vector<Point2f>& corners,
|
||||||
bool patternWasFound );
|
bool patternWasFound );
|
||||||
|
|
||||||
enum { CALIB_CB_SYMMETRIC_GRID = 1, CALIB_CB_ASYMMETRIC_GRID = 2 };
|
enum { CALIB_CB_SYMMETRIC_GRID = 1, CALIB_CB_ASYMMETRIC_GRID = 2,
|
||||||
|
CALIB_CB_CLUSTERING = 4, CALIB_CB_WHITE_CIRCLES = 8 };
|
||||||
|
|
||||||
//! finds circles' grid pattern of the specified size in the image
|
//! finds circles' grid pattern of the specified size in the image
|
||||||
CV_EXPORTS_W bool findCirclesGrid( const Mat& image, Size patternSize,
|
CV_EXPORTS_W bool findCirclesGrid( const Mat& image, Size patternSize,
|
||||||
|
@ -1937,7 +1937,13 @@ void drawChessboardCorners( Mat& image, Size patternSize,
|
|||||||
bool findCirclesGrid( const Mat& image, Size patternSize,
|
bool findCirclesGrid( const Mat& image, Size patternSize,
|
||||||
vector<Point2f>& centers, int flags )
|
vector<Point2f>& centers, int flags )
|
||||||
{
|
{
|
||||||
Ptr<SimpleBlobDetector> detector = new SimpleBlobDetector();
|
SimpleBlobDetector::Params params;
|
||||||
|
if(flags & CALIB_CB_WHITE_CIRCLES)
|
||||||
|
{
|
||||||
|
params.filterByColor = true;
|
||||||
|
params.blobColor = 255;
|
||||||
|
}
|
||||||
|
Ptr<SimpleBlobDetector> detector = new SimpleBlobDetector(params);
|
||||||
//Ptr<FeatureDetector> detector = new MserFeatureDetector();
|
//Ptr<FeatureDetector> detector = new MserFeatureDetector();
|
||||||
vector<KeyPoint> keypoints;
|
vector<KeyPoint> keypoints;
|
||||||
detector->detect(image, keypoints);
|
detector->detect(image, keypoints);
|
||||||
@ -1947,6 +1953,13 @@ bool findCirclesGrid( const Mat& image, Size patternSize,
|
|||||||
points.push_back (keypoints[i].pt);
|
points.push_back (keypoints[i].pt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if((flags & CALIB_CB_CLUSTERING) && (flags & CALIB_CB_ASYMMETRIC_GRID))
|
||||||
|
{
|
||||||
|
CirclesGridClusterFinder circlesGridClusterFinder;
|
||||||
|
circlesGridClusterFinder.findGrid(points, patternSize, centers);
|
||||||
|
return !centers.empty();
|
||||||
|
}
|
||||||
|
|
||||||
CirclesGridFinderParameters parameters;
|
CirclesGridFinderParameters parameters;
|
||||||
parameters.vertexPenalty = -0.6f;
|
parameters.vertexPenalty = -0.6f;
|
||||||
parameters.vertexGain = 1;
|
parameters.vertexGain = 1;
|
||||||
|
@ -43,9 +43,259 @@
|
|||||||
#include "circlesgrid.hpp"
|
#include "circlesgrid.hpp"
|
||||||
//#define DEBUG_CIRCLES
|
//#define DEBUG_CIRCLES
|
||||||
|
|
||||||
|
#ifdef DEBUG_CIRCLES
|
||||||
|
#include <opencv2/highgui/highgui.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
using namespace cv;
|
using namespace cv;
|
||||||
using namespace std;
|
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)
|
Graph::Graph(size_t n)
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < n; i++)
|
for (size_t i = 0; i < n; i++)
|
||||||
|
@ -45,9 +45,33 @@
|
|||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <list>
|
||||||
|
#include <numeric>
|
||||||
|
|
||||||
#include "precomp.hpp"
|
#include "precomp.hpp"
|
||||||
|
|
||||||
|
class CirclesGridClusterFinder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CirclesGridClusterFinder()
|
||||||
|
{
|
||||||
|
squareSize = 1.0f;
|
||||||
|
maxRectifiedDistance = squareSize / 2.0;
|
||||||
|
}
|
||||||
|
void findGrid(const std::vector<cv::Point2f> points, cv::Size patternSize, std::vector<cv::Point2f>& centers);
|
||||||
|
|
||||||
|
//cluster 2d points by geometric coordinates
|
||||||
|
void hierarchicalClustering(const std::vector<cv::Point2f> points, const cv::Size &patternSize, std::vector<cv::Point2f> &patternPoints);
|
||||||
|
private:
|
||||||
|
void findCorners(const std::vector<cv::Point2f> &hull2f, std::vector<cv::Point2f> &corners);
|
||||||
|
void findOutsideCorners(const std::vector<cv::Point2f> &corners, std::vector<cv::Point2f> &outsideCorners);
|
||||||
|
void getSortedCorners(const std::vector<cv::Point2f> &hull2f, const std::vector<cv::Point2f> &corners, const std::vector<cv::Point2f> &outsideCorners, std::vector<cv::Point2f> &sortedCorners);
|
||||||
|
void rectifyPatternPoints(const cv::Size &patternSize, const std::vector<cv::Point2f> &patternPoints, const std::vector<cv::Point2f> &sortedCorners, std::vector<cv::Point2f> &rectifiedPatternPoints);
|
||||||
|
void parsePatternPoints(const cv::Size &patternSize, const std::vector<cv::Point2f> &patternPoints, const std::vector<cv::Point2f> &rectifiedPatternPoints, std::vector<cv::Point2f> ¢ers);
|
||||||
|
|
||||||
|
float squareSize, maxRectifiedDistance;
|
||||||
|
};
|
||||||
|
|
||||||
class Graph
|
class Graph
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user