3

我有一个相对较大的 RGBA 图像(转换为 numpy),我需要替换所有未出现在列表中的颜色。我怎么能以pythonic快速的方式做到这一点?

使用简单的迭代我有一个解决这个问题的方法,但是由于图像非常大(2500 x 2500),这个过程非常慢。

# Keep only these colors in the image, otherwise replace with (0,255,0,255)
palette = [[0,0,0,255],[0, 255, 0,255], [255, 0, 0,255], [128, 128, 128,255], [0, 0, 255,255], [255, 0, 255,255], [0, 255, 255,255], [255, 255, 255,255], [128, 128, 0,255], [0, 128, 128,255], [128, 0, 128,255]]

# Current slow solution with a 2500 x 2500 x 4 array (mask)
for z in range(mask.shape[0]):
    for y in range(mask.shape[1]):
        if (mask[z,y,:].tolist() not in palette):
            mask[z, y] = (0,255,0,255)

每张图像的预期操作时间:不到半分钟

当前时间:两分钟

4

3 回答 3

2

这绝对不是您应该关注的时间窗口。这是一种方法broadcasting

# palette.shape == (4,11)
palette = np.array(palette).transpose()

# sample a.shape == (2,2,4)
a= np.array([[[ 28, 231, 203, 235],
         [255, 0, 0,255]],

       [[ 50, 152,  36, 151],
        [252,  43,  63,  25]]])

# mask
# all(2) force all channels to be equal
# any(-1) matches any color
mask = (a[:,:,:, None] == palette).all(2).any(-1)

# replace color
rep_color = np.array([0,255,0,255])

# np.where to the rescue:
ret = np.where(mask[:,:,None], a, rep_color[None,None,:])

样本:

在此处输入图像描述

变成

在此处输入图像描述

对于a = np.random.randint(0,256, (2500,2500,4)),它需要:

每个循环 5.26 秒 ± 179 毫秒(平均值 ± 标准偏差。7 次运行,每个循环 1 个)


更新:如果你强制一切都可以,np.uint8你可以将通道合并到一个int32并获得更快的速度:

a = np.random.randint(0,256, (2500,2500,4), dtype=np.uint8)
p = np.array(palette, dtype=np.uint8).transpose()

# zip the data into 32 bits
# could be even faster if we handle the memory directly
aa = a[:,:,0] * (2**24) + a[:,:,1]*(2**16) + a[:,:,2]*(2**8) + a[:,:,3]
pp = p[0]*(2**24) + p[1]*(2**16) + p[2]*(2**8) + p[3]
mask = (aa[:,:,None]==pp).any(-1)
ret = np.where(mask[:,:,None], a, rep_color[None,None,:])

这需要:

每个循环 1.34 秒 ± 29.7 毫秒(平均值 ± 标准偏差。7 次运行,每个循环 1 个)

于 2019-07-11T22:13:29.360 回答
2

我尝试了pyvips。这是一个线程化的流式图像处理库,因此速度很快且不需要太多内存。

import sys
import pyvips
from functools import reduce

# Keep only these colors in the image, otherwise replace with (0,255,0,255)
palette = [[0,0,0,255], [0, 255, 0,255], [255, 0, 0,255], [128, 128, 128,255], [0, 0, 255,255], [255, 0, 255,255], [0, 255, 255,255], [255, 255, 255,255], [128, 128, 0,255], [0, 128, 128,255], [128, 0, 128,255]]

im = pyvips.Image.new_from_file(sys.argv[1], access="sequential")

# test our image against each sample ... bandand() will AND all image bands
# together, ie. we want pixels where they all match
masks = [(im == colour).bandand() for colour in palette]

# OR all the masks together to find pixels which are in the palette
mask = reduce((lambda x, y: x | y), masks)

# pixels not in the mask become [0, 255, 0, 255]
im = mask.ifthenelse(im, [0, 255, 0, 255])

im.write_to_file(sys.argv[2])

在这台 2015 i5 笔记本电脑上使用 2500x 2500 像素的 PNG,我看到:

$ /usr/bin/time -f %M:%e ./replace-pyvips.py ~/pics/x.png y.png
55184:0.92

因此,最大内存为 55mb,经过时间为 0.92 秒。

我尝试了 Quang Hoang 的优秀 numpy 版本进行比较:

p = np.array(palette).transpose()

# mask
# all(2) force all channels to be equal
# any(-1) matches any color 
mask = (a[:,:,:, None] == p).all(2).any(-1)

# replace color
rep_color = np.array([0,255,0,255])

# np.where to the rescue:
a = np.where(mask[:,:,None], a, rep_color[None,None,:])

im = Image.fromarray(a.astype('uint8'))
im.save(sys.argv[2])

在相同的 2500 x 2500 像素图像上运行:

$ /usr/bin/time -f %M:%e ./replace-broadcast.py ~/pics/x.png y.png
413504:3.08

410MB内存的峰值,3.1s过去了。

正如 Hoang 所说,通过比较 uint32 可以进一步加快这两个版本的速度。

于 2019-07-12T10:04:01.833 回答
0

使用此代码,我能够在 33 到 37 秒之间的任何位置替换随机生成的 2500 x 2500 图像。你让我的机器在 51 到 57 秒之间执行的方法。

mask = np.random.rand(2500,2500,4)
mask = np.floor(mask * 255)

palette = np.array([[0,0,0,255],[0, 255, 0,255], [255, 0, 0,255], [128, 128, 128,255], [0, 0, 255,255], [255, 0, 255,255], [0, 255, 255,255], [255, 255, 255,255], [128, 128, 0,255], [0, 128, 128,255], [128, 0, 128,255]])
default = np.array([0,255,0,255])
for z in range(mask.shape[0]):
    for y in range(mask.shape[1]):
        if not mask[z,y,:] in palette:
            mask[z,y,:] = default
于 2019-07-11T19:49:57.727 回答