7

Is it possible to create a polygon from a set of points along a line with rough curvature, such that the points are selected between two values of curvature?

I am attempting to retrieve an approximated curvilinear quadrilateral shape from a given image using python's opencv package (cv2).

For example: Given an image after edge detection such as this:

and after finding contours with cv2.findContours such as this:

(Sidenote: It would be great if this would actually give a square-ish shape rather than going around the line - an algorithm to close in the gap in this image's shape on it's right side is also required. Dilation/erosion may work but will likely get rid of certain features that may be desired to be kept.)

after that, we can use polyDPApprox on the contours like this:

However, this is not curvature dependent - it's just approximating by use of largest deviance from the lines. If we want to leave out some of the fine detail (the idea being that these are likely from errors) and keep the points with smaller curvature (broad shape) - can we use a function to provide something like this?:

(The red fill in just shows that the shape would be closed in to a curvilinear quadrilateral.)

Related question: Is it possible in OpenCV to plot local curvature as a heat-map representing an object's "pointiness"?

Here is the function used to analyze the input image in case anyone wants it:

# input binary image
def find_feature_points(img):
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    cv2.namedWindow('img', WINDOW_NORMAL)
    cv2.imshow("img", gray)
    cv2.waitKey(0)

    contours, hierarchy = cv2.findContours(gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

    # Draw contours to image
    print contours
    copy = img.copy()
    # img - Image.
    # pts - Array of polygonal curves.
    # npts - Array of polygon vertex counters.
    # ncontours - Number of curves.
    # isClosed - Flag indicating whether the drawn polylines are closed or not. If they are closed, the function draws a line from the last vertex of each curve to its first vertex.
    # color - Polyline color.
    # thickness - Thickness of the polyline edges.
    # lineType - Type of the line segments. See the line() description.
    # shift - Number of fractional bits in the vertex coordinates.
    cv2.polylines(img=copy, pts=contours, isClosed=1,  color=(0,0,255), thickness=3)

    cv2.namedWindow('contour', WINDOW_NORMAL)
    cv2.imshow("contour", copy)
    cv2.waitKey(0)

    # Find approximation to contours
    approx_conts = []
    for c in contours:
        curve = c
        epsilon = 200
        closed = True
        approx_conts.append(cv2.approxPolyDP(curve, epsilon, closed))

    # draw them
    cv2.drawContours(img, approx_conts, -1, (0, 255, 0), 3)
    cv2.namedWindow('approx', WINDOW_NORMAL)
    cv2.imshow("approx", img)
    cv2.waitKey(0)
    return 
Community
  • 1
  • 1
chase
  • 3,592
  • 8
  • 37
  • 58

1 Answers1

0

Here's a possible solution. The idea is:

  1. Obtain binary image. Load image, convert to grayscale, and Otsu's threshold
  2. Find convex hull. Determine the surrounding perimeter of the object and draw this onto a mask
  3. Perform morphological operations. Fill small holes using morph close to connect the contour
  4. Draw outline. Find the external contour of the mask and draw onto the image

Input image -> Result

enter image description here enter image description here

Code

import cv2
import numpy as np

# Load image, convert to grayscale, threshold
image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]

# Find convex hull and draw onto a mask
mask = np.zeros(image.shape, dtype=np.uint8)
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cnt = cnts[0]
hull = cv2.convexHull(cnt,returnPoints = False)
defects = cv2.convexityDefects(cnt,hull)
for i in range(defects.shape[0]):
    s,e,f,d = defects[i,0]
    start = tuple(cnt[s][0])
    end = tuple(cnt[e][0])
    far = tuple(cnt[f][0])
    cv2.line(mask,start,end,[255,255,255],3)

# Morph close to fill small holes
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
close = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel, iterations=2)

# Draw outline around input image
close = cv2.cvtColor(close, cv2.COLOR_BGR2GRAY)
cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cv2.drawContours(image,cnts,0,(36,255,12),1)

cv2.imshow('image', image)
cv2.waitKey()
nathancy
  • 42,661
  • 14
  • 115
  • 137