简体   繁体   中英

How to get the only min area rectangle on a multiple contours image with cv2.minAreaRect(cnt)?

I want to use only one rectangle to cover the circle in this image:

我的形象

And get this result with cv2.minAreaRect(cnt) :

加工后

This image seems to be divided into multiple parts. Maybe it's because there are some breakpoint on the edge of this image. can you tell me how to use only one rectangle to cover this circle of my image? Thank you very much!

This is my code:

def draw_min_rect_circle(img, cnts):  # conts = contours
    img = np.copy(img)

    for cnt in cnts:
        x, y, w, h = cv2.boundingRect(cnt)
        cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2)  # blue

        min_rect = cv2.minAreaRect(cnt)  # min_area_rectangle
        min_rect = np.int0(cv2.boxPoints(min_rect))
        cv2.drawContours(img, [min_rect], 0, (0, 255, 0), 2)  # green

        (x, y), radius = cv2.minEnclosingCircle(cnt)
        center, radius = (int(x), int(y)), int(radius)  # center and radius of minimum enclosing circle
        img = cv2.circle(img, center, radius, (0, 0, 255), 2)  # red
return img

You probably searched for contours with the cv2.findContours() and iterated through them to draw the rectangle on the image. The problem is that your image doesn't have the circle made out of one connected line but many broken lines.

Contours are curves joining all the continuous points (along the boundary), having same color or intensity (OpenCV documentation).

So to get a better result you should first prepare your image before you search for contours. You can use various tools for preprocessing the image (you can search the OpenCV documentation). In this case I would try to perform the procedure called "closing" with a small kernel. Closing is dilation followed by erosion of pixels. It can help connect your small contours to a one big contour (circle). Then you can select the biggest one and draw a bounding rectangle.

Example:

Input image:

在此处输入图像描述

import cv2
import numpy as np

img = cv2.imread('test.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
kernel = np.ones((3,3), dtype=np.uint8)
closing = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
_, contours, hierarchy = cv2.findContours(closing, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
cnt = max(contours, key=cv2.contourArea)
x,y,w,h = cv2.boundingRect(cnt)
cv2.rectangle(img, (x,y), (x+w, y+h), (255,255,0), 1)
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

Result:

在此处输入图像描述

Image after performing the closing operation:

在此处输入图像描述

Hope it helps. Cheers!

It is possible to join all the contours into a single contour, as they are just numpy arrays of point coordinates describing the contour. You can use np.concatenate(contours) and it seems the cv2.minAreaRect function doesn't care that the points in the new array are not continuous. In my case this worked better than using the closing function, as I have more complex objects. If you want you can give it a try, it's simple. This is how your function should look like:

def draw_min_rect_circle(img, cnts):  # conts = contours
    img = np.copy(img)

    join_cnts = np.concatenate(cnts)

    x, y, w, h = cv2.boundingRect(join_cnts)
    cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2)  # blue

    min_rect = cv2.minAreaRect(join_cnts)  # min_area_rectangle
    min_rect = np.int0(cv2.boxPoints(min_rect))
    cv2.drawContours(img, [min_rect], 0, (0, 255, 0), 2)  # green

    (x, y), radius = cv2.minEnclosingCircle(join_cnts)
    center, radius = (int(x), int(y)), int(radius)  # center and radius of minimum enclosing circle
    img = cv2.circle(img, center, radius, (0, 0, 255), 2)  # red
    
return img

What you need to do is that you need to somehow obtain only 1 contour from the image by merging the contours, that is a bit difficult to do, so if you only want an enclosing rect around all contours, you can do something as such

def draw_min_rect_circle(img, cnts):  # conts = contours
    img = np.copy(img)
    x1,y1 = np.inf
    x2,y2 = 0
    for cnt in cnts:
        x, y, w, h = cv2.boundingRect(cnt)
        if x > x1:
           x1=x
        if y > y1:
           y1=y
        if x2 < x+w
           x2 = x+w
        if y2 < y+h
           y2 = y+h
     w = x2 - x1
     h = y2 - y1
     r = math.sqrt((w*w) + (h*h)) / 2

     cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2)
     cv2.circle(img, (x1+w/2,y1+h/2), r, (0, 0, 255), 2)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM