Convert abstract mask to geometric shape and calculate all edges

Viewed 123

I am very new at Python so what I am trying to do is this:

I already made a mask of floor of given image:

https://i.ibb.co/0r17SnT/www.png

What I want to do is to connect all points by geometric straight lines and find the length of each edge, like I drew on this example:

https://i.ibb.co/tbgyYF5/www.png

I am using openCV, and so far I tried to find some extreme points but it is not what i need i guess.

def getCalculatedImg():

    # Load image
    img = cv2.imread('img.jpg')
    gray_image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # Apply cv2.threshold() to get a binary image
    ret, thresh = cv2.threshold(gray_image, 50, 255, cv2.THRESH_BINARY)

    # Find contours:
    contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    cntrs = contours[0]
    c = max(contours, key=cv2.contourArea)

    # Obtain outer coordinates
    left_coordinates_separated = [(c[c[:, :, 0].argmin()][0])[0], (c[c[:, :, 0].argmin()][0])[1]]
    left = tuple(left_coordinates_separated)
    right_coordinates_separated = [(c[c[:, :, 0].argmax()][0])[0], (c[c[:, :, 0].argmax()][0])[1]]
    right = tuple(right_coordinates_separated)
    top_coordinates_separated = [(c[c[:, :, 1].argmin()][0])[0], (c[c[:, :, 1].argmin()][0])[1]]
    top = tuple(top_coordinates_separated)
    bottom_coordinates_separated = [(c[c[:, :, 1].argmax()][0])[0], (c[c[:, :, 1].argmax()][0])[1]]
    bottom = tuple(bottom_coordinates_separated)

    # Draw contours and dots to image:
    cv2.drawContours(img, [c], -1, (36, 255, 12), 2)
    cv2.circle(img, left, 8, (0, 50, 255), -1)
    cv2.circle(img, right, 8, (0, 255, 255), -1)
    cv2.circle(img, top, 8, (255, 50, 0), -1)
    cv2.circle(img, bottom, 8, (255, 255, 0), -1)
    print('Left point: {}'.format(left))
    print('Right point: {}'.format(right))
    print('Top point: {}'.format(top))
    print('Bottom point: {}'.format(bottom))

    # Output
    cv2.imwrite('outlined_boundered_image.jpg', img)

    return img
1 Answers

It seems what you want is a straight line interpolation of the boundary and the length of the lines. One way of getting one is to use Splines of degree one. If you're more interested in the actual length of the boundary it would be more appropriate to use degree 3 interpolation and an integral. So let me know if you want that.

First I used blur and a sobel filter to obtain a clear picture of the boundary.

import cv2
import matplotlib.pyplot as plt
import numpy as np
from scipy.interpolate import UnivariateSpline

picture = cv2.imread('area.png', cv2.IMREAD_GRAYSCALE)
picture2 = cv2.GaussianBlur(picture, (3, 3), 0, 0, cv2.BORDER_DEFAULT)
picture3 = np.abs(cv2.Sobel(picture2, cv2.CV_16S,0,1))+np.abs(cv2.Sobel(picture2,  cv2.CV_16S,0,1))
picture4 = (picture3>0.3*picture3.max()).T

plt.imshow(picture4.T, cmap='gray')

filtered picture

I can't give the image to UnivariateSpline to interpolate yet since there are multiple y pixels for each x. I solved that by taking the average of the y values for each x as the value of the function at that point.

x,y = np.where(picture4)
idx = np.unique(x, return_index = True)[1]
y = np.array(tuple(map(np.mean,np.split(y,idx)[1:])))

f = UnivariateSpline(x[idx],y,k=1,s=10**4)
knots = f.get_knots()
print(len(knots))

x2 = np.linspace(0,picture.shape[1])
plt.plot(x2, -f(x2))

And it gives me a straight line interpolation with 33 lines of the original boundary. If you think 33 lines is too much you can just increase s. Then it uses less lines. straight line interpolation

And finally I calculate the length of the line segments

dx = knots[1:]-knots[:-1]
dy = f(knots[1:])-f(knots[:-1])
lengths = (dx**2+dy**2)**(1/2)
lengths

They are

array([ 48.00108553,  24.00049131,  48.59189432,  48.00784009,
        24.00411983,  12.0353518 ,   7.45847594,  76.56726501,
        50.2062363 ,  62.97882834,  55.66753909,  59.85823117,
       192.18816002,  24.0756812 ,  12.0380576 ,   6.30271185,
        68.6638399 ,   8.91837947,   4.92632077,  11.31860656,
        68.12574347,  18.55007016,  24.08123637,  48.59346072,
        12.23820314,  18.36509318,  93.8749539 ,   8.61755003,
        15.5131074 ,  43.85394011,  56.05155661,   9.3525071 ,
        11.54485654])

Edit I thought It would be interesting to see a few pictures with different amounts of lines. If you're not happy with the results please try to describe what's wrong.

pictures with different amounts of lines

Related