Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to determine rotation of a shape?

I have following shape.

enter image description here

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.

enter image description here

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.

like image 975
serwus Avatar asked Dec 14 '25 02:12

serwus


1 Answers

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:

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

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.

like image 117
Micka Avatar answered Dec 16 '25 15:12

Micka



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!