1

Extract Circular ROI & Show Radius of the Circle in Tkinter Label

I am requesting help from python experts in this community. I have searched about my problem all over Stackexchange as well as the Github community. But I didn't find anything helpful. I have created a Tkinter GUI. In this GUI, I can upload my image from the destination folder. In Select of the evaluation section, I have written a script through which I can automatically view my ROI region in the circular part. The GUI is displayed at the bottom part of this question.

Help required Section: I am having trouble in creating a script through which:

  1. when I click on Upload ROI button, only the selected ROI portion of the image gets saved at the destination folder i.e path = 'Data/images/' + name + '_' + method + ext
  2. I can view the Radius of the circle somewhere on the the Tkinter GUI.

def ROI(self, image, method):
    if method == 'ROI':
        image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
        blimage = cv2.medianBlur(image, 15)
        circles = cv2.HoughCircles(blimage, cv2.HOUGH_GRADIENT, 1, 255, param1=100, param2=60, minRadius=0,
                                   maxRadius=0)

        if circles is not None:
            circles = np.uint16(np.around(circles))
            for i in circles[0, :]:
                cv2.circle(image, (i[0], i[1]), i[2], (0, 255, 0), 6)
                cv2.circle(image, (i[0], i[1]), 2, (0, 0, 255), 3)
        cv2.waitKey()

    else:
        print('method is wrong')

    return image

GUI enter image description here

shantmanu
  • 49
  • 2
  • 12
  • Read [Python and Tkinter lambda function](https://stackoverflow.com/a/11005426/7414759), [Why does Tkinter image not show up if created in a function?](https://stackoverflow.com/a/16424553/7414759) – stovfl Dec 25 '19 at 11:19
  • (2) create `Label` (somewherer in GUI) and set text with your radius. – furas Dec 25 '19 at 17:18
  • (1) I don't know how do it with CV2 but you can get image as numpy array and convert to `PIL.Image` and then you can convert `RGB` to `RGBA` (`A` = alpha channel) and in Alpha channel you can draw black background with white circle and save in `png`. All pixes with black color in alpha channel will be transparent so you will see only pixels inside circle. You can also crop image before or after adding alpha channel. – furas Dec 25 '19 at 17:22
  • (1) cv2 gives image as numpy array so you cna uses slicing to crop image - you can use circle corrdinates for this. And after croping you could convert to RGBA and probably you could draw `cv2.circle` on last layer. – furas Dec 25 '19 at 17:25

1 Answers1

2

UPDATE:

I added variable border to calculate x1,y1,x2,y2 so now it crops with borderline. Images show results for old code without border.


If you have only one circle (x,y,r) then you can use it to crop image

image = image[y-r:y+r, x-r:x+r]

I test it on some image with circle bigger then image and I had to use int16 instead of unit16 to get -1 instead of 65535 for 170-171 (y-r). Add I had to use min(), max()to get0instead-1`

def ROI(self, image, method):
    if method == 'ROI':
        image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
        blimage = cv2.medianBlur(image, 15)
        circles = cv2.HoughCircles(blimage, cv2.HOUGH_GRADIENT, 1, 255, param1=100, param2=60, minRadius=0,
                                   maxRadius=0)
        if circles is not None:
            #print(circles)

            # need `int` instead of `uint` to correctly calculate `y-r` (to get `-1` instead of `65535`)
            circles = np.int16(np.around(circles)) 

            for x,y,r in circles[0, :]:
                print('x, y, r:', x, y, r)

                border = 6

                cv2.circle(image, (x, y), r, (0, 255, 0), border)
                cv2.circle(image, (x, y), 2, (0, 0, 255), 3)

                height, width = image.shape
                print('height, width:', height, width)

                # calculate region to crop
                x1 = max(x-r - border//2, 0)      # eventually  -(border//2+1)
                x2 = min(x+r + border//2, width)  # eventually  +(border//2+1)
                y1 = max(y-r - border//2, 0)      # eventually  -(border//2+1)
                y2 = min(y+r + border//2, height) # eventually  +(border//2+1)
                print('x1, x2:', x1, x2)
                print('y1, y2:', y1, y2)

                # crop image 
                image = image[y1:y2,x1:x2]
                print('height, width:', image.shape)
    else:
        print('method is wrong')

    return image

For more circles you would have to first calculate region used for all circles (get drom all circles minimal values x-r,y-r and maximal values x+r,y+r) and next crop image.

enter image description here

Later I will try to use alpha channel to remove backgroud outside circle.


Image used for test (if someone else would like to test code)

enter image description here


EDIT: I added code which create black image with white circle to remove background.

def ROI(self, image, method):
    if method == 'ROI':
        image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
        blimage = cv2.medianBlur(image, 15)
        circles = cv2.HoughCircles(blimage, cv2.HOUGH_GRADIENT, 1, 255, param1=100, param2=60, minRadius=0,
                                   maxRadius=0)
        if circles is not None:
            print(circles)
            circles = np.int16(np.around(circles)) # need int instead of uint to correctly calculate y-r (to get -1 instead of 65535)
            for x,y,r in circles[0, :]:
                print('x, y, r:', x, y, r)
                height, width = image.shape
                print('height, width:', height, width)

                border = 6

                cv2.circle(image, (x, y), r, (0, 255, 0), border)
                cv2.circle(image, (x, y), 2, (0, 0, 255), 3)

                mask = np.zeros(image.shape, np.uint8) # black background
                cv2.circle(mask, (x, y), r, (255), border)  # white mask for black border
                cv2.circle(mask, (x, y), r, (255), -1) # white mask for (filled) circle
                #image = cv2.bitwise_and(image, mask)  # image with black background
                image = cv2.bitwise_or(image, ~mask)  # image with white background

                x1 = max(x-r - border//2, 0)      # eventually  -(border//2+1)
                x2 = min(x+r + border//2, width)  # eventually  +(border//2+1)
                y1 = max(y-r - border//2, 0)      # eventually  -(border//2+1)
                y2 = min(y+r + border//2, height) # eventually  +(border//2+1)
                print('x1, x2:', x1, x2)
                print('y1, y2:', y1, y2)

                image = image[y1:y2,x1:x2]
                print('height, width:', image.shape)
    else:
        print('method is wrong')

    return image

enter image description here

furas
  • 134,197
  • 12
  • 106
  • 148
  • Wao, that's brilliant. Thanks :). You explain so well that I am totally a fan of it. I especially wait for your answer. It's so to the point and well explained. You are a genius. – shantmanu Dec 26 '19 at 05:05
  • thanks for your answer. there is a little problem. i.e. the image is getting cropped from all the four side borderlines. How it can be resolved? – shantmanu Dec 26 '19 at 05:18
  • 1
    your borderline has 6 pixels (3 pixels goes inside circle, 3 pixels goes outside circle) so add/substract 3 pixels from `x1,y1,x2,y2` before you use `min()`, `max()`. I will add it to code. – furas Dec 26 '19 at 08:24
  • 1
    Now code uses `border//2` - ie. `x1 = max(x-r - border//2, 0)`, `x2 = min(x+r + border//2, width)`. Eventually you may need little more ie. `borer//2+1` or `borer//2+2`- ie. `x1 = max(x-r - (border//2+1), 0)`, `x2 = min(x+r + (border//2+1), width)`. – furas Dec 26 '19 at 08:39
  • Thanks, @furas. that was really great help. – shantmanu Dec 26 '19 at 17:50