1

I want to extract two circles and want to find out this circle is concentric or not.

I made some code by referring some sites and chatGPT, however, there is limitation.

I want to draw the 'CIRCLE', not an 'Ellipse'.

Below is the picture (it is the ring shape red laser beam) and my algorithm.

    import cv2
    import numpy as np

    # Load the Image
    img = cv2.imread("covered_2.jpg", cv2.IMREAD_GRAYSCALE)

    # Blur the Image
    img_blur = cv2.GaussianBlur(img, (5, 5), 0)

    # Image Binerize
    _, img_thresh = cv2.threshold(img_blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

    # Set the kernal size for closing
    kernel = np.ones((15, 15), np.uint8)

    #Closing the Image
    img_close = cv2.morphologyEx(img_thresh, cv2.MORPH_CLOSE, kernel)
    contours, _ = cv2.findContours(img_close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    max_contour = max(contours, key=cv2.contourArea)

    # Extract the outer ellipse
    ellipse = cv2.fitEllipse(max_contour)
    center, axes, angle = ellipse
    major_axis, minor_axis = axes
    img_ellipse = np.zeros_like(img)
    cv2.ellipse(img_ellipse, ellipse, 255, 2)

    # Finding the end points of the major axis(?)
    cos_angle = np.cos(np.deg2rad(angle))
    sin_angle = np.sin(np.deg2rad(angle))
    x1 = int(center[0] - 0.5 * major_axis * sin_angle)
    y1 = int(center[1] + 0.5 * major_axis * cos_angle)
    x2 = int(center[0] + 0.5 * major_axis * sin_angle)
    y2 = int(center[1] - 0.5 * major_axis * cos_angle)

    # Finding the end points of the minor axis(?)
    x3 = int(center[0] - 0.5 * minor_axis * cos_angle)
    y3 = int(center[1] - 0.5 * minor_axis * sin_angle)
    x4 = int(center[0] + 0.5 * minor_axis * cos_angle)
    y4 = int(center[1] + 0.5 * minor_axis * sin_angle)

    # Draw lines on the new image
    img_lines = np.zeros_like(img)
    cv2.line(img_lines, (x1, y1), (x2, y2), 255, 2)
    cv2.line(img_lines, (x3, y3), (x4, y4), 255, 2)
    cv2.line(img, (x1, y1), (x2, y2), 255, 2)
    cv2.line(img, (x3, y3), (x4, y4), 255, 2)
    cv2.ellipse(img, ellipse, 255, 2)

    # Show image in the new windows
    cv2.imshow('Input Image', img)
    cv2.imshow('Binary Image', img_thresh)
    cv2.imshow('Closed Image', img_close)
    cv2.imshow('Fitted Ellipse', img_ellipse)
    cv2.imshow('Extracted Lines', img_lines)

    # Save the Image
    cv2.imwrite('edge_detect_circle.jpg', img)
    cv2.imwrite('edge_detect_circle_close.jpg', img_close)
    cv2.imwrite('edge_detect_ellipse_close.jpg', img_ellipse)
    cv2.imwrite('edge_detect_lines_close.jpg', img_lines)
    cv2.waitKey()
    cv2.destroyAllWindows()

The final goal is compensate this speckled image and draw the 2 circles and figure out they are concentric or not.

Original Image is this: ring shape laser beam, speckled

1

I tried to designate some area and make its color inverted to find the inner ellipse. However, It did not work.

Additionally, I tried some Hough Circle transformation, but it also did not work.

As the last method, I used Canny edge detection to find out the center of the drawn line, which didn’t work either.

Cris Luengo
  • 55,762
  • 10
  • 62
  • 120
  • Maybe you could click [edit], select your code with the mouse and click `{}` in the Formatting Toolbar beside **Bold** and *Italic* to format it as code. – Mark Setchell May 24 '23 at 07:37
  • Please add another, marked up diagram showing exactly what you hope to find. And also show another diagram suffering from the *"ellipse problem"* so we can see the problems. – Mark Setchell May 24 '23 at 07:39
  • I couldn't bear to look at the misformatting of this post. there. please don't format posts like that again. read https://stackoverflow.com/editing-help – Christoph Rackwitz May 24 '23 at 08:52
  • same problem (if filtered enough), different user: https://stackoverflow.com/questions/76301410/fitellipse-for-spring-washer – Christoph Rackwitz May 24 '23 at 08:54
  • another Q&A on stack overflow: https://stackoverflow.com/questions/26574945/how-to-find-the-center-of-circle-using-the-least-square-fit-in-python – Christoph Rackwitz May 26 '23 at 17:00

1 Answers1

2

Why you are using fitEllipse ?
If you want circle (not ellipse), why don't you fit circle?

If OpenCV doesn't provide circle fitting (I think so), implement it yourself. ("How to" will be found when just google. I did this with keywords "Circle fitting least square")

I tried it with Simple Least Square, and obtained fitting result below. enter image description here

As you see, result center positions are not same perfectly. But I can't do final judge from this result, because I don't know how much difference is necessary to determine that the circles are not concentric.


This question is tagged [python], but I'm not a Python user, so I tried it in C++.

Note: edge extracting step is not same as yours. (I used easiest way for me.) So, if you fit circle to your contour data, the result may be a bit different.

//Circle fitting (Simple Least Square)
void FitCircle( const std::vector<cv::Point> &Ps, cv::Point2f &C, float &r )
{
    cv::Mat A( Ps.size(), 3, CV_32F );
    cv::Mat B( Ps.size(), 1, CV_32F );
    for( int i=0; i<Ps.size(); ++i )
    {
        const auto &P = Ps[i];
        A.at<float>( i,0 ) = P.x;
        A.at<float>( i,1 ) = P.y;
        A.at<float>( i,2 ) = 1;
        B.at<float>( i ) = P.x*P.x + P.y*P.y;
    }
    cv::Mat X;
    cv::solve( A,B, X, cv::DECOMP_SVD );
    C.x = X.at<float>(0) * 0.5f;
    C.y = X.at<float>(1) * 0.5f;
    r = sqrt( X.at<float>(2) + C.x*C.x + C.y*C.y );
}

int main()
{
    //Load image as gray-scale
    cv::Mat SrcImg = cv::imread( "Ring.png", cv::IMREAD_GRAYSCALE );
    if( SrcImg.empty() )return 0;

    //Extract two edge groups (corresponding to two circles)
    std::vector< cv::Point > Edge[2];
    {
        //Extract all edge points
        std::vector< cv::Point > Points;
        {
            cv::Mat Tmp[2];
            cv::threshold( SrcImg, Tmp[0], 128, 255, cv::THRESH_BINARY );
            cv::morphologyEx( SrcImg, Tmp[1], cv::MORPH_GRADIENT, cv::getStructuringElement( cv::MORPH_RECT, cv::Size(3,3) ) );
            cv::threshold( Tmp[1], Tmp[1], 100, 255, cv::THRESH_BINARY );
            cv::bitwise_and( Tmp[0], Tmp[1], Tmp[0] );
            cv::findNonZero( Tmp[0], Points );
        }

        //Split edge points to 2 group.
        cv::Point2f Center;
        float Radius;
        cv::minEnclosingCircle( Points, Center, Radius );
            //Here, the scale value 0.6 below is heuristic value for me.
            //But, proper scale can be given with your knowledge, I think.
        float SqDistThresh = std::pow( Radius * 0.6f, 2.0f );

        Edge[0].reserve( Points.size() );
        Edge[1].reserve( Points.size() );
        for( const auto &P : Points )
        {
            float xx = P.x - Center.x;
            float yy = P.y - Center.y;
            Edge[ xx*xx + yy*yy < SqDistThresh ].push_back( P );
        }
    }
    
    //Visualize 2 groups, fit circle for each and draw the result
    cv::Mat Show;
    cv::cvtColor( SrcImg, Show, cv::COLOR_GRAY2BGR );
    Show *= 0.2;
    cv::Vec3b Col[2] = { {0,255,0}, {0,0,255} };
    for( int i=0; i<2; ++i )
    {
        for( const auto &P : Edge[i] )
        {   Show.at<cv::Vec3b>(P) = Col[i]*0.5; }

        cv::Point2f Center;
        float Radius;
        FitCircle( Edge[i], Center, Radius );

        cv::circle( Show, Center, cvRound(Radius), Col[i] );
        cv::drawMarker( Show, Center, Col[i], cv::MARKER_CROSS );

        std::cout << Center << std::endl;
    }
    cv::imshow( "Show", Show );
    cv::waitKey();
    return 0;
}
fana
  • 1,370
  • 2
  • 7
  • You clearly have the code to generate this so I feel your answer would be far more helpful and actually up-votable if you included it. – Mark Setchell May 24 '23 at 09:27
  • I added my code now. (Why I did not post my code is, it is not python.) – fana May 24 '23 at 09:41
  • Thank you, I think that makes your answer far more useful and have upvoted it. – Mark Setchell May 24 '23 at 09:43
  • I believe, the reason of down-vote is not "no code". If so, please tell me the wrong point of my story. (for example, if "OpenCV has circle fitting function, don't implement yourself", I want to know it.) – fana May 24 '23 at 09:52
  • Sorry, I have no idea why people downvote without specifying a reason - it seems the least one could do in order to be courteous and help folk learn. – Mark Setchell May 24 '23 at 10:58
  • 1
    It would be nice if someone could convert this to python. In fact, it would be nice if it was added to OpenCV as a new fitCircle() method. – fmw42 May 24 '23 at 15:03
  • I don't think so.... I think simple least-square fitting (as in this post) is often useless in real scenes. Because completely removing all outliers in preprocessing is unrealistic. – fana May 24 '23 at 16:10
  • 1
    @fmw42 perhaps, analogously to other fitting and estimation tasks, modeled as an extension to fitEllipse, with flags for additional constraints (lock down ellipse axis, lock down aspect ratio to get circle, lock down center or radius, ...). -- whenever I identified such opportunities in the past, I usually open an issue for a feature request. there are many people looking to enrich their resume with "open source contributions". such features strike me as nicely contained tasks, also not terribly complex. someone already researched the theory behind the task. it's then only about implementation – Christoph Rackwitz May 26 '23 at 10:37
  • 2
    `@Christoph Rackwitz` I just submitted a feature request. Thanks for the suggestion. Feel free to add to it. – fmw42 May 26 '23 at 15:35
  • for cross-reference, that issue is https://github.com/opencv/opencv/issues/23693 – Christoph Rackwitz May 26 '23 at 16:56