我有一个n x n数组,想要接收它的轮廓值。例如,
[4,5,6,7]
[2 下,2,6,第3]
[4 下,4,9,的 4
[8,1,6,1]
从这里开始,我会得到这个[4,5,6,7,3,4,1,6,1,8,4,2]
(见粗体)
基本上,获得2D阵列边缘所有值的一维数组的最强有效方法是什么? 我问,因为我假设有一个numPy函数可以帮助解决这个问题(我还没有找到它!),而不是用循环手动执行它?
答案 0 :(得分:2)
In [1]: arr=np.arange(16).reshape(4,4)
In [2]: arr
Out[2]:
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15]])
以顺时针顺序执行此操作的相对直接的方法是:
In [5]: alist=[arr[0,:-1], arr[:-1,-1], arr[-1,::-1], arr[-2:0:-1,0]]
In [6]: alist
Out[6]: [array([0, 1, 2]), array([ 3, 7, 11]), array([15, 14, 13, 12]), array([8, 4])]
In [7]: np.concatenate(alist)
Out[7]: array([ 0, 1, 2, 3, 7, 11, 15, 14, 13, 12, 8, 4])
从某种意义上说,它是一个循环,因为我必须构建4个切片。但如果4与n
相比较小,则价格较小。它必须在某种程度上连接。
如果顺序无关紧要,我们可以简化一些切片(例如忘记相反的顺序等)。
alist=[arr[0,:], arr[1:,-1], arr[-1,:-1], arr[1:-1,0]]
如果我不关心订单,或重复计算我可以使用的角落:
np.array([arr[[0,n],:], arr[:,[0,n]].T]).ravel()
消除重复的角落
In [18]: np.concatenate((arr[[0,n],:].ravel(), arr[1:-1,[0,n]].ravel()))
Out[18]: array([ 0, 1, 2, 3, 12, 13, 14, 15, 4, 7, 8, 11])
答案 1 :(得分:2)
这是一种矢量化方法,用于创建此类边缘像素/元素的蒙版,然后简单地索引到数组中以获取这些 -
def border_elems(a, W): # Input array : a, Edgewidth : W
n = a.shape[0]
r = np.minimum(np.arange(n)[::-1], np.arange(n))
return a[np.minimum(r[:,None],r)<W]
同样,这并不完全意味着性能,但更多的情况可能会改变边缘宽度或仅创建此类边缘元素的掩模。掩码将是:np.minimum(r[:,None],r)<W
,如在最后一步中创建的那样。
示例运行 -
In [89]: a
Out[89]:
array([[49, 49, 12, 90, 42],
[91, 58, 92, 16, 78],
[97, 19, 58, 84, 84],
[86, 31, 80, 78, 69],
[29, 95, 38, 51, 92]])
In [90]: border_elems(a,1)
Out[90]: array([49, 49, 12, 90, 42, 91, 78, 97, 84, 86, 69, 29, 95, 38, 51, 92])
In [91]: border_elems(a,2) # Note this will select all but the center one : 58
Out[91]:
array([49, 49, 12, 90, 42, 91, 58, 92, 16, 78, 97, 19, 84, 84, 86, 31, 80,
78, 69, 29, 95, 38, 51, 92])
对于通用形状,我们可以这样扩展 -
def border_elems_generic(a, W): # Input array : a, Edgewidth : W
n1 = a.shape[0]
r1 = np.minimum(np.arange(n1)[::-1], np.arange(n1))
n2 = a.shape[1]
r2 = np.minimum(np.arange(n2)[::-1], np.arange(n2))
return a[np.minimum(r1[:,None],r2)<W]
2D convolution
基于通用形状的解决方案
这是另一个2D convolution
来处理通用的2D形状 -
from scipy.signal import convolve2d
k = np.ones((3,3),dtype=int) # kernel
boundary_elements = a[convolve2d(np.ones(a.shape,dtype=int),k,'same')<9]
示例运行 -
In [36]: a
Out[36]:
array([[4, 3, 8, 3, 1],
[1, 5, 6, 6, 7],
[9, 5, 2, 5, 9],
[2, 2, 8, 4, 7]])
In [38]: k = np.ones((3,3),dtype=int)
In [39]: a[convolve2d(np.ones(a.shape,dtype=int),k,'same')<9]
Out[39]: array([4, 3, 8, 3, 1, 1, 7, 9, 9, 2, 2, 8, 4, 7])
答案 2 :(得分:1)
假设您的列表采用以下格式:
l = [
[4, 5, 6, 7],
[2, 2, 6, 3],
[4, 4, 9, 4],
[8, 1, 6, 1]
]
使用列表推导,您可以通过这种快速的单行程获得您想要的效果:
out = list(l[0]) + # [4, 5, 6, 7]
list([i[-1] for i in l[1:-1]]) + # [3, 4]
list(reversed(l[-1])) + # [1, 6, 1, 8]
list(reversed([i[0] for i in l[1:-1]])) # [4, 2]
print(out) # gives [4, 5, 6, 7, 3, 4, 1, 6, 1, 8, 4, 2]
无论你有一个普通的python列表还是一个numpy数组,这都有效。
关于效率,在20000x20000矩阵上使用%timeit
,此方法花了16.4ms
。
l = np.random.random(20000, 20000)
%timeit list(l[0]) + list(...) + list(...) + list(...)
100 loops, best of 3: 16.4 ms per loop
我确信有更有效的方法可以完成这项任务,但我认为这对单线解决方案来说非常有用。
答案 3 :(得分:1)
您也可以使用itertools.groupby
和list comprehension
,如下例所示:
a = [
[4,5,6,7],
[2,2,6,3],
[4,4,9,4],
[8,1,6,1],
]
from itertools import groupby
def edges(a = list):
final, i = [], []
for k, _ in groupby(a[1:-1], lambda x : [x[0], x[-1]]):
i += k
return a[0] + [k for n in range(1,len(i), 2) for k in i[n:n+1]] + a[-1][::-1] + [k for n in range(0, len(i), 2) for k in i[n:n+1] ][::-1]
输出:
print(edges(a))
>>> [4, 5, 6, 7, 3, 4, 1, 6, 1, 8, 4, 2]
使用timeit
进行测试:
a = [
[4,5,6,7],
[2,2,6,3],
[4,4,9,4],
[8,1,6,1],
]
from itertools import groupby
def edges():
final, i = [], []
for k, _ in groupby(a[1:-1], lambda x : [x[0], x[-1]]):
i += k
return a[0] + [k for n in range(1,len(i), 2) for k in i[n:n+1]] + a[-1][::-1] + [k for n in range(0, len(i), 2) for k in i[n:n+1] ][::-1]
if __name__ == '__main__':
import timeit
print(timeit.timeit("edges()", setup="from __main__ import edges", number = 100))
最佳时间为0.0006266489999688929
答案 4 :(得分:1)
它可能比其他答案中提到的替代品慢,因为它正在创建一个掩码(这是我的用例),它可以在你的情况下使用:
def mask_borders(arr, num=1):
mask = np.zeros(arr.shape, bool)
for dim in range(arr.ndim):
mask[tuple(slice(0, num) if idx == dim else slice(None) for idx in range(arr.ndim))] = True
mask[tuple(slice(-num, None) if idx == dim else slice(None) for idx in range(arr.ndim))] = True
return mask
正如已经说过的,这会创建并返回一个mask
,其中边框被屏蔽(True
):
>>> mask_borders(np.ones((5,5)))
array([[ True, True, True, True, True],
[ True, False, False, False, True],
[ True, False, False, False, True],
[ True, False, False, False, True],
[ True, True, True, True, True]], dtype=bool)
>>> # Besides supporting arbitary dimensional input it can mask multiple border rows/cols
>>> mask_borders(np.ones((5,5)), 2)
array([[ True, True, True, True, True],
[ True, True, True, True, True],
[ True, True, False, True, True],
[ True, True, True, True, True],
[ True, True, True, True, True]], dtype=bool)
要获取“边框”值,需要将boolean indexing应用于数组:
>>> arr = np.array([[4,5,6,7], [2,2,6,3], [4,4,9,4], [8,1,6,1]])
>>> arr[mask_borders(arr)]
array([4, 5, 6, 7, 2, 3, 4, 4, 8, 1, 6, 1])
答案 5 :(得分:0)
查看这个完整的解决方案。 This is the source
def border(array, corner=0, direction='cw'):
"""
Extract the values arround the border of a 2d array.
Default settings start from top left corner and move clockwise.
Corners are only used once.
Parameters
----------
array : array_like
A 2d array
corner : {0, 1, 2, 3}
Specify the corner to start at.
0 - start at top left corner (default)
1 - start at top right corner
2 - start at bottom right corner
3 - start at bottom left corner
direction : {'cw', 'ccw'}
Specify the direction to walk around the array
cw - clockwise (default)
ccw - counter-clockwise
Returns
-------
border : ndarray
Values around the border of `array`.
Examples
--------
>>> x, y = np.meshgrid(range(1,6), range(5))
>>> array=x*y
>>> array[0,0]=999
array([[999, 0, 0, 0, 0],
[ 1, 2, 3, 4, 5],
[ 2, 4, 6, 8, 10],
[ 3, 6, 9, 12, 15],
[ 4, 8, 12, 16, 20]])
>>> border(array)
array([999, 0, 0, 0, 0, 5, 10, 15, 20, 16, 12, 8, 4,
3, 2, 1, 999])
>> border(array, corner=2)
array([ 20, 16, 12, 8, 4, 3, 2, 1, 999, 0, 0, 0, 0,
5, 10, 15, 20])
>>> border(array, direction='ccw')
array([999, 1, 2, 3, 4, 8, 12, 16, 20, 15, 10, 5, 0,
0, 0, 0, 999])
>>> border(array, corner=2, direction='ccw')
array([ 20, 15, 10, 5, 0, 0, 0, 0, 999, 1, 2, 3, 4,
8, 12, 16, 20])
"""
if corner > 0:
# Rotate the array so we start on a different corner
array = np.rot90(array, k=corner)
if direction is 'ccw':
# Transpose the array so we march around counter-clockwise
array = array.T
border = []
border += list(array[0, :-1]) # Top row (left to right), not the last element.
border +=list(array[:-1, -1]) # Right column (top to bottom), not the last element.
border +=list(array[-1, :0:-1]) # Bottom row (right to left), not the last element.
border +=list(array[::-1, 0]) # Left column (bottom to top), all elements element.
# NOTE: in that last statement, we include the last element to close the path.
return np.array(border)