Jack O'neill Jack O'neill - 17 days ago 9
Python Question

Get indices of feature position on image mask in python,opencv2,numpy

I am trying to extract index / position of two areas of a image mask and I am using a combination of python, opencv2, numpy and scipy.

I have a binary

mask
with the same size as the image.
After generating labels and sizes of it with

label_im, nb_labels = ndimage.label(mask)
sizes = ndimage.sum(mask, label_im, range(nb_labels + 1))


With this information, I am able to extract the biggest areas.

Let's say we have a 10x10 matrix:

1 1 1 0 0 0 0 0 0 0
1 1 1 0 0 0 0 0 0 0
1 1 1 0 0 0 0 2 2 0
0 0 0 0 0 0 0 2 2 0
0 3 3 3 3 0 0 2 2 0
0 3 3 3 3 0 0 2 2 0
0 3 3 3 3 0 0 2 2 0
0 3 3 3 3 0 0 2 2 0
0 3 3 3 3 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0


I have 3 different areas (1,2,3) and I already know, that I need to further analyse area 2 and 3 (because those are the two biggest).

Now I want to find the index of the pixels which are


  1. topmost leftmost of the regions - marked below as (n)

  2. lowermost rightmost of the regions - marked below with [n]



same matrix as above:

1 1 1 0 0 0 0 0 0 0
1 1 1 0 0 0 0 0 0 0
1 1 1 0 0 0 0 (2) 2 0
0 0 0 0 0 0 0 2 2 0
0 (3) 3 3 3 0 0 2 2 0
0 3 3 3 3 0 0 2 2 0
0 3 3 3 3 0 0 2 2 0
0 3 3 3 3 0 0 2 [2] 0
0 3 3 3 [3] 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0


How is the fastest way to do that?

Problem 1:
I'm struggling with the syntax for those matrix conditionals.
For example, I try:

mask2 = mask[label_im == 2]


It should get a binary mask again, but with only the cells set on which the original mask has a 2.
I'm not exactly sure what the result of the above generates. And I'm therefor not able to process it further.

Problem 2:
After that I will have to do something like finding the the indices of Row/Column where the topmost leftmost pixel is set.
Same to lowermost point.

For example for region 2, the results should be: (like the matrix above)

point1-X = 7
point1-Y = 2
point2-X = 8
point2-Y = 7


where X stands for column, and Y for row (starting at index 0)

Or is there a better way to solve a problem like that with the combination of python/opencv2/numpy/scipy?

Answer

NumPy based solution

On a flattened version of the input image array, the topmost leftmost places would be the first ones, whereas lowermost rightmost would be the last ones. So, one trick would be to get the flattened indices of those two regions with np.nonzero and simply look for min and max indices to get those expected outputs respectively. Finally, we would use np.unravel_index to retrieve back row, col indices corresponding to original 2D format.

Thus, one approach would be -

# Flattend indices for those two regions using the given labels
idx0 = np.nonzero(a.ravel()==2)[0]
idx1 = np.nonzero(a.ravel()==3)[0]

# Get the min, max indices for them. Use np.unravel_index to retrieve 
# back row, col indices corresponding to original format of 2D input.
idxs = [idx0.min(), idx0.max(), idx1.min(), idx1.max()]
out = np.column_stack(np.unravel_index(idxs,a.shape))

Sample run -

In [137]: a
Out[137]: 
array([[1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
       [1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
       [1, 1, 1, 0, 0, 0, 0, 2, 2, 0],
       [0, 0, 0, 0, 0, 0, 0, 2, 2, 0],
       [0, 3, 3, 3, 3, 0, 0, 2, 2, 0],
       [0, 3, 3, 3, 3, 0, 0, 2, 2, 0],
       [0, 3, 3, 3, 3, 0, 0, 2, 2, 0],
       [0, 3, 3, 3, 3, 0, 0, 2, 2, 0],
       [0, 3, 3, 3, 3, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])

In [138]: idx0 = np.nonzero(a.ravel()==2)[0]
     ...: idx1 = np.nonzero(a.ravel()==3)[0]
     ...: idxs = [idx0.min(), idx0.max(), idx1.min(), idx1.max()]
     ...: out = np.column_stack(np.unravel_index(idxs,a.shape))
     ...: 

In [139]: out
Out[139]: 
array([[2, 7],  # topmost leftmost of region -1
       [7, 8],  # lowermost rightmost of region -1
       [4, 1],  # topmost leftmost of region -2
       [8, 4]]) # lowermost rightmost of region -2

OpenCV based solution

OpenCV's cv2.findContours could be used to find the contours, which in itself is a pretty efficient implementation. Thus, we would be restricting the finding of indices to the contours only for a performance-oriented solution. Shown below is the function to get the topmost leftmost and lowermost rightmost row, column indices for each region -

def TL_LR(a, label):
    _,contours,hierarchy = cv2.findContours((a==label).astype('uint8'),\
                                            cv2.RETR_TREE,cv2.RETR_LIST)
    idx = contours[0].reshape(-1,2)
    lidx = idx[:,0] + a.shape[1]*idx[:,1]
    return np.unravel_index([lidx.min(), lidx.max()],a.shape)

Please note that for OpenCV versions prior to 3.0, we would be getting two outputs only from cv2.findContours. So, for those versions, skip the _ at that step.

Let's use it on the given sample -

In [188]: TL_LR(a,2)
Out[188]: (array([2, 7]), array([7, 8]))

In [189]: TL_LR(a,3)
Out[189]: (array([4, 8]), array([1, 4]))

In [190]: out  # Output from previous approach
Out[190]: 
array([[2, 7],
       [7, 8],
       [4, 1],
       [8, 4]])