Flojomojo
Flojomojo

Reputation: 75

Detecting shapes with gaps in them in an image using Python

I have the following image: a image of a vague outline

I want to extract the following outline so that I can check if there is a point inside the outline: image with the necessary outline correctly drawn

I have the following python code to extract the outline of the shape using cv2.findContours:

import cv2
import numpy as np

img = cv2.imread(
    r'inp.png', cv2.IMREAD_COLOR)

def parse_shape(img):
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

    # Dilate the image 
    kernel = np.ones((3,3), np.uint8)
    dilate = cv2.dilate(hsv, kernel, iterations = 2)

    # Get all the white pixels
    lower_white_hsv = np.array([0, 0, 200])
    upper_white_hsv = np.array([255, 255, 255])

    # Create a mask for the white pixels and find contours
    mask_hsv = cv2.inRange(dilate, lower_white_hsv, upper_white_hsv)
    contours, _ = cv2.findContours(mask_hsv, cv2.RETR_TREE , cv2.CHAIN_APPROX_SIMPLE)
    if len(contours) == 0:
        print("No contours found from mask")
        return None

    # Filter out contours that are too small 
    # This filters out some noise but not all
    contour_area_threshold = 600

    valid_contours = []
    for cnt in contours:
        if cv2.contourArea(cnt) > contour_area_threshold:
            valid_contours.append(cnt)

    if len(valid_contours) == 0:
        print("No valid contours found from mask")
        return None

    # Draw the contours on the image and show the resulting image
    draw_img = img.copy()
    cv2.drawContours(draw_img, valid_contours, -1, (0, 255, 0), cv2.FILLED)
    cv2.imshow("draw_img", draw_img)
    cv2.imshow("mask_hsv", mask_hsv)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    return valid_contours

# Do stuff
lines = parse_shape(img)

Which produces the following resultimage with the necessary outline with gaps drawn

Now this is pretty close, but as you can see, there are still some larger gaps, which cannot be fixed by just increasing the dilution of the image. Is there any way to fill in the gaps? If the shape is correctly detected, checking if a point is inside the shape is pretty trivial, but I can't seem to find a solution to the gaps.

Edit: This is the raw input image and the code that "transforms" it into the image above: raw input image

import cv2
import numpy as np


img = cv2.imread('raw_inp.jpg', cv2.IMREAD_COLOR)


def extract_red_line(img):
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    # Define lower and uppper limits of what we call "red"
    lower_red_hsv = np.array([12, 185, 175])
    upper_red_hsv = np.array([20, 255, 245])

    return cv2.inRange(hsv, lower_red_hsv, upper_red_hsv)


red_line_extraced = extract_red_line(img)
cv2.imshow('red_lines', red_line_extraced)
cv2.waitKey(0)
cv2.destroyAllWindows()

Upvotes: 0

Views: 265

Answers (1)

KRG
KRG

Reputation: 956

This is not a generic but a workaround solution.
I added a config 'DILATE_PARAMETER' which specifies how many times to dilate, to create a closed area removing gaps. Then will find the contour of this area giving a closed contour.
'CONTOUR_AREA_THRESHOLD' also need to be fine-tuned for your use case.
One of the issues is that the contour will be a little far from the actual required one.

import cv2
import numpy as np

DILATE_PARAMETER = 8
CONTOUR_AREA_THRESHOLD = 5000

img = cv2.imread('ring.png', cv2.IMREAD_COLOR)

def parse_shape(img):

    # Dilate the image
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    kernel = np.ones((3,3), np.uint8)
    dilate = cv2.dilate(img_gray, kernel, iterations = 2)
    
    for _ in range(DILATE_PARAMETER-1):
        dilate = cv2.dilate(dilate, kernel, iterations = 2)


    # Create a mask for the white pixels and find contours
    mask = cv2.inRange(dilate, 200, 255)
    contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL , cv2.CHAIN_APPROX_SIMPLE)
    if len(contours) == 0:
        print("No contours found from mask")
        return None

    # Filter out contours that are too small 
    # This filters out some noise but not all
    

    valid_contours = []
    for cnt in contours:
        if cv2.contourArea(cnt) > CONTOUR_AREA_THRESHOLD:
            valid_contours.append(cnt)
    
    if len(valid_contours) == 0:
        print("No valid contours found from mask")
        return None

    # Draw the contours on the image and show the resulting image
    draw_img = img.copy()
    cv2.drawContours(draw_img, valid_contours, -1, (0, 255, 0), 5)
    cv2.imwrite("result.png",draw_img)
    cv2.imshow("draw_img", draw_img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    return valid_contours

# Do stuff
lines = parse_shape(img)

output

enter image description here

Upvotes: 1

Related Questions