How to get length of each side of a shape in an image?

Viewed 943

Suppose we have an image as below:

Input

I want to determine the number of sides, and the length of each side.

Here in the image, we have three edges as straight lines, and the upper one is a curved edge. I am able to find the length of the three straight edges using Canny edge detection. We can have four vertices coordinates, and we can calculate the length of the three straight edges/lines, but unable to find the length of the curved edge.

find number of sides python This is a good answer for getting the number of edges in an image, and we get the coordinates of vertices through the code in the above link. Further to get the length of the sides using the coordinates, we can use below code to get the length, if the edges are straight lines:

for pt in zip(points,np.roll(points,-1,0)):
#     print( pt )
    x = pt[0][0]
    y = pt[1][0]
    d = math.sqrt((x[1]-y[1])*(x[1]-y[1]) + (x[0]-y[0])*(x[0]-y[0]))
    print('length between point:',x,'and', y,'is', d)

To simplify my question: I want to find, how many edges/sides are there for a shape in an image, and their respective lengths, if the image shape has any curved sides.

1 Answers

My idea would be to get the contour of the shape, try to detect "corners", e.g. using Harris corner detection, find matching points from the contour, and piecewise calculate the length of the edges using cv2.arcLength.

The input for the below extract_and_measure_edges method needs some binarized contour image like that one derived from your actual input image:

Input

So, the pre-processing must be adapted to the input images, and is out of scope of my answer! In the below code, the pre-processing is for the given input image, not for the two other examples.

import cv2
import matplotlib.pyplot as plt
import numpy as np


def extract_and_measure_edges(img_bin):

    # Detect possible corners, and extract candidates
    dst = cv2.cornerHarris(img_bin, 2, 3, 0.04)
    cand = []
    for i, c in enumerate(np.argwhere(dst > 0.1 * np.max(dst)).tolist()):
        c = np.flip(np.array(c))
        if len(cand) == 0:
            cand.append(c)
        else:
            add = True
            for j, d in enumerate(cand):
                d = np.array(d)
                if np.linalg.norm(c - d) < 5:
                    add = False
                    break
            if add:
                cand.append(c)

    # Get indices of actual, nearest matching contour points
    corners = sorted([np.argmin(np.linalg.norm(c - cnt.squeeze(), axis=1))
                      for c in cand])

    # Extract edges from contour, and measure their lengths
    output = cv2.cvtColor(np.zeros_like(img_bin), cv2.COLOR_GRAY2BGR)
    for i_c, c in enumerate(corners):
        if i_c == len(corners) - 1:
            edge = np.vstack([cnt[c:, ...], cnt[0:corners[0], ...]])
        else:
            edge = cnt[c:corners[i_c + 1], ...]

        loc = tuple(np.mean(edge.squeeze(), axis=0, dtype=int).tolist())
        color = tuple(np.random.randint(0, 255, 3).tolist())
        length = cv2.arcLength(edge, False)
        cv2.polylines(output, [edge], False, color, 2)
        cv2.putText(output, '{:.2f}'.format(length), loc, cv2.FONT_HERSHEY_COMPLEX, 0.5, color, 1)
    return output


# Read and pre-process image, extract contour of shape
# TODO: MODIFY TO FIT YOUR INPUT IMAGES
img = cv2.imread('2B2m4.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
thr = cv2.threshold(gray, 16, 255, cv2.THRESH_BINARY_INV)[1]
cnts = cv2.findContours(thr, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cnt = max(cnts, key=cv2.contourArea)
thr = cv2.drawContours(np.zeros_like(thr), [cnt], -1, 255, 1)

# Extract and measure edges, and visualize output
out = extract_and_measure_edges(thr)
plt.figure(figsize=(18, 6))
plt.subplot(1, 3, 1), plt.imshow(img), plt.title('Original input image')
plt.subplot(1, 3, 2), plt.imshow(thr, cmap='gray'), plt.title('Contour needed')
plt.subplot(1, 3, 3), plt.imshow(out), plt.title('Results')
plt.tight_layout(), plt.show()

That's the output:

Output #1

Example #2:

Example #2

Output:

Output #2

Example #3:

Example #3

Output:

Output #3

(I haven't paid attention to the correct color ordering...)

----------------------------------------
System information
----------------------------------------
Platform:      Windows-10-10.0.19041-SP0
Python:        3.9.1
PyCharm:       2021.1.1
Matplotlib:    3.4.2
NumPy:         1.19.5
OpenCV:        4.5.2
----------------------------------------
Related