3

I'm trying to detect a circular object in the middle of my images. Here is a sample image:

The left half is the greyscaled and Gaussian blurred input image; the right half is the same image after Otsu thresholding. The tiny silver of shadow on the lower left corner is leading the Otsu threshold astray. Is there any way to set a circular region of interest so the corner noises can be avoided?

rayryeng
  • 102,964
  • 22
  • 184
  • 193
My other car is a cadr
  • 1,429
  • 1
  • 13
  • 21
  • I don't want to discourage you, but to be able to locate a circular ROI with sufficient accuracy that you get rid of the dark corner, you need to binarize the object first to find its center and size. A chicken and egg problem. –  Oct 27 '14 at 19:58
  • I would prefer to not blur this much, it is dissolving edges. Again, in shadowing type of case, I prefer to apply adaptive thresholding or gradient to get only principle edges that avoids cases like you have in left-bottom. After that you can apply hough-circle or contour etc. – Pervez Alam Oct 28 '14 at 06:24

2 Answers2

5

Using the Hough Circle Transform directly on a good thresholded image kind of works for this specific case, even though the detected circle is a little bit offset:

cv::Mat thres;
cv::threshold(gray, thres, 110, 255, cv::THRESH_BINARY);

std::vector<cv::Vec3f> circles;
cv::HoughCircles(thres, circles, cv::HOUGH_GRADIENT, 1, thres.rows/2, 20, 15);
for (size_t i = 0; i < circles.size(); i++)
{
    cv::Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
    int radius = cvRound(circles[i][2]);
    cv::circle(input, center, 3, cv::Scalar(0, 255, 255), -1);
    cv::circle(input, center, radius, cv::Scalar(0, 0, 255), 1);
}

On more complex cases you might have to try other threshold methods, as well as fill the internal parts (holes) of the segments to reconstruct them back to an elliptical form.

The processing pipeline illustrated below performs the following operations to improve the detection of the coin:

  • Converts the input image to grayscale;
  • Applies a threshold;
  • Executes a morphology operation to join nearby segments;
  • Fills the holes inside a segment;
  • and finally, invokes cv::HoughCircles() to detect the circular shape.

It's possible to notice that the coin detection is a little bit more centralized with this approach. Anyway, here's the C++ sample code for that magic:

// Load input image
cv::Mat input = cv::imread("coin.jpg");
if (input.empty())
{
    std::cout << "!!! Failed to open image" << std::endl;
    return -1;
}

// Convert it to grayscale
cv::Mat gray;
cv::cvtColor(input, gray, cv::COLOR_BGR2GRAY);

// Threshold the grayscale image for segmentation purposes
cv::Mat thres;
cv::threshold(gray, thres, 110, 255, cv::THRESH_BINARY);
//cv::imwrite("threhsold.jpg", thres);

// Dirty trick to join nearby segments
cv::Mat element = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(15, 15));
cv::morphologyEx(thres, thres, cv::MORPH_OPEN, element);
//cv::imwrite("morph.jpg", thres);

// Fill the holes inside the segments
fillHoles(thres);
//cv::imwrite("filled.jpg", thres);

// Apply the Hough Circle Transform to detect circles
std::vector<cv::Vec3f> circles;
cv::HoughCircles(thres, circles, cv::HOUGH_GRADIENT, 1, thres.rows/2, 20, 15);
std::cout << "* Number of detected circles: " << circles.size() << std::endl;

for (size_t i = 0; i < circles.size(); i++)
{
    cv::Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
    int radius = cvRound(circles[i][2]);
    cv::circle(input, center, 3, cv::Scalar(0,255,255), -1);
    cv::circle(input, center, radius, cv::Scalar(0,0,255), 1);
}

cv::imshow("Output", input);
//cv::imwrite("output.jpg", input);

cv::waitKey(0);

Helper function:

void fillHoles(cv::Mat& img)
{
    if (img.channels() > 1)
    {
        std::cout << "fillHoles !!! Image must be single channel" << std::endl;
        return;
    }

    cv::Mat holes = img.clone();
    cv::floodFill(holes, cv::Point2i(0,0), cv::Scalar(1));

    for (int i = 0; i < (img.rows * img.cols); i++)
        if (holes.data[i] == 255)
            img.data[i] = 0;
}
Community
  • 1
  • 1
karlphillip
  • 92,053
  • 36
  • 243
  • 426
  • 1
    Thank you so much! But I'm still wondering, now that I know approximately where the coin lies, is there a way to set a circular region of interest before Otsu thresholding so that the dark corner can be ignored? I'm trying to find the most precise location of the coin as possible. – My other car is a cadr Oct 28 '14 at 18:40
  • 1
    You can't define a region of interest in that image before executing OTSU simply because OTSU (or any other threshold operation) is part of the process to locate that region. :) But you could try other things like histogram equalization, blur, or other filtering operations to try to get rid of that dark spot before the image is thresholded. – karlphillip Oct 28 '14 at 18:49
1

You could use Hough for finding circles:

/// Apply the Hough Transform to find the circles
HoughCircles( src_gray, circles, CV_HOUGH_GRADIENT, 1, src_gray.rows/8, 200, 100, 0, 0 );

After you find the biggest circle, you can set to 0 all the pixels outside

dynamic
  • 46,985
  • 55
  • 154
  • 231