Reputation: 75
I want to extract the following outline so that I can check if there is a point inside the outline:
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 result
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:
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
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
Upvotes: 1