Repository for OpenCV's extra modules
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

// 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 &centers, 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 &center, 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 &centers,
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 &center, 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