Python中高效地将颜色转换为透明。

5

GIMP有一个便捷的功能,允许您将任意颜色转换为Alpha通道。

基本上所有像素相对于所选择的颜色变得透明,具体取决于它们离该颜色有多远。

我想使用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))

但是这样做的代价太高了,相比于将字段设置为0(6分钟 vs 1小时),这个迭代需要花费大约10倍的时间。

这似乎更像是一个Python的问题,而不是算法问题。我在C++中做过类似的事情,在性能方面并没有那么糟糕。

有人有关于如何解决这个问题的建议吗?

4个回答

7

以下是我仅使用 numpy 矩阵运算的尝试。

我的输入图像 colortrans.png 如下:

输入图像

我想将带有一定容忍度 +/- (25, 0, 25) 的紫色对角部分 (128, 0, 128) 设为透明,从而产生一些透明渐变。

以下是代码:

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)

生成的 alpha 通道文件 colortrans_alpha.png 如下图所示:

Alpha channel

最终输出的图像文件 colortrans_output.png 如下图所示:

Output image

这是您想要实现的效果吗?

这对我很有帮助!我认为楼主可能关心时间,所以在您的帖子中可能要包括运行所需的时间。 - Reedinationer
我的结果非常奇怪。以下的imgur链接展示了我的输入和输出,以及我的代码链接。我不知道我可能做错了什么,导致图像如此奇异 - 我基本上只是复制粘贴了你的代码。 - Logan Price
我的结果非常奇怪。以下的imgur链接展示了我的输入和输出,以及我的代码链接。我不知道我可能做了什么让图像变得如此奇异 - 我基本上只是复制粘贴了你的代码。 - undefined

3

我尝试使用pyvips

这个版本计算您文件中每个RGB像素与目标颜色之间的勾股距离,然后通过将该距离度量按容差进行缩放来生成alpha值。

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

制作:

enter image description here

为测试速度,我使用了一张1920x1080像素的JPG图片:

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

在这台2015年的双核笔记本电脑上,执行此操作只需要0.7秒。


2

我曾经完成了一个项目,使用PIL(Python图像库)模块将接近白色的所有像素转换为透明像素。我不确定如何实现您提到的“相对于所选颜色距离”的算法,但我的代码如下:

Original Answer翻译成:“最初的回答”

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")

这对我来说在1.605秒内通过了一张1920 X 1080的图片,所以也许如果你把你的逻辑实现到这里,你会看到你想要的速度提升?如果每次都初始化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%的CPU,而它已经处于1-2%的空闲状态...

最初的回答:


实现就在上面,那个循环是你所要做的一切,以使像素相对于其与所选颜色的距离透明。问题是,正如我所说的,计算成本。 - Makogan
@Makogan 哦,我明白了,也许我误解了问题,我以为你是在说opencv很慢,而你正试图实现另一个可能会更快的库... 你考虑过多线程/多进程吗? 你可以一次传递给每个工作线程一行像素或者其他东西。 - Reedinationer
我从未在Python中使用多线程,听说这很麻烦。你有什么反馈吗? - Makogan
@Makogan 我认为这并不糟糕,我会尝试编辑我的示例来利用它。 - Reedinationer
@Makogan 今天我没有时间,但我会尽量明天上传! - Reedinationer

0

去除白色背景

这里有一种有效的方法可以去除白色背景。您可以更改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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接