0

I am creating program that helps processing microstructure images. One of the function is detecting circles with the same radius. User draws one circle, my program spots others. I've already implemented distance transform method

I am trying to create method that uses HoughCircles. However, I am confused with its parameters.

My code:

def find_circles_with_radius_haugh(path, radius):
    img = cv2.imread(path)
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    circles = cv2.HoughCircles(img_gray, cv2.HOUGH_GRADIENT, int(radius),
                               1.5,
                               param1=80, param2=40,
                               minRadius=int(radius * 0.9),
                               maxRadius=int(radius * 1.1))

    res = list()
    if circles is not None:
        for i in circles[0, :]:
            res.append((i[0], i[1], i[2]))

    return res

Original picture: enter image description here

My result of detecting circles with radius 57 pixels (+- 10%): enter image description here

Please help me with better processing images like that.

I might try findContours method, but I don't know any filters that will make borders on this picture clearer.

2 Answers 2

3

I tried a little.
My idea is simply using filter2D instead of Hough-Transform.
Because detection target is the circles has specific radius, if edge of circles detected clearly, the center of the circles will be able to found by convoluting circular mask to the edge image.

I checked the filter2D(=convolution) result with following code (C++).

int main()
{
    //This source image "MicroSpheres.png" was copied from this question
    cv::Mat Src = cv::imread( "MicroSpheres.png", cv::IMREAD_GRAYSCALE );
    if( Src.empty() )return 0;
    //Test with 50% Scale
    cv::resize( Src, Src, cv::Size(0,0), 0.5, 0.5, cv::INTER_AREA );
    cv::imshow( "Src", Src );
    const int Radius = cvRound(57 * 0.5);   //So, Radius is also 50% scale

    //Trying to detect edge of circles
    cv::Mat EdgeImg;
    {
        cv::Mat Test;
        cv::medianBlur( Src, Test, 5 );
        cv::morphologyEx( Test, Test, cv::MORPH_GRADIENT, cv::Mat() );
        cv::imshow( "Test", Test );

        cv::adaptiveThreshold( Test, EdgeImg, 255, cv::ADAPTIVE_THRESH_GAUSSIAN_C, cv::THRESH_BINARY, (Test.rows/6)|0x01, -6 );
        cv::imshow( "EdgeImg", EdgeImg );
    }

    cv::Mat BufferFor_imwrite = EdgeImg.clone();

    //filter2D
    cv::Mat FilterResult;
    {
        const int FilterRadius = Radius + 2;
        const int FilterSize = FilterRadius*2 + 1;
        cv::Mat Filter = cv::Mat::zeros( FilterSize,FilterSize, CV_32F );
        cv::circle( Filter, cv::Point(FilterRadius,FilterRadius), Radius/2, cv::Scalar(-1), -1 );
        cv::circle( Filter, cv::Point(FilterRadius,FilterRadius), Radius, cv::Scalar(1), 3 );
    
        cv::filter2D( EdgeImg, FilterResult, CV_32F, Filter );
    }

    {//Very lazy check of the filter2D result.
        double Min, Max;
        cv::minMaxLoc( FilterResult, &Min, &Max );
        double scale = 255 / (Max-Min);
        cv::Mat Show;
        FilterResult.convertTo( Show, CV_8U, scale, -Min*scale );
        cv::imshow( "Filter2D_Result", Show );

        cv::vconcat( BufferFor_imwrite, Show, BufferFor_imwrite );

        //(Estimating center of circles based onthe  filter2D result.)
        //  Here, just only simple thresholding is implemented.
        //  At least non-maximum suppression must be done, I think.
        cv::Mat Centers;
        cv::threshold( FilterResult, Centers, (Max+Min)*0.6, 255, cv::THRESH_BINARY );
        Centers.convertTo( Centers, CV_8U );
        Show = Src * 0.5;
        Show.setTo( cv::Scalar(255), Centers );
        cv::imshow( "Centers", Show );

        cv::vconcat( BufferFor_imwrite, Show, BufferFor_imwrite );
    }
    if( cv::waitKey() == 's' ){ cv::imwrite( "Result.png", BufferFor_imwrite ); }
    return 0;
}

The following image is result. 3 images are concatenated vertically.

  • edge detection result
  • filter2D result
  • Circle center estimation result (very lazy. just binarized the filter2D result and overlapped it onto source image.)

I can't say this is perfect, but it looks like that the result roughly indicates some centers.

enter image description here

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you! Actrually, your method seems to be more accurate than DistanceTransform or HoughCircles.
1

Rewrote @fana code in Python

import cv2
import numpy as np

img = cv2.imread('spheres1.bmp')

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = cv2.resize(gray, (0, 0), gray, 0.5, 0.5, cv2.INTER_AREA)
cv2.imwrite("resized.png", gray)

radius = round(57 * 0.5)

test = cv2.medianBlur(gray, 5)
struct_elem = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
# might be better to use "I" matrix
# struct_elem = np.ones((3,3), np.uint8) 

test = cv2.morphologyEx(test, cv2.MORPH_GRADIENT, kernel=struct_elem)
cv2.imwrite("MorphologyEx.png", test)

edge_img = cv2.adaptiveThreshold(test, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, int(len(test) / 6) | 0x01, -6)
cv2.imwrite("EdgeImg.png", edge_img );

buffer_for_imwrite = edge_img.copy()

filter_radius = radius + 2
filter_size = filter_radius * 2 + 1
img_filter = np.zeros((filter_size, filter_size))

cv2.circle(img_filter, (filter_radius, filter_radius), int(radius / 2), -1, -1)
cv2.circle(img_filter, (filter_radius, filter_radius), radius, 1, 3)
# second circle better to generate with smaller width like this:
# cv2.circle(img_filter, (filter_radius, filter_radius), radius, 1, 2)
cv2.imwrite("Filter.png", img_filter)

filter_result = cv2.filter2D(edge_img, cv2.CV_32F, img_filter)
cv2.imwrite("FilterResult.png", filter_result)

min_val, max_val, _, _ = cv2.minMaxLoc(filter_result)
scale = 255 / (max_val - min_val)
show = np.uint8(filter_result * scale - min_val * scale)

cv2.imwrite("Filter2D_Result.png", show)

_, centers = cv2.threshold(filter_result, (max_val + min_val) * 0.6, 255, cv2.THRESH_BINARY)
centers = np.uint8(centers)

show = gray * 0.5
show[np.where(centers == 255)] = 255
cv2.imwrite("Centers.png", show)

3 Comments

In my code, purpose of FilterResult.convertTo( Show, CV_8U, scale, -Min*scale ); is just visualize the value of FilterResult. This don't change the value of FilterResult.
In your code, it seems that value of min_val (and max_val too) used for threshold will be of the data before scaling.
Yep, that was an error. Made edits

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.