3
votes

I have an image to process.I need detect all the circles in the image.Here is it. org image

And here is my code.

import cv2
import cv2.cv as cv
img = cv2.imread(imgpath)
cv2.imshow("imgorg",img)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
cv2.imshow("gray",gray)
ret,thresh = cv2.threshold(gray, 199, 255, cv.CV_THRESH_BINARY_INV)
cv2.imshow("thresh",thresh)
cv2.waitKey(0)
cv2.destrotAllWindows()

Then,I got a image like this. enter image description here

And I tried to use erode and dilate to divided them into single.But it doesnt work.My question is how to divide these contacted circles into single,so i can detect them.

According to @Micka's idea,I tried to process the image in following way,and here is my code.

import cv2
import cv2.cv as cv
import numpy as np

def findcircles(img,contours):
    minArea = 300;
    minCircleRatio = 0.5;
    for  contour  in contours:

        area = cv2.contourArea(contour)
        if area < minArea: 
            continue

        (x,y),radius = cv2.minEnclosingCircle(contour)
        center = (int(x),int(y))
        radius = int(radius)
        circleArea = radius*radius*cv.CV_PI;

        if area/circleArea < minCircleRatio:
             continue;
        cv2.circle(img, center, radius, (0, 255, 0), 2)
        cv2.imshow("imggg",img)

img = cv2.imread("a.png")
cv2.imshow("org",img)

gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,threshold = cv2.threshold(gray, 199, 255,cv. CV_THRESH_BINARY_INV)
cv2.imshow("threshold",threshold)

blur = cv2.medianBlur(gray,5)
cv2.imshow("blur",blur)

laplacian=cv2.Laplacian(blur,-1,ksize = 5,delta = -50)
cv2.imshow("laplacian",laplacian)

kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(7,7))
dilation = cv2.dilate(laplacian,kernel,iterations = 1)
cv2.imshow("dilation", dilation)

result= cv2.subtract(threshold,dilation) 
cv2.imshow("result",result)

contours, hierarchy = cv2.findContours(result,cv2.RETR_LIST,cv2.CHAIN_APPROX_NONE)
findcircles(gray,contours)

But I dont get the same effect as @Micka's.I dont know which step is wrong.

2
can you save and upload the thresholded image as a png file without compression artifacts? - Micka
Check this. - dhanushka
@Micka already uploaded - Elivis
@elivis did your previous approach (hough circles) not work well after optimizations? Your earlier approach ( stackoverflow.com/questions/33409851/… ) was looking quite ok I think... - Micka
@Micka I didn't satisfied with my result,so i want to improve it.Actually i tried many ways to deal with this,but it alway dont work.Thank you for your help :-). - Elivis

2 Answers

10
votes

Adapting the idea of @jochen I came to this:

  1. extract the full circle mask as you've done (I called it fullForeground )

enter image description here

  1. from your colored image, compute grayscale, blur (median blur size 7) it and and extract edges, for example with cv::Laplacian This laplacian thresholded > 50 gives:

cv::Laplacian(blurred, lap, 0, 5); // no delta lapMask = lap > 50; // thresholding to values > 50

enter image description here

This one dilated once gives:

cv::dilate(lapMask, dilatedThresholdedLaplacian, cv::Mat()); // dilate the edge mask once

enter image description here

Now subtraction fullForeground - dilatedThresholdedLaplacian (same as and_not operator for this type of masks) gives:

enter image description here

from this you can compute contours. For each contour you can compute the area and compare it to the area of an enclosing circle, giving this code and result:

std::vector<std::vector<cv::Point> > contours;
cv::findContours(separated.clone(), contours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);

double minArea = 500;
double minCircleRatio = 0.5;
for(unsigned int i=0; i<contours.size(); ++i)
{
    double cArea = cv::contourArea(contours[i]);
    if(cArea < minArea) continue;

    //filteredContours.push_back(contours[i]);
    //cv::drawContours(input, contours, i, cv::Scalar(0,255,0), 1);
    cv::Point2f center;
    float radius;
    cv::minEnclosingCircle(contours[i], center, radius);

    double circleArea = radius*radius*CV_PI;

    if(cArea/circleArea < minCircleRatio) continue;

    cv::circle(input, center, radius, cv::Scalar(0,0,255),2);
}

enter image description here

here is another image showing the coverage:

enter image description here

hope this helps

3
votes

I think the first mistake ist the value of thesh. In your example the command cv2.threshold converts all white areas to black and everything else to white. I would suggest using a smaller value for thesh so that all black pixel get converted to white and all white or "colored" pixels (inside the circles) get converted to black or vise versa. The value of thesh should be a little bigger than the brightest of the black pixels.
See opencv docu for threshold for more information.
Afterwards I would let opencv find all contours in the thresholded image and filter them for "valid" circles, e.g. by size and shape.
If that is not sufficiant you could segment the inner circle from the rest of the image: First compute threasholdImageA with all white areas colored black. Then compute threasholdImageB with all the black areas being black. Afterwards combine both, threasholdImageA and threasholdImageB, (e.g. with numpy.logical_and) to have a binary image with only the inner circle being white and the rest black. Of course the values for the threshold have to be chosen wisely to get the specific result. That way also circles where the inner part directly touches the background will be segmented.