I have following shape.

It may be rotated by unknown angle. I want to determine its rotation in reference to horizontal axis (so shape above would have rotation equal to 0). Best idea I have come up so far is to determine contours of the shape, find minimum area rectangle and then take its rotation as rotation of shape itself.
Mat mask = imread("path_to_image");
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
vector<RotatedRect> rotatedRects;
findContours(mask, contours, hierarchy, RetrievalModes::RETR_TREE, ContourApproximationModes::CHAIN_APPROX_SIMPLE);
const auto& largestContour = max_element(contours.begin(), contours.end(),
[](const auto& e1, const auto& e2) { return e1.size() < e2.size(); });
RotatedRect rotatedRect = minAreaRect(*largestContour);
The problem is that rectangle doesn't border the shape in expected way.

I'm not sure if I can go with that and simply calculate rotation from it anyway, because shape comes from other image processing and I don't know if rectangle would not laid on a different diagonal.
Is there more reliable and better way of finding rotation of this shape?
Edit: Image with shape can be in different scale.
here's the simple logic of finding the center of gravity and the furthest contour point from it. It has an offset of 6 degrees for that contour, either because of the actual contour shape, or because of a slightly wrong center of gravity.
int main(int argc, char* argv[])
{
//cv::Mat input = cv::imread("C:/StackOverflow/Input/rotatedShape1.png", cv::IMREAD_GRAYSCALE);
cv::Mat input = cv::imread("C:/StackOverflow/Input/rotatedShape5.png", cv::IMREAD_GRAYSCALE);
std::string outString = "C:/StackOverflow/Output/rotatedShape5.png";
cv::Mat output;
cv::cvtColor(input, output, cv::COLOR_GRAY2BGR);
std::vector<std::vector<cv::Point> > contours;
cv::findContours(input, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);
std::vector<cv::Point> biggestContour;
double biggestArea = 0;
for (int i = 0; i < contours.size(); ++i)
{
double cArea = cv::contourArea(contours[i]);
if (cArea > biggestArea)
{
biggestArea = cArea;
biggestContour = contours[i];
}
}
if (biggestContour.size() == 0)
{
std::cout << "error: no contour found. Press enter to quit." << std::endl;
std::cin.get();
return 0;
}
cv::Point2f centerOfMass(0,0);
float invContourSize = 1.0f / biggestContour.size();
for (int i = 0; i < biggestContour.size(); ++i)
{
centerOfMass = centerOfMass + (invContourSize * cv::Point2f(biggestContour[i]));
}
float furthestDist = 0;
cv::Point2f furthestPoint = centerOfMass;
for (int i = 0; i < biggestContour.size(); ++i)
{
float cDist = cv::norm(cv::Point2f(biggestContour[i]) - centerOfMass);
if (cDist > furthestDist)
{
furthestDist = cDist;
furthestPoint = biggestContour[i];
}
}
// find points with very similar distance
float maxDifference = 20; // magic number
std::vector<cv::Point2f> listOfFurthestPoints;
for (int i = 0; i < biggestContour.size(); ++i)
{
float cDist = cv::norm(cv::Point2f(biggestContour[i]) - furthestPoint);
if (cDist < maxDifference)
{
listOfFurthestPoints.push_back( biggestContour[i] );
// render:
cv::circle(output, biggestContour[i], 0, cv::Scalar(255, 0, 255), 0);
}
}
cv::Point2f cogFP(0, 0);
float invListSize = 1.0f / listOfFurthestPoints.size();
for (int i = 0; i < listOfFurthestPoints.size(); ++i)
{
cogFP = cogFP + (invListSize * cv::Point2f(listOfFurthestPoints[i]));
}
std::cout << cogFP - centerOfMass << std::endl;
float angle = acos((cogFP - centerOfMass).x / cv::norm(cogFP - centerOfMass)); // scalar product of [1,0] and point
std::cout << angle * 180 / CV_PI << std::endl;
cv::line(output, centerOfMass, cogFP, cv::Scalar(0, 255, 0), 1);
cv::circle(output, centerOfMass, 5, cv::Scalar(0, 0, 255), 1);
cv::circle(output, cogFP, 3, cv::Scalar(255, 0, 0), 1);
cv::imwrite(outString, output);
cv::imshow("input", input);
cv::imshow("output", output);
cv::waitKey(0);
return 0;
}
this is the ouput for several rotations:





I would love to try the circle method, using RANSAC to find the best 2 circles, but maybe won't have the time...
Another way could be to find the turning points of the smoothed contour.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With