#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