Эффективное преобразование цвета в прозрачность в Python - PullRequest
2 голосов
/ 09 апреля 2019

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 минут против часа)

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

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

Ответы [ 3 ]

4 голосов
/ 09 апреля 2019

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

Мое входное изображение colortrans.png выглядит так:

Input image

Я хочу сделать диагональную фиолетовую часть (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] & mask[:, :, 1] & 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 выглядит следующим образом:

Alpha channel

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

Output image

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

2 голосов
/ 09 апреля 2019

Я сделал проект, который преобразовал все пиксели, близкие к белому, в прозрачные пиксели, используя модуль 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 голос
/ 11 апреля 2019

Я попробовал использовать 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:

enter image description here

Я могу запустить его так:

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

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

enter image description here

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...