Quantify longest axis and width of irregular shapes within a single image
Question:
Original question
I have about 80-100 images such as (A). Each image is composed of shapes that were filled with black color after marking the outline in ImageJ. I need to extract each individual shape as in (B). I then need to find the widths of the longest axis as in (C).
And I then need to calculate all the possible widths at regular points within the black shape and return a .csv file containing these values.
I have been doing this manually and there must be a quicker way to do it. Any idea how I can go about doing this?
Partial solution as per Epsi’s answer
import matplotlib.pyplot as plt
import cv2
import numpy as np
image = cv2.imread("Desktop/Analysis/NormalCol0.tif")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh_img = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# Find contours, find rotated rectangle, obtain four verticies, and draw
contours, hierarchy = cv2.findContours(thresh_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
lengths = []
for i in contours:
x_y,width_height,angle_of_rotation = cv2.minAreaRect(i)
Height = print(width_height[1])
rect = cv2.minAreaRect(i)
box = np.int0(cv2.boxPoints(rect))
image_contours = np.zeros(image.shape)
# Draw the contours on the empty image
cv2.drawContours(image, [box], 0, (36,255,12), 3)
# OR:
# cv2.polylines(image, [box], True, (36,255,12), 3)
plt.imshow(image)
cv2.imwrite(output, image)
Output:
4031.0
20.877727508544922
51.598445892333984
23.852108001708984
21.0
21.21320343017578
19.677398681640625
43.0
I am able to get the length of each shape. I next need to figure out how to get width of the shape at regular points along the length of the shape?
Answers:
As I’m not that good with python, I wont post code, but will post links to explanations and documentary.
How I would do it is as follows:
-
Invert the image, so that all the white becomes black and black becomes white. https://www.delftstack.com/howto/python/opencv-invert-image/#invert-images-using-numpy.invert-method-in-python
-
Use findContours to find all the (now) white contours. https://pythonexamples.org/python-opencv-cv2-find-contours-in-image/
-
use minAreaRect to create bounding boxes around the contours that is positioned in a way that the area of the box is as small as possible. https://theailearner.com/tag/cv2-minarearect/
-
from the created bounding boxes, take the longest side, this will represent the length of your contour.
-
You can also get the angle of rotation from the bounding boxes
I have prepared a notebook with @Anyurag’s partial answer (as part of the question post), based on @Epsi’s idea:
https://colab.research.google.com/drive/1tBnU6Xdc58dLHzBZPwKz_Y_mCb1Pp40e?usp=sharing
The notebook can be helpful as a starting point for someone digging into the problem.
Of course my answer is also partial, because as @Anyurag noticed, there remains an issue of how to get width of the shape at regular points along the length of the shape.
Original question
I have about 80-100 images such as (A). Each image is composed of shapes that were filled with black color after marking the outline in ImageJ. I need to extract each individual shape as in (B). I then need to find the widths of the longest axis as in (C).
And I then need to calculate all the possible widths at regular points within the black shape and return a .csv file containing these values.
I have been doing this manually and there must be a quicker way to do it. Any idea how I can go about doing this?
Partial solution as per Epsi’s answer
import matplotlib.pyplot as plt
import cv2
import numpy as np
image = cv2.imread("Desktop/Analysis/NormalCol0.tif")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh_img = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# Find contours, find rotated rectangle, obtain four verticies, and draw
contours, hierarchy = cv2.findContours(thresh_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
lengths = []
for i in contours:
x_y,width_height,angle_of_rotation = cv2.minAreaRect(i)
Height = print(width_height[1])
rect = cv2.minAreaRect(i)
box = np.int0(cv2.boxPoints(rect))
image_contours = np.zeros(image.shape)
# Draw the contours on the empty image
cv2.drawContours(image, [box], 0, (36,255,12), 3)
# OR:
# cv2.polylines(image, [box], True, (36,255,12), 3)
plt.imshow(image)
cv2.imwrite(output, image)
Output:
4031.0
20.877727508544922
51.598445892333984
23.852108001708984
21.0
21.21320343017578
19.677398681640625
43.0
I am able to get the length of each shape. I next need to figure out how to get width of the shape at regular points along the length of the shape?
As I’m not that good with python, I wont post code, but will post links to explanations and documentary.
How I would do it is as follows:
-
Invert the image, so that all the white becomes black and black becomes white. https://www.delftstack.com/howto/python/opencv-invert-image/#invert-images-using-numpy.invert-method-in-python
-
Use findContours to find all the (now) white contours. https://pythonexamples.org/python-opencv-cv2-find-contours-in-image/
-
use minAreaRect to create bounding boxes around the contours that is positioned in a way that the area of the box is as small as possible. https://theailearner.com/tag/cv2-minarearect/
-
from the created bounding boxes, take the longest side, this will represent the length of your contour.
-
You can also get the angle of rotation from the bounding boxes
I have prepared a notebook with @Anyurag’s partial answer (as part of the question post), based on @Epsi’s idea:
https://colab.research.google.com/drive/1tBnU6Xdc58dLHzBZPwKz_Y_mCb1Pp40e?usp=sharing
The notebook can be helpful as a starting point for someone digging into the problem.
Of course my answer is also partial, because as @Anyurag noticed, there remains an issue of how to get width of the shape at regular points along the length of the shape.