pythonopencvcontourroi

Efficient way to get ROI from original image using contours found in mask


the task that I'm trying to accomplish is isolating certain objects in an image through finding contours in the mask of the image, then taking each contour (based on area) and isolating it , and then using this contour to crop the same region in the original image, in order to get the pixel values of the region, e.g.:

enter image description here enter image description here enter image description here enter image description here enter image description here

the code I wrote in order to get just one contour and then isolating it with the original pixel value:

import cv2
import matplotlib.pyplot as plt
import numpy as np

image = cv2.imread("./xxxx/xx.png")

mask = cv2.imread("./xxxx/xxx.png")

# making them the same size (function I wrote)
image, mask = resize_two_images(image,mask)

#grayscalling the mask (using cv2.cvtCOLOR)
mask = to_gray(mask)

# a function I wrote to display images using plt
display(image,"image: original image")
display(mask,"mask: mask of the image")


th, mask = cv2.threshold(mask, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
contours, hierarchy = cv2.findContours(
    mask, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)

for i in range(len(contours)):
    hier = hierarchy[0][i][3]
    cnt = contours[i]
    cntArea = cv2.contourArea(cnt) 
    if 1000 < cntArea < 2000:
        break
        #breaking because I'm just keeping the first contour that fills the condtion
        
# Creating two zero numpy array    
img1 = np.zeros(image.shape, dtype=np.uint8)
img2 = img1.copy()

# drawing the contour which will basiclly give the edges of the object
cv2.drawContours(img1, [cnt], -1, (255,255,255), 2)

# drawing indise the the edges 
cv2.fillPoly(img2, [cnt], (255,255,255))

# adding the filled poly with the drawn contour gives bigger object that
# contains the both the edges and the insides of the object
img1 = img1 + img2
display(img1,"img1: final")

res = np.bitwise_and(img1,image)
display(res,"res : the ROI with original pixel values")

#cropping the ROI (the object we want)
x,y,w,h = cv2.boundingRect(cnt)
# (de)increased values in order to get nonzero borders or lost pixels
res1 = res[y-1:y+h+1,x-1:x+w+1]
display(res1,"res1: cropped ROI")

The problem is that yes I found a way to do it for just one contour, but is there another way where I can do it more efficiently because per image there could be hundreds of contours.


Solution

  • It's not clear if you want to have just one image with all selected contours as the output, or one individual image per selected contour. You could get one image with all selected contours in a efficient manner. First select all the contours you want to work with, then, plot all the contours filling them with white color so you can use this as a mask, and then mask the original image:

    selected_contours = [c for c in contours if cv2.contourArea(c) >= 2000]
    # the last parameter, negative line thickness, fills the contour
    mask = cv2.drawContours(img1, selected_contours, -1, (255,255,255), -1)
    res = np.bitwise_and(mask,image)