I have a batch of similar images which I need to analyze.
After
skimage.morphology.reconstruction
(example of usage here)I get something like this image:
I'm interested in the edges of shape, specifically the steepness of the slope along vertical lines in the original grayscale image. I thought I could use Canny to get the contours which indeed gave me:
My code:
im_th= cv2.inRange(img, 0, 500, cv2.THRESH_BINARY)
seed = np.copy(im_th)
seed[1:-1, 1:-1] = im_th.max()
mask = im_th
filled = reconstruction(seed, mask, method='erosion').astype(np.uint8)
kernel = cv2.getStructuringElement(cv2.MORPH_CROSS,(4,4))
closed = cv2.morphologyEx(filled,cv2.MORPH_CLOSE, kernel=kernel)
edges = cv2.Canny(cv2.medianBlur(closed, 5), 50, 150)
Per suggestions of Christoph Rackwitz and mimocha, I attempted using findContours
and HoughLinesP
. Both results look promising, but require further work to be of use.
Code:
contours, hierarchy = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contour_img = cv2.drawContours(cv2.cvtColor(closed, cv2.COLOR_GRAY2BGR), contours, -1, (255,0,0), 3)
Resulting image (overlay over the closed image):
The contours are found nicely, I still 151 contour lines. I would want to smoothen the result and get less lines.
Code:
threshold = 15
min_line_length = 30
max_line_gap = 200
line_image = np.copy(cv2.cvtColor(closed, cv2.COLOR_GRAY2BGR)) * 0
lines = cv2.HoughLinesP(edges, 1, np.pi / 180, threshold, None, min_line_length, max_line_gap)
for line in lines:
for x1,y1,x2,y2 in line:
cv2.line(line_image,(x1,y1),(x2,y2),(255,0,0),5)
lines_edges = cv2.addWeighted(cv2.cvtColor(closed, cv2.COLOR_GRAY2BGR), 0.2, line_image, 1, 0)
Resulting image:
HoughLinesP indeed guarantees straight lines, and it find the lines nicely, but I still have some lines which are too "thick" (you can see in the image some thick lines which are actually parallel lines). Thus I need some method for refining this results.
I would find the two relevant sections of the image by finding the wider bars, and cutting there. This is a bit of code using DIPlib (disclaimer: I'm an author), but it's quite easy to do this with just about any other package too.
import diplib as dip
import math
img = dip.ImageRead('5oC9n.png')
# Colapse the x axis by summation
y = dip.Sum(img, process=[True, False]).Squeeze()
# In our 1D result, find the transition points
y = dip.Abs(dip.Dx(y))
y = dip.Shrinkage(y, 5) # ignore any local maxima below 5
maxima = dip.SubpixelMaxima(y)
# We want segments between 1st and 2nd, and between 3rd and 4th.
# We'll add a bit of margin too
assert(len(maxima) == 4)
# First segment
top = math.ceil(maxima[0].coordinates[0]) + 2
bottom = math.floor(maxima[1].coordinates[0]) - 2
img1 = img[:, top:bottom]
# Second segment
top = math.ceil(maxima[2].coordinates[0]) + 2
bottom = math.floor(maxima[3].coordinates[0]) - 2
img2 = img[:, top:bottom]
Next, finding the slopes of the lines, could be done by fitting a straight line to each of the edges. This other answer of mine does exactly that (the answer computes the distance between two parallel edges, but in doing so it fits a straight line and finds its normal (i.e. the slope).