Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Efficiently converting color to transparency in python

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?

like image 609
Makogan Avatar asked Dec 22 '22 23:12

Makogan


2 Answers

Here is my attempt only using numpy matrix operations.

My input image colortrans.png looks like this:

Input image

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:

Alpha channel

And, the final output image colortrans_output.png looks like this:

Output image

Is that, what you wanted to achieve?

like image 91
HansHirse Avatar answered Jan 01 '23 16:01

HansHirse


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:

enter image description here

I can run it like this:

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

To make:

enter image description here

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.

like image 37
jcupitt Avatar answered Jan 01 '23 16:01

jcupitt