Convexhull around a diffuse binary mask

Hi,

I am new to OpenCV and am currently trying to enclose a binary mask, with fairly diffuse white/True regions in a convex hull.
Here is an example mask, and the result of the simple one liner one can use in skimage:

enclosed_mask = morph.convex_hull_image(mask)

For reasons I won’t go into, I am trying to achieve the same with openCV, and I have spent the day spinning my wheels.

Following the tutorial in the official docs, and tweaking various resources I came across here what I have tried, with the same mask image.

import cv2
import tifffile as tiff
import numpy as np
import matplotlib.pylab as plt
import skimage.morphology as morphology

mask = tiff.imread("/home/pauln/CraftProspect/TER/wildfire_samples/unet_masks_0.1_2.tiff")
enclosed_mask = morphology.convex_hull_image(mask)

# Find contours: OpenCV 4.x
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

contours_poly = [None]*len(contours)
hulls = [None]*len(contours)
for i, c in enumerate(contours):
    contours_poly[i] = cv2.approxPolyDP(c, 3, True)
    hulls[i] = cv2.convexHull(c)

hull = np.zeros(mask.shape, np.uint8)
hull = cv2.drawContours(hull, hulls, -1, 255, cv2.FILLED)

fig,ax = plt.subplots(1,3,figsize=(12,4))
ax[0].imshow(mask, cmap="gray")
ax[1].imshow(enclosed_mask, cmap="gray")
ax[2].imshow(hull, cmap="gray")

plt.show()
plt.savefig("/home/pauln/cv2mask.png")

I get out an image which is unchanged.

I have played about with a few variations of this, and the best I have managed is to have some smaller regions enclosed, but still a very diffuse image.

Could someone please set me on the right track on how to achieve the same result as the skimage method?

For a bonus point, is there a way within openCV to separately enclose two or more distinct (but still diffuse) regions in the same mask, perhaps separated by some arbitrary distance (like morphology.convex_hull_object()

Thanks in advanced!

Edit: correct image attached

basically, findcontours + convex hull (over ALL points from the contours) + drawing… is the “equivalent”

scrap the approxpolydp, it’s superfluous. that includes contours_poly.

the loop and associated variables merely constructs a convex hull per contour, which isn’t what you want. scrap that.

I think cv2.convexHull(np.vstack(contours)) should work. takes all the points from all contours, and does a convex hull once.

if you need something non-convex… I don’t know if OpenCV has anything for that, but the general problem is called “alpha shapes”. idea is that a convex hull puts an infinite ruler against the points, while alpha shapes do the same with a circle of a set radius (infinite radius → convex hull).

Thanks for your quick response @crackwitz .

I’ve made some changes to my code, but it is still not quite there. As attached.

import cv2
import tifffile as tiff
import numpy as np
import matplotlib.pylab as plt
import skimage.morphology as morphology

mask = tiff.imread("/home/pauln/CraftProspect/TER/wildfire_samples/unet_masks_0.1_2.tiff")
enclosed_mask = morphology.convex_hull_image(mask)

# Find contours: OpenCV 4.x
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

enclosed = cv2.convexHull(np.vstack(contours))

hull = np.zeros(mask.shape, np.uint8)
hull = cv2.drawContours(hull, enclosed, -1, 255, cv2.FILLED)

fig,ax = plt.subplots(1,3,figsize=(12,4))
ax[0].imshow(mask, cmap="gray")
ax[0].set_title("mask")
ax[1].imshow(enclosed_mask, cmap="gray")
ax[1].set_title("enclosed_mask")
ax[2].imshow(hull, cmap="gray")
ax[2].set_title("hull")


plt.show()
plt.savefig("/home/pauln/cv2mask.pdf")

Weirdly, the result is a set of pixels, however I think this is a step towards the solution. The points which are plotted sit at the vertices of the convex hull produced in skimage.

Do you know how I might take this to an enclosed convex hull?

drawContours(..., [enclosed], ...)

it’s a single contour. drawContours expects a list of them. if it gets a numpy array (or anything iterable like a list), it iterates over the outer dimension… and that causes “contours” containing single points, if you gave it a single contour.