My other car is a cadr
My other car is a cadr

Reputation: 1431

Circular Region of Interest in Opencv before thresholding

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?

Upvotes: 3

Views: 3746

Answers (2)

karlphillip
karlphillip

Reputation: 93410

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;
}

Upvotes: 5

dynamic
dynamic

Reputation: 48091

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

Upvotes: 1

Related Questions