Search code examples
pythonopencvhough-transform

grayscale image causing inaccuracies in HoughCircles() method


This is a follow-up to openCV polygon detection. For the second image, I wasn't getting any rectangles detected, but that was because my threshold values weren't correct for that image.

I used otsu thresholding, and added a constraint to remove small and other irrelevant rectangles that got detected.

import numpy as np
import cv2 as cv
import math

img = cv.imread("t1.jpeg")



n=0

#rectangle parameters
width=0 
height=0

start_x=0 
start_y=0
end_x=0 
end_y=0

#houghcircles parameters     
minr=0 
maxr=0
mind=0

maxarea=0
area=0

output = img.copy()
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret2,th = cv.threshold(gray,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)



#rectangle detection

contours, _ = cv.findContours(th, cv.RETR_TREE, cv.CHAIN_APPROX_NONE)

for contour in contours:

    approx = cv.approxPolyDP(contour, 0.01* cv.arcLength(contour, True), True)
    
    cv.drawContours(img, [approx], 0, (0, 0, 0), 5)
    
    x = approx.ravel()[0]
    y = approx.ravel()[1]

    x1 ,y1, w, h = cv.boundingRect(approx)
    a=w*h    
    if len(approx) == 4 and x>15  :
            
        aspectRatio = float(w)/h
        if  aspectRatio >= 2.5 and a>50:          
          print(x1,y1,w,h)
          width=w
          height=h   
          start_x=x1
          start_y=y1
          end_x=start_x+width
          end_y=start_y+height      
          cv.rectangle(output, (start_x,start_y), (end_x,end_y), (0,0,255),3)
          cv.putText(output, "rectangle "+str(x1)+" , " +str(y1-5), (x1, y1-5), cv.FONT_HERSHEY_COMPLEX, 0.5, (0, 0, 0))
        

minr=int(17*width/192)
maxr=int(7*width/64)
mind=int(width//5)


print("start",start_x,start_y)
print("width",width)
print("height",height)
print("minr", minr)
print("maxr",maxr)
print("mind",mind)

cv.imshow("op1",output)

#circle detection,converting binary to decimal.

circles = cv.HoughCircles(gray, cv.HOUGH_GRADIENT, 1, mind,param1=50, param2=20, minRadius=minr, maxRadius=maxr)
detected_circles = np.uint16(np.around(circles))

for (x, y ,r) in detected_circles[0, :]:
    if(y>start_y and x>start_x and y<start_y+height and x<start_x+width):
        
        cf= ((x-start_x)*8)/width
        fp= cf-math.floor(cf)
        
        
        if(fp>0.50):
            idx=math.ceil(cf)
        else:
            idx=math.floor(cf)

        
        
        exp=int(4- (0.5* (idx+1)))
       
        n+= 2**exp
        print("circle",x,y,r)
        cv.circle(output, (x, y), r, (0, 0, 0), 3)
        cv.circle(output, (x, y), 2, (0, 255, 255), 3)
        
print(n)
cv.imshow("th",th)
cv.imshow("gray",gray)
cv.imshow('output',output)

cv.waitKey(0)
cv.destroyAllWindows()

The final result was this:

enter image description here

Now the rectangle was detected successfully, however the circle wasn't detected properly. The detected circles are roughly of the same size as the target circle, which means the parameters for HoughCircles() are correct, although the circle wasn't detected at the correct location.

This is probably because the glare results in the circle almost disappearing in the grayscale image (which I used in the HoughCircles() method):

enter image description here

What can I do to use the HoughCircles() method for this image?

edit: Fixed a minor error in the code. The problem still persists, however i tested with other images, and it worked with the one where the grayscale image was decent enough: enter image description here


Solution

  • Here is one way to extract the circle in Python/OpenCV. Convert to LAB, separate the B channel, then threshold on the dark circle.

    Input:

    enter image description here

    import cv2
    import numpy as np
    
    # load images
    img = cv2.imread('4_sign.jpg')
    
    # convert to LAB
    lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
    
    # separate B channel
    b = lab[:,:,2]
    
    # threshold and invert
    thresh = cv2.threshold(b, 105, 255, cv2.THRESH_BINARY)[1]
    thresh = 255 - thresh
    
    # apply morphology to clean it up
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7,7))
    morph = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (11,11))
    morph = cv2.morphologyEx(morph, cv2.MORPH_CLOSE, kernel)
    
    # get min enclosing circle
    # numpy points are (y,x), so need to transpose
    points = np.argwhere(morph.transpose()>0)
    
    center, radius = cv2.minEnclosingCircle(points)
    print('center:', center, 'radius:', radius)
    
    # draw circle on copy of input
    result = img.copy()
    cx = int(round(center[0]))
    cy = int(round(center[1]))
    rr = int(round(radius))
    cv2.circle(result, (cx,cy), rr, (255,255,255), 2)
    
    # save output
    cv2.imwrite('4_sign_circle.jpg', result)
    
    # display results
    cv2.imshow('thresh',thresh)
    cv2.imshow('morph',morph)
    cv2.imshow('result',result)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    

    Result:

    enter image description here

    ADDITION

    Here is a version using HoughCircles.

    import cv2
    import numpy as np
    
    # load images
    img = cv2.imread('4_sign.jpg')
    ht, wd = img.shape[:2]
    minhw = min(ht,wd)
    
    # convert to LAB
    lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
    
    # separate B channel
    b = lab[:,:,2]
    
    # threshold and invert
    thresh = cv2.threshold(b, 105, 255, cv2.THRESH_BINARY)[1]
    thresh = 255 - thresh
    
    # apply morphology to clean it up
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7,7))
    morph = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (11,11))
    morph = cv2.morphologyEx(morph, cv2.MORPH_CLOSE, kernel)
    
    # get circle from HoughCircles
    min_dist = int(minhw/20)
    circles = cv2.HoughCircles(morph, cv2.HOUGH_GRADIENT, 1, minDist=min_dist, param1=100, param2=10, minRadius=30, maxRadius=60)
    #print(circles)
    
    result = img.copy()
    for circle in circles[0]:
        # draw the circle on copy of input
        (x,y,r) = circle
        center = (x,y)
        radius = r
        print('center:', center, 'radius:', radius)
        x = int(x)
        y = int(y)
        cv2.circle(result, center, radius, (255, 255, 255), 2)
    
    # save output
    cv2.imwrite('4_sign_circle2.jpg', result)
    
    # display results
    cv2.imshow('thresh',thresh)
    cv2.imshow('morph',morph)
    cv2.imshow('result',result)
    cv2.waitKey(0)
    

    Result:

    enter image description here

    Textual Information:

    center: (410.5, 686.5) radius: 54.4