Reputation: 327
I'm looking for an algorithm that will detect the end of a curved line. I'm going to convert a binary image into a point cloud as coordinates, and I need to find the end of the line so I can start another algorithm.
I was thinking of taking the average of vectors for the N nearest '1' pixels to each point, and saying that the pixel with the longest vector must be an endpoint, because if a point is in the middle of a line then the average of the vectors will cancel out. However, I figure this must be a problem that is well known in image processing so I thought I'd throw it up here to see if anybody knows a 'proper' algorithm.
Upvotes: 4
Views: 742
Reputation: 71
If you apply thinning to the line, so your line is just one pixel thick, You can leverage morphologyEX and use MORPH_HITMISS in OpenCV. Essentially you create a template (kernel or filter) for every possible corner (there are 8 possible) and convolve by each one. The result of each convolution will be 1 in the place where the kernel matches and 0 otherwise. So you can do the same manually if you feell that you can do a better job in c.
here is an example. It takes as input_image any image of zeros and ones where the lines are one pixel thick.
import numpy as np
import cv2
import matplotlib.pylab as plt
def find_endoflines(input_image, show=0):
kernel_0 = np.array((
[-1, -1, -1],
[-1, 1, -1],
[-1, 1, -1]), dtype="int")
kernel_1 = np.array((
[-1, -1, -1],
[-1, 1, -1],
[1,-1, -1]), dtype="int")
kernel_2 = np.array((
[-1, -1, -1],
[1, 1, -1],
[-1,-1, -1]), dtype="int")
kernel_3 = np.array((
[1, -1, -1],
[-1, 1, -1],
[-1,-1, -1]), dtype="int")
kernel_4 = np.array((
[-1, 1, -1],
[-1, 1, -1],
[-1,-1, -1]), dtype="int")
kernel_5 = np.array((
[-1, -1, 1],
[-1, 1, -1],
[-1,-1, -1]), dtype="int")
kernel_6 = np.array((
[-1, -1, -1],
[-1, 1, 1],
[-1,-1, -1]), dtype="int")
kernel_7 = np.array((
[-1, -1, -1],
[-1, 1, -1],
[-1,-1, 1]), dtype="int")
kernel = np.array((kernel_0,kernel_1,kernel_2,kernel_3,kernel_4,kernel_5,kernel_6, kernel_7))
output_image = np.zeros(input_image.shape)
for i in np.arange(8):
out = cv2.morphologyEx(input_image, cv2.MORPH_HITMISS, kernel[i,:,:])
output_image = output_image + out
return output_image
if show == 1:
show_image = np.reshape(np.repeat(input_image, 3, axis=1),(input_image.shape[0],input_image.shape[1],3))*255
show_image[:,:,1] = show_image[:,:,1] - output_image *255
show_image[:,:,2] = show_image[:,:,2] - output_image *255
plt.imshow(show_image)
Upvotes: 0
Reputation: 51226
If the line will only ever be one or perhaps two pixels thick, you can use the approach suggested by Malcolm McLean in a comment.
Otherwise, one way to do this is to compute, for each red pixel, the red pixel in the same component that is furthest away, as well as how far away that furthest pixel is. (In graph theory terms, the distance between these two pixels is the eccentricity of each pixel.) Pixels near the end of a long line will have the greatest eccentricities, because the shortest path between them and points at the other end of the line is long. (Notice that, whatever the maximum eccentricity turns out to be, there will be at least two pixels having it, since the distance from a to b is the same as the distance from b to a.)
If you have n red pixels, all eccentricities (and corresponding furthest pixels) can be computed in O(n^2) time: for each pixel in turn, start a BFS at that pixel, and take the deepest node you find as its furthest pixel (there may be several; any will do). Each BFS runs in O(n) time, because there are only a constant number of edges (4 or 8, depending on how you model pixel connectivity) incident on any pixel.
For robustness you might consider taking the top 10 or 50 (etc.) pixel pairs and checking that they form 2 well-separated, well-defined clusters. You could then take the average position within each cluster as your 2 endpoints.
Upvotes: 2