Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Radius of a disk in a binary image

I have binarized images like this one:

enter image description here

I need to determine the center and radius of the inner solid disk. As you can see, it is surrounded by a textured area which touches it, so that simple connected component detection doesn't work. Anyway, there is a void margin on a large part of the perimeter.

A possible cure could be by eroding until all the texture disappears or disconnects from the disk, but this can be time consuming and the number of iterations is unsure. (In addition, in some unlucky cases there are tiny holes in the disk, which will grow with erosion.)

Any better suggestion to address this problem in a robust and fast way ? (I tagged OpenCV, but this is not mandated, what matters is the approach.)

like image 236
Yves Daoust Avatar asked Dec 24 '22 02:12

Yves Daoust


2 Answers

You can:

  1. Invert the image
  2. Find the largest axis-aligned rectangle containing only zeros, (I used my C++ code from this answer). The algorithm is pretty fast.
  3. Get the center and radius of the circle from the rectangle

enter image description here

Code:

#include <opencv2\opencv.hpp>

using namespace std;
using namespace cv;

// https://stackoverflow.com/a/30418912/5008845
cv::Rect findMaxRect(const cv::Mat1b& src)
{
    cv::Mat1f W(src.rows, src.cols, float(0));
    cv::Mat1f H(src.rows, src.cols, float(0));

    cv::Rect maxRect(0,0,0,0);
    float maxArea = 0.f;

    for (int r = 0; r < src.rows; ++r)
    {
        for (int c = 0; c < src.cols; ++c)
        {
            if (src(r, c) == 0)
            {
                H(r, c) = 1.f + ((r>0) ? H(r-1, c) : 0);
                W(r, c) = 1.f + ((c>0) ? W(r, c-1) : 0);
            }

            float minw = W(r,c);
            for (int h = 0; h < H(r, c); ++h)
            {
                minw = std::min(minw, W(r-h, c));
                float area = (h+1) * minw;
                if (area > maxArea)
                {
                    maxArea = area;
                    maxRect = cv::Rect(cv::Point(c - minw + 1, r - h), cv::Point(c+1, r+1));
                }
            }
        }
    }

    return maxRect;
}


int main()
{
    cv::Mat1b img = cv::imread("path/to/img", cv::IMREAD_GRAYSCALE);

    // Correct image
    img = img > 127;

    cv::Rect r = findMaxRect(~img);

    cv::Point center ( std::round(r.x + r.width / 2.f), std::round(r.y + r.height / 2.f));
    int radius = std::sqrt(r.width*r.width + r.height*r.height) / 2;

    cv::Mat3b out;
    cv::cvtColor(img, out, cv::COLOR_GRAY2BGR);
    cv::rectangle(out, r, cv::Scalar(0, 255, 0));
    cv::circle(out, center, radius, cv::Scalar(0, 0, 255));

    return 0;
}
like image 117
Miki Avatar answered Dec 25 '22 14:12

Miki


My method is to use morph-open, findcontours, and minEnclosingCircle as follow:

enter image description here

#!/usr/bin/python3
# 2018/11/29 20:03 
import cv2

fname = "test.png"
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

th, threshed = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY)

kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
morphed = cv2.morphologyEx(threshed, cv2.MORPH_OPEN, kernel, iterations = 3)

cnts = cv2.findContours(morphed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]

cnt = max(cnts, key=cv2.contourArea)
pt, r = cv2.minEnclosingCircle(cnt)

pt = (int(pt[0]), int(pt[1]))
r = int(r)

print("center: {}\nradius: {}".format(pt, r))

The final result:

enter image description here

center: (184, 170)
radius: 103
like image 28
Kinght 金 Avatar answered Dec 25 '22 14:12

Kinght 金