Python 如何检查二维NumPy数组中是否包含特定的值模式?

Python 如何检查二维NumPy数组中是否包含特定的值模式?,python,arrays,numpy,pattern-matching,Python,Arrays,Numpy,Pattern Matching,我有一个大的NumPy.arrayfield\u array和一个较小的数组match\u array,两者都由int值组成。使用以下示例,如何检查字段数组的任何匹配数组形段是否包含与匹配数组中的值完全对应的值 import numpy raw_field = ( 24, 25, 26, 27, 28, 29, 30, 31, 23, \ 33, 34, 35, 36, 37, 38, 39, 40, 32, \

我有一个大的
NumPy.array
field\u array
和一个较小的数组
match\u array
,两者都由
int
值组成。使用以下示例,如何检查
字段数组
的任何匹配数组形段是否包含与
匹配数组
中的值完全对应的值

import numpy
raw_field = ( 24,  25,  26,  27,  28,  29,  30,  31,  23, \
              33,  34,  35,  36,  37,  38,  39,  40,  32, \
             -39, -38, -37, -36, -35, -34, -33, -32, -40, \
             -30, -29, -28, -27, -26, -25, -24, -23, -31, \
             -21, -20, -19, -18, -17, -16, -15, -14, -22, \
             -12, -11, -10,  -9,  -8,  -7,  -6,  -5, -13, \
              -3,  -2,  -1,   0,   1,   2,   3,   4,  -4, \
               6,   7,   8,   4,   5,   6,   7,  13,   5, \
              15,  16,  17,   8,   9,  10,  11,  22,  14)
field_array = numpy.array(raw_field, int).reshape(9,9)
match_array = numpy.arange(12).reshape(3,4)

这些示例应该返回
True
,因为
匹配数组所描述的模式在
[6:9,3:7]
上对齐,一种解决方案是在
数组块中一次搜索整个
搜索(一个“块”是
搜索
形状的切片)直到找到匹配的段或
数组的
搜索\u用尽。我可以使用它来获取匹配块的坐标,或者通过发送
True
False
来获取
return\u coords
可选参数的
bool
结果

def seek_array(search_in, search_for, return_coords = False):
    """Searches for a contiguous instance of a 2d array `search_for` within a larger `search_in` 2d array.
If the optional argument return_coords is True, the xy coordinates of the zeroeth value of the first matching segment of search_in will be returned, or None if there is no matching segment.
If return_coords is False, a boolean will be returned.
 * Both arrays must be sent as two-dimensional!"""
    si_x, si_y = search_in.shape
    sf_x, sf_y = search_for.shape

    for y in xrange(si_y-sf_y+1):
        for x in xrange(si_x-sf_x+1):
            if numpy.array_equal(search_for, search_in[x:x+sf_x, y:y+sf_y]):
                return (x,y) if return_coords else True  # don't forget that coordinates are transposed when viewing NumPy arrays!
    return None if return_coords else False

我想知道
NumPy
是否还没有一个功能可以做同样的事情,尽管…

NumPy中没有内置这样的搜索功能,但它肯定可以在NumPy中实现

只要阵列不是太大*,就可以使用滚动窗口方法:

from skimage.util import view_as_windows

windows = view_as_windows(field_array, match_array.shape)
函数
view\u as\u windows
完全是用NumPy编写的,因此如果您没有skimage,您可以随时从中复制代码

然后,要查看子数组是否出现在较大的数组中,可以编写:

>>> (windows == match_array).all(axis=(2,3)).any()
True
>>> (windows == match_array).all(axis=(2,3)).nonzero()
(array([6]), array([3]))
要查找子数组左上角匹配位置的索引,可以编写:

>>> (windows == match_array).all(axis=(2,3)).any()
True
>>> (windows == match_array).all(axis=(2,3)).nonzero()
(array([6]), array([3]))
这种方法也适用于高维数组


*虽然数组
windows
不占用额外的内存(只有跨步和形状会更改以创建数据的新视图),但写入
windows==match_数组
会创建一个大小为(7、6、3、4)的布尔数组,即504字节的内存。如果使用非常大的阵列,这种方法可能不可行。

方法#1

这种方法源自to,其设计目的是将滑动块从2D数组重新排列到列中。因此,为了解决我们这里的问题,可以将来自
field\u array
的滑块堆叠为列,并与
match\u array
的列向量版本进行比较

这里是重新排列/堆叠功能的正式定义-

def im2col(A,BLKSZ):   

    # Parameters
    M,N = A.shape
    col_extent = N - BLKSZ[1] + 1
    row_extent = M - BLKSZ[0] + 1

    # Get Starting block indices
    start_idx = np.arange(BLKSZ[0])[:,None]*N + np.arange(BLKSZ[1])

    # Get offsetted indices across the height and width of input array
    offset_idx = np.arange(row_extent)[:,None]*N + np.arange(col_extent)

    # Get all actual indices & index into input array for final output
    return np.take (A,start_idx.ravel()[:,None] + offset_idx.ravel())
为了解决我们的问题,下面是基于
im2col
-

# Get sliding blocks of shape same as match_array from field_array into columns
# Then, compare them with a column vector version of match array.
col_match = im2col(field_array,match_array.shape) == match_array.ravel()[:,None]

# Shape of output array that has field_array compared against a sliding match_array
out_shape = np.asarray(field_array.shape) - np.asarray(match_array.shape) + 1

# Now, see if all elements in a column are ONES and reshape to out_shape. 
# Finally, find the position of TRUE indices
R,C = np.where(col_match.all(0).reshape(out_shape))
问题中给定样本的输出为-

In [151]: R,C
Out[151]: (array([6]), array([3]))
方法#2

鉴于opencv已经有了一个模板匹配函数,可以进行差异平方运算,您可以使用该函数并查找零差异,这将是您的匹配位置。因此,如果您可以访问cv2(opencv模块),那么实现将如下所示-

import cv2
from cv2 import matchTemplate as cv2m

M = cv2m(field_array.astype('uint8'),match_array.astype('uint8'),cv2.TM_SQDIFF)
R,C = np.where(M==0)
给我们-

In [204]: R,C
Out[204]: (array([6]), array([3]))

标杆管理 本节比较了为解决此问题而建议的所有方法的运行时。本节中列出的各种方法都归功于它们的贡献者

方法定义-

def seek_array(search_in, search_for, return_coords = False):
    si_x, si_y = search_in.shape
    sf_x, sf_y = search_for.shape
    for y in xrange(si_y-sf_y+1):
        for x in xrange(si_x-sf_x+1):
            if numpy.array_equal(search_for, search_in[x:x+sf_x, y:y+sf_y]):
                return (x,y) if return_coords else True
    return None if return_coords else False

def skimage_based(field_array,match_array):
    windows = view_as_windows(field_array, match_array.shape)
    return (windows == match_array).all(axis=(2,3)).nonzero()

def im2col_based(field_array,match_array):   
    col_match = im2col(field_array,match_array.shape)==match_array.ravel()[:,None]
    out_shape = np.asarray(field_array.shape) - np.asarray(match_array.shape) + 1  
    return np.where(col_match.all(0).reshape(out_shape))

def cv2_based(field_array,match_array):
    M = cv2m(field_array.astype('uint8'),match_array.astype('uint8'),cv2.TM_SQDIFF)
    return np.where(M==0)
运行时测试-

案例1(问题样本数据):

案例2(较大的随机数据):


为了补充已经发布的答案,我想补充一个,考虑到由于浮点精度导致的错误,例如矩阵来自图像处理,其中数字需要进行浮点运算

您可以递归较大矩阵的索引,搜索较小矩阵。然后可以提取与较小矩阵大小匹配的较大矩阵的子矩阵

如果“大”矩阵的子矩阵和“小”矩阵的内容都匹配,则有一个匹配

下面的示例演示如何返回找到匹配的大型矩阵中位置的第一个索引。扩展此函数以返回找到的匹配位置数组(如果这是目的的话)是很简单的

import numpy as np

def find_submatrix(a, b):
    """ Searches the first instance at which 'b' is a submatrix of 'a', iterates
        rows first. Returns the indexes of a at which 'b' was found, or None if
        'b' is not contained within 'a'"""
    a_rows=a.shape[0]
    a_cols=a.shape[1]

    b_rows=b.shape[0]
    b_cols=b.shape[1]

    row_diff = a_rows - b_rows
    col_diff = a_cols - b_cols

    for idx_row in np.arange(row_diff):
        for idx_col in np.arange(col_diff):
            row_indexes = [idx + idx_row for idx in np.arange(b_rows)]
            col_indexes = [idx + idx_col for idx in np.arange(b_cols)]

            submatrix_indexes = np.ix_(row_indexes, col_indexes)
            a_submatrix = a[submatrix_indexes]

            are_equal = np.allclose(a_submatrix, b)  # allclose is used for floating point numbers, if they
                                                     # are close while comparing, they are considered equal.
                                                     # Useful if your matrices come from operations that produce
                                                     # floating point numbers.
                                                     # You might want to fine tune the parameters to allclose()
            if (are_equal):
                return[idx_col, idx_row]

    return None
使用上述函数,您可以运行以下示例:

large_mtx = np.array([[1,  2, 3, 7, 4, 2, 6],
                      [4,  5, 6, 2, 1, 3, 11],
                      [10, 4, 2, 1, 3, 7, 6],
                      [4,  2, 1, 3, 7, 6, -3],
                      [5,  6, 2, 1, 3, 11, -1],
                      [0,  0, -1, 5, 4, -1, 2],
                      [10, 4, 2, 1, 3, 7, 6],
                      [10, 4, 2, 1, 3, 7, 6] 
                     ])

# Example 1: An intersection at column 2 and row 1 of large_mtx
small_mtx_1 = np.array([[4, 2], [2,1]])
intersect = find_submatrix(large_mtx, small_mtx_1)
print "Example 1, intersection (col,row): " + str(intersect)

# Example 2: No intersection
small_mtx_2 = np.array([[-14, 2], [2,1]])
intersect = find_submatrix(large_mtx, small_mtx_2)
print "Example 2, intersection (col,row): " + str(intersect)
将打印:

Example 1, intersection: [1, 2] Example 2, intersection: None 示例1,交叉点:[1,2] 示例2,交叉点:无
下面是一个使用
stride\u tricks
模块中的
as\u stride()
函数的解决方案

import numpy as np
from numpy.lib.stride_tricks import as_strided

# field_array (I modified it to have two matching arrays)
A = np.array([[ 24,  25,  26,  27,  28,  29,  30,  31,  23],
              [ 33,   0,   1,   2,   3,  38,  39,  40,  32],
              [-39,   4,   5,   6,   7, -34, -33, -32, -40],
              [-30,   8,   9,  10,  11, -25, -24, -23, -31],
              [-21, -20, -19, -18, -17, -16, -15, -14, -22],
              [-12, -11, -10,  -9,  -8,  -7,  -6,  -5, -13],
              [ -3,  -2,  -1,   0,   1,   2,   3,   4,  -4],
              [  6,   7,   8,   4,   5,   6,   7,  13,   5],
              [ 15,  16,  17,   8,   9,  10,  11,  22,  14]])

# match_array
B = np.arange(12).reshape(3,4)


# Window view of A
A_w = as_strided(A, shape=(A.shape[0] - B.shape[0] + 1,
                           A.shape[1] - B.shape[1] + 1,
                           B.shape[0], B.shape[1]),
                    strides=2*A.strides).reshape(-1, B.shape[0], B.shape[1])

match = (A_w == B).all(axis=(1,2))
我们还可以在一个图中找到每个匹配块的第一个元素的索引

where = np.where(match)[0]
ind_flat = where + (B.shape[1] - 1)*(np.floor(where/(A.shape[1] - B.shape[1] + 1)).astype(int))
ind = [tuple(row) for row in np.array(np.unravel_index(ind_flat, A.shape)).T]
结果

print(match.any())
True

print(ind)
[(1, 1), (6, 3)]

这是对这个问题的回答,因为代码正确地完成了我所需要的功能,尽管可能有更有效的方法。一般来说,政策是拒绝回答问题本身,是吗?我的意思是,我们可以选择直接回答我们的问题,正是因为这个原因,我想…我可能应该选择一种不同的方式来开始回答文章。我现在就编辑它。我只是觉得“如何”问题比“更好的方式”问题更适合SO的格式,因为像“更好”这样的主观词汇往往会以不同意见的形式带来麻烦。“我怎么能——?”问题只问方法;为此,我提供了一个解决方案,我希望其他人提供更多更好的解决方案。仅此而已。可能值得指出的是,
raw_字段
中的x和y坐标最终会在我注意到的对齐切片中被转置……出于好奇,这些方法与ajcr的
skimage
解决方案或我发布的逐块方法相比如何?(如果您不知道或不想测试它,这是完全可以理解的。XD)cv2的
cv2
解决方案看起来非常有趣,但我没有这个模块,也不知道它的任何内容。不过,我会把它放在我要结帐的物品清单上@Augusta无需担心,添加了基准代码和结果。希望这有帮助!哇,真是太神奇了!我原以为我的普通支票会很慢,但我没想到会有更快的替代品!