I have images where there will be outer boundaries like rectangles or even hand shapes and within their borders will be some other shapes including complex ones.
I need to separate the outer shape (rectangle, etc.) and the inner content.
I would like them to maintain their thickness, etc.
For example this is how my input images look, in simple cases:
All my images will be black and white like that.
I would like the above image to be separated like this:
I have tried many things, but nothing seems to work. For example this:
def remove_outer_boundary(image_path,
border_thickness=20,
inpaint_radius=3):
"""
Removes only the outer frame lines of thickness `border_thickness`,
preserving all interior content.
Args:
image_path (str): Path to the input image.
border_thickness (int): Approximate pixel thickness of the outer border.
inpaint_radius (int): Radius for the inpainting algorithm.
Returns:
result (np.ndarray): Image with only the border removed.
"""
# 1. Load and gray
img = cv2.imread(image_path)
if img is None:
raise FileNotFoundError(f"Cannot read: {image_path}")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 2. Binary inverse threshold to pick up lines
thresh = cv2.adaptiveThreshold(
gray, 255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY_INV,
blockSize=51, C=10)
# 3. Find largest external contour
contours, _ = cv2.findContours(
thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if not contours:
return img.copy()
outer = max(contours, key=cv2.contourArea)
# 4. Fill that contour to get its full region
filled = np.zeros_like(gray)
cv2.drawContours(filled, [outer], -1, 255, thickness=cv2.FILLED)
# 5. Morphological gradient to isolate just the border
k = border_thickness // 2 * 2 + 1 # make it odd
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (k, k))
border_mask = cv2.morphologyEx(filled, cv2.MORPH_GRADIENT, kernel)
# 6. Inpaint only the border_mask
result = cv2.inpaint(img, border_mask, inpaint_radius,
cv2.INPAINT_TELEA)
return result
I was trying to see if removing the outer rectangle might work. But it needs thickness etc. stuff. And doesn't work in some other cases such as hand shapes (outlines), leaves some smudges, etc.
One technique that should work with other things than rectangles. But it make the assumption that you have a white background, and anything non-white is a "shape"
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread("shapeToSeparate.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 1 if we have a shape, 0 if it is background
binary = (gray<250)*np.uint8(1)
# firstPixel should belong to outer shape (the first pixel, in reading
# order that is a shape can't be inner: if it was in another shape, we
# would have encountered a pixel of that other shape before)
# Note that this is an unraveled index W*y+x.
firstPixel = np.argmax(binary)
# Split the image into shapes, from this binary
# comps is an image of indexes
# comps[y,x]=k means that kth component ("shape") contains pixel x,y
nComp, comps = cv2.connectedComponents(binary)
# compVal is the component that contains the first pixel
# and we know that first pixel is belong the the outer shape
# so compVal is the component number of the outer shape
compVal = comps.ravel()[firstPixel]
# so, outComp is a binary image of the outer shape
# true if the pixel is the outer shape, false otherwise
outComp=comps==compVal
# We could just play with components now. But I make the assumption that we
# may want to have some colors (as long as non pure-white) and we want to find
# them in the result
# so rest of the code copies the part of the image either in the left
# or right side of the result
# Result is a white image twice as wide as the original (you may want
# something else, that is an arbitrary decision of mine. Question was
# about separating shape, not about how to render the separated shapes
# so I improvised)
H,W,C=img.shape
result = np.full((H, 2*W, C), 255, dtype=np.uint8)
result[:, :W][outComp] = img[outComp] # Copy out component to the left
result[:, W:][~outComp] = img[~outComp] # and rest to the right
ā