I am using cv2 to compute the line withing the object present in the mask image attached. The orientation/shape of the object might vary (horizontal, vertical) from the other mask images in my dataset. But the problem is, the method I have used to compute the line is not reliable. It works for the few images but failed to draw a line accurately for other mask images. Could anyone suggest an alternative approach?
this is the raw mask image:
This is how a line shall be drawn (considering object orientation)
Here is the code which represents my approach. I will highly appreciate any help from your side.
import numpy as np
import cv2
import matplotlib.pyplot as plt
image_bgr = cv2.imread(IMAGE_PATH)
mask = masks[2]
mask_uint8 = mask.astype(np.uint8) * 255
contours, _ = cv2.findContours(mask_uint8, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for c in contours:
# Calculate the centroid (center point) of the contour
M = cv2.moments(c)
cx = int(M['m10'] / M['m00'])
cy = int(M['m01'] / M['m00'])
cv2.drawContours(image_bgr, [c], -1, (255, 0, 0), 3)
cv2.circle(image_bgr, (cx, cy), 5, (0, 255, 0), -1)
left_side_point = tuple(c[c[:, :, 0].argmin()][0])
right_side_point = tuple(c[c[:, :, 0].argmax()][0])
center_point = (cx, cy)
left_center_point = ((left_side_point[0] + center_point[0]) // 2, (left_side_point[2] + center_point[2]) // 2)
right_center_point = ((right_side_point[0] + center_point[0]) // 2, (right_side_point[2] + center_point[2]) // 2)
cv2.line(image_bgr, left_side_point, left_center_point, (0, 0, 255), 2)
cv2.line(image_bgr, left_center_point, center_point, (0, 0, 255), 2)
cv2.line(image_bgr, center_point, right_center_point, (0, 0, 255), 2)
cv2.line(image_bgr, right_center_point, right_side_point, (0, 0, 255), 2)
plt.imshow(image_bgr)
plt.show()
´´´
[1]: https://i.sstatic.net/ZQja9VmS.png
[2]: https://i.sstatic.net/mLK66nAD.png
When I was doing particle scan analysis, I eventually went with a PCA-like approach:
%matplotlib notebook
import matplotlib.pyplot as plt
import numpy as np
import cv2
im = cv2.imread("mask.png", 0) # read as gray
y, x = np.where(im) # get non-zero elements
centroid = np.mean(x), np.mean(y) # get the centroid of the particle
x_diff = x - centroid[0] # center x
y_diff = y - centroid[1] # center y
cov_matrix = np.cov(x_diff, y_diff) # get the convariance
eigenvalues, eigenvectors = np.linalg.eig(cov_matrix) # apply EVD
indicesForSorting = np.argsort(eigenvalues)[::-1] # sort to get the primary first
eigenvalues = eigenvalues[indicesForSorting]
eigenvectors = eigenvectors[:, indicesForSorting]
plt.figure()
plt.imshow(im, cmap = "gray") # plot image
vecPrimary = eigenvectors[:, 0] * np.sqrt(eigenvalues[0])
plt.plot([centroid[0] - vecPrimary[0], centroid[0] + vecPrimary[0]],
[centroid[1] - vecPrimary[1], centroid[1] + vecPrimary[1]])
vecSecondary = eigenvectors[:, 1] * np.sqrt(eigenvalues[1])
plt.plot([centroid[0] - vecSecondary[0], centroid[0] + vecSecondary[0]],
[centroid[1] - vecSecondary[1], centroid[1] + vecSecondary[1]])
I like this approach because it also scales the line. If this is not desirable in your case, you can get the angle of this line and draw an infinite one, then mask it with the image. Hope this helps you further
Edit: drawing the lines with opencv
### same analysis as before
im = cv2.imread("mask.png")
pt1 = (int(centroid[0] - vecPrimary[0]), int(centroid[1] - vecPrimary[1]))
pt2 = (int(centroid[0] + vecPrimary[0]), int(centroid[1] + vecPrimary[1]))
cv2.line(im, pt1, pt2, (255, 0, 0), 2) # blue line
pt1 = (int(centroid[0] - vecSecondary[0]), int(centroid[1] - vecSecondary[1]))
pt2 = (int(centroid[0] + vecSecondary[0]), int(centroid[1] + vecSecondary[1]))
cv2.line(im, pt1, pt2, (0, 0, 255), 2) # redline
cv2.imwrite("maskWithLines.png", im)
Results:
EDIT: as I said in my answer, just multiply the vectors by a scale and then use the mask to biwise_and
:
im = cv2.imread("mask.png")
imGray = cv2.imread("mask.png", 0) # read imgray
y, x = np.where(imGray) # get non-zero elements
centroid = np.mean(x), np.mean(y) # get the centroid of the particle
x_diff = x - centroid[0] # center x
y_diff = y - centroid[1] # center y
cov_matrix = np.cov(x_diff, y_diff) # get the convariance
eigenvalues, eigenvectors = np.linalg.eig(cov_matrix) # apply EVD
indicesForSorting = np.argsort(eigenvalues)[::-1] # sort to get the primary first and secondary second
eigenvalues = eigenvalues[indicesForSorting] # sort eigenvalues
eigenvectors = eigenvectors[:, indicesForSorting] # sort eigenvectors
Scale = 100 # this can be adjusted, iut is actually not important as long as it is a very high value
vecPrimary = eigenvectors[:, 0] * np.sqrt(eigenvalues[0]) * Scale
vecSecondary = eigenvectors[:, 1] * np.sqrt(eigenvalues[1]) * Scale
pt1 = (int(centroid[0] - vecPrimary[0]), int(centroid[1] - vecPrimary[1]))
pt2 = (int(centroid[0] + vecPrimary[0]), int(centroid[1] + vecPrimary[1]))
cv2.line(im, pt1, pt2, (255, 0, 0), 2) # blue line
pt1 = (int(centroid[0] - vecSecondary[0]), int(centroid[1] - vecSecondary[1]))
pt2 = (int(centroid[0] + vecSecondary[0]), int(centroid[1] + vecSecondary[1]))
cv2.line(im, pt1, pt2, (0, 0, 255), 2) # red line
im = cv2.bitwise_and(im, im, mask = imGray) # mask the lines
cv2.imwrite("maskWithLines.png", im)
The results will look like this: