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.
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.
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.
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]
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]
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
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.
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.
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.
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]
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]
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