I solved the problem designing a function which is based on the idea of @Abhishek.
Here is the pseudocode, and its C++ implementation.
checkCircleSectors(imagePoints, circle, circleExpansionFactor, theta, acceptThreshold)
Divide the circle in n sectors, using angle theta (so n = 2*PI /theta)
(Optional) Slightly expand or reduce the radius of the circle by circleExpansionFactor, if needed.
for each point "curPoint" within the image (imagePoints){
if the distance between curPoint and center.circle is lesser/equal to circle.radius{
let "curAngle" be the angle between circle.center and curPoint, calculated using curPoint as origin.
let "curSector" be the sector of circle which contains curAngle
upgrade the ranking of curSector by one.
}
}
if there are more than acceptThreshold sectors whose rank is zero
the circle is not acceptable
else
the circle is acceptable
Notes:
I found it very useful to slightly expand the radius of the circle (I use a 1.25 expand factor) beacause sometimes (expecially in the case of handwritten circles) the detection could be inaccurate.
Here is my C++ implementation of the concept:
boolean ImgCheck::checkCircleSectors(vector<Point> tgtPoints, Point tgtCenter, int tgtRadius, float tgtRadiusExp, int tgtStep, int tgtThreshold){
vector<int> circleData( 360 / tgtStep, 0);
int detectionReliability = 0;
tgtRadius = tgtRadius * tgtRadiusExp;
/* Analyze the sectors. */
for(size_t i=0; i<tgtPoints.size(); i++){
Point curCartesianPoint = getCartesianCoordinates(tgtCenter, tgtPoints[i]);
float angleRad = arctangent2(curCartesianPoint);
int angleDeg = angleRad * (180/M_PI);
if(distance(tgtPoints[i],tgtCenter) <= tgtRadius){
circleData.at(angleDeg / tgtStep) += 1;
}
}
/* Count the postive-ranked sectors. */
for(size_t i = 0; i< circleData.size(); i++){
if(circleData[i] > 0)
detectionReliability += 100.0/(360/tgtStep);
}
if(detectionReliability >= tgtThreshold)
return true;
}
return false;
}
Notes:
My preference for the last three parameters is the following:
- tgtRadiusExp = 1.25 (radius expanded by a quarter of its length)
- tgtStep = 5 (theta = 5 degrees -> circle divided in 72 sectors)
- tgtTreshold = 75 (at least 75 non-zero sectors required to pass the test)
The function getCartesianCoordinates (source code below) converts OpenCV coordinates in cartesian coordinates. It takes two arguments:
- Point tgtOrigin are the openCV coordinates of the point which must be used as origin.
- Point tgtPoint are the openCV coordinates of another point.
The function returns the coordinates of tgtPoint, converted using tgtOrigin as origin.
Point getCartesianCoordinates(Point tgtOrigin, Point tgtPoint){
Point resPoint = tgtPoint - tgtOrigin;
return Point(resPoint.x, -resPoint.y);
}