Удалить цвета из изображения, если их нет в списке Python - PullRequest
3 голосов
/ 11 июля 2019

У меня есть относительно большое изображение RGBA (преобразованное в numpy), которое мне нужно заменить всеми цветами, которые не отображаются в списке. Как я мог сделать это быстрым питонским способом?

Используя простую итерацию, у меня есть решение этой проблемы, однако из-за того, что изображения достаточно велики (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)

Ожидаемое время работы на изображение: менее получаса

Текущее время: две минуты

Ответы [ 3 ]

2 голосов
/ 12 июля 2019

Это определенно не те временные окна, на которые вы должны смотреть. Вот подход с 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,:])

Образец:

enter image description here

становится

enter image description here

и для 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 циклу каждый)

2 голосов
/ 12 июля 2019

Я пошел с 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])

С 2500x 2500 пикселями PNG на этом ноутбуке i5 2015 года я вижу:

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

Таким образом, максимум 55 МБ памяти и 0,92 с истекшего времени.

Я попробовал отличную версию Nangy Quang Hoang для сравнения:

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 2500pixel image:

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

Пик 410 МБ памяти и 3,1 с.

Обе версии можно ускорить, сравнив uint32, как говорит Хоанг.

0 голосов
/ 11 июля 2019

Используя этот код, я смог заменить случайно сгенерированное изображение размером 2500 x 2500 в любом месте от 33 до 37 секунд.Метод, который вы использовали для запуска моей машины от 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...