Dreko
Dreko

Reputation: 39

Fitting ellipse to random distributed uniform regular shapes

We can think of the shapes in the representative picture as randomly scattered pencils or sticks on a table. I've been trying to find the areas of each shape by fitting ellipses, but I haven't been able to fit ellipses properly. Can you help me? Thanks.

First image is : input image

enter image description here

The code that I tried,

import cv2
import numpy as np
import random as rng
import math

img = cv2.imread('sticks.png', 1)
imge= cv2.cvtColor(img,cv2.COLOR_RGB2BGR)
gray = cv2.cvtColor(imge, cv2.COLOR_BGR2GRAY)
blur = cv2.blur(gray, (2,2), 3)


rng.seed(1)
def thresh_callback(val):
threshold = val

canny_output = cv2.Canny(blur, threshold, threshold * 4)


contours, _ = cv2.findContours(canny_output, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
minRect = [None]*len(contours)
minEllipse = [None]*len(contours)
for i, c in enumerate(contours):
    minRect[i] = cv2.minAreaRect(c)
    if c.shape[0] > 5:
        minEllipse[i] = cv2.fitEllipse(c)
        (x,y),(minor_axis,major_axis),angle = minEllipse[i]
        half_major= major_axis/2
        half_minor= minor_axis/2
        pixel= 37.795275591
        half_major1= half_major/pixel
        half_minor1= half_minor/pixel
        area= math.pi * half_major1 * half_major1
        print(area)
        drawing = np.zeros((canny_output.shape[1], canny_output.shape[1], 3), dtype=np.uint8)

for i, c in enumerate(contours):
    color = (rng.randint(0,256), rng.randint(0,256), rng.randint(0,256))
    cv2.drawContours(drawing, contours, i, color)
    if c.shape[0] > 5:
        cv2.ellipse(drawing, minEllipse[i], color, 1)
    


cv2.imshow('Fitting Ellips', drawing)


source_window = 'Source'
cv2.namedWindow(source_window)
cv2.imshow(source_window, img)
max_thresh = 255
thresh = 100
cv2.createTrackbar('Canny Thresh:', source_window,thresh, max_thresh, thresh_callback)
thresh_callback(thresh)
cv2.waitKey()

Second image is: expected result (fitting ellipse each line like this)

enter image description here

Upvotes: 1

Views: 202

Answers (1)

Shamshirsaz.Navid
Shamshirsaz.Navid

Reputation: 2362

This is not the final result and definitely has errors. You need to take the time to achieve the desired result. But it can be a good idea to start with:

import sys
import cv2
import math
import numpy as np

# Check it there is a black area in specific position of an image
def checkPointArea(im, pt):
    x, y = pt[0], pt[1]
    return im[y, x, 0] == 0 or im[y, x+1, 0] == 0 or im[y, x-1, 0] == 0 or im[y+1, x, 0] == 0 or im[y-1, x, 0] == 0


# Load image
pth = sys.path[0]
im = cv2.imread(pth+'/im.jpg')
H, W = im.shape[:2]


# Make grayscale and black and white versions
im = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
bw = cv2.threshold(im, 110, 255, cv2.THRESH_BINARY)[1]

# Try to clear the parts of the image that are stuck together
bw = cv2.dilate(bw, np.ones((5, 5), np.uint8))

# Convert im back to BGR
im = cv2.cvtColor(im, cv2.COLOR_GRAY2BGR)

# Make some copies
org = im.copy()
empty = im.copy()
empty[:] = 255


# Find contours and sort them by position
cnts, _ = cv2.findContours(bw, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
cnts.sort(key=lambda x: cv2.boundingRect(x)[0])

# Thikness of random lines
thickness = 5

# Find and draw ellipses
for cnt in cnts:
    x, y, w, h = cv2.boundingRect(cnt)
    if w < W:
        cv2.rectangle(im, (x, y), (x+w, y+h), (10, 230, 0)
                      if w < h else (200, 0, 128), 1)
        hw, hh = w//2, h//2
        cx, cy = x+hw, y+hh

        r = int(math.sqrt(w**2+h**2))
        t, c = math.atan(hw/hh), (255, 0, 0)
        if checkPointArea(org, (x, y)) and checkPointArea(org, (x+w-1, y+h-1)):
            t, c = math.atan(hw/-hh), (100, 0, 200)
        deg = math.degrees(t)
        if w <= thickness*2:
            deg = 0
        if h <= thickness*2:
            deg = 90
        cv2.ellipse(im, (x, y), (1, 1), 0, 0, 360, c, 4)
        cv2.ellipse(im, (cx, cy), (thickness, r//2),
                    deg, 0, 360, (40, 0, 255), 2, lineType=cv2.LINE_AA)
        #cv2.ellipse(empty, (x, y), (1, 1), 0, 0, 360, c, 2)
        cv2.ellipse(empty, (cx, cy), (thickness, r//2),
                    deg, 0, 360, c, 2, lineType=cv2.LINE_AA)

# Save output
bw = cv2.cvtColor(bw, cv2.COLOR_GRAY2BGR)
top = np.hstack((org, empty))
btm = np.hstack((bw, im))
cv2.imwrite(pth+'/im_.png', np.vstack((top, btm)))

Each section:

enter image description here

Final Result:

enter image description here

Errors:
You have to spend more time for these two parts, the first is due to my weak code. Removable with more time. The second is due to the overlap of two lines. Clearing the image did not help this part. You may be able to prevent such interference from occurring later.

enter image description here

Upvotes: 1

Related Questions