How to draw lines parallel to the width of minimum area rectangle joing opposite contour points using OpenCV python

Question:

I am trying to draw lines parallel to the width of minimum area rectangle drawn on a contour which connect the opposite points of the contour. What I intend to do with this is to find the real width of the contour.
To find the real width of the objects, I currently use a reference object inside the images and to find pixel per cm value which is used to convert the width value obtained in pixel to cm.To find the width I am using Minimum Area Rectangle method present in OpenCV, I have also tried finding the leftmost and rightmost contour points and then joining them to find there length but the values are always over predicted.
Following is the code that I am using to find the values.

commodity_mask = copy.deepcopy(r['masks'][i]).astype(np.uint8)
im, contours_m, hierarchy  = cv2.findContours(commodity_mask, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)
contours = max(contours_m, key=cv2.contourArea)leftmost = tuple(contours[contours[:,:,0].argmin()][0])
rightmost = tuple(contours[contours[:,:,0].argmax()][0])
topmost = tuple(contours[contours[:,:,1].argmin()][0])
bottommost = tuple(contours[contours[:,:,1].argmax()][0])
cv2.circle(minrect, leftmost, 8, (0, 50, 255), -1)
cv2.circle(minrect, rightmost, 8, (0, 50, 255), -1)
cv2.line(minrect, leftmost, rightmost, (0,0,255),2)
dist = np.sqrt((leftmost[0]-rightmost[0])**2 + (leftmost[1]-rightmost[1])**2)
dist = dist*ratio     #finding distance b/w leftmost and rightmost contour point and converting them to pixels.

commodity_rect = cv2.minAreaRect(commodity_contour)  # (top-left corner(x,y), (width, height), angle of rotation)
cv2.drawContours(minrect, [np.int0(cv2.boxPoints(commodity_rect))], 0, (0, 255, 0), 2)
(xrect, yrect), (wrect, hrect), alpha = commodity_rect
print('Width: ', wrect * ratio, 'Height: ', hrect * ratio)

The r['masks'][i] value is the ith contour obtained in from the image.

So far I have achieved is the following image.

contours

The green boxes are the minimum area rectangle drawn on the contour and the red line joins the leftmost and rightmost points of the contour.

What I want to do is the following.

enter image description here

Draw lines parallel to the width of the rectangle(yellow ones) which join the opposite points of the contour, then I can find their lengths and use them to find the width of the contour.

This is the original image, without the rectangles drawn.

enter image description here

Asked By: Ahmad Javed

||

Answers:

This will help you to find your desired line coordinates. I have used the cv2.pointPolygonTest on contours point to find your desired points. more about cv2.pointPolygonTest here

import numpy as np
import cv2

image = cv2.imread('image.png')

image = cv2.resize(image, (800, 800))
output_image = image.copy()

gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

HSV = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
low = np.array([33, 91, 21])
high = np.array([253, 255, 255])

mask = cv2.inRange(HSV, low, high)
contours, hierarchy = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
sorted_contour = sorted(contours, key=lambda x: cv2.contourArea(x), reverse=True)

# As I know there is 6 max contour we want I'm selecting 6 contour only to creating perfact mask
selected_contour = sorted_contour[:6]
blank_mask = np.zeros_like(mask)
mask = cv2.drawContours(blank_mask, selected_contour, -1, 255, -1)
cv2.imshow("mask", mask)

for i, cnt in enumerate(selected_contour):
    # find min enclosing rect
    commodity_rect = cv2.minAreaRect(cnt)
    cv2.drawContours(output_image, [np.int0(cv2.boxPoints(commodity_rect))], 0, (0, 255, 0), 2)

    # find center point of rect
    M = cv2.moments(cnt)
    cX = int(M["m10"] / M["m00"])
    cY = int(M["m01"] / M["m00"])
    cv2.circle(output_image, (cX, cY), 5, (0, 255, 255), -1)

    selceted_points = [[px, cY] for [[px, py]] in cnt if cv2.pointPolygonTest(cnt, (px, cY), measureDist=False) == 0]

    left_point = min(selceted_points, key=lambda x: x[0])
    right_point = max(selceted_points, key=lambda x: x[0])

    print(f"nfor {i}th contour : ")
    print("left_point : ", left_point)
    print("right_point : ", right_point)

    cv2.circle(output_image, tuple(left_point), 3, (255, 0, 0), -1)
    cv2.circle(output_image, tuple(right_point), 3, (255, 0, 0), -1)
    cv2.line(output_image, tuple(left_point), tuple(right_point), (0, 0, 255), 1)

cv2.imshow("output_image", output_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

Output:

for 0th contour : 
left_point :  [606, 613]
right_point :  [786, 613]

for 1th contour : 
left_point :  [73, 233]
right_point :  [211, 233]

for 2th contour : 
left_point :  [329, 248]
right_point :  [501, 248]

for 3th contour : 
left_point :  [58, 637]
right_point :  [246, 637]

for 4th contour : 
left_point :  [364, 600]
right_point :  [508, 600]

for 5th contour : 
left_point :  [605, 237]
right_point :  [753, 237]

Mask Image:
enter image description here

Output Image:
enter image description here

Answered By: Vatsal Parsaniya

In solution one, I misunderstood something and find a horizontal line on the bounding box,
this is your desired line cordinates.

import numpy as np
import cv2


def get_order_points(pts):
    # first - top-left,
    # second - top-right
    # third - bottom-right
    # fourth - bottom-left

    rect = np.zeros((4, 2), dtype="int")

    # top-left point will have the smallest sum
    # bottom-right point will have the largest sum
    s = pts.sum(axis=1)
    rect[0] = pts[np.argmin(s)]
    rect[2] = pts[np.argmax(s)]

    # top-right point will have the smallest difference
    # bottom-left will have the largest difference
    diff = np.diff(pts, axis=1)
    rect[1] = pts[np.argmin(diff)]
    rect[3] = pts[np.argmax(diff)]

    return rect


image = cv2.imread('image.png')

image = cv2.resize(image, (800, 800))
output_image = image.copy()

gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

HSV = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
low = np.array([33, 91, 21])
high = np.array([253, 255, 255])

mask = cv2.inRange(HSV, low, high)
contours, hierarchy = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
sorted_contour = sorted(contours, key=lambda x: cv2.contourArea(x), reverse=True)

# As I know there is 6 max contour we want I'm selecting 6 contour only to creating perfact mask
selected_contour = sorted_contour[:6]
blank_mask = np.zeros_like(mask)
mask = cv2.drawContours(blank_mask, selected_contour, -1, 255, -1)
cv2.imshow("mask", mask)
cv2.imwrite("mask.png", mask)

for i, cnt in enumerate(selected_contour):
    # find min enclosing rect
    commodity_rect = cv2.minAreaRect(cnt)
    bounding_rect_points = np.array(cv2.boxPoints(commodity_rect), dtype=np.int)
    cv2.drawContours(output_image, [bounding_rect_points], 0, (0, 255, 0), 2)

    tl, tr, br, bl = get_order_points(bounding_rect_points)

    left_point = (tl + bl) // 2
    right_point = (tr + br) // 2

    print(f"nfor {i}th contour : ")
    print("left_point : ", left_point)
    print("right_point : ", right_point)

    cv2.circle(output_image, tuple(left_point), 3, (255, 0, 0), -1)
    cv2.circle(output_image, tuple(right_point), 3, (255, 0, 0), -1)
    cv2.line(output_image, tuple(left_point), tuple(right_point), (0, 0, 255), 1)

cv2.imshow("output_image", output_image)
cv2.imwrite("output_image.png", output_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

Output:

for 0th contour : 
left_point :  [606 597]
right_point :  [785 622]

for 1th contour : 
left_point :  [ 64 236]
right_point :  [214 236]

for 2th contour : 
left_point :  [325 201]
right_point :  [507 295]

for 3th contour : 
left_point :  [ 56 638]
right_point :  [244 619]

for 4th contour : 
left_point :  [359 574]
right_point :  [504 625]

for 5th contour : 
left_point :  [605 241]
right_point :  [753 241]

Output_Image:
enter image description here

Answered By: Vatsal Parsaniya

have you tried something like this:

commodity_rect = cv2.minAreaRect(commodity_contour) #Find the minAreaRect for each contour
minAxis = round(min(commodity_rect[1])) #Find minor axis size
maxAxis = round(max(commodity_rect[1])) #Find major axis size
Answered By: Francisco Massucci