简体   繁体   中英

Checking if Adjacent Values are in a Numpy Matrix

So, I am currently trying to figure out a more optimal solution to determine connected components in an image. Currently, I have an array with coordinates that have certain values. I want to create groups of these coordinates based on if they are touching. I am using a numpy array, and currently I have to check if each value (top left, top middle, top right, middle-left, middle right, bottom left, bottom middle, bottom right) is in that array. I do so via this code:

for x in range (0, groupCoords.shape[0]):
            global tgroup
            xCoord = groupCoords.item((x,0))
            yCoord = groupCoords.item((x,1))
            new = np.array([[xCoord, yCoord]])
            if np.equal(Arr,[xCoord, yCoord+1]).all(1).any():
                tgroup = np.append(tgroup, [[xCoord,yCoord+1]], axis=0)
                new = np.append(new, [[xCoord,yCoord+1]], axis=0)
                index = np.argwhere((Arr == [xCoord,yCoord+1]).all(1))
                Arr = np.delete(Arr, (index), axis=0)

            if np.equal(Arr,[xCoord, yCoord-1]).all(1).any():
                tgroup = np.append(tgroup, [[xCoord, yCoord-1]],axis=0)
                new = np.append(new, [[xCoord,yCoord-1]], axis=0)
                index = np.argwhere((Arr == [xCoord,yCoord-1]).all(1))
                Arr = np.delete(Arr, (index), axis=0)

            if np.equal(Arr,[xCoord+1, yCoord]).all(1).any():
                tgroup = np.append(tgroup, [[xCoord+1,yCoord]],axis=0)
                new = np.append(new, [[xCoord+1,yCoord]], axis=0)
                index = np.argwhere((Arr == [xCoord+1,yCoord]).all(1))
                Arr = np.delete(Arr, (index), axis=0)

            if np.equal(Arr,[xCoord+1, yCoord+1]).all(1).any():
                tgroup = np.append(tgroup, [[xCoord+1,yCoord+1]],axis=0)
                new = np.append(new, [[xCoord+1,yCoord+1]], axis=0)
                index = np.argwhere((Arr == [xCoord+1,yCoord+1]).all(1))
                Arr = np.delete(Arr, (index), axis=0)

            if np.equal(Arr,[xCoord+1, yCoord-1]).all(1).any():
                tgroup = np.append(tgroup, [[xCoord+1,yCoord-1]],axis=0)
                new = np.append(new, [[xCoord+1,yCoord-1]], axis=0)
                index = np.argwhere((Arr == [xCoord+1,yCoord-1]).all(1))
                Arr = np.delete(Arr, (index), axis=0)

            if np.equal(Arr,[xCoord-1, yCoord]).all(1).any():
                tgroup = np.append(tgroup, [[xCoord-1,yCoord]],axis=0)
                new = np.append(new, [[xCoord-1,yCoord]], axis=0)
                index = np.argwhere((Arr == [xCoord-1,yCoord]).all(1))
                Arr = np.delete(Arr, (index), axis=0)

            if np.equal(Arr,[xCoord-1, yCoord+1]).all(1).any():
                tgroup = np.append(tgroup, [[xCoord-1,yCoord+1]],axis=0)
                new = np.append(new, [[xCoord-1,yCoord+1]], axis=0)
                index = np.argwhere((Arr == [xCoord-1,yCoord+1]).all(1))
                Arr = np.delete(Arr, (index), axis=0)

            if np.equal(Arr,[xCoord-1, yCoord-1]).all(1).any():
                tgroup = np.append(tgroup, [[xCoord-1,yCoord-1]],axis=0)
                new = np.append(new, [[xCoord-1,yCoord-1]], axis=0)
                index = np.argwhere((Arr == [xCoord-1,yCoord-1]).all(1))
                Arr = np.delete(Arr, (index), axis=0)

However, this clearly takes a significant amount of time if the image is large. I had the idea to just create an boolean matrix with dimensions of the width and height of the image, and then assign the value "true" to values in the matrix which correspond to pixels in the image (the image is black-white).

I was wondering, is it possible to, instead of having to check each value like that, determine if their are elements labled "true" directly surrounding another "true" value?

This is what the input array would look like:

[
 [0 0]
 [0 1]
 [0 2]
 [10 2]

]

The output would look like

[
 [0 0]
 [0 1]
 [0 2]
]

The function I am hoping to refine would check if the "true" values are touching, and create a 'network' of all values touching (it would keep running with the new values found).

Approach #1

We could get the euclidean distances and see if any of the distances is within sqrt(2) , which would cover up-down with distance = 1 and diagonal with distance = sqrt(2) . This would give us a mask, which when indexed into the group coordinates array would give us the connected ones from it.

Thus, an implementation using Scipy's cdist for getting those euclidean distances, would be -

from scipy.spatial.distance import cdist

out = groupCoords[(cdist(groupCoords,Arr)<1.5).any(1)]

Sample run -

In [401]: Arr
Out[401]: 
array([[ 5,  4],
       [11, 12],
       [ 5,  3],
       [ 1,  3],
       [15,  8],
       [55, 21]])

In [402]: groupCoords
Out[402]: 
array([[2, 3],  # In neighbourhood of (1,3)
       [5, 6],
       [6, 2],  # In neighbourhood of (5,3)
       [5, 3],  # In neighbourhood of (5,4)
       [5, 8]])

In [403]: groupCoords[(cdist(groupCoords,Arr)<1.5).any(1)]
Out[403]: 
array([[2, 3],
       [6, 2],
       [5, 3]])

Approach #2

Another way would be checking with absolute element-wise differences between the first columns of the two arrays and similarly for the second columns. Finally, get a joint mask from these two masks and checking any match and indexing again into group array for the filtered coordinates.

Thus, implementation for such a method would be -

col0_mask = (np.abs(groupCoords[:,0,None] - Arr[:,0])<=1)
col1_mask = (np.abs(groupCoords[:,1,None] - Arr[:,1])<=1)
out = groupCoords[(col0_mask & col1_mask).any(1)]

Approach #3

Another approach and probably would be better if you have Arr as a boolean array instead of as a 2-column coordinates array. The idea is to dilate this boolean array of Arr and then see which coordinates from groupCoords would also lie in this dilated image. For the dilation, we would use a 3 x 3 kernel of all ones to cover all of those neighborhood places. For detecting those common points, we need to draw an image with those groupCoords .

Thus, the code would be -

from scipy.ndimage.morphology import binary_dilation

img = np.zeros(Arr.shape,dtype=bool)
img[groupCoords[:,0],groupCoords[:,1]] = 1
out = np.argwhere(binary_dilation(Arr,np.ones((3,3))) & img)

Sample run -

In [444]: # Inputs : groupCoords and let's create a sample array for Arr
     ...: groupCoords = np.array([[2,3],[5,6],[6,2],[5,3],[5,8]])
     ...: 
     ...: Arr_Coords = np.array([[5,4],[11,12],[5,3],[1,3],[15,8],[55,21]])
     ...: Arr = np.zeros(Arr_Coords.max(0)+1,dtype=bool)
     ...: Arr[Arr_Coords[:,0], Arr_Coords[:,1]] = 1
     ...: 

In [445]: img = np.zeros(Arr.shape,dtype=bool)
     ...: img[groupCoords[:,0],groupCoords[:,1]] = 1
     ...: out = np.argwhere(binary_dilation(Arr,np.ones((3,3))) & img)
     ...: 

In [446]: out
Out[446]: 
array([[2, 3],
       [5, 3],
       [6, 2]])

Depending on the ultimate goal of your code, you might find scipy.ndimage.label and its relatives useful.

For example,

In [44]: from scipy.ndimage import label

In [45]: x
Out[45]: 
array([[ True,  True, False, False,  True],
       [False, False, False,  True,  True],
       [False,  True, False,  True, False],
       [ True,  True, False, False, False]], dtype=bool)

In [46]: x.astype(int)  # More concise, easier to read
Out[46]: 
array([[1, 1, 0, 0, 1],
       [0, 0, 0, 1, 1],
       [0, 1, 0, 1, 0],
       [1, 1, 0, 0, 0]])

label returns two values. The first is an array with the same size as the input array. Each distinct connected component in the input is assigned an integer value, starting at 1. The background is 0. The second return value is the number of components found.

In [47]: labeled_arr, nlabels = label(x)

In [48]: nlabels
Out[48]: 3

In [49]: labeled_arr
Out[49]: 
array([[1, 1, 0, 0, 2],
       [0, 0, 0, 2, 2],
       [0, 3, 0, 2, 0],
       [3, 3, 0, 0, 0]], dtype=int32)

In the follwing, where(labeled_array = i) returns a tuple containing two arrays. These arrays are the row and column indices, resp., of the connected components:

In [50]: for i in range(1, nlabels+1):
    ...:     print(where(labeled_arr == i))
    ...:     
(array([0, 0]), array([0, 1]))
(array([0, 1, 1, 2]), array([4, 3, 4, 3]))
(array([2, 3, 3]), array([1, 0, 1]))

You can zip those together to convert them to lists of (row, col) pairs:

In [52]: for i in range(1, nlabels+1):
    ...:     print(list(zip(*where(labeled_arr == i))))
    ...:     
[(0, 0), (0, 1)]
[(0, 4), (1, 3), (1, 4), (2, 3)]
[(2, 1), (3, 0), (3, 1)]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM