You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1983 lines
65 KiB
1983 lines
65 KiB
// This file is part of OpenCV project. |
|
// It is subject to the license terms in the LICENSE file found in the top-level directory |
|
// of this distribution and at http://opencv.org/license.html. |
|
|
|
#include "precomp.hpp" |
|
#include <opencv2/core.hpp> |
|
#include <unordered_map> |
|
#include <numeric> |
|
|
|
namespace cv { |
|
namespace ximgproc { |
|
|
|
// typedef |
|
typedef std::vector<Point> VP; |
|
typedef std::vector<VP> VVP; |
|
typedef unsigned int uint; |
|
|
|
// ellipse format |
|
struct Ellipse { |
|
Point2f center; |
|
float a, b; |
|
float radius; |
|
float score; |
|
|
|
Ellipse() { |
|
center = Point2f(0, 0); |
|
a = 0.f, b = 0.f; |
|
radius = 0.f, score = 0.f; |
|
}; |
|
|
|
Ellipse(Point2f _center, float _a, float _b, float _radius, |
|
float _score = 0.f) { |
|
center = _center; |
|
this->a = _a, this->b = _b; |
|
this->radius = _radius, this->score = _score; |
|
}; |
|
|
|
Ellipse(float _center_x, float _center_y, float _a, float _b, float _radius, |
|
float _score = 0.f) { |
|
center = Point2f(_center_x, _center_y); |
|
this->a = _a, this->b = _b; |
|
this->radius = _radius, this->score = _score; |
|
}; |
|
|
|
Ellipse(const Ellipse &other) { |
|
center = other.center; |
|
a = other.a, b = other.b; |
|
radius = other.radius, score = other.score; |
|
}; |
|
|
|
bool operator<(const Ellipse &other) const { |
|
if (score == other.score) { |
|
float lhs_e = b / a; |
|
float rhs_e = other.b / other.a; |
|
if (lhs_e == rhs_e) |
|
return false; |
|
return lhs_e > rhs_e; |
|
} |
|
return score > other.score; |
|
}; |
|
|
|
virtual ~Ellipse() = default; |
|
}; |
|
|
|
static int inline signal(float val) { |
|
return val > 0.f ? 1 : -1; |
|
} |
|
|
|
static bool sortPoint(const Point &lhs, const Point &rhs) { |
|
if (lhs.x == rhs.x) { |
|
return lhs.y < rhs.y; |
|
} |
|
return lhs.x < rhs.x; |
|
} |
|
|
|
static Point2f lineCrossPoint(Point2f l1p1, Point2f l1p2, Point2f l2p1, Point2f l2p2) { |
|
Point2f crossPoint; |
|
float k1, k2, b1, b2; |
|
if (l1p1.x == l1p2.x && l2p1.x == l2p2.x) { |
|
crossPoint = Point2f(0, 0); |
|
return crossPoint; |
|
} |
|
if (l1p1.x == l1p2.x) { |
|
crossPoint.x = l1p1.x; |
|
k2 = (l2p2.y - l2p1.y) / (l2p2.x - l2p1.x); |
|
b2 = l2p1.y - k2 * l2p1.x; |
|
crossPoint.y = k2 * crossPoint.x + b2; |
|
return crossPoint; |
|
} |
|
if (l2p1.x == l2p2.x) { |
|
crossPoint.x = l2p1.x; |
|
k2 = (l1p2.y - l1p1.y) / (l1p2.x - l1p1.x); |
|
b2 = l1p1.y - k2 * l1p1.x; |
|
crossPoint.y = k2 * crossPoint.x + b2; |
|
return crossPoint; |
|
} |
|
|
|
k1 = (l1p2.y - l1p1.y) / (l1p2.x - l1p1.x); |
|
k2 = (l2p2.y - l2p1.y) / (l2p2.x - l2p1.x); |
|
b1 = l1p1.y - k1 * l1p1.x; |
|
b2 = l2p1.y - k2 * l2p1.x; |
|
if (k1 == k2) |
|
crossPoint = Point2f(0, 0); |
|
else { |
|
crossPoint.x = (b2 - b1) / (k1 - k2); |
|
crossPoint.y = k1 * crossPoint.x + b1; |
|
} |
|
return crossPoint; |
|
} |
|
|
|
static void pointToMat(Point2f p1, Point2f p2, Mat& mat) { |
|
mat.at<float>(0, 0) = p1.x; |
|
mat.at<float>(0, 1) = p1.y; |
|
mat.at<float>(1, 0) = p2.x; |
|
mat.at<float>(1, 1) = p2.y; |
|
} |
|
|
|
static float valueOfPoints(Point2f p3, Point2f p2, Point2f p1, Point2f p4, Point2f p5, Point2f p6) { |
|
float result = 1; |
|
|
|
Point2f v = lineCrossPoint(p1, p2, p3, p4); |
|
Point2f w = lineCrossPoint(p5, p6, p3, p4); |
|
Point2f u = lineCrossPoint(p5, p6, p1, p2); |
|
|
|
Mat B = Mat(2, 2, CV_32F); |
|
Mat C = Mat(2, 2, CV_32F); |
|
|
|
pointToMat(u, v, B); |
|
pointToMat(p1, p2, C); |
|
Mat A = C * B.inv(); |
|
result *= |
|
A.at<float>(0, 0) * A.at<float>(1, 0) / (A.at<float>(0, 1) * A.at<float>(1, 1)); |
|
|
|
pointToMat(p3, p4, C); |
|
pointToMat(v, w, B); |
|
A = C * B.inv(); |
|
result *= |
|
A.at<float>(0, 0) * A.at<float>(1, 0) / (A.at<float>(0, 1) * A.at<float>(1, 1)); |
|
|
|
pointToMat(p5, p6, C); |
|
pointToMat(w, u, B); |
|
A = C * B.inv(); |
|
result *= |
|
A.at<float>(0, 0) * A.at<float>(1, 0) / (A.at<float>(0, 1) * A.at<float>(1, 1)); |
|
return result; |
|
} |
|
|
|
static float inline pointDistance2(const Point &A, const Point &B) { |
|
return float(((B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y))); |
|
} |
|
|
|
static float getMinAnglePI(float alpha, float beta) { |
|
auto pi = float(CV_PI); |
|
auto pi2 = float(2.0 * CV_PI); |
|
|
|
// normalize data in [0, 2*pi] |
|
float a = fmod(alpha + pi2, pi2); |
|
float b = fmod(beta + pi2, pi2); |
|
|
|
// normalize data in [0, pi] |
|
if (a > pi) |
|
a -= pi; |
|
if (b > pi) |
|
b -= pi; |
|
|
|
if (a > b) |
|
swap(a, b); |
|
|
|
float diff1 = b - a; |
|
float diff2 = pi - diff1; |
|
|
|
return min(diff1, diff2); |
|
} |
|
|
|
// ellipse data |
|
struct EllipseData { |
|
bool isValid; |
|
float ta, tb; |
|
float ra, rb; |
|
Point2f Ma, Mb, Cab; |
|
std::vector<float> Sa, Sb; |
|
}; |
|
|
|
// implement of ellipse detector |
|
class EllipseDetectorImpl { |
|
|
|
// preprocessing - Gaussian Smooth. |
|
Size _kernelSize; // size of the Gaussian filter |
|
double _sigma; // sigma of the Gaussian filter |
|
|
|
// selection strategy - Step 1 - Discard noisy or straight arcs. |
|
int _minEdgeLength; // minimum edge size |
|
float _minOrientedRectSide; // minimum size of the oriented bounding box containing the arc |
|
|
|
// selection strategy - Step 2 - Remove according to mutual convexity. |
|
float _positionThreshold; |
|
|
|
// selection strategy - Step 3 - Number of points considered for slope estimation when estimating the center. |
|
unsigned _uNs; // find at most Ns parallel chords |
|
|
|
// selection strategy - Step 3 - Discard pairs of arcs if their estimated center is not close enough |
|
float _maxCenterDistance; // maximum distance in pixel between 2 center points |
|
float _maxCenterDistance2; // pow(_maxCenterDistance, 2) |
|
|
|
// validation - Points within a threshold are considered to lie on the ellipse contour. |
|
float _maxDistanceToEllipseContour; // maximum distance between a point and the contour |
|
|
|
// validation - Assign a score. |
|
float _minScore; // minimum score to confirm a detection |
|
float _minReliability; // minimum auxiliary score to confirm a detection |
|
|
|
// auxiliary variables |
|
Size _imgSize; // input image size |
|
|
|
int ACC_N_SIZE, ACC_R_SIZE, ACC_A_SIZE; // size of accumulator |
|
int *accN, *accR, *accA; // pointer to accumulator |
|
|
|
public: |
|
float countsOfFindEllipse; |
|
float countsOfGetFastCenter; |
|
|
|
EllipseDetectorImpl(); |
|
|
|
~EllipseDetectorImpl() = default; |
|
|
|
// detect the ellipses in the gray image |
|
void detect(Mat1b &image, std::vector<Ellipse> &ellipses); |
|
|
|
// set the parameters of the detector |
|
void setParameters(float maxCenterDistance, float minScore, float minReliability); |
|
|
|
private: |
|
// keys for hash table |
|
static const ushort PAIR_12 = 0x00; |
|
static const ushort PAIR_23 = 0x01; |
|
static const ushort PAIR_34 = 0x02; |
|
static const ushort PAIR_14 = 0x03; |
|
|
|
static uint inline generateKey(uchar pair, ushort u, ushort v); |
|
|
|
void preProcessing(Mat1b &image, Mat1b &dp, Mat1b &dn); |
|
|
|
void clusterEllipses(std::vector<Ellipse> &ellipses); |
|
|
|
static float |
|
getMedianSlope(std::vector<Point2f> &med, Point2f ¢ers, std::vector<float> &slopes); |
|
|
|
void getFastCenter(std::vector<Point> &e1, std::vector<Point> &e2, EllipseData &data); |
|
|
|
void detectEdges13(Mat1b &DP, VVP &points_1, VVP &points_3); |
|
|
|
void detectEdges24(Mat1b &DN, VVP &points_2, VVP &points_4); |
|
|
|
void |
|
findEllipses(Point2f ¢er, VP &edge_i, VP &edge_j, VP &edge_k, EllipseData &data_ij, |
|
EllipseData &data_ik, std::vector<Ellipse> &ellipses); |
|
|
|
static Point2f getCenterCoordinates(EllipseData &data_ij, EllipseData &data_ik); |
|
|
|
void |
|
getTriplets124(VVP &pi, VVP &pj, VVP &pk, std::unordered_map<uint, EllipseData> &data, |
|
std::vector<Ellipse> &ellipses); |
|
|
|
void |
|
getTriplets231(VVP &pi, VVP &pj, VVP &pk, std::unordered_map<uint, EllipseData> &data, |
|
std::vector<Ellipse> &ellipses); |
|
|
|
void |
|
getTriplets342(VVP &pi, VVP &pj, VVP &pk, std::unordered_map<uint, EllipseData> &data, |
|
std::vector<Ellipse> &ellipses); |
|
|
|
void |
|
getTriplets413(VVP &pi, VVP &pj, VVP &pk, std::unordered_map<uint, EllipseData> &data, |
|
std::vector<Ellipse> &ellipses); |
|
|
|
static void labeling(Mat1b &image, VVP &segments, int minLength); |
|
}; |
|
|
|
EllipseDetectorImpl::EllipseDetectorImpl() { |
|
// Default Parameters Settings |
|
_kernelSize = Size(5, 5); |
|
_sigma = 1.0; |
|
_positionThreshold = 1.0f; |
|
_maxCenterDistance = 100.0f * 0.05f; |
|
_maxCenterDistance2 = _maxCenterDistance * _maxCenterDistance; |
|
_minEdgeLength = 16; |
|
_minOrientedRectSide = 3.0f; |
|
_maxDistanceToEllipseContour = 0.1f; |
|
_minScore = 0.7f; |
|
_minReliability = 0.5; |
|
_uNs = 16; |
|
} |
|
|
|
void EllipseDetectorImpl::setParameters(float maxCenterDistance, float minScore, |
|
float minReliability) { |
|
_maxCenterDistance = maxCenterDistance; |
|
_minScore = minScore; |
|
_minReliability = minReliability; |
|
|
|
_maxCenterDistance2 = _maxCenterDistance * _maxCenterDistance; |
|
} |
|
|
|
uint inline EllipseDetectorImpl::generateKey(uchar pair, ushort u, ushort v) { |
|
return (pair << 30) + (u << 15) + v; |
|
} |
|
|
|
float EllipseDetectorImpl::getMedianSlope(std::vector<Point2f> &med, Point2f ¢ers, |
|
std::vector<float> &slopes) { |
|
// med : vector of points |
|
// centers : centroid of the points in med |
|
// slopes : vector of the slopes |
|
|
|
size_t pointCount = med.size(); |
|
// CV_Assert(pointCount >= 2); |
|
|
|
size_t halfSize = pointCount >> 1; |
|
size_t quarterSize = halfSize >> 1; |
|
|
|
std::vector<float> xx, yy; |
|
slopes.reserve(halfSize); |
|
xx.reserve(pointCount); |
|
yy.reserve(pointCount); |
|
|
|
for (unsigned i = 0; i < halfSize; i++) { |
|
Point2f &p1 = med[i]; |
|
Point2f &p2 = med[halfSize + i]; |
|
|
|
xx.push_back(p1.x); |
|
xx.push_back(p2.x); |
|
yy.push_back(p1.y); |
|
yy.push_back(p2.y); |
|
|
|
float den = (p2.x - p1.x); |
|
float num = (p2.y - p1.y); |
|
|
|
den = (std::fabs(den) >= 1e-5) ? den : 0.00001f; // FIXIT: algorithm is not reliable |
|
|
|
slopes.push_back(num / den); |
|
} |
|
|
|
std::nth_element(slopes.begin(), slopes.begin() + quarterSize, slopes.end()); |
|
std::nth_element(xx.begin(), xx.begin() + halfSize, xx.end()); |
|
std::nth_element(yy.begin(), yy.begin() + halfSize, yy.end()); |
|
centers.x = xx[halfSize]; |
|
centers.y = yy[halfSize]; |
|
|
|
return slopes[quarterSize]; |
|
} |
|
|
|
void EllipseDetectorImpl::getFastCenter(std::vector<Point> &e1, std::vector<Point> &e2, |
|
EllipseData &data) { |
|
countsOfGetFastCenter++; |
|
data.isValid = true; |
|
|
|
auto size_1 = unsigned(e1.size()); |
|
auto size_2 = unsigned(e2.size()); |
|
|
|
unsigned hsize_1 = size_1 >> 1; |
|
unsigned hsize_2 = size_2 >> 1; |
|
|
|
Point &med1 = e1[hsize_1]; |
|
Point &med2 = e2[hsize_2]; |
|
|
|
Point2f M12, M34; |
|
float q2, q4; |
|
|
|
{ |
|
auto dx_ref = float(e1[0].x - med2.x); |
|
auto dy_ref = float(e1[0].y - med2.y); |
|
|
|
if (dx_ref == 0) |
|
dx_ref = 0.00001f; |
|
|
|
float m_ref = dy_ref / dx_ref; |
|
data.ra = m_ref; |
|
|
|
// find points with same slope as reference |
|
std::vector<Point2f> med; |
|
med.reserve(hsize_2); |
|
|
|
unsigned minPoints = (_uNs < hsize_2) ? _uNs : hsize_2; |
|
|
|
std::vector<uint> indexes(minPoints); |
|
if (_uNs < hsize_2) { |
|
unsigned iSzBin = hsize_2 / unsigned(_uNs); |
|
unsigned iIdx = hsize_2 + (iSzBin / 2); |
|
|
|
for (unsigned i = 0; i < _uNs; i++) { |
|
indexes[i] = iIdx; |
|
iIdx += iSzBin; |
|
} |
|
} else |
|
std::iota(indexes.begin(), indexes.end(), hsize_2); |
|
|
|
for (uint ii = 0; ii < minPoints; ii++) { |
|
uint i = indexes[ii]; |
|
|
|
auto x1 = float(e2[i].x); |
|
auto y1 = float(e2[i].y); |
|
|
|
uint begin = 0; |
|
uint end = size_1 - 1; |
|
|
|
auto xb = float(e1[begin].x); |
|
auto yb = float(e1[begin].y); |
|
float res_begin = ((xb - x1) * dy_ref) - ((yb - y1) * dx_ref); |
|
int sign_begin = signal(res_begin); |
|
if (sign_begin == 0) { |
|
med.emplace_back((xb + x1) * 0.5f, (yb + y1) * 0.5f); |
|
continue; |
|
} |
|
|
|
auto xe = float(e1[end].x); |
|
auto ye = float(e1[end].y); |
|
float res_end = ((xe - x1) * dy_ref) - ((ye - y1) * dx_ref); |
|
int sign_end = signal(res_end); |
|
if (sign_end == 0) { |
|
med.emplace_back((xe + x1) * 0.5f, (ye + y1) * 0.5f); |
|
continue; |
|
} |
|
|
|
if ((sign_begin + sign_end) != 0) |
|
continue; |
|
|
|
|
|
// search parallel arc |
|
uint j = (begin + end) >> 1; |
|
while (end - begin > 2) { |
|
auto x2 = float(e1[j].x); |
|
auto y2 = float(e1[j].y); |
|
float res = ((x2 - x1) * dy_ref) - ((y2 - y1) * dx_ref); |
|
int sign_res = signal(res); |
|
|
|
if (sign_res == 0) { |
|
med.emplace_back((x2 + x1) * 0.5f, (y2 + y1) * 0.5f); |
|
break; |
|
} |
|
|
|
if (sign_res + sign_begin == 0) { |
|
sign_end = sign_res; |
|
end = j; |
|
} else { |
|
sign_begin = sign_res; |
|
begin = j; |
|
} |
|
j = (begin + end) >> 1; |
|
} |
|
|
|
med.emplace_back((e1[j].x + x1) * 0.5f, (e1[j].y + y1) * 0.5f); |
|
} |
|
|
|
if (med.size() < 2) { |
|
data.isValid = false; |
|
return; |
|
} |
|
|
|
q2 = getMedianSlope(med, M12, data.Sa); |
|
} |
|
|
|
{ |
|
auto dx_ref = float(med1.x - e2[0].x); |
|
auto dy_ref = float(med1.y - e2[0].y); |
|
|
|
if (dx_ref == 0) |
|
dx_ref = 0.00001f; |
|
|
|
float m_ref = dy_ref / dx_ref; |
|
data.rb = m_ref; |
|
|
|
// find points with same slope as reference |
|
std::vector<Point2f> med; |
|
med.reserve(hsize_1); |
|
|
|
uint minPoints = (_uNs < hsize_1) ? _uNs : hsize_1; |
|
|
|
std::vector<uint> indexes(minPoints); |
|
if (_uNs < hsize_1) { |
|
unsigned iSzBin = hsize_1 / unsigned(_uNs); |
|
unsigned iIdx = hsize_1 + (iSzBin / 2); |
|
|
|
for (unsigned i = 0; i < _uNs; i++) { |
|
indexes[i] = iIdx; |
|
iIdx += iSzBin; |
|
} |
|
} else |
|
std::iota(indexes.begin(), indexes.end(), hsize_1); |
|
|
|
|
|
for (uint ii = 0; ii < minPoints; ii++) { |
|
uint i = indexes[ii]; |
|
|
|
auto x1 = float(e1[i].x); |
|
auto y1 = float(e1[i].y); |
|
|
|
uint begin = 0; |
|
uint end = size_2 - 1; |
|
|
|
auto xb = float(e2[begin].x); |
|
auto yb = float(e2[begin].y); |
|
float res_begin = ((xb - x1) * dy_ref) - ((yb - y1) * dx_ref); |
|
int sign_begin = signal(res_begin); |
|
if (sign_begin == 0) { |
|
med.emplace_back((xb + x1) * 0.5f, (yb + y1) * 0.5f); |
|
continue; |
|
} |
|
|
|
auto xe = float(e2[end].x); |
|
auto ye = float(e2[end].y); |
|
float res_end = ((xe - x1) * dy_ref) - ((ye - y1) * dx_ref); |
|
int sign_end = signal(res_end); |
|
if (sign_end == 0) { |
|
med.emplace_back((xe + x1) * 0.5f, (ye + y1) * 0.5f); |
|
continue; |
|
} |
|
|
|
if ((sign_begin + sign_end) != 0) |
|
continue; |
|
|
|
uint j = (begin + end) >> 1; |
|
|
|
while (end - begin > 2) { |
|
auto x2 = float(e2[j].x); |
|
auto y2 = float(e2[j].y); |
|
float res = ((x2 - x1) * dy_ref) - ((y2 - y1) * dx_ref); |
|
int sign_res = signal(res); |
|
|
|
if (sign_res == 0) { |
|
med.emplace_back((x2 + x1) * 0.5f, (y2 + y1) * 0.5f); |
|
break; |
|
} |
|
|
|
if (sign_res + sign_begin == 0) { |
|
sign_end = sign_res; |
|
end = j; |
|
} else { |
|
sign_begin = sign_res; |
|
begin = j; |
|
} |
|
j = (begin + end) >> 1; |
|
} |
|
|
|
med.emplace_back((e2[j].x + x1) * 0.5f, (e2[j].y + y1) * 0.5f); |
|
} |
|
|
|
if (med.size() < 2) { |
|
data.isValid = false; |
|
return; |
|
} |
|
q4 = getMedianSlope(med, M34, data.Sb); |
|
} |
|
|
|
if (q2 == q4) { |
|
data.isValid = false; |
|
return; |
|
} |
|
|
|
float invDen = 1 / (q2 - q4); |
|
data.Cab.x = (M34.y - q4 * M34.x - M12.y + q2 * M12.x) * invDen; |
|
data.Cab.y = (q2 * M34.y - q4 * M12.y + q2 * q4 * (M12.x - M34.x)) * invDen; |
|
data.ta = q2; |
|
data.tb = q4; |
|
data.Ma = M12; |
|
data.Mb = M34; |
|
} |
|
|
|
Point2f |
|
EllipseDetectorImpl::getCenterCoordinates(EllipseData &data_ij, EllipseData &data_ik) { |
|
float xx[7]; |
|
float yy[7]; |
|
|
|
xx[0] = data_ij.Cab.x; |
|
xx[1] = data_ik.Cab.x; |
|
yy[0] = data_ij.Cab.y; |
|
yy[1] = data_ik.Cab.y; |
|
|
|
{ |
|
// 1-1 |
|
float q2 = data_ij.ta; |
|
float q4 = data_ik.ta; |
|
Point2f &M12 = data_ij.Ma; |
|
Point2f &M34 = data_ik.Ma; |
|
|
|
float invDen = 1 / (q2 - q4); |
|
xx[2] = (M34.y - q4 * M34.x - M12.y + q2 * M12.x) * invDen; |
|
yy[2] = (q2 * M34.y - q4 * M12.y + q2 * q4 * (M12.x - M34.x)) * invDen; |
|
} |
|
|
|
{ |
|
// 1-2 |
|
float q2 = data_ij.ta; |
|
float q4 = data_ik.tb; |
|
Point2f &M12 = data_ij.Ma; |
|
Point2f &M34 = data_ik.Mb; |
|
|
|
float invDen = 1 / (q2 - q4); |
|
xx[3] = (M34.y - q4 * M34.x - M12.y + q2 * M12.x) * invDen; |
|
yy[3] = (q2 * M34.y - q4 * M12.y + q2 * q4 * (M12.x - M34.x)) * invDen; |
|
} |
|
|
|
{ |
|
// 2-2 |
|
float q2 = data_ij.tb; |
|
float q4 = data_ik.tb; |
|
Point2f &M12 = data_ij.Mb; |
|
Point2f &M34 = data_ik.Mb; |
|
|
|
float invDen = 1 / (q2 - q4); |
|
xx[4] = (M34.y - q4 * M34.x - M12.y + q2 * M12.x) * invDen; |
|
yy[4] = (q2 * M34.y - q4 * M12.y + q2 * q4 * (M12.x - M34.x)) * invDen; |
|
} |
|
|
|
{ |
|
// 2-1 |
|
float q2 = data_ij.tb; |
|
float q4 = data_ik.ta; |
|
Point2f &M12 = data_ij.Mb; |
|
Point2f &M34 = data_ik.Ma; |
|
|
|
float invDen = 1 / (q2 - q4); |
|
xx[5] = (M34.y - q4 * M34.x - M12.y + q2 * M12.x) * invDen; |
|
yy[5] = (q2 * M34.y - q4 * M12.y + q2 * q4 * (M12.x - M34.x)) * invDen; |
|
} |
|
|
|
xx[6] = (xx[0] + xx[1]) * 0.5f; |
|
yy[6] = (yy[0] + yy[1]) * 0.5f; |
|
|
|
|
|
// median |
|
std::nth_element(xx, xx + 3, xx + 7); |
|
std::nth_element(yy, yy + 3, yy + 7); |
|
float xc = xx[3]; |
|
float yc = yy[3]; |
|
|
|
return {xc, yc}; |
|
} |
|
|
|
|
|
void EllipseDetectorImpl::labeling(Mat1b &image, VVP &segments, int minLength) { |
|
const int RG_STACK_SIZE = 2048; |
|
int stackInt[RG_STACK_SIZE]; |
|
Point stackPoint[RG_STACK_SIZE]; |
|
|
|
Mat_<uchar> image_clone = image.clone(); |
|
Size imgSize = image.size(); |
|
int h = imgSize.height, w = imgSize.width; |
|
|
|
Point point; |
|
for (int y = 0; y < h; ++y) { |
|
for (int x = 0; x < w; ++x) { |
|
if ((image_clone(y, x)) != 0) { |
|
// for each point |
|
int curInt = 0; |
|
int i = x + y * w; |
|
stackInt[curInt] = i; |
|
curInt++; |
|
|
|
// clear point list |
|
int curPoint = 0; |
|
while (curInt > 0) { |
|
curInt--; |
|
i = stackInt[curInt]; |
|
int x2 = i % w; |
|
int y2 = i / w; |
|
|
|
point.x = x2; |
|
point.y = y2; |
|
|
|
if ((image_clone(y2, x2))) { |
|
stackPoint[curPoint] = point; |
|
curPoint++; |
|
image_clone(y2, x2) = 0; |
|
} |
|
|
|
// 4 directions |
|
if (x2 > 0 && (image_clone(y2, x2 - 1) != 0)) { |
|
stackInt[curInt] = i - 1; |
|
curInt++; |
|
} |
|
if (y2 > 0 && (image_clone(y2 - 1, x2) != 0)) { |
|
stackInt[curInt] = i - w; |
|
curInt++; |
|
} |
|
if (y2 < h - 1 && (image_clone(y2 + 1, x2) != 0)) { |
|
stackInt[curInt] = i + w; |
|
curInt++; |
|
} |
|
if (x2 < w - 1 && (image_clone(y2, x2 + 1) != 0)) { |
|
stackInt[curInt] = i + 1; |
|
curInt++; |
|
} |
|
|
|
// 8 directions |
|
if (x2 > 0 && y2 > 0 && (image_clone(y2 - 1, x2 - 1) != 0)) { |
|
stackInt[curInt] = i - w - 1; |
|
curInt++; |
|
} |
|
if (x2 > 0 && y2 < h - 1 && (image_clone(y2 + 1, x2 - 1) != 0)) { |
|
stackInt[curInt] = i + w - 1; |
|
curInt++; |
|
} |
|
if (x2 < w - 1 && y2 > 0 && (image_clone(y2 - 1, x2 + 1) != 0)) { |
|
stackInt[curInt] = i - w + 1; |
|
curInt++; |
|
} |
|
if (x2 < w - 1 && y2 < h - 1 && (image_clone(y2 + 1, x2 + 1) != 0)) { |
|
stackInt[curInt] = i + w + 1; |
|
curInt++; |
|
} |
|
} |
|
|
|
if (curPoint >= minLength) { |
|
std::vector<Point> component; |
|
component.reserve(curPoint); |
|
for (int j = 0; j < curPoint; j++) |
|
component.push_back(stackPoint[j]); |
|
segments.push_back(component); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
void EllipseDetectorImpl::detectEdges13(Mat1b &DP, VVP &points_1, VVP &points_3) { |
|
// vector of connected edge points |
|
VVP contours; |
|
// labeling 8-connected edge points, discarding edge too small |
|
labeling(DP, contours, _minEdgeLength); // label point on the same arc |
|
int contourSize = int(contours.size()); |
|
|
|
// for each edge |
|
for (int i = 0; i < contourSize; i++) { |
|
VP &edgeSegment = contours[i]; |
|
|
|
// selection strategy - constraint on axes aspect ratio |
|
RotatedRect oriented = minAreaRect(edgeSegment); |
|
float orMin = min(oriented.size.width, oriented.size.height); |
|
|
|
if (orMin < _minOrientedRectSide) { |
|
continue; |
|
} |
|
|
|
// order edge points of the same arc |
|
std::sort(edgeSegment.begin(), edgeSegment.end(), sortPoint); |
|
int edgeSegmentSize = unsigned(edgeSegment.size()); |
|
|
|
// get extrema of the arc |
|
Point &left = edgeSegment[0]; |
|
Point &right = edgeSegment[edgeSegmentSize - 1]; |
|
|
|
// find convexity |
|
int countTop = 0; |
|
int lx = left.x; |
|
for (int k = 1; k < edgeSegmentSize; ++k) { |
|
if (edgeSegment[k].x == lx) |
|
continue; |
|
countTop += (edgeSegment[k].y - left.y); |
|
lx = edgeSegment[k].x; |
|
} |
|
|
|
int width = abs(right.x - left.x) + 1; |
|
int height = abs(right.y - left.y) + 1; |
|
int countBottom = (width * height) - edgeSegmentSize - countTop; |
|
|
|
if (countBottom > countTop) |
|
points_1.push_back(edgeSegment); |
|
else if (countBottom < countTop) |
|
points_3.push_back(edgeSegment); |
|
} |
|
} |
|
|
|
void EllipseDetectorImpl::detectEdges24(Mat1b &DN, VVP &points_2, VVP &points_4) { |
|
// vector of connected edge points |
|
VVP contours; |
|
// labeling 8-connected edge points, discarding edge too small |
|
labeling(DN, contours, _minEdgeLength); // label point on the same arc |
|
int contourSize = int(contours.size()); |
|
|
|
// for each edge |
|
for (int i = 0; i < contourSize; i++) { |
|
VP &edgeSegment = contours[i]; |
|
|
|
// selection strategy - constraint on axes aspect ratio |
|
RotatedRect oriented = minAreaRect(edgeSegment); |
|
float orMin = min(oriented.size.width, oriented.size.height); |
|
|
|
if (orMin < _minOrientedRectSide) { |
|
continue; |
|
} |
|
|
|
// order edge points of the same arc |
|
std::sort(edgeSegment.begin(), edgeSegment.end(), sortPoint); |
|
int edgeSegmentSize = unsigned(edgeSegment.size()); |
|
|
|
// get extrema of the arc |
|
Point &left = edgeSegment[0]; |
|
Point &right = edgeSegment[edgeSegmentSize - 1]; |
|
|
|
// find convexity |
|
int countBottom = 0; |
|
int lx = left.x; |
|
for (int k = 0; k < edgeSegmentSize; ++k) { |
|
if (edgeSegment[k].x == lx) |
|
continue; |
|
countBottom += (left.y - edgeSegment[k].y); |
|
lx = edgeSegment[k].x; |
|
} |
|
|
|
int width = abs(right.x - left.x) + 1; |
|
int height = abs(right.y - left.y) + 1; |
|
int countTop = (width * height) - edgeSegmentSize - countBottom; |
|
|
|
if (countBottom > countTop) |
|
points_2.push_back(edgeSegment); |
|
else if (countBottom < countTop) |
|
points_4.push_back(edgeSegment); |
|
} |
|
} |
|
|
|
#define T124 pjf,pjm,pjl,pif,pim,pil |
|
#define T231 pil,pim,pif,pjf,pjm,pjl |
|
#define T342 pif,pim,pil,pjf,pjm,pjl |
|
#define T413 pif,pim,pil,pjl,pjm,pjf |
|
|
|
void EllipseDetectorImpl::getTriplets124(VVP &pi, VVP &pj, VVP &pk, |
|
std::unordered_map<uint, EllipseData> &data, |
|
std::vector<Ellipse> &ellipses) { |
|
// get arcs length |
|
auto sz_i = ushort(pi.size()); |
|
auto sz_j = ushort(pj.size()); |
|
auto sz_k = ushort(pk.size()); |
|
|
|
// for each edge i |
|
for (ushort i = 0; i < sz_i; i++) { |
|
VP &edge_i = pi[i]; |
|
auto sz_ei = ushort(edge_i.size()); |
|
|
|
Point &pif = edge_i[0]; |
|
Point &pim = edge_i[sz_ei / 2]; |
|
Point &pil = edge_i[sz_ei - 1]; |
|
|
|
// 1 -> reverse 1 |
|
VP rev_i(edge_i.size()); |
|
std::reverse_copy(edge_i.begin(), edge_i.end(), rev_i.begin()); |
|
|
|
// for each edge j |
|
for (ushort j = 0; j < sz_j; ++j) { |
|
VP &edge_j = pj[j]; |
|
auto sz_ej = ushort(edge_j.size()); |
|
|
|
Point &pjf = edge_j[0]; |
|
Point &pjm = edge_j[sz_ej / 2]; |
|
Point &pjl = edge_j[sz_ej - 1]; |
|
|
|
// constraints on position |
|
if (pjl.x > pif.x + _positionThreshold) |
|
continue; |
|
|
|
// constraints on CNC |
|
const float CNC_THRESHOLD = 0.3f; |
|
if (fabs(valueOfPoints(T124) - 1) > CNC_THRESHOLD) |
|
continue; |
|
|
|
uint key_ij = generateKey(PAIR_12, i, j); |
|
|
|
// for each edge k |
|
for (ushort k = 0; k < sz_k; ++k) { |
|
VP &edge_k = pk[k]; |
|
auto sz_ek = ushort(edge_k.size()); |
|
|
|
Point &pkl = edge_k[sz_ek - 1]; |
|
|
|
// constraints on position |
|
if (pkl.y < pil.y - _positionThreshold) |
|
continue; |
|
|
|
uint key_ik = generateKey(PAIR_14, i, k); |
|
|
|
// find centers |
|
EllipseData data_ij, data_ik; |
|
|
|
// if the data for the pair i-j have not been computed yet |
|
if (data.count(key_ij) == 0) { |
|
getFastCenter(edge_j, rev_i, data_ij); |
|
// insert computed data in the hash table |
|
data.insert(std::pair<uint, EllipseData>(key_ij, data_ij)); |
|
} else { |
|
// otherwise, just lookup the data in the hash table |
|
data_ij = data.at(key_ij); |
|
} |
|
|
|
// if the data for the pair i-k have not been computed yet |
|
if (data.count(key_ik) == 0) { |
|
getFastCenter(edge_i, edge_k, data_ik); |
|
// insert computed data in the hash table |
|
data.insert(std::pair<uint, EllipseData>(key_ik, data_ik)); |
|
} else { |
|
// otherwise, just lookup the data in the hash table |
|
data_ik = data.at(key_ik); |
|
} |
|
|
|
// invalid centers |
|
if (!data_ij.isValid || !data_ik.isValid) |
|
continue; |
|
|
|
// selection strategy - Step 3. |
|
// the computed centers are not close enough |
|
if (pointDistance2(data_ij.Cab, data_ik.Cab) > _maxCenterDistance2) |
|
continue; |
|
|
|
// find ellipse parameters |
|
// get the coordinates of the center (xc, yc) |
|
Point2f center = getCenterCoordinates(data_ij, data_ik); |
|
// find remaining parameters (A, B, rho) |
|
findEllipses(center, edge_i, edge_j, edge_k, data_ij, data_ik, ellipses); |
|
} |
|
} |
|
} |
|
} |
|
|
|
void EllipseDetectorImpl::getTriplets231(VVP &pi, VVP &pj, VVP &pk, |
|
std::unordered_map<uint, EllipseData> &data, |
|
std::vector<Ellipse> &ellipses) { |
|
// get arc length |
|
auto sz_i = ushort(pi.size()); |
|
auto sz_j = ushort(pj.size()); |
|
auto sz_k = ushort(pk.size()); |
|
|
|
// for each edge i |
|
for (ushort i = 0; i < sz_i; i++) { |
|
VP &edge_i = pi[i]; |
|
auto sz_ei = ushort(edge_i.size()); |
|
|
|
Point &pif = edge_i[0]; |
|
Point &pim = edge_i[sz_ei / 2]; |
|
Point &pil = edge_i[sz_ei - 1]; |
|
|
|
// 2 -> reverse 2 |
|
VP rev_i(edge_i.size()); |
|
std::reverse_copy(edge_i.begin(), edge_i.end(), rev_i.begin()); |
|
|
|
// for each edge j |
|
for (ushort j = 0; j < sz_j; ++j) { |
|
VP &edge_j = pj[j]; |
|
auto sz_ej = ushort(edge_j.size()); |
|
|
|
Point &pjf = edge_j[0]; |
|
Point &pjm = edge_j[sz_ej / 2]; |
|
Point &pjl = edge_j[sz_ej - 1]; |
|
|
|
// constraints on position |
|
if (pjf.y < pif.y - _positionThreshold) |
|
continue; |
|
|
|
// constraints on CNC |
|
const float CNC_THRESHOLD = 0.3f; |
|
if (fabs(valueOfPoints(T231) - 1) > CNC_THRESHOLD) |
|
continue; |
|
|
|
// 3 -> reverse 3 |
|
VP rev_j(edge_j.size()); |
|
std::reverse_copy(edge_j.begin(), edge_j.end(), rev_j.begin()); |
|
|
|
uint key_ij = generateKey(PAIR_23, i, j); |
|
|
|
// for each edge k |
|
for (ushort k = 0; k < sz_k; ++k) { |
|
VP &edge_k = pk[k]; |
|
|
|
Point &pkf = edge_k[0]; |
|
|
|
// constraints on position |
|
if (pkf.x < pil.x - _positionThreshold) |
|
continue; |
|
|
|
uint key_ik = generateKey(PAIR_12, k, i); |
|
|
|
// find centers |
|
EllipseData data_ij, data_ik; |
|
|
|
// if the data for the pair i-j have not been computed yet |
|
if (data.count(key_ij) == 0) { |
|
getFastCenter(rev_i, rev_j, data_ij); |
|
// insert computed date in the hash table |
|
data.insert(std::pair<uint, EllipseData>(key_ij, data_ij)); |
|
} else { |
|
// otherwise, just lookup the data in the hash table |
|
data_ij = data.at(key_ij); |
|
} |
|
|
|
// if the data for the pair i-k have not been computed yet |
|
if (data.count(key_ik) == 0) { |
|
// 1 -> reverse 1 |
|
VP rev_k(edge_k.size()); |
|
std::reverse_copy(edge_k.begin(), edge_k.end(), rev_k.begin()); |
|
|
|
getFastCenter(edge_i, rev_k, data_ik); |
|
data.insert(std::pair<uint, EllipseData>(key_ik, data_ik)); |
|
} else { |
|
// otherwise, just lookup the data in the hash table |
|
data_ik = data.at(key_ik); |
|
} |
|
|
|
// invalid centers |
|
if (!data_ij.isValid || !data_ik.isValid) |
|
continue; |
|
|
|
// selection strategy - Step 3. |
|
// the computed centers are not close enough |
|
if (pointDistance2(data_ij.Cab, data_ik.Cab) > _maxCenterDistance2) |
|
continue; |
|
|
|
// find ellipse parameters |
|
// get the coordinates of the center (xc, yc) |
|
Point2f center = getCenterCoordinates(data_ij, data_ik); |
|
// find remaining parameters (A, B, rho) |
|
findEllipses(center, edge_i, edge_j, edge_k, data_ij, data_ik, ellipses); |
|
} |
|
} |
|
} |
|
} |
|
|
|
void EllipseDetectorImpl::getTriplets342(VVP &pi, VVP &pj, VVP &pk, |
|
std::unordered_map<uint, EllipseData> &data, |
|
std::vector<Ellipse> &ellipses) { |
|
// get arcs length |
|
auto sz_i = ushort(pi.size()); |
|
auto sz_j = ushort(pj.size()); |
|
auto sz_k = ushort(pk.size()); |
|
|
|
// for each edge i |
|
for (ushort i = 0; i < sz_i; i++) { |
|
VP &edge_i = pi[i]; |
|
auto sz_ei = ushort(edge_i.size()); |
|
|
|
Point &pif = edge_i[0]; |
|
Point &pim = edge_i[sz_ei / 2]; |
|
Point &pil = edge_i[sz_ei - 1]; |
|
|
|
// 3 -> reverse 3 |
|
VP rev_i(edge_i.size()); |
|
std::reverse_copy(edge_i.begin(), edge_i.end(), rev_i.begin()); |
|
|
|
// for each edge j |
|
for (ushort j = 0; j < sz_j; ++j) { |
|
VP &edge_j = pj[j]; |
|
auto sz_ej = ushort(edge_j.size()); |
|
|
|
Point &pjf = edge_j[0]; |
|
Point &pjm = edge_j[sz_ej / 2]; |
|
Point &pjl = edge_j[sz_ej - 1]; |
|
|
|
// constraints on position |
|
if (pjf.x < pil.x - _positionThreshold) |
|
continue; |
|
|
|
// constraints on CNC |
|
const float CNC_THRESHOLD = 0.3f; |
|
if (fabs(valueOfPoints(T342) - 1) > CNC_THRESHOLD) |
|
continue; |
|
|
|
// 4 -> reverse 4 |
|
VP rev_j(edge_j.size()); |
|
std::reverse_copy(edge_j.begin(), edge_j.end(), rev_j.begin()); |
|
|
|
uint key_ij = generateKey(PAIR_34, i, j); |
|
|
|
// for each edge k |
|
for (ushort k = 0; k < sz_k; ++k) { |
|
VP &edge_k = pk[k]; |
|
|
|
Point &pkf = edge_k[0]; |
|
|
|
// constraints on position |
|
if (pkf.y > pif.y + _positionThreshold) |
|
continue; |
|
|
|
uint key_ik = generateKey(PAIR_23, k, i); |
|
|
|
// find centers |
|
EllipseData data_ij, data_ik; |
|
|
|
// if the data for the pair i-j have not been computed yet |
|
if (data.count(key_ij) == 0) { |
|
getFastCenter(edge_i, rev_j, data_ij); |
|
// insert computed data in the hash table |
|
data.insert(std::pair<uint, EllipseData>(key_ij, data_ij)); |
|
} else { |
|
// otherwise, just lookup the data in the hash table |
|
data_ij = data.at(key_ij); |
|
} |
|
|
|
// if the data for the pair i-k have not been computed yet |
|
if (data.count(key_ik) == 0) { |
|
// 2 -> reverse 2 |
|
VP rev_k(edge_k.size()); |
|
std::reverse_copy(edge_k.begin(), edge_k.end(), rev_k.begin()); |
|
|
|
getFastCenter(rev_i, rev_k, data_ik); |
|
data.insert(std::pair<uint, EllipseData>(key_ik, data_ik)); |
|
} else { |
|
// otherwise, just lookup the data in the hash table |
|
data_ik = data.at(key_ik); |
|
} |
|
|
|
// invalid centers |
|
if (!data_ij.isValid || !data_ik.isValid) |
|
continue; |
|
|
|
// selection strategy - Step 3. |
|
// the computed centers are not close enough |
|
if (pointDistance2(data_ij.Cab, data_ik.Cab) > _maxCenterDistance2) |
|
continue; |
|
|
|
// find ellipse parameters |
|
// get the coordinates of the center (xc, yc) |
|
Point2f center = getCenterCoordinates(data_ij, data_ik); |
|
// find remaining parameters (A, B, rho) |
|
findEllipses(center, edge_i, edge_j, edge_k, data_ij, data_ik, ellipses); |
|
} |
|
} |
|
|
|
} |
|
} |
|
|
|
void EllipseDetectorImpl::getTriplets413(VVP &pi, VVP &pj, VVP &pk, |
|
std::unordered_map<uint, EllipseData> &data, |
|
std::vector<Ellipse> &ellipses) { |
|
// get arch length |
|
auto sz_i = ushort(pi.size()); |
|
auto sz_j = ushort(pj.size()); |
|
auto sz_k = ushort(pk.size()); |
|
|
|
// for each edge i |
|
for (ushort i = 0; i < sz_i; i++) { |
|
VP &edge_i = pi[i]; |
|
auto sz_ei = ushort(edge_i.size()); |
|
|
|
Point &pif = edge_i[0]; |
|
Point &pim = edge_i[sz_ei / 2]; |
|
Point &pil = edge_i[sz_ei - 1]; |
|
|
|
// 4 -> reverse 4 |
|
VP rev_i(edge_i.size()); |
|
std::reverse_copy(edge_i.begin(), edge_i.end(), rev_i.begin()); |
|
|
|
// for each edge j |
|
for (ushort j = 0; j < sz_j; ++j) { |
|
VP &edge_j = pj[j]; |
|
auto sz_ej = ushort(edge_j.size()); |
|
|
|
Point &pjf = edge_j[0]; |
|
Point &pjm = edge_j[sz_ej / 2]; |
|
Point &pjl = edge_j[sz_ej - 1]; |
|
|
|
// constraints on position |
|
if (pjl.y > pil.y + _positionThreshold) |
|
continue; |
|
|
|
// constraints on CNC |
|
const float CNC_THRESHOLD = 0.3f; |
|
if (fabs(valueOfPoints(T413) - 1) > CNC_THRESHOLD) |
|
continue; |
|
|
|
uint key_ij = generateKey(PAIR_14, j, i); |
|
|
|
// for each edge k |
|
for (ushort k = 0; k < sz_k; ++k) { |
|
VP &edge_k = pk[k]; |
|
auto sz_ek = ushort(edge_k.size()); |
|
|
|
Point &pkl = edge_k[sz_ek - 1]; |
|
|
|
// constraints on position |
|
if (pkl.x > pif.x + _positionThreshold) |
|
continue; |
|
|
|
uint key_ik = generateKey(PAIR_34, k, i); |
|
|
|
// find centers |
|
EllipseData data_ij, data_ik; |
|
|
|
// if the data for the pair i-j have not been computed yet |
|
if (data.count(key_ij) == 0) { |
|
getFastCenter(edge_i, edge_j, data_ij); |
|
// insert computed date in the hash table |
|
data.insert(std::pair<uint, EllipseData>(key_ij, data_ij)); |
|
} else { |
|
// otherwise, just lookup the data in the hash table |
|
data_ij = data.at(key_ij); |
|
} |
|
|
|
// if the data for the pair i-k have not been computed yet |
|
if (data.count(key_ik) == 0) { |
|
getFastCenter(rev_i, edge_k, data_ik); |
|
data.insert(std::pair<uint, EllipseData>(key_ik, data_ik)); |
|
} else { |
|
// otherwise, just lookup the data in the hash table |
|
data_ik = data.at(key_ik); |
|
} |
|
|
|
// invalid centers |
|
if (!data_ij.isValid || !data_ik.isValid) |
|
continue; |
|
|
|
// selection strategy - Step 3. |
|
// the computed centers are not close enough |
|
if (pointDistance2(data_ij.Cab, data_ik.Cab) > _maxCenterDistance2) |
|
continue; |
|
|
|
// find ellipse parameters |
|
// get the coordinates of the center (xc, yc) |
|
Point2f center = getCenterCoordinates(data_ij, data_ik); |
|
// find remaining parameters (A, B, rho) |
|
findEllipses(center, edge_i, edge_j, edge_k, data_ij, data_ik, ellipses); |
|
|
|
} |
|
} |
|
} |
|
} |
|
|
|
void EllipseDetectorImpl::preProcessing(Mat1b &image, Mat1b &dp, Mat1b &dn) { |
|
// smooth image |
|
GaussianBlur(image, image, _kernelSize, _sigma); |
|
|
|
// temp variables |
|
Mat1b edges;// edge mask |
|
Mat1s dx, dy; // sobel derivatives |
|
|
|
// detect edges |
|
Sobel(image, dx, CV_16S, 1, 0, 3, 1, 0, BORDER_REPLICATE); |
|
Sobel(image, dy, CV_16S, 0, 1, 3, 1, 0, BORDER_REPLICATE); |
|
|
|
// calculate magnitude of gradient |
|
Size imgSize = image.size(); |
|
edges.create(imgSize); |
|
Mat1f magGrad(imgSize.height, imgSize.width, 0.f); |
|
float maxGrad(0); |
|
for (int i = 0; i < imgSize.height; i++) { |
|
auto *tmpMag = magGrad.ptr<float>(i); |
|
for (int j = 0; j < imgSize.width; j++) { |
|
auto val = float(abs(dx[i][j]) + abs(dy[i][j])); |
|
tmpMag[j] = val; |
|
maxGrad = (val > maxGrad) ? val : maxGrad; |
|
} |
|
} |
|
|
|
// set magic numbers |
|
const int NUM_BINS = 64; |
|
const double PERCENT_OF_PIXEL_NOT_EDGES = 0.9f, RATIO_THRESHOLD = 0.3f; |
|
|
|
// compute histogram |
|
int binSize = cvFloor(maxGrad / float(NUM_BINS) + 0.5f) + 1; |
|
binSize = max(1, binSize); |
|
int bins[NUM_BINS] = {0}; |
|
for (int i = 0; i < imgSize.height; i++) { |
|
auto *tmpMag = magGrad.ptr<float>(i); |
|
for (int j = 0; j < imgSize.width; j++) |
|
bins[int(tmpMag[j]) / binSize]++; |
|
} |
|
|
|
// select the thresholds |
|
float total = 0.f; |
|
auto target = float(imgSize.height * imgSize.width * PERCENT_OF_PIXEL_NOT_EDGES); |
|
int lowTh, highTh(0); |
|
|
|
while (total < target) { |
|
total += bins[highTh]; |
|
highTh++; |
|
} |
|
highTh = cvFloor(highTh * binSize); |
|
lowTh = cvFloor(RATIO_THRESHOLD * float(highTh)); |
|
|
|
// buffer |
|
int *magBuffer[3]; |
|
AutoBuffer<int> buffer((imgSize.width + 2) * (imgSize.height + 2) + |
|
(imgSize.width + 2) * 3); |
|
magBuffer[0] = buffer.data(); |
|
magBuffer[1] = magBuffer[0] + imgSize.width + 2; |
|
magBuffer[2] = magBuffer[1] + imgSize.width + 2; |
|
uchar *map = (uchar *) (magBuffer[2] + imgSize.width + 2); |
|
ptrdiff_t mapStep = imgSize.width + 2; |
|
|
|
int maxSize = MAX(1 << 10, imgSize.width * imgSize.height / 10); |
|
std::vector<uchar *> stack; |
|
stack.resize(maxSize); |
|
uchar **stackTop = 0, **stackBottom = 0; |
|
stackTop = stackBottom = &stack[0]; |
|
|
|
memset(magBuffer[0], 0, (imgSize.width + 2) * sizeof(int)); |
|
memset(map, 1, mapStep); |
|
memset(map + mapStep * (imgSize.height + 1), 1, mapStep); |
|
|
|
Mat magRow; |
|
magRow.create(1, imgSize.width, CV_32F); |
|
|
|
// calculate magnitude and angle of gradient, perform non-maxima suppression. |
|
// fill the map with one of the following values: |
|
// 0 - the pixel might belong to an edge |
|
// 1 - the pixel can not belong to an edge |
|
// 2 - the pixel does belong to an edge |
|
for (int i = 0; i <= imgSize.height; i++) { |
|
int *tmpMag = magBuffer[(i > 0) + 1] + 1; |
|
const short *tmpDx = dx.ptr<short>(i); |
|
const short *tmpDy = dy.ptr<short>(i); |
|
uchar *tmpMap; |
|
int prevFlag = 0; |
|
|
|
if (i < imgSize.height) { |
|
tmpMag[-1] = tmpMag[imgSize.width] = 0; |
|
for (int j = 0; j < imgSize.width; j++) |
|
tmpMag[j] = abs(tmpDx[j]) + abs(tmpDy[j]); |
|
} else |
|
memset(tmpMag - 1, 0, (imgSize.width + 2) * sizeof(int)); |
|
|
|
// at the very beginning we do not have a complete ring |
|
// buffer of 3 magnitude rows for non-maxima suppression |
|
if (i == 0) |
|
continue; |
|
|
|
tmpMap = map + mapStep * i + 1; |
|
tmpMap[-1] = tmpMap[imgSize.width] = 1; |
|
|
|
tmpMag = magBuffer[1] + 1; // take the central row |
|
tmpDx = (short *) (dx[i - 1]); |
|
tmpDy = (short *) (dy[i - 1]); |
|
|
|
ptrdiff_t magStep1, magStep2; |
|
magStep1 = magBuffer[2] - magBuffer[1]; |
|
magStep2 = magBuffer[0] - magBuffer[1]; |
|
|
|
if ((stackTop - stackBottom) + imgSize.width > maxSize) { |
|
int stackSize = (int) (stackTop - stackBottom); |
|
maxSize = MAX(maxSize * 3 / 2, maxSize + 8); |
|
stack.resize(maxSize); |
|
stackBottom = &stack[0]; |
|
stackTop = stackBottom + stackSize; |
|
} |
|
|
|
const int CANNY_SHIFT = 15; |
|
const float TAN22_5 = 0.4142135623730950488016887242097f; // tan(22.5) = sqrt(2) - 1 |
|
const int TG22 = (int) (TAN22_5 * (1 << CANNY_SHIFT) + 0.5); |
|
|
|
// #define CANNY_PUSH(d) *(d) = (uchar)2, *stack_top++ = (d) |
|
// #define CANNY_POP(d) ((d) = *--stack_top) |
|
|
|
for (int j = 0; j < imgSize.width; j++) { |
|
int x = tmpDx[j], y = tmpDy[j]; |
|
int s = x ^ y; |
|
int m = tmpMag[j]; |
|
x = abs(x), y = abs(y); |
|
if (m > lowTh) { |
|
int tg22x = x * TG22; |
|
int tg67x = tg22x + ((x + x) << CANNY_SHIFT); |
|
|
|
y <<= CANNY_SHIFT; |
|
if (y < tg22x) { |
|
if (m > tmpMag[j - 1] && m >= tmpMag[j + 1]) { |
|
if (m > highTh && !prevFlag && tmpMap[j - mapStep] != 2) { |
|
tmpMap[j] = (uchar) 2; |
|
*stackTop++ = (tmpMap + j); |
|
prevFlag = 1; |
|
} else |
|
tmpMap[j] = (uchar) 0; |
|
continue; |
|
} |
|
} else if (y > tg67x) { |
|
if (m > tmpMag[j + magStep2] && m >= tmpMag[j + magStep1]) { |
|
if (m > highTh && !prevFlag && tmpMap[j - mapStep] != 2) { |
|
tmpMap[j] = (uchar) 2; |
|
*stackTop++ = (tmpMap + j); |
|
prevFlag = 1; |
|
} else |
|
tmpMap[j] = (uchar) 0; |
|
continue; |
|
} |
|
} else { |
|
s = s < 0 ? -1 : 1; |
|
if (m > tmpMag[j + magStep2 - s] && m > tmpMag[j + magStep1 + s]) { |
|
if (m > highTh && !prevFlag && tmpMap[j - mapStep] != 2) { |
|
tmpMap[j] = (uchar) 2; |
|
*stackTop++ = (tmpMap + j); |
|
prevFlag = 1; |
|
} else |
|
tmpMap[j] = (uchar) 0; |
|
continue; |
|
} |
|
} |
|
} |
|
prevFlag = 0; |
|
tmpMap[j] = (uchar) 1; |
|
} |
|
|
|
// scroll the ring buffer |
|
tmpMag = magBuffer[0]; |
|
magBuffer[0] = magBuffer[1]; |
|
magBuffer[1] = magBuffer[2]; |
|
magBuffer[2] = tmpMag; |
|
} |
|
|
|
// track the edges (hysteresis thresholding) |
|
while (stackTop > stackBottom) { |
|
uchar *m; |
|
if ((stackTop - stackBottom) + 8 > maxSize) { |
|
int stackSize = (int) (stackTop - stackBottom); |
|
maxSize = MAX(maxSize * 3 / 2, maxSize + 8); |
|
stack.resize(maxSize); |
|
stackBottom = &stack[0]; |
|
stackTop = stackBottom + stackSize; |
|
} |
|
|
|
m = *--stackTop; |
|
if (!m[-1]) { |
|
*(m - 1) = (uchar) 2; |
|
*stackTop++ = m - 1; |
|
} |
|
if (!m[1]) { |
|
*(m + 1) = (uchar) 2; |
|
*stackTop++ = m + 1; |
|
} |
|
if (!m[-mapStep - 1]) { |
|
*(m - mapStep - 1) = (uchar) 2; |
|
*stackTop++ = m - mapStep - 1; |
|
} |
|
if (!m[-mapStep]) { |
|
*(m - mapStep) = (uchar) 2; |
|
*stackTop++ = m - mapStep; |
|
} |
|
if (!m[-mapStep + 1]) { |
|
*(m - mapStep + 1) = (uchar) 2; |
|
*stackTop++ = m - mapStep + 1; |
|
} |
|
if (!m[mapStep - 1]) { |
|
*(m + mapStep - 1) = (uchar) 2; |
|
*stackTop++ = m + mapStep - 1; |
|
} |
|
if (!m[mapStep]) { |
|
*(m + mapStep) = (uchar) 2; |
|
*stackTop++ = m + mapStep; |
|
} |
|
if (!m[mapStep + 1]) { |
|
*(m + mapStep + 1) = (uchar) 2; |
|
*stackTop++ = m + mapStep + 1; |
|
} |
|
} |
|
|
|
// final pass, form the final image |
|
for (int i = 0; i < imgSize.height; i++) { |
|
const uchar *tmpMap = map + mapStep * (i + 1) + 1; |
|
uchar *tmpDst = edges[i]; |
|
for (int j = 0; j < imgSize.width; j++) |
|
tmpDst[j] = (uchar) -(tmpMap[j] >> 1); |
|
} |
|
|
|
// for each edge points, compute the edge direction |
|
for (int i = 0; i < imgSize.height; i++) { |
|
auto *tmpDx = dx.ptr<short>(i); |
|
auto *tmpDy = dy.ptr<short>(i); |
|
auto *tmpE = edges.ptr<uchar>(i); |
|
auto *tmpDp = dp.ptr<uchar>(i); |
|
auto *tmpDn = dn.ptr<uchar>(i); |
|
|
|
for (int j = 0; j < imgSize.width; j++) { |
|
if (!((tmpE[j] <= 0) || (tmpDx[j] == 0) || (tmpDy[j] == 0))) { |
|
// angle of the tangent |
|
float phi = -(float(tmpDx[j])) / float(tmpDy[j]); |
|
// along positive or negative diagonal |
|
if (phi > 0) |
|
tmpDp[j] = (uchar) 255; |
|
else if (phi < 0) |
|
tmpDn[j] = (uchar) 255; |
|
} |
|
} |
|
} |
|
} |
|
|
|
void EllipseDetectorImpl::detect(Mat1b &image, std::vector<Ellipse> &ellipses) { |
|
countsOfFindEllipse = 0, countsOfGetFastCenter = 0; |
|
|
|
// set the image size |
|
_imgSize = image.size(); |
|
|
|
// initialize temporary data structures |
|
Mat1b dp = Mat1b::zeros(_imgSize); // arcs along positive diagonal |
|
Mat1b dn = Mat1b::zeros(_imgSize); // arcs along negative diagonal |
|
|
|
// initialize accumulator dimensions |
|
ACC_N_SIZE = 101, ACC_R_SIZE = 180, ACC_A_SIZE = max(_imgSize.height, _imgSize.width); |
|
|
|
// allocate accumulators |
|
accN = new int[ACC_N_SIZE], accR = new int[ACC_R_SIZE], accA = new int[ACC_A_SIZE]; |
|
|
|
// other temporary |
|
VVP points_1, points_2, points_3, points_4; // vector of points, one for each convexity class |
|
std::unordered_map<uint, EllipseData> centers; // hash map for reusing already computed EllipseData |
|
|
|
// preprocessing |
|
// find edge point with coarse convexity along positive (dp) or negative (dn) diagonal |
|
preProcessing(image, dp, dn); |
|
|
|
// detect edge and find convexity |
|
detectEdges13(dp, points_1, points_3); |
|
detectEdges24(dn, points_2, points_4); |
|
|
|
// find triplets |
|
getTriplets124(points_1, points_2, points_4, centers, ellipses); |
|
getTriplets231(points_2, points_3, points_1, centers, ellipses); |
|
getTriplets342(points_3, points_4, points_2, centers, ellipses); |
|
getTriplets413(points_4, points_1, points_3, centers, ellipses); |
|
|
|
// std::sort by score |
|
std::sort(ellipses.begin(), ellipses.end()); |
|
|
|
// free accumulator memory |
|
delete[]accN; |
|
delete[]accR; |
|
delete[]accA; |
|
|
|
// cluster detections |
|
clusterEllipses(ellipses); |
|
} |
|
|
|
void EllipseDetectorImpl::findEllipses(Point2f ¢er, VP &edge_i, VP &edge_j, VP &edge_k, |
|
EllipseData &data_ij, EllipseData &data_ik, |
|
std::vector<Ellipse> &ellipses) { |
|
countsOfFindEllipse++; |
|
// find ellipse parameters |
|
|
|
// 0-initialize accumulators |
|
memset(accN, 0, sizeof(int) * ACC_N_SIZE); |
|
memset(accR, 0, sizeof(int) * ACC_R_SIZE); |
|
memset(accA, 0, sizeof(int) * ACC_A_SIZE); |
|
|
|
|
|
// get size of the 4 vectors of slopes (2 pairs of arcs) |
|
int sz_ij1 = int(data_ij.Sa.size()); |
|
int sz_ij2 = int(data_ij.Sb.size()); |
|
int sz_ik1 = int(data_ik.Sa.size()); |
|
int sz_ik2 = int(data_ik.Sb.size()); |
|
|
|
// get the size of the 3 arcs |
|
size_t sz_ei = edge_i.size(); |
|
size_t sz_ej = edge_j.size(); |
|
size_t sz_ek = edge_k.size(); |
|
|
|
// center of the estimated ellipse |
|
float a0 = center.x; |
|
float b0 = center.y; |
|
|
|
// estimation of remaining parameters |
|
// uses 4 combinations of parameters. See Table 1 and Sect [3.2.3] of the paper. |
|
// ij1 and ik |
|
{ |
|
float q1 = data_ij.ra; |
|
float q3 = data_ik.ra; |
|
float q5 = data_ik.rb; |
|
|
|
for (int ij1 = 0; ij1 < sz_ij1; ij1++) { |
|
float q2 = data_ij.Sa[ij1]; |
|
|
|
float q1xq2 = q1 * q2; |
|
// ij1 and ik1 |
|
for (int ik1 = 0; ik1 < sz_ik1; ik1++) { |
|
float q4 = data_ik.Sa[ik1]; |
|
|
|
float q3xq4 = q3 * q4; |
|
|
|
// see Eq. [13-18] in the paper |
|
|
|
float a = (q1xq2 - q3xq4); // gama |
|
float b = (q3xq4 + 1) * (q1 + q2) - (q1xq2 + 1) * (q3 + q4); // beta |
|
float Kp = (-b + sqrt(b * b + 4 * a * a)) / (2 * a); // K+ |
|
float zplus = ((q1 - Kp) * (q2 - Kp)) / ((1 + q1 * Kp) * (1 + q2 * Kp)); |
|
|
|
// available or not |
|
if (zplus >= 0.0f) continue; |
|
|
|
float Np = sqrt(-zplus); // N+ |
|
float rho = atan(Kp); // rho tmp |
|
int rhoDeg; |
|
if (Np > 1.f) { |
|
// inverse and convert to [0, 180) |
|
Np = 1.f / Np; |
|
rhoDeg = cvRound((rho * 180 / CV_PI) + 180) % 180; |
|
} else { |
|
// convert to [0, 180) |
|
rhoDeg = cvRound((rho * 180 / CV_PI) + 90) % 180; |
|
} |
|
|
|
int iNp = cvRound(Np * 100); |
|
|
|
if (0 <= iNp && iNp < ACC_N_SIZE && 0 <= rhoDeg && rhoDeg < ACC_R_SIZE) { |
|
++accN[iNp]; // increment N accumulator |
|
++accR[rhoDeg]; // increment R accumulator |
|
} |
|
} |
|
|
|
// ij1 and ik2 |
|
for (int ik2 = 0; ik2 < sz_ik2; ik2++) { |
|
float q4 = data_ik.Sb[ik2]; |
|
|
|
float q5xq4 = q5 * q4; |
|
|
|
// See Eq. [13-18] in the paper |
|
|
|
float a = (q1xq2 - q5xq4); |
|
float b = (q5xq4 + 1) * (q1 + q2) - (q1xq2 + 1) * (q5 + q4); |
|
float Kp = (-b + sqrt(b * b + 4 * a * a)) / (2 * a); |
|
float zplus = ((q1 - Kp) * (q2 - Kp)) / ((1 + q1 * Kp) * (1 + q2 * Kp)); |
|
|
|
if (zplus >= 0.0f) |
|
continue; |
|
|
|
float Np = sqrt(-zplus); |
|
float rho = atan(Kp); |
|
int rhoDeg; |
|
if (Np > 1.f) { |
|
// inverse and convert to [0, 180) |
|
Np = 1.f / Np; |
|
rhoDeg = cvRound((rho * 180 / CV_PI) + 180) % 180; |
|
} else { |
|
// convert to [0, 180) |
|
rhoDeg = cvRound((rho * 180 / CV_PI) + 90) % 180; |
|
} |
|
|
|
int iNp = cvRound(Np * 100); // [0, 100] |
|
|
|
if (0 <= iNp && iNp < ACC_N_SIZE && 0 <= rhoDeg && rhoDeg < ACC_R_SIZE) { |
|
++accN[iNp]; // increment N accumulator |
|
++accR[rhoDeg]; // increment R accumulator |
|
} |
|
} |
|
|
|
} |
|
} |
|
|
|
//ij2 and ik |
|
{ |
|
float q1 = data_ij.rb; |
|
float q3 = data_ik.rb; |
|
float q5 = data_ik.ra; |
|
|
|
for (int ij2 = 0; ij2 < sz_ij2; ij2++) { |
|
float q2 = data_ij.Sb[ij2]; |
|
|
|
float q1xq2 = q1 * q2; |
|
//ij2 and ik2 |
|
for (int ik2 = 0; ik2 < sz_ik2; ik2++) { |
|
float q4 = data_ik.Sb[ik2]; |
|
|
|
float q3xq4 = q3 * q4; |
|
|
|
// See Eq. [13-18] in the paper |
|
|
|
float a = (q1xq2 - q3xq4); |
|
float b = (q3xq4 + 1) * (q1 + q2) - (q1xq2 + 1) * (q3 + q4); |
|
float Kp = (-b + sqrt(b * b + 4 * a * a)) / (2 * a); |
|
float zplus = ((q1 - Kp) * (q2 - Kp)) / ((1 + q1 * Kp) * (1 + q2 * Kp)); |
|
|
|
if (zplus >= 0.0f) |
|
continue; |
|
|
|
float Np = sqrt(-zplus); |
|
float rho = atan(Kp); |
|
int rhoDeg; |
|
if (Np > 1.f) { |
|
// inverse and convert to [0, 180) |
|
Np = 1.f / Np; |
|
rhoDeg = cvRound((rho * 180 / CV_PI) + 180) % 180; |
|
} else { |
|
// convert to [0, 180) |
|
rhoDeg = cvRound((rho * 180 / CV_PI) + 90) % 180; |
|
} |
|
|
|
int iNp = cvRound(Np * 100); // [0, 100] |
|
|
|
if (0 <= iNp && iNp < ACC_N_SIZE && 0 <= rhoDeg && rhoDeg < ACC_R_SIZE) { |
|
++accN[iNp]; // increment N accumulator |
|
++accR[rhoDeg]; // increment R accumulator |
|
} |
|
} |
|
|
|
// ij2 and ik1 |
|
for (int ik1 = 0; ik1 < sz_ik1; ik1++) { |
|
float q4 = data_ik.Sa[ik1]; |
|
|
|
float q5xq4 = q5 * q4; |
|
|
|
// See Eq. [13-18] in the paper |
|
|
|
float a = (q1xq2 - q5xq4); |
|
float b = (q5xq4 + 1) * (q1 + q2) - (q1xq2 + 1) * (q5 + q4); |
|
float Kp = (-b + sqrt(b * b + 4 * a * a)) / (2 * a); |
|
float zplus = ((q1 - Kp) * (q2 - Kp)) / ((1 + q1 * Kp) * (1 + q2 * Kp)); |
|
|
|
if (zplus >= 0.0f) { |
|
continue; |
|
} |
|
|
|
float Np = sqrt(-zplus); |
|
float rho = atan(Kp); |
|
int rhoDeg; |
|
if (Np > 1.f) { |
|
// inverse and convert to [0, 180) |
|
Np = 1.f / Np; |
|
rhoDeg = cvRound((rho * 180 / CV_PI) + 180) % 180; |
|
} else { |
|
// convert to [0, 180) |
|
rhoDeg = cvRound((rho * 180 / CV_PI) + 90) % 180; |
|
} |
|
|
|
int iNp = cvRound(Np * 100); // [0, 100] |
|
|
|
if (0 <= iNp && iNp < ACC_N_SIZE && 0 <= rhoDeg && rhoDeg < ACC_R_SIZE) { |
|
++accN[iNp]; // increment N accumulator |
|
++accR[rhoDeg]; // increment R accumulator |
|
} |
|
} |
|
} |
|
} |
|
|
|
// find peak in N and K accumulator |
|
int iN = (int)std::distance(accN, std::max_element(accN, accN + ACC_N_SIZE)); |
|
int iK = (int)std::distance(accR, std::max_element(accR, accR + ACC_R_SIZE)) + 90; |
|
|
|
// recover real values |
|
auto fK = float(iK); |
|
float Np = float(iN) * 0.01f; |
|
float rho = fK * float(CV_PI) / 180.f; // deg 2 rad |
|
float Kp = tan(rho); |
|
|
|
// estimate A. See Eq. [19 - 22] in Sect [3.2.3] of the paper |
|
for (ushort l = 0; l < sz_ei; ++l) { |
|
Point &pp = edge_i[l]; |
|
float sk = 1.f / sqrt(Kp * Kp + 1.f); // cos rho |
|
float x0 = ((pp.x - a0) * sk) + (((pp.y - b0) * Kp) * sk); |
|
float y0 = -(((pp.x - a0) * Kp) * sk) + ((pp.y - b0) * sk); |
|
float Ax = sqrt((x0 * x0 * Np * Np + y0 * y0) / ((Np * Np) * (1.f + Kp * Kp))); |
|
int A = cvRound(abs(Ax / cos(rho))); |
|
if ((0 <= A) && (A < ACC_A_SIZE)) |
|
++accA[A]; |
|
} |
|
|
|
for (ushort l = 0; l < sz_ej; ++l) { |
|
Point &pp = edge_j[l]; |
|
float sk = 1.f / sqrt(Kp * Kp + 1.f); |
|
float x0 = ((pp.x - a0) * sk) + (((pp.y - b0) * Kp) * sk); |
|
float y0 = -(((pp.x - a0) * Kp) * sk) + ((pp.y - b0) * sk); |
|
float Ax = sqrt((x0 * x0 * Np * Np + y0 * y0) / ((Np * Np) * (1.f + Kp * Kp))); |
|
int A = cvRound(abs(Ax / cos(rho))); |
|
if ((0 <= A) && (A < ACC_A_SIZE)) |
|
++accA[A]; |
|
} |
|
|
|
for (ushort l = 0; l < sz_ek; ++l) { |
|
Point &pp = edge_k[l]; |
|
float sk = 1.f / sqrt(Kp * Kp + 1.f); |
|
float x0 = ((pp.x - a0) * sk) + (((pp.y - b0) * Kp) * sk); |
|
float y0 = -(((pp.x - a0) * Kp) * sk) + ((pp.y - b0) * sk); |
|
float Ax = sqrt((x0 * x0 * Np * Np + y0 * y0) / ((Np * Np) * (1.f + Kp * Kp))); |
|
int A = cvRound(abs(Ax / cos(rho))); |
|
if ((0 <= A) && (A < ACC_A_SIZE)) |
|
++accA[A]; |
|
} |
|
|
|
// find peak in A accumulator |
|
int A = (int)std::distance(accA, std::max_element(accA, accA + ACC_A_SIZE)); |
|
auto fA = float(A); |
|
|
|
// find B value. See Eq [23] in the paper |
|
float fB = abs(fA * Np); |
|
|
|
// got all ellipse parameters |
|
Ellipse ell(a0, b0, fA, fB, fmod(rho + float(CV_PI) * 2.f, float(CV_PI))); |
|
|
|
// get the score. See Sect [3.3.1] in the paper |
|
|
|
// find the number of edge pixel lying on the ellipse |
|
float _cos = cos(-ell.radius); |
|
float _sin = sin(-ell.radius); |
|
|
|
float invA2 = 1.f / (ell.a * ell.a); |
|
float invB2 = 1.f / (ell.b * ell.b); |
|
|
|
float invNofPoints = 1.f / float(sz_ei + sz_ej + sz_ek); |
|
int counter_on_perimeter = 0; |
|
|
|
for (ushort l = 0; l < sz_ei; ++l) { |
|
float tx = float(edge_i[l].x) - ell.center.x; |
|
float ty = float(edge_i[l].y) - ell.center.y; |
|
float rx = (tx * _cos - ty * _sin); |
|
float ry = (tx * _sin + ty * _cos); |
|
|
|
float h = (rx * rx) * invA2 + (ry * ry) * invB2; |
|
if (abs(h - 1.f) < _maxDistanceToEllipseContour) |
|
++counter_on_perimeter; |
|
} |
|
|
|
for (ushort l = 0; l < sz_ej; ++l) { |
|
float tx = float(edge_j[l].x) - ell.center.x; |
|
float ty = float(edge_j[l].y) - ell.center.y; |
|
float rx = (tx * _cos - ty * _sin); |
|
float ry = (tx * _sin + ty * _cos); |
|
|
|
float h = (rx * rx) * invA2 + (ry * ry) * invB2; |
|
if (abs(h - 1.f) < _maxDistanceToEllipseContour) |
|
++counter_on_perimeter; |
|
} |
|
|
|
for (ushort l = 0; l < sz_ek; ++l) { |
|
float tx = float(edge_k[l].x) - ell.center.x; |
|
float ty = float(edge_k[l].y) - ell.center.y; |
|
float rx = (tx * _cos - ty * _sin); |
|
float ry = (tx * _sin + ty * _cos); |
|
|
|
float h = (rx * rx) * invA2 + (ry * ry) * invB2; |
|
if (abs(h - 1.f) < _maxDistanceToEllipseContour) |
|
++counter_on_perimeter; |
|
} |
|
|
|
// no points found on the ellipse |
|
if (counter_on_perimeter <= 0) |
|
return; |
|
|
|
// compute score |
|
float score = float(counter_on_perimeter) * invNofPoints; |
|
if (score < _minScore) |
|
return; |
|
|
|
// compute reliability |
|
float di, dj, dk; |
|
{ |
|
Point2f p1(float(edge_i[0].x), float(edge_i[0].y)); |
|
Point2f p2(float(edge_i[sz_ei - 1].x), float(edge_i[sz_ei - 1].y)); |
|
p1.x -= ell.center.x; |
|
p1.y -= ell.center.y; |
|
p2.x -= ell.center.x; |
|
p2.y -= ell.center.y; |
|
Point2f r1((p1.x * _cos - p1.y * _sin), (p1.x * _sin + p1.y * _cos)); |
|
Point2f r2((p2.x * _cos - p2.y * _sin), (p2.x * _sin + p2.y * _cos)); |
|
di = abs(r2.x - r1.x) + abs(r2.y - r1.y); |
|
} |
|
{ |
|
Point2f p1(float(edge_j[0].x), float(edge_j[0].y)); |
|
Point2f p2(float(edge_j[sz_ej - 1].x), float(edge_j[sz_ej - 1].y)); |
|
p1.x -= ell.center.x; |
|
p1.y -= ell.center.y; |
|
p2.x -= ell.center.x; |
|
p2.y -= ell.center.y; |
|
Point2f r1((p1.x * _cos - p1.y * _sin), (p1.x * _sin + p1.y * _cos)); |
|
Point2f r2((p2.x * _cos - p2.y * _sin), (p2.x * _sin + p2.y * _cos)); |
|
dj = abs(r2.x - r1.x) + abs(r2.y - r1.y); |
|
} |
|
{ |
|
Point2f p1(float(edge_k[0].x), float(edge_k[0].y)); |
|
Point2f p2(float(edge_k[sz_ek - 1].x), float(edge_k[sz_ek - 1].y)); |
|
p1.x -= ell.center.x; |
|
p1.y -= ell.center.y; |
|
p2.x -= ell.center.x; |
|
p2.y -= ell.center.y; |
|
Point2f r1((p1.x * _cos - p1.y * _sin), (p1.x * _sin + p1.y * _cos)); |
|
Point2f r2((p2.x * _cos - p2.y * _sin), (p2.x * _sin + p2.y * _cos)); |
|
dk = abs(r2.x - r1.x) + abs(r2.y - r1.y); |
|
} |
|
|
|
// this allows to get rid of thick edges |
|
float rel = min(1.f, ((di + dj + dk) / (3 * (ell.a + ell.b)))); |
|
|
|
if (rel < _minReliability) |
|
return; |
|
|
|
// assign the new score |
|
ell.score = (score + rel) * 0.5f; |
|
|
|
// the tentative detection has been confirmed |
|
ellipses.emplace_back(ell); |
|
} |
|
|
|
void EllipseDetectorImpl::clusterEllipses(std::vector<Ellipse> &ellipses) { |
|
const float aDistanceThreshold = 0.1f, bDistanceThreshold = 0.1f; |
|
const float rDistanceThreshold = 0.1f; |
|
const float DcRatioThreshold = 0.1f, rCircleThreshold = 0.9f; |
|
|
|
int ellipseCount = int(ellipses.size()); |
|
if (ellipseCount == 0) |
|
return; |
|
|
|
// the first ellipse is assigned to a cluster |
|
std::vector<Ellipse> clusters; |
|
clusters.emplace_back(ellipses[0]); |
|
|
|
bool foundCluster = false; |
|
for (int i = 0; i < ellipseCount; i++) { |
|
int clusterSize = int(clusters.size()); |
|
|
|
Ellipse &e1 = ellipses[i]; |
|
float ba_e1 = e1.b / e1.a; |
|
|
|
foundCluster = false; |
|
for (int j = 0; j < clusterSize; j++) { |
|
Ellipse &e2 = clusters[j]; |
|
float ba_e2 = e2.b / e2.a; |
|
|
|
float cDistanceThreshold = min(e1.b, e2.b) * DcRatioThreshold; |
|
cDistanceThreshold = cDistanceThreshold * cDistanceThreshold; |
|
|
|
// filter centers |
|
float cDistance = ((e1.center.x - e2.center.x) * (e1.center.x - e2.center.x) + |
|
(e1.center.y - e2.center.y) * (e1.center.y - e2.center.y)); |
|
if (cDistance > cDistanceThreshold) |
|
continue; |
|
|
|
// filter a |
|
float aDistance = abs(e1.a - e2.a) / max(e1.a, e2.a); |
|
if (aDistance > aDistanceThreshold) |
|
continue; |
|
|
|
// filter b |
|
float bDistance = abs(e1.b - e2.b) / min(e1.b, e2.b); |
|
if (bDistance > bDistanceThreshold) |
|
continue; |
|
|
|
// filter angle |
|
float rDistance = getMinAnglePI(e1.radius, e2.radius) / float(CV_PI); |
|
if ((rDistance > rDistanceThreshold) && (ba_e1 < rCircleThreshold) && |
|
(ba_e2 < rCircleThreshold)) |
|
continue; |
|
|
|
// same cluster as e2 |
|
foundCluster = true; |
|
break; |
|
} |
|
|
|
// create a new cluster |
|
if (!foundCluster) |
|
clusters.push_back(e1); |
|
} |
|
clusters.swap(ellipses); |
|
} |
|
|
|
// find ellipses in images |
|
void findEllipses( |
|
InputArray image, OutputArray ellipses, |
|
float scoreThreshold, float reliabilityThreshold, |
|
float centerDistanceThreshold) { |
|
|
|
// check image empty and type |
|
CV_Assert( |
|
!image.empty() && (image.isMat() || image.isUMat())); |
|
|
|
// check ellipses type |
|
int type = CV_32FC(6); |
|
if (ellipses.fixedType()) { |
|
type = ellipses.type(); |
|
CV_CheckType(type, type == CV_32FC(6), "Wrong type of output ellipses"); |
|
} |
|
|
|
// set class parameters |
|
Size imgSize = image.size(); |
|
float maxCenterDistance = |
|
sqrt(float(imgSize.width * imgSize.width + imgSize.height * imgSize.height)) * |
|
centerDistanceThreshold; |
|
EllipseDetectorImpl edi; |
|
edi.setParameters(maxCenterDistance, scoreThreshold, reliabilityThreshold); |
|
|
|
// detect - ellipse format |
|
std::vector<Ellipse> ellipseResults; |
|
Mat1b grayImage = image.getMat(); |
|
if (image.channels() != 1) |
|
cvtColor(image, grayImage, COLOR_BGR2GRAY); |
|
edi.detect(grayImage, ellipseResults); |
|
|
|
// convert - ellipse format to std::vector<Vec6f> |
|
std::vector<Vec6f> _ellipses; |
|
for (size_t i = 0; i < ellipseResults.size(); i++) { |
|
Ellipse tmpEll = ellipseResults[i]; |
|
Vec6f tmpVec(tmpEll.center.x, tmpEll.center.y, tmpEll.a, tmpEll.b, tmpEll.score, |
|
tmpEll.radius); |
|
_ellipses.push_back(tmpVec); |
|
} |
|
Mat(_ellipses).copyTo(ellipses); |
|
} |
|
} // namespace ximgproc |
|
} // namespace cv
|
|
|