Эффективное преобразование цвета в прозрачность в python

#python #image #opencv #image-processing #computer-vision

#python #изображение #opencv #обработка изображений #компьютерное зрение

Вопрос:

В GIMP есть удобная функция, позволяющая преобразовать произвольный цвет в альфа-канал.

По существу, все пиксели становятся прозрачными относительно того, насколько они далеки от выбранного цвета.

Я хочу воспроизвести эту функциональность с помощью opencv.

Я попытался выполнить итерацию по изображению:

     for x in range(rows):
        for y in range(cols):
            mask_img[y, x][3] = cv2.norm(img[y, x] - (255, 255, 255, 255))
  

Но это непомерно дорого, для выполнения этой итерации требуется примерно в 10 раз больше времени, чем для простой установки поля в 0 (6 минут против часа)

Это кажется скорее проблемой python, чем алгоритмической проблемой. Я делал подобные вещи на C , и это не так плохо с точки зрения производительности.

У кого-нибудь есть предложения по достижению этого?

Ответ №1:

Вот моя попытка использовать только numpy матричные операции.

Мое входное изображение colortrans.png выглядит следующим образом:

Входное изображение

Я хочу сделать диагональную фиолетовую часть (128, 0, 128) прозрачной с некоторым допуском /- (25, 0, 25) влево и вправо, что приведет к некоторому градиенту прозрачности.

Здесь идет код:

 import cv2
import numpy as np

# Input image
input = cv2.imread('images/colortrans.png', cv2.IMREAD_COLOR)

# Convert to RGB with alpha channel
output = cv2.cvtColor(input, cv2.COLOR_BGR2RGBA)

# Color to make transparent
col = (128, 0, 128)

# Color tolerance
tol = (25, 0, 25)

# Temporary array (subtract color)
temp = np.subtract(input, col)

# Tolerance mask
mask = (np.abs(temp) <= tol)
mask = (mask[:, :, 0] amp; mask[:, :, 1] amp; mask[:, :, 2])

# Generate alpha channel
temp[temp < 0] = 0                                            # Remove negative values
alpha = (temp[:, :, 0]   temp[:, :, 1]   temp[:, :, 2]) / 3   # Generate mean gradient over all channels
alpha[mask] = alpha[mask] / np.max(alpha[mask]) * 255         # Gradual transparency within tolerance mask
alpha[~mask] = 255                                            # No transparency outside tolerance mask

# Set alpha channel in output
output[:, :, 3] = alpha

# Output images
cv2.imwrite('images/colortrans_alpha.png', alpha)
cv2.imwrite('images/colortrans_output.png', output)
  

Результирующий альфа-канал colortrans_alpha.png выглядит следующим образом:

Альфа-канал

И конечное выходное изображение colortrans_output.png выглядит следующим образом:

Вывод изображения

Это то, чего вы хотели достичь?

Комментарии:

1. Это выглядит полезным для меня! Я думаю, что OP беспокоится о времени, поэтому, возможно, укажите, сколько времени требуется для запуска в вашем сообщении.

Ответ №2:

Я попробовал использовать pyvips.

Эта версия вычисляет пифагорейское расстояние между каждым пикселем RGB в вашем файле и целевым цветом, затем преобразует альфа-значение, масштабируя этот показатель расстояния на допуск.

 import sys 
import pyvips 

image = pyvips.Image.new_from_file(sys.argv[1], access='sequential')

# Color to make transparent
col = [128, 0, 128]

# Tolerance ... ie., how close to target before we become solid
tol = 25

# for each pixel, pythagorean distance from target colour
d = sum(((image - col) ** 2).bandsplit()) ** 0.5

# scale d so that distances > tol become 255
alpha = 255 * d / tol

# attach the alpha and save
image.bandjoin(alpha).write_to_file(sys.argv[2])
  

На хорошем тестовом изображении @HansHirse:

введите описание изображения здесь

Я могу запустить его следующим образом:

 $ ./mktrans.py ~/pics/colortrans.png x.png
  

Чтобы сделать:

введите описание изображения здесь

Чтобы проверить скорость, я попробовал jpg с разрешением 1920х1080 пикселей:

 $ time ./mktrans.py ~/pics/horse1920x1080.jpg x.png
real    0m0.708s
user    0m1.020s
sys 0m0.029s
  

Итак, 0,7 секунды на этом двухъядерном ноутбуке 2015 года.

Ответ №3:

Я выполнил проект, который преобразовал все пиксели, близкие к белым, в прозрачные пиксели, используя модуль PIL (библиотека изображений python). Я не уверен, как реализовать ваш алгоритм для «относительно того, насколько они далеки от выбранного цвета», но мой код выглядит так:

 from PIL import Image

planeIm = Image.open('InputImage.png')
planeIm = planeIm.convert('RGBA')
datas = planeIm.getdata()

newData = []
for item in datas:
    if item[0] > 240 and item[1] > 240 and item[2] > 240:
        newData.append((255, 255, 255, 0)) # transparent pixel
    else:
        newData.append(item) # unedited pixel
planeIm.putdata(newData)
planeIm.save('output.png', "PNG")
  

Для меня изображение с разрешением 1920 X 1080 обрабатывается за 1,605 секунды, так что, может быть, если вы внедрите в это свою логику, вы увидите желаемые улучшения скорости?

Это может быть еще быстрее, если newData инициализируется, а не редактируется .append() каждый раз! Что-то вроде:

 planeIm = Image.open('EGGW spider.png')
planeIm = planeIm.convert('RGBA')
datas = planeIm.getdata()

newData = [(255, 255, 255, 0)] * len(datas)
for i in range(len(datas)):
    if datas[i][0] > 240 and datas[i][1] > 240 and datas[i][2] > 240:
        pass # we already have (255, 255, 255, 0) there
    else:
        newData[i] = datas[i]
planeIm.putdata(newData)
planeIm.save('output.png', "PNG")
  

Хотя для меня этот второй подход длится 2,067 секунды…

многопоточность

Пример потоковой обработки для вычисления другого изображения будет выглядеть следующим образом:

 from PIL import Image
from threading import Thread
from queue import Queue
import time

start = time.time()
q = Queue()

planeIm = Image.open('InputImage.png')
planeIm = planeIm.convert('RGBA')
datas = planeIm.getdata()
new_data = [0] * len(datas)

print('putting image into queue')
for count, item in enumerate(datas):
    q.put((count, item))

def worker_function():
    while True:
        # print("Items in queue: {}".format(q.qsize()))
        index, pixel = q.get()
        if pixel[0] > 240 and pixel[1] > 240 and pixel[2] > 240:
            out_pixel = (0, 0, 0, 0)
        else:
            out_pixel = pixel
        new_data[index] = out_pixel
        q.task_done()

print('starting workers')
worker_count = 100
for i in range(worker_count):
    t = Thread(target=worker_function)
    t.daemon = True
    t.start()
print('main thread waiting')
q.join()
print('Queue has been joined')
planeIm.putdata(new_data)
planeIm.save('output.png', "PNG")

end = time.time()

elapsed = end - start
print('{:3.3} seconds elapsed'.format(elapsed))
  

Что для меня сейчас занимает 58,1 секунды! Ужасная разница в скорости! Я бы приписал это:

  • Приходится дважды перебирать каждый пиксель, один раз, чтобы поместить его в очередь, и один раз, чтобы обработать его и записать в new_data список.
  • Накладные расходы, необходимые для создания потоков. Создание каждого нового потока займет несколько мс, так что в сумме может получиться большое количество (в данном случае 100).
  • Для изменения пикселей использовался простой алгоритм, потоковая передача будет сиять, когда для каждого ввода требуется большое количество вычислений (больше похоже на ваш случай)
  • Потоковая передача не использует несколько ядер, для этого вам нужна многопроцессорная обработка -> мой диспетчер задач говорит, что я использовал только 10% своего процессора, и он уже работает на холостом ходу на 1-2%…

Комментарии:

1. Реализация приведена прямо выше, этот цикл — это буквально все, что вам нужно сделать, чтобы сделать пиксели прозрачными относительно их расстояния до выбранного цвета. Проблема, как я уже сказал, в вычислительных затратах.

2. @Makogan О, я вижу, возможно, я неправильно понял проблему, я думал, что вы говорите opencv , что это медленно, и вы пытались реализовать другую библиотеку, которая, вероятно, будет быстрее… Рассматривали ли вы многопоточность / многопроцессорность? Вы могли бы передавать каждому рабочему строку пикселей за раз или что-то в этом роде

3. Я никогда не использовал многопоточный python, я слышал это; это боль. У вас есть какие-либо отзывы по этому поводу?

4. @Makogan Я думаю, что это не так уж плохо, я постараюсь отредактировать свой пример, чтобы использовать его

5. @Makogan Сегодня у меня не было времени, но я постараюсь загрузить его завтра!

Ответ №4:

Удалить белый фон

Вот эффективный способ удаления белого фона. Вы можете изменить np.argwhere условие, чтобы выбрать определенный цвет.

 image = Image.open(in_pth)
# convert image to numpy array
transparent_img = np.ones((480,640,4),dtype=np.uint8) * 255
transparent_img[:,:,:3] = np.asarray(image)
white_idx = np.argwhere(np.sum(transparent_img, axis=-1) > 1000)
transparent_img[white_idx[:,0],white_idx[:,1],-1] = 0