How to crop the internal area of a contour?

后端 未结 4 1850
忘了有多久
忘了有多久 2020-11-28 08:33

I am working on Retinal fundus images.The image consists of a circular retina on a black background. With OpenCV, I have managed to get a contour which surrounds the whole c

相关标签:
4条回答
  • 2020-11-28 09:15

    It is unclear in your question whether you want to actually crop out the information that is defined within the contour or mask out the information that isn't relevant to the contour chosen. I'll explore what to do in both situations.


    Masking out the information

    Assuming you ran cv2.findContours on your image, you will have received a structure that lists all of the contours available in your image. I'm also assuming that you know the index of the contour that was used to surround the object you want. Assuming this is stored in idx, first use cv2.drawContours to draw a filled version of this contour onto a blank image, then use this image to index into your image to extract out the object. This logic masks out any irrelevant information and only retain what is important - which is defined within the contour you have selected. The code to do this would look something like the following, assuming your image is a grayscale image stored in img:

    import numpy as np
    import cv2
    img = cv2.imread('...', 0) # Read in your image
    # contours, _ = cv2.findContours(...) # Your call to find the contours using OpenCV 2.4.x
    _, contours, _ = cv2.findContours(...) # Your call to find the contours
    idx = ... # The index of the contour that surrounds your object
    mask = np.zeros_like(img) # Create mask where white is what we want, black otherwise
    cv2.drawContours(mask, contours, idx, 255, -1) # Draw filled contour in mask
    out = np.zeros_like(img) # Extract out the object and place into output image
    out[mask == 255] = img[mask == 255]
    
    # Show the output image
    cv2.imshow('Output', out)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    

    If you actually want to crop...

    If you want to crop the image, you need to define the minimum spanning bounding box of the area defined by the contour. You can find the top left and lower right corner of the bounding box, then use indexing to crop out what you need. The code will be the same as before, but there will be an additional cropping step:

    import numpy as np
    import cv2
    img = cv2.imread('...', 0) # Read in your image
    # contours, _ = cv2.findContours(...) # Your call to find the contours using OpenCV 2.4.x
    _, contours, _ = cv2.findContours(...) # Your call to find the contours
    idx = ... # The index of the contour that surrounds your object
    mask = np.zeros_like(img) # Create mask where white is what we want, black otherwise
    cv2.drawContours(mask, contours, idx, 255, -1) # Draw filled contour in mask
    out = np.zeros_like(img) # Extract out the object and place into output image
    out[mask == 255] = img[mask == 255]
    
    # Now crop
    (y, x) = np.where(mask == 255)
    (topy, topx) = (np.min(y), np.min(x))
    (bottomy, bottomx) = (np.max(y), np.max(x))
    out = out[topy:bottomy+1, topx:bottomx+1]
    
    # Show the output image
    cv2.imshow('Output', out)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    

    The cropping code works such that when we define the mask to extract out the area defined by the contour, we additionally find the smallest horizontal and vertical coordinates which define the top left corner of the contour. We similarly find the largest horizontal and vertical coordinates that define the bottom left corner of the contour. We then use indexing with these coordinates to crop what we actually need. Note that this performs cropping on the masked image - that is the image that removes everything but the information contained within the largest contour.

    Note with OpenCV 3.x

    It should be noted that the above code assumes you are using OpenCV 2.4.x. Take note that in OpenCV 3.x, the definition of cv2.findContours has changed. Specifically, the output is a three element tuple output where the first image is the source image, while the other two parameters are the same as in OpenCV 2.4.x. Therefore, simply change the cv2.findContours statement in the above code to ignore the first output:

    _, contours, _ = cv2.findContours(...) # Your call to find contours
    
    0 讨论(0)
  • 2020-11-28 09:22

    Expansion of nathancy's answer -

    You can use any of Contour feature like Bounding Rectangle, Minimum enclosing circle, Fit Ellipse etc. More details here.

    0 讨论(0)
  • 2020-11-28 09:28

    Here's another approach to crop out a rectangular ROI. The main idea is to find the edges of the retina using Canny edge detection, find contours, and then extract the ROI using Numpy slicing. Assuming you have an input image like this:

    Extracted ROI

    import cv2
    
    # Load image, convert to grayscale, and find edges
    image = cv2.imread('1.jpg')
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY)[1]
    
    # Find contour and sort by contour area
    cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cnts = cnts[0] if len(cnts) == 2 else cnts[1]
    cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
    
    # Find bounding box and extract ROI
    for c in cnts:
        x,y,w,h = cv2.boundingRect(c)
        ROI = image[y:y+h, x:x+w]
        break
    
    cv2.imshow('ROI',ROI)
    cv2.imwrite('ROI.png',ROI)
    cv2.waitKey()
    
    0 讨论(0)
  • 2020-11-28 09:34

    This is a pretty simple way. Mask the image with transparency.

    Read the image
    
    Make a grayscale version.
    
    Otsu Threshold
    
    Apply morphology open and close to thresholded image as a mask
    
    Put the mask into the alpha channel of the input
    
    Save the output
    


    Input:

    import cv2
    import numpy as np
    
    
    # load image as grayscale
    img = cv2.imread('retina.jpeg')
    
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
    # threshold input image using otsu thresholding as mask and refine with morphology
    ret, mask = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU) 
    kernel = np.ones((9,9), np.uint8)
    mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
    mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
    
    # put thresh into 
    result = img.copy()
    result = cv2.cvtColor(result, cv2.COLOR_BGR2BGRA)
    result[:, :, 3] = mask
    
    # save resulting masked image
    cv2.imwrite('retina_masked.png', result)
    


    0 讨论(0)
提交回复
热议问题