Creek
Creek

Reputation: 215

How can I use cv2.minAreaRect to obtain the largest contour, even if the image has broken regions?

This is the original image. enter image description here

I want to use cv2.minAreaRect to obtain the maximum contour, as shown in the following image. enter image description here

Attempt 1 - Fail

enter image description here

cnt, hierarchy  = cv2.findContours(im_bw, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
min_rect = cv2.minAreaRect(cnt[0])
box = np.int0(cv2.boxPoints(min_rect))
cv2.drawContours(temp_result, [box], 0, (255, 0, 0), 2)

Attempt 2 - Fail

I referred to this post to obtain the ordered coordinates for drawing. However, I obtained the following result, where the lines don't match and the four points cannot be used with cv2.minAreaRect. enter image description here

def order_points(pts):
    # initialzie a list of coordinates that will be ordered
    # such that the first entry in the list is the top-left,
    # the second entry is the top-right, the third is the
    # bottom-right, and the fourth is the bottom-left
    rect = np.zeros((4, 2), dtype = "float32")

    # the top-left point will have the smallest sum, whereas
    # the bottom-right point will have the largest sum
    s = np.sum(pts, axis = 1)
    rect[0] = pts[np.argmin(s)] # top-left
    rect[2] = pts[np.argmax(s)] # bottom-right

    # now, compute the difference between the points, the
    # top-right point will have the smallest difference,
    # whereas the bottom-left will have the largest difference
    diff = np.diff(pts, axis = 1)
    rect[1] = pts[np.argmin(diff)] # top-right
    rect[3] = pts[np.argmax(diff)] # bottom-left

    # return the ordered coordinates
    return rect
#########################################################################
# pts = [(93, 50), (109, 82), (76, 47), (93, 77), (58, 38), (76, 72), (36, 32), (54, 67), (20, 27), (35, 62), (3, 22), (18, 56), (111, 54), (128, 87)]

t = order_points(pts)
cv2.line(temp_result, pt1=(int(t[0][0]), int(t[0][1])), pt2=(int(t[1][0]), int(t[1][1])), color=(0, 0, 255), thickness=2)
cv2.line(temp_result, pt1=(int(t[3][0]), int(t[3][1])), pt2=(int(t[2][0]), int(t[2][1])), color=(0, 0, 255), thickness=2)

Any help will be appreciated.

Upvotes: 0

Views: 834

Answers (1)

Rotem
Rotem

Reputation: 32144

We may find the Convex hull of the merged contours:

merged_cnt = cv2.convexHull(np.vstack(cnt))

Code sample:

import cv2
import numpy as np

im_bw = cv2.imread('im_bw.png', cv2.IMREAD_GRAYSCALE)  # Read im_bw as grayscale

temp_result = cv2.cvtColor(im_bw, cv2.COLOR_GRAY2BGR)

cnt, hierarchy  = cv2.findContours(im_bw, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# Merge all the contours into one large contour - the result is the convex hull of all contour.
merged_cnt = cv2.convexHull(np.vstack(cnt))

cv2.drawContours(temp_result, [merged_cnt], 0, (255, 0, 0), 2)

# Show result for testing
cv2.imshow('temp_result', temp_result)
cv2.waitKey()
cv2.destroyAllWindows()

Result:
enter image description here


In case the objective is to approximate the contour to rectangular polygon, we may use simplify_contour method from the following answer

approx = simplify_contour(merged_cnt)

cv2.drawContours(temp_result, [approx], 0, (0, 0, 255), 2)

Result: enter image description here


In case the objective is to find minAreaRect as stated in the title:

min_rect = cv2.minAreaRect(merged_cnt)  # Fine minAreaRect of the merged contours
box = np.int0(cv2.boxPoints(min_rect))

We are getting the following output:
enter image description here

Upvotes: 5

Related Questions