Jack O'neill - 1 year ago 66

Python Question

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`

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

- topmost leftmost of the regions - marked below as (n)
- 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?

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.

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 Source

**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]])
```