【问题标题】:How can I efficiently map each pixel of a three channel image to one channel?如何有效地将三通道图像的每个像素映射到一个通道?
【发布时间】:2018-09-26 13:47:00
【问题描述】:

我正在编写一个 python 程序来预处理图像以用作语义分割任务的标签。原始图像具有三个通道,其中代表每个像素的三个值的向量代表该像素的类标签。例如,[0,0,0] 的像素可能是 1 类,[0,0,255] 可能是 2 类,依此类推。

我需要将这些图像转换为单通道图像,像素值从 0 开始并连续增加以表示每个类。本质上,我需要将旧图像中的 [0,0,0] 转换为新图像中的 0,将 [0,0,255] 转换为 1,以此类推。

图片的分辨率相当高,宽度和高度超过 2000 像素。我需要为数百张图像执行此操作。我目前的方法涉及迭代每个像素并用相应的标量值替换 3 维值。

filename="file.png"
label_list = [[0,0,0], [0,0,255]] # for example. there are more classes like this
image = imread(filename)
new_image = np.empty((image.shape[0], image.shape[1]))
for i in range(image.shape[0]):
    for j in range(image.shape[1]):
        for k, label in enumerate(label_list):
            if np.array_equal(image[i][j], label):
                new_image[i][j] = k
                break   
imsave("newname.png", new_image)

问题是上面的程序效率非常低,每张图片都需要几分钟的运行时间。这对于处理我所有的图像来说太过分了,因此我需要改进它。

首先,我认为可以通过将label_list 转换为numpy 数组和using np.where 来删除最内层的循环。但是,我不确定如何通过np.where 在二维数组中找到一维数组,以及它是否会有所改进。

来自this thread,我尝试定义一个函数并将其直接应用于图像。但是,我需要将每个 3 维标签映射到一个标量。字典不能包含列表作为键。有没有更好的方法来做到这一点,会有所帮助吗?

有没有办法(大幅度)提高效率,或者有没有更好的方法来完成上述程序的工作?

谢谢。

【问题讨论】:

    标签: python arrays image performance numpy


    【解决方案1】:

    方法#1

    这是viewsnp.searchsorted 的一种方法-

    # https://stackoverflow.com/a/45313353/ @Divakar
    def view1D(a, b): # a, b are arrays
        a = np.ascontiguousarray(a)
        b = np.ascontiguousarray(b)
        void_dt = np.dtype((np.void, a.dtype.itemsize * a.shape[1]))
        return a.view(void_dt).ravel(),  b.view(void_dt).ravel()
    
    # Trace back a 2D array back to given labels
    def labelrows(a2D, label_list):
        # Reduce array and labels to 1D
        a1D,b1D = view1D(a2D, label_list)
    
        # Use searchsorted to trace back label indices
        sidx = b1D.argsort()
        return sidx[np.searchsorted(b1D, a1D, sorter=sidx)]
    

    因此,要将其用于3D 图像数组,我们需要重新整形,将高度和宽度合并为一维,并保持颜色通道保持原样,并使用标签功能。

    方法#2

    针对具有[0,255] 范围的图像元素进行了调整,我们可以利用矩阵乘法进行降维,从而进一步提高性能,就像这样 -

    def labelpixels(img3D, label_list):
        # scale array
        s = 256**np.arange(img.shape[-1])
    
        # Reduce image and labels to 1D
        img1D = img.reshape(-1,img.shape[-1]).dot(s)
        label1D = np.dot(label_list, s)
    
        # Use searchsorted to trace back label indices
        sidx = label1D.argsort()
        return sidx[np.searchsorted(label1D, img1D, sorter=sidx)]
    

    关于如何扩展图像案例并验证的示例运行 -

    In [194]: label_list = [[0,255,255], [0,0,0], [0,0,255], [255, 0, 255]]
    
    In [195]: idx = [2,0,3,1,0,3,1,2] # We need to retrieve this back
    
    In [196]: img = np.asarray(label_list)[idx].reshape(2,4,3)
    
    In [197]: img
    Out[197]: 
    array([[[  0,   0, 255],
            [  0, 255, 255],
            [255,   0, 255],
            [  0,   0,   0]],
    
           [[  0, 255, 255],
            [255,   0, 255],
            [  0,   0,   0],
            [  0,   0, 255]]])
    
    In [198]: labelrows(img.reshape(-1,img.shape[-1]), label_list)
    Out[198]: array([2, 0, 3, 1, 0, 3, 1, 2])
    
    In [217]: labelpixels(img, label_list)
    Out[217]: array([2, 0, 3, 1, 0, 3, 1, 2])
    

    最后,输出需要重新整形为2D -

    In [222]: labelpixels(img, label_list).reshape(img.shape[:-1])
    Out[222]: 
    array([[2, 0, 3, 1],
           [0, 3, 1, 2]])
    

    【讨论】:

    • 谢谢。这几乎是我需要的。然而,虽然我想将每个 3-D 向量映射到一个标量,但我想保持空间配置 - 输出将是另一个图像,在本例中为 array([[2,0,3,1],[0,3,1,2]])。我可以通过将上面获得的一维数组重新映射到新图像来做到这一点,但是有没有办法直接做到这一点?
    • @GoodDeeds 是的,在最后一步使用重塑。
    • @GoodDeeds 不,我们需要 256 的幂,以便给每个通道足够的偏移量/“范围”。因此,第一个通道获得 256 范围,第二个通道获得 256*256,依此类推。
    猜你喜欢
    • 2012-06-29
    • 1970-01-01
    • 1970-01-01
    • 2014-01-24
    • 2021-07-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多