3

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.

Elivis
  • 307
  • 1
  • 6
  • 18
  • can you save and upload the thresholded image as a png file without compression artifacts? – Micka Jan 07 '16 at 09:01
  • Check [this](http://stackoverflow.com/a/26971178/2571705). – dhanushka Jan 07 '16 at 09:01
  • 1
    @Micka already uploaded – Elivis Jan 07 '16 at 09:09
  • @elivis did your previous approach (hough circles) not work well after optimizations? Your earlier approach ( http://stackoverflow.com/questions/33409851/imageprocessing-find-the-center-of-the-circle-in-a-jpeg-image-by-using-opencv ) was looking quite ok I think... – Micka Jan 07 '16 at 09:47
  • @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 Jan 08 '16 at 01:15
  • please try `laplacian=cv2.Laplacian(blur,-1,ksize = 5)` followed by something like `laplacian = laplacian > 50` (not sure whether in-place and/or > operator are allowed/available in python openCV) – Micka Jan 08 '16 at 09:06
  • Do you means if the grayscale of a pixel is smaller than 50,then delete this pixel? – Elivis Jan 08 '16 at 09:31
  • not the grayscale but the value of the laplacian in that pixel, yes! So if the laplacian pixel has a value of smaller than (or equal) 50, set it to 0 and if it has a value of bigger than 50 set it to 255. So the `delta = -50` might do the same for an unsigned 8 bit image result (at least setting the zero values), but you should display the result and maybe upload it to compare to my result – Micka Jan 08 '16 at 12:43
  • however, if you just use the `delta = -50` then there are laplacian values that are between 0 and 205 (or maybe up to 255 depending on how the delta is working before or after saturation). Now if you subtract `fullForeground - dilatedThresholdedLaplacian` this won't work because values should be 255 or 0 (instead you maybe subtract 255 - 100 etc). But if you use a pixel-wise `AND NOT` operation, it should work! – Micka Jan 08 '16 at 12:56

2 Answers2

11

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

Micka
  • 19,585
  • 4
  • 56
  • 74
  • I already tried this method, it is useful for me.But I have a question.In the second step,I use "laplacian=cv2.Laplacian(blur,-1,ksize = 5,delta = -50)" to extract the edges,but the effect is not good enough.Can you show ne some detail about extract edges.Thanks! – Elivis Jan 08 '16 at 07:17
  • I updated the answer. I didn't use a delta! Instead of laplacian you could try Sobel magnitude or canny edge detection too! Laplacian was just the first thing I tried :D – Micka Jan 08 '16 at 08:54
3

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.

jochen
  • 413
  • 2
  • 10
  • @jochen you mean something like edge detection (for that special image where circles look like having a black border)? Probably thresholding isn't perfect there. – Micka Jan 07 '16 at 09:41
  • I think thresholding is very usefull here because the foreground and background are distinguishable by their color, not only the gradient between them. I will extend my answer with the possibility to segment the inner circle from the rest. – jochen Jan 07 '16 at 10:03
  • ah ok, you want to threshold both: foreground/background (like in the original question) and additionally the black borders of the circles (to exclude them from the connected circles)? That might be a good idea! – Micka Jan 07 '16 at 10:07