hazziqueeee
hazziqueeee

Reputation: 11

Finding circles of different exposure using HoughCircles and FindContours

I am new to image processing and edge detection so I hope you guys can help me understand a little bit on opencv python. Disclaimer: this is a work project.

This is a 3 part question regarding houghcircles, contours, and image processing. The full code will be at the end of this wall of text.

PART A, Consider this image AA. For this image, thresholding is easy and it is possible to isolate only the big outer circle, and the two inner circle. With this, it is easy to use Hough Circles to find the circles. However, I never had any luck tuning Hough Circle function that includes dp, minDist, param1, param2, minRad and maxRad to work for different image. I use Hough function with only 2 parameters and although it worked well, I feel like I'm not utilising the whole capability of the function. It is robust enough to work on other image BB, but struggles in image CC. The same parameter can get 2/3 circles correct. So my question is, what are the approach for tuning the Hough Circles function to get a set of parameters that work without fail? Do I have to brute force it and keep hand tuning until I find something that works? Is there a way where I can tell hough circles to only find circles in the left region and ignore everything else?

PART B, I understand that Hough Circles has some limitation if the min and max radius is not known (HoughCircles can't detect circles on this image) and to get a set of parameters to work on all would be tough. Which is why I am considering using Contours instead. As I understand it, contours works only for white edges image on black. So it is important to process the image to have only white edge. My problem here is that contours seem to have trouble separating the two inner circle as two separate circle, when the white image threshold seems to be touching. How should I approach this problem to optimise contours to detect two separate circles instead of grouping them as one?

PART C, The image that I have provided so far are the best case scenario where the inner circles are brighter than the large outer circle. Suppose now I have different image image DD and image EE. Now thresholding this image to isolate only the inner circles are much harder. None of my existing code for part A and part B works. It seems like I need to process the image a lot more. I tried methods of adaptivethreshold, dilate, erode, canny, opening, closing, gradient but still I could not get it to work as intended. Actually the closest I have come to got it to work is when I tried adaptive threshold of 179 block size and 5 c with erode and dilate of 6 iterations. With this, I could find the circles but not accurately. So I'm hoping to get suggestion on what type of image processing would I require or should I just take better images instead.

Now here is the code I used. Thanks for taking the time to read my problem and I hope you guys could help me with this.

import numpy as np
import cv2

# https://stackoverflow.com/questions/28327020/opencv-detect-mouse-position-clicking-over-a-picture
def onMouse(event, x, y, flags, param):
    if event == cv2.EVENT_LBUTTONDOWN:
       print('x = %d, y = %d'%(x, y))

def createCircle(img,out,dp,minDist):
    circles = cv2.HoughCircles(img, cv2.HOUGH_GRADIENT, dp, minDist)
    if circles is not None:
        circles = np.round(circles[0, :]).astype("int") #convert the (x, y) coordinates and radius of the circles to integers

    for (x, y, r) in circles:
        cv2.circle(out, (x, y), r, (0, 255, 0), 4) # draw the circle in the output image
        
    return x, y ,r 
        
def createCircle2(img,out,dp,minDist,param1,param2,minRad,maxRad):
    circles = cv2.HoughCircles(img, cv2.HOUGH_GRADIENT, dp, minDist,param1,param2,minRad,maxRad)
    if circles is not None:
        circles = np.round(circles[0, :]).astype("int") #convert the (x, y) coordinates and radius of the circles to integers

    for (x, y, r) in circles:
        cv2.circle(out, (x, y), r, (0, 255, 0), 4) # draw the circle in the output image
    
    return x, y, r

def showImage(name,img,sizescale):
    if sizescale == 1:
        cv2.imshow("%s"%name, img)
    else:
        cv2.imshow("%s"%name, cv2.resize(img,(0,0),fx=sizescale,fy=sizescale))

    cv2.setMouseCallback("%s"%name, onMouse) # enable coordinate function
    cv2.waitKey(0)  # wait infinitely
    cv2.destroyAllWindows() # destroy window once any key is pressed
    
    
    
    #inner brighter (PART A and B)
img = "AA.jpg" 
# img = "BB.jpg"
# img = "CC.jpg"

    #inner darker   (PART C)
# img = "DD.jpg"
# img = "EE.jpg"

scale = 0.3               # resize images
sigma = 0.33              # best according to link below regarding image_median
cv2.destroyAllWindows()
image = cv2.imread(img)
output1 = image.copy()
output2 = image.copy()
output3 = image.copy()  
kernel = np.ones((3,3), np.uint8) 

########################################################################################## HOUGH CIRCLES (PART A)

image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(image_gray, (5, 5),20)
image_median = np.median(blur)

#https://stackoverflow.com/questions/41893029/opencv-canny-edge-detection-not-working-properly
if image_median > 191:                                                  #light image
    canny_lower = int(max(0, (1.0 - 2*sigma) * (255-image_median)))
    canny_upper = int(min(255, (1.0 + 2*sigma) * (255-image_median)))
elif image_median > 127:
    canny_lower = int(max(0, (1.0 - sigma) * (255-image_median)))
    canny_upper = int(min(255, (1.0 + sigma) * (255-image_median)))
elif image_median < 63:                                                 # dark image
    canny_lower = int(max(0, (1.0 - 2*sigma) * image_median))
    canny_upper = int(min(255, (1.0 + 2*sigma) * image_median))
else:
    canny_lower = int(max(0, (1.0 - sigma) * image_median))
    canny_upper = int(min(255, (1.0 + sigma) * image_median))
# print(image_median,canny_lower,canny_upper)


ret, thresh1 = cv2.threshold(blur,15, 255, cv2.THRESH_BINARY)                  # for outer big circle
ret2, thresh2= cv2.threshold(blur, 0, 255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)   # for inner circles
canny = cv2.Canny(thresh2,canny_lower,canny_upper)                             # for inner circles

# showImage("thresh1", thresh1, scale)
# showImage("thresh2", thresh2, scale)
# showImage("canny", canny, scale)


createCircle(thresh1,output1,10,5000)
createCircle(thresh2,output1,10,10000) 
createCircle(canny,output1,18,50000)

# createCircle2(opening,output1,10,10000,350,10,300,900)
# createCircle2(thresh3,output1,20,10000,300,10,200,500)
# createCircle2(canny,output1,50,50000,300,10,200,500)

showImage("output", output1, scale)


################################################################################################# CONTOURS (PART B)

ret3, thresh3 = cv2.threshold(blur,127, 255, cv2.THRESH_BINARY)

contours, hierarchy = cv2.findContours(thresh3,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
print(len(contours))
cnt = contours


for i in range (len(cnt)):
    (x,y),radius = cv2.minEnclosingCircle(cnt[i])
    center = (int(x),int(y))
    radius = int(radius)
    if radius > 100 :
        cv2.circle(output2,center,radius,(0,255,0),4)
        print('Circle' + str(i) + ': Center =' + str(center) + 'Radius =' + str(radius))


# showImage("thresh3", thresh3, scale)
showImage("output2", output2, scale)


################################################################################################# CONTOURS (PART C)


bs = 179   #blocksize
c = 5      #c
thresh4 = cv2.adaptiveThreshold(blur,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY_INV,bs,c)

ero_n = 6
dil_n = 6
erode = cv2.erode(thresh4, kernel, iterations=ero_n)
dilate = cv2.dilate(erode, kernel, iterations=dil_n)

contours2, hierarchy2 = cv2.findContours(dilate,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
print(len(contours2))
cnt2 = contours2


for i in range (len(cnt2)):
    (x,y),radius = cv2.minEnclosingCircle(cnt2[i])
    center = (int(x),int(y))
    radius = int(radius)
    if radius > 150 :
        cv2.circle(output3,center,radius,(0,255,0),4)
        print('Circle' + str(i) + ': Center =' + str(center) + 'Radius =' + str(radius))
        
showImage("output3", output3, scale)

Upvotes: 1

Views: 1354

Answers (1)

hazziqueeee
hazziqueeee

Reputation: 11

Its a shame no one responded to my question. But I think I solved part B. To separate two circles that are touching and prevent them from being considered as one contour, I used watershed function. Here's the link to the answer (https://www.pyimagesearch.com/2015/11/02/watershed-opencv/). With contour + watershed, I was able to accurately get a circle of the two bright circle within. With this, I completely abandon the HoughCircle method and make sure that I always feed in good image.

Upvotes: 0

Related Questions