问题
GIMP has a convenient function that allows you to convert an arbitrary color to an alpha channel.
Essentially all pixels become transparent relative to how far away from the chosen color they are.
I want to replicate this functionality with opencv.
I tried iterating through the image:
for x in range(rows):
for y in range(cols):
mask_img[y, x][3] = cv2.norm(img[y, x] - (255, 255, 255, 255))
But this is prohibitively expensive, it takes about 10 times longer to do that iteration than it takes to simply set the field to 0 (6 minutes vs an hour)
This seems more a python problem than an algorithmic problem. I have done similar things in C++ and it's not as as bad in terms of performance.
Does anyone have suggestions on achieving this?
回答1:
Here is my attempt only using numpy
matrix operations.
My input image colortrans.png
looks like this:
I want to make the diagonal purple part (128, 0, 128)
transparent with some tolerance +/- (25, 0, 25)
to the left and right, resulting in some transparency gradient.
Here comes the code:
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)
The resulting alpha channel colortrans_alpha.png
looks like this:
And, the final output image colortrans_output.png
looks like this:
Is that, what you wanted to achieve?
回答2:
I had a go using pyvips.
This version calculates the pythagorean distance between each RGB pixel in your file and the target colour, then makes an alpha by scaling that distance metric by a tolerance.
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])
On @HansHirse's nice test image:
I can run it like this:
$ ./mktrans.py ~/pics/colortrans.png x.png
To make:
To test speed, I tried on a 1920x1080 pixel jpg:
$ time ./mktrans.py ~/pics/horse1920x1080.jpg x.png
real 0m0.708s
user 0m1.020s
sys 0m0.029s
So 0.7s on this two-core 2015 laptop.
回答3:
I've done a project that converted all pixels that are close to white into transparent pixels using the PIL
(python image library) module. I'm not sure how to implement your algorithm for "relative to how far away from chosen color they are", but my code looks like:
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")
This goes through a 1920 X 1080 image for me in 1.605 seconds, so maybe if you implement your logic into this you will see the speed improvements you want?
It might be even faster if newData
is initialized instead of being .append()
ed every time too! Something like:
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")
Although for me this second approach runs at 2.067 seconds...
multithreading
An example of threading to calculate a different image would look like:
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))
Which for me now takes 58.1 seconds! A terrible speed difference! I would attribute this to:
- Having to iterate each pixel twice, once to put it into a queue and once to process it and write it to the
new_data
list. - The overhead needed to create threads. Each new thread will take a few ms to create, so making a large amount (100 in this case) can add up.
- A simple algorithm was used to modify the pixels, threading would shine when large amounts of computation are required on each input (more like your case)
- Threading doesn't utilize multiple cores, you need multiprocessing to get that -> my task manager says I was only using 10% of my CPU and it idles at 1-2% already...
来源:https://stackoverflow.com/questions/55582117/efficiently-converting-color-to-transparency-in-python