Reputation: 1431
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
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:
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);
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
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