使用彩色背景使图形透明

6

我有一些问题。我需要一个黑色背景的图形,上面绘制有几个白色圆圈。

我使用以下代码实现了这个要求:

import numpy
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, aspect = "equal", axisbg = "black")
ax.add_artist(plt.Circle((0., 0., .5), color =   "white")) 

plt.xlim(-5, 5)
plt.ylim(-5, 5)

fig.savefig("test.png", dpi = 300)

plt.show()

这将产生以下结果:
现在,我想要做的是使这个图像变透明。这意味着只有白色圆形应该变得透明。你可能已经能够看到问题的出现,因为如果我设置transparent=True。黑色背景会自动变成透明的,我会失去我的图形中的黑色。
我尝试的另一件事是不在savefig中设置transparent=True,而是实际上在plt.Circle中设置选项alpha=0.。这使得白色圆圈变成了透明的,这是最终的目标。然而,由于它是透明的,我留下了整个黑色背景。有什么方法来解决这个问题吗?
总结一下我的目标:
我想保存一个透明版本的图形,在这个图形中白色圆形是透明的,而黑色部分不是。
我知道我可以使用不同的程序,比如inkscapegimp来创建我想要的东西。但是,由于我需要执行其他操作,我真的需要在Python中完成它。
谢谢!

请将此简化为最小示例。作为一名物理学家,我喜欢这张图片,但我认为您可以用不到10行代码来演示这个问题。如果您让答案易于理解您正在做什么,您将获得更好的答案。 - tacaswell
谢谢您的建议。 - The Dude
1
从SO的角度来看,这个问题要好得多(我在我的博士论文中研究了堵塞系统和它们奇怪的声子),而且是一个有趣的问题!AGG绘制图像的方式是通过添加层来实现的,如果您想指定图像的正面,这种方法很有效,但是您正在指定负面。有一些方法可以让Agg为画布提供RGBA缓冲区,然后您���以修复它并将其放回画布作为图像,但这似乎是一个过于迂回的解决方案。 - tacaswell
非常感谢tcaswell提供的悬赏。如果没有它,我可能仍然没有答案。 - The Dude
你好。这似乎是一个有趣的问题,应该被解决。 - tacaswell
应该早点想到这个:https://dev59.com/anXYa4cB1Zd3GeqP7IW5#18167650 - tacaswell
3个回答

5

编辑3:

已经澄清了根本问题是:

如何在由imshow生成的matplotlib图像前面放置一个“黑色和透明”的遮罩层?该遮罩层应该来源于以前绘制的matplotlib黑白图像。

以下代码通过访问和混合图形rgba位图来演示此功能:

import numpy as np
import matplotlib.pyplot as plt

import matplotlib.cm as cm
import matplotlib.mlab as mlab


def get_rgba_bitmap(fig):
    fig.canvas.draw()
    tab = fig.canvas.copy_from_bbox(fig.bbox).to_string_argb()
    ncols, nrows = fig.canvas.get_width_height()
    return np.fromstring(tab, dtype=np.uint8).reshape(nrows, ncols, 4)

def black_white_to_black_transpa(rgba):
    rgba[:, :, 3] = 255 - rgba[:, :, 0]
    rgba[:, :, 0:3] = 0

def over(rgba1, rgba2):
    if rgba1.shape != rgba2.shape:
        raise ValueError("rgba1 and rgba2 shall have same size")
    alpha = np.expand_dims(rgba1[:, :, 3] / 255., 3)
    rgba =  np.array(rgba1 * alpha + rgba2 * (1.-alpha), dtype = np.uint8)
    return rgba[:, :, 0:3]


# fig 1)
fig1 = plt.figure(facecolor = "white")
fig1.set_dpi(300)
ax1 = fig1.add_subplot(1, 1, 1, aspect = "equal", axisbg = "black")
ax1.add_artist(plt.Circle((0., 0., .5), color =   "white")) 
ax1.set_xlim(-5, 5)
ax1.set_ylim(-5, 5)
bitmap_rgba1 = get_rgba_bitmap(fig1)
black_white_to_black_transpa(bitmap_rgba1)

# fig 2
fig2 = plt.figure(facecolor = "white")
fig2.set_dpi(300)
delta = 0.025
ax2 = fig2.add_subplot(1, 1, 1, aspect = "equal", axisbg = "black")
ax2.set_xlim(-5, 5)
ax2.set_ylim(-5, 5)
x = y = np.arange(-3.0, 3.0, delta)
X, Y = np.meshgrid(x, y)
Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
Z = Z2-Z1  # difference of Gaussians
im = ax2.imshow(Z, interpolation='bilinear', cmap=cm.jet,
                origin='lower', extent=[-5, 5, -5, 5],
                vmax=abs(Z).max(), vmin=-abs(Z).max())
bitmap_rgba2 = get_rgba_bitmap(fig2)    

# now saving the composed figure 
fig = plt.figure()
fig.patch.set_alpha(0.0) 
ax = fig.add_axes([0., 0., 1., 1.])
ax.patch.set_alpha(0.0) 
ax.imshow(over(bitmap_rgba1, bitmap_rgba2))
plt.axis('off')
fig.savefig("test_transpa.png", dpi=300)

plt.show()

给定: enter image description here 我使用了您最初的光子测试样例,照片质量似乎还可以。
现在,如果您也想让图片背景透明,请按照以下步骤操作:
1. 将fig1的背景设置为“白色”,即fig1 = plt.figure(facecolor='white')。因为当它传递给black_white_to_black_transpa时,白色将变成透明。
2. 将fig2的背景设置为透明fig2.patch.set_alpha(0.0),因为它将不被修改地存储到bitmap_rgba2中。
3. 最后,在over函数内混合bitmap_rgba1bitmap_rgba2时,需要注意alpha通道(请参见下面的可能修改)。
def over(rgba1, rgba2):
    if rgba1.shape != rgba2.shape:
        raise ValueError("rgba1 and rgba2 shall have same size")
    alpha1 = np.expand_dims(rgba1[:, :, 3] / 255., axis=3)
    alpha2 = np.expand_dims(rgba2[:, :, 3] / 255., axis=3)
    alpha = 1. - (1.-alpha1) * (1.-alpha2)
    C1 = rgba1[:, :, 0:3]
    C2 = rgba2[:, :, 0:3]
    C = (alpha1 * C1 + (1-alpha1) * alpha2 * C2) / alpha
    rgba =  np.empty_like(rgba1, dtype = np.uint8)
    rgba[:, :, 0:3] = C
    rgba[:, :, 3] = 255 * alpha[:, :, 0]
    return rgba

上次修改: 看起来to_string_argb返回的数组与imshow期望的数组存在不一致(RGB通道的顺序)。一个可能的解决方案是将ax.imshow(over(bitmap_rgba1, bitmap_rgba2))更改为:

over_tab = over(bitmap_rgba1, bitmap_rgba2)
over_tab[:, :, 0:3] = over_tab[:, :, ::-1][:, :, 1:4]
ax.imshow(over_tab)

这正是我想要的,而且它运行得非常好。非常感谢! - The Dude
我还有一个小问题。我一直在尝试使用facecolor = 'white',但是我无法让我的白色背景变成透明的。这个白色背景类似于你最终图像中的灰色背景。 - The Dude
您的 get_rgba_bitmap(fig) 函数翻转了 imshowcmap。我该如何避免或纠正这个问题呢?我正在使用我自己创建的颜色映射表,而在创建位图之前反转它不是一个选择。 - The Dude
确实。我没有注意到这一点,有点惊讶(我不确定这是否是预期的行为,我必须再次阅读文档)。您介意尝试我的编辑中添加的代码吗? - GBy
请注意,技术上这不是颜色映射的反转,而是红色和蓝色通道的反转。您可以通过设置另一个颜色映射来进行检查,例如 cmap=cm.reds。这可能是 to_string_argb 中的错误,但我并不完全确定。 - GBy
显示剩余4条评论

1

使用颜色条遮盖的圆形

色图可以具有alpha通道,因此如果您的数据在网格上具有高vs低值来显示圆形vs非圆形,则其中一个值集可以是透明的。

仅当使用transparent关键字以编程方式保存图形时才对我起作用;而在Python图像窗口中则不行。

从matplotlib库示例之一开始(在像gimp一样的软件中,我可以剪切和粘贴段落,并且透明度是正确的):

# plot transparent circles with a black background
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
from matplotlib.cm import Greys


dark_low = ((0., 1., 1.),
         (.3, 1., 0.),
         (1., 0., 0.))
         
cdict = {'red':  dark_low,

     'green': dark_low,

     'blue': dark_low}

cdict3 = {'red':  dark_low,

     'green': dark_low,

     'blue': dark_low,
               
     'alpha': ((0.0, 0.0, 0.0),
               (0.3, 0.0, 1.0),
               (1.0, 1.0, 1.0))
    }

greys = LinearSegmentedColormap('Greys', cdict)
plt.register_cmap(cmap=greys)
    
dropout_high = LinearSegmentedColormap('Dropout', cdict3)
plt.register_cmap(cmap = dropout_high)

# Make some illustrative fake data:

x = np.arange(0, np.pi, 0.1)
y = np.arange(0, 2*np.pi, 0.1)
X, Y = np.meshgrid(x,y)
Z = np.cos(X) * np.sin(Y) * 10

# Make the figure:

plt.figure()
plt.subplot(1,3,1)
plt.imshow(Z, cmap=Greys)
plt.title('Smooth\ncolorbar')
plt.colorbar()
plt.subplot(1,3,2)
plt.imshow(Z, cmap=greys)
plt.title('Linear\ncolorbar')
plt.colorbar()
plt.subplot(1,3,3)
plt.imshow(Z, cmap = dropout_high)
plt.title('Alpha crops\n colorbar')
plt.colorbar()
plt.savefig('dropout_cmap', transparent=True)

Adapting colorbar to a alpha-channel visual mask

作为另一张图片的覆盖层。有趣的是,带有alpha通道的颜色条没有透明度。这似乎是一个错误。

Plots with and without alpha-channel over another image


@TheDude,您是否确实希望具有透明度的图像在另一张图像上进行遮罩?我没有看到问题的原始状态,但是某处提到需要大量透明圆形。 - cphlewis

0

这可能不是你想要的答案,但它给出了你想要的图片!我认为你想用黑色填充圆形外部区域,并将背景保持透明,而不是相反。计算单个圆形的边界并使用fill_between是微不足道的。对于多个圆可能会更棘手!

import numpy as np
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, aspect = "equal", )

# A Circle
xy=(1,1); r=3

# more points is smoother
xdata=np.linspace(-5,5,1001)
ydata=np.linspace(-5,5,1001)

# circle edges (top and bottom)
c1=np.sqrt((xy[0]**2-xdata**2)+r**2)+xy[1]
c2=-np.sqrt((xy[0]**2-xdata**2)+r**2)+xy[1]

c1=np.where(np.isnan(c1),xy[0],c1)
c2=np.where(np.isnan(c2),xy[0],c2)

ax.fill_between(xdata,5,c1,color='black')
ax.fill_between(xdata,-5,c2,color='black')

plt.xlim(-5, 5)
plt.ylim(-5, 5)

fig.savefig("test.png", dpi = 300, transparent=True)

以中心点(1,1)和半径3的透明圆形


问题被简化得太多了,OP想要的应用程序是放置约1k个圆形以制作光子晶体(请参阅编辑历史记录)。 - tacaswell
好的,我可以看出我的解决方案对于这个应用程序不起作用。在问题的脚本末尾使用os.system("convert test.png -transparent white image_transparent.png")没有任何理由吗?我知道这不是一个matplotlib的解决方案,但这是一个简单的方法。 - Thom Chubb
这可能对楼主来说已经足够好了,但我正在寻找一个纯mpl的解决方案 ;) - tacaswell
好的,但我认为在黑色背景上绘制圆形并不是解决这个问题的方法,某种填充圆之间的空隙将是一种解决方案。是否值得我编写一个解决方案来确定1000x1000网格中在圆内的部分,并使用pcolormesh来填充它们? - Thom Chubb
如果你想用这种方式实现,你应该从AGG中获取RGBA缓冲区(它会更快地计算哪些像素在圆内或圆外),然后将该数组渲染回新画布。 - tacaswell

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