0

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.

Jeru Luke
  • 20,118
  • 13
  • 80
  • 87
  • 1
    Yes, all you need to do is to take your drawContours code inside the if conditional, and instead of 'break', keep drawing contours that qualify the size requirement (or any other filter that you add). Put your numpy array creation code above the for loop. – Knight Forked May 22 '21 at 15:05
  • Not quite sure what you mean by more efficiently. You will have to go through all the contours that qualify. And you don't have to create numpu arrays for each contour, once you create it then you can draw all your filtered contours on your 'output' image. – Knight Forked May 22 '21 at 15:14
  • You do not need to create two image: the contour and the filled contour and then add them. You can just create the filled contour and just dilate it with morphology. – fmw42 May 22 '21 at 15:46
  • What I meant by efficiency is making the code less time consuming because per image it takes >1sec and if I can have less memory consuming code. with that, I have considered your suggestions, made the creation of the numpy arrays more efficient and didn't create one at each iteration. didn't use dilation because it took more time for some reason. – Mohamed AboMokh May 22 '21 at 17:28

1 Answers1

0

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)
Carlos Melus
  • 1,472
  • 2
  • 7
  • 12
  • 1
    what I want is all the selected contours per image from all the images in the dataset, and your solution gives me the option to remove the polyfill function and the need to create another zeros array, so thank you :). – Mohamed AboMokh May 25 '21 at 13:00