Search code examples
pythonopencvnumpycontourarea

Redraw contours to original image after erosion


I have a function that erodes certain contours more or less depending on their area size. However, once they are cropped they lose the proper coordinate data that corresponds to the original image.

How can I redraw my eroded_contours to the original image while maintaining their original position? Or is there a better approach to using custom erosion based on contour area size?

edged = cv2.Canny(original_image.copy(), 50, 200)
contours, hierarchy = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

def show_each_contour(original_image):
    for i,c in enumerate(contours):
        area = cv2.contourArea(c)
        if area > 0:
            rect = cv2.boundingRect(c)
            x,y,w,h = rect
            start_row, start_col = int(x), int(y)
            end_row, end_col = int(x+x+w), int(y+y+h)

            cv2.rectangle(original_image, (x,y), (x+w,y+h), (0,0,255), 2)
            cropped = original_image[y:y+h, x:x+w]

            if area < 2000:
                kernel = np.ones((5,5), np.uint8)
                numberOfIterations = area / 200
            else:
                kernel = np.ones((5,5), np.uint8)
                numberOfIterations = 7

            eroded_contours = cv2.erode(cropped.copy(), kernel, iterations = int(numberOfIterations))

        #This won't work correctly because the coordinates are different now
        #cv2.drawContours(original_image, eroded_contours , -1, (0,255,255), 3)

Solution

  • If I understood correctly what you are asking, you were pretty close to your goal.

    Here is what I came up with (using Python 3.6 & OpenCV 3.2) :

    edged = cv2.Canny(input_img.copy(), 50, 200)
    _, contours, hierarchy = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # note that this function returns 3 values
    
    def show_each_contour(original_image):
        margin = 2 # you can set the margin to 0 to better understand its effect
        for i,c in enumerate(contours):
            area = cv2.contourArea(c)
            if area > 0:
                rect = cv2.boundingRect(c)
                x,y,w,h = rect
    
                cv2.rectangle(original_image, (x-margin,y-margin), (x+w+margin,y+h+margin), (0,0,255), 2)
                cropped = original_image[y-margin:y+h+margin, x-margin:x+w+margin]
    
                if area < 2000:
                    kernel = np.ones((5,5), np.uint8)
                    numberOfIterations = area / 200
                else:
                    kernel = np.ones((5,5), np.uint8)
                    numberOfIterations = 7
    
                eroded_shape = cv2.erode(cropped.copy(), kernel, iterations = int(numberOfIterations))
                original_image[y-margin:y+h+margin, x-margin:x+w+margin] = eroded_shape # we copy the eroded_shape back into the original_image
    

    I didn't change much of your code besides the last line where I copy the eroded shape into the right location of the original image.

    Result

    input and output images Input image on the left side, and the output on the right side.

    Hope this helps and please tell me if this is not what you were looking for.