Python / numpy:删除3D数组的空(零)边框

3
我有一个3D的numpy数组,可以认为是一张图片(确切地说它是场点的值)。我想在所有维度上删除边框(0值,注意可能存在负值)。限制条件是每个分子的维度保持不变,例如,我只想删除边框,以便该维度中“最大”的条目仍在边框内。因此,整个数据集(小型,大小不是问题)需要考虑在内。
2D示例:
0  0  0  0  0
0  1  0  0  0
0  1  1  0  0
0  0  0  0  0
0  0  0  0  0

0  0  0  0  0
0  0  0  0  0
0  0  1  0  0
0  0  0  1  0
0  0  0  1  0

这里需要删除顶部行和最左侧以及最右侧的列。在整个数据集中,它们只包含0值。

处理后的结果如下:

1  0  0
1  1  0
0  0  0
0  0  0

0  0  0
0  1  0
0  0  1
0  0  1

由于我不是numpy专家,我在定义算法以满足我的需求时遇到了困难。我需要找到每个维度中不为0的最小和最大索引,然后使用它来裁剪数组。

类似于这个,但是在3D中,裁剪必须考虑整个数据集。

我该如何实现这个?

2019年2月13日更新:

所以我尝试了这里的3个答案(其中一个似乎已被删除,它使用zip),Martins和norok2s答案。输出维度相同,因此我假设它们都有效。

我选择Martins解决方案,因为我可以轻松提取边界框以将其应用于测试集。

2019年2月25日更新:

如果还有人关注这个问题,我想要进一步的输入。正如所说,这些实际上不是图像,而是“场值”,意味着浮点数而不是灰度图像(uint8),这意味着我至少需要使用float16,但这需要太多内存。(我有48GB可用,但即使对于50%的训练集也不够用)。


较小的数组应该放在最大数组的哪个位置?我的意思是,在一维数组中,假设最大的对象是[1, 0, 1, 1],而较小的对象(缩小)是[1, 1],它应该变成[0, 0, 1, 1](末尾),[0, 1, 1, 0](中间)还是[1, 1, 0, 0](开头)? - norok2
一开始,所有的东西都有相同的大小。最终结果中,每个剩余值/像素的“相对”坐标应保持不变。 - beginner_
@beginner_ 请检查我的最新编辑。现在它应该按照您的要求工作了。 - Martin
@beginner_ 你的问题得到解答了吗? - Martin
@Martin 这里很忙。还没有机会验证哪个答案最好。 - beginner_
下面有非常好的答案。 - Konchog
3个回答

5
尝试这个:这是一个主要的算法。我不确定您希望从示例中提取哪些方面,但下面的算法应该很容易根据您的需求进行修改。
注意:此算法提取带有所有零值边界“删除”的CUBE。因此,在立方体的每个侧面上都有一些值!=0。
import numpy as np

# testing dataset
d = np.zeros(shape = [5,5,5]) 

# fill some values
d[3,2,1]=1
d[3,3,1]=1
d[1,3,1]=1
d[1,3,4]=1

# find indexes in all axis
xs,ys,zs = np.where(d!=0) 
# for 4D object
# xs,ys,zs,as = np.where(d!=0) 

# extract cube with extreme limits of where are the values != 0
result = d[min(xs):max(xs)+1,min(ys):max(ys)+1,min(zs):max(zs)+1] 
# for 4D object
# result = d[min(xs):max(xs)+1,min(ys):max(ys)+1,min(zs):max(zs)+1,min(as):max(as)+1]

>>> result.shape
(3, 2, 4)

案例1:

d = np.zeros(shape = [5,5,5])

d[3,2,1]=1
# ...  just one value

>>> result.shape # works

(1,1,1)

案例2:#错误案例-仅包含零-生成的3D没有尺寸->错误。
d = np.zeros(shape = [5,5,5]) # no values except zeros
>>> result.shape


Traceback (most recent call last):
  File "C:\Users\zzz\Desktop\py.py", line 7, in <module>
    result = d[min(xs):max(xs)+1,min(ys):max(ys)+1,min(zs):max(zs)+1]
ValueError: min() arg is an empty sequence
编辑:由于我的解决方案没有得到足够的认可和理解,我将提供一个四维体的例子,其中三个维度用于图像,第四个维度用于存储图像。
import numpy as np


class ImageContainer(object):
    def __init__(self,first_image):
        self.container =  np.uint8(np.expand_dims(np.array(first_image), axis=0))

    def add_image(self,image):
        #print(image.shape)
        temp = np.uint8(np.expand_dims(np.array(image), axis=0))
        #print(temp.shape)
        self.container  = np.concatenate((self.container,temp),axis = 0)
        print('container shape',self.container.shape)

# Create image container storage

image = np.zeros(shape = [5,5,3]) # some image
image[2,2,1]=1 # put something random in it
container = ImageContainer(image)
image = np.zeros(shape = [5,5,3]) # some image
image[2,2,2]=1
container.add_image(image)
image = np.zeros(shape = [5,5,3]) # some image
image[2,3,0]=1    # if we set [2,2,0] = 1, we can expect all images will have just 1x1 pixel size
container.add_image(image)
image = np.zeros(shape = [5,5,3]) # some image
image[2,2,1]=1
container.add_image(image)
>>> container.container.shape
('container shape', (4, 5, 5, 3)) # 4 images, size 5x5, 3 channels


# remove borders to all images at once
xs,ys,zs,zzs = np.where(container.container!=0) 
# for 4D object

# extract cube with extreme limits of where are the values != 0
result = container.container[min(xs):max(xs)+1,min(ys):max(ys)+1,min(zs):max(zs)+1,min(zzs):max(zzs)+1]

>>> print('Final shape:',result.shape) 


('Final shape', (4, 1, 2, 3)) # 4 images, size: 1x2, 3 channels

好的,我有点困惑了。现在应该正确了。将3D物体放入其中,在"result"数组中,极值!=0的最小立方体应与侧面接触。 - Martin
我可能漏掉了什么,或者这对整个数据集不起作用。如果我有10个3D图像,我需要找到一个边界框,它将包括所有10个图像上的所有非零值。 - beginner_
如果您有10个3D图像,那么您只需将该数组放入我的脚本中,它就应该可以工作。 - Martin
我认为在我的脚本中测试数据集已经很清楚了。 - Martin
@norok2 如果 OP 有灰度图像堆叠以创建3D体,则我的答案正是 OP 所需的。 - Martin
显示剩余6条评论

3

你可以将问题看作对放在一个数组中的所有形状进行特定边界框修剪的过程。

因此,如果你有一个n维修剪函数,解决方案就是应用该函数。

实现这一点的一种方法是:

import numpy as np

def trim(arr, mask):
    bounding_box = tuple(
        slice(np.min(indexes), np.max(indexes) + 1)
        for indexes in np.where(mask))
    return arr[bounding_box]

有一种稍微更灵活的解决方案(您可以指定要操作的轴),可以在FlyingCircus中找到(免责声明:我是该软件包的主要作者)。

因此,如果您有一个n维数组列表(在arrs中),您可以首先使用np.stack()将它们堆叠起来,然后修剪结果:

import numpy as np

arr = np.stack(arrs, -1)
trimmed_arr = trim(arr, arr != 0)

可以使用np.split()将其分离回来,例如:

trimmed_list = np.split(trimmed_arr, arr.shape[-1], -1)

编辑:

我刚意识到这个方法与其他答案基本相同,只不过对我来说看起来更加清晰。


这很酷。我更喜欢将其作为单个数组的一行代码返回:return arr[tuple(slice(np.min(idx), np.max(idx) + 1) for idx in np.where(arr != 0))] - Konchog

2

更新:

基于马丁的解决方案,使用min/max和np.where,但将其推广到任何维度,您可以按照以下方式进行操作:


(Note: The original answer has been translated as "最初的回答".)
def bounds_per_dimension(ndarray):
    return map(
        lambda e: range(e.min(), e.max() + 1),
        np.where(ndarray != 0)
    )

def zero_trim_ndarray(ndarray):
    return ndarray[np.ix_(*bounds_per_dimension(ndarray))]

d = np.array([[
    [0, 0, 0, 0, 0],
    [0, 1, 0, 0, 0],
    [0, 1, 1, 0, 0],
    [0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0],
], [
    [0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0],
    [0, 0, 1, 0, 0],
    [0, 0, 0, 1, 0],
    [0, 0, 0, 1, 0],
]])

zero_trim_ndarray(d)

使用range()np.ix_()将会非常缓慢。 如果您对使用slice()/ arr []方法进行计时(正如我答案中所使用的那样),即使是对于这个简单的以d为输入的例子,您也会得到大约2倍的速度差异。 - norok2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接