2012-10-23 205 views
6

假設你想縮放一個透明圖像,但還不知道背景的顏色,你將其複合到以後。不幸的是,PIL似乎包含了完全透明像素的顏色值,導致了不好的結果。有沒有辦法告訴PIL調整大小忽略完全透明的像素?PIL保存透明度和顏色的縮放圖像?

import PIL.Image 

filename = "trans.png" # http://qrc-designer.com/stuff/trans.png 
size = (25,25) 

im = PIL.Image.open(filename) 
print im.mode # RGBA 

im = im.resize(size, PIL.Image.LINEAR) # the same with CUBIC, ANTIALIAS, transform 
# im.show() # does not use alpha 
im.save("resizelinear_"+filename) 


# PIL scaled image has dark border 

original image with (0,0,0,0) black but fully transparent background output image with black halo proper output scaled with gimp

與(0,0,0,0)(黑色但完全透明)背景(左)

輸出圖像與黑暈(中)的原始圖像

正確的輸出與gimp比例縮放(右)

編輯:它看起來像實現我正在尋找的,我將不得不修改resize函數本身的採樣,使它會忽略具有完全透明度的像素。

edit2:我發現了一個非常醜陋的解決方案。它將全透明像素的顏色值設置爲周圍非完全透明像素的平均值,以便在調整大小時最大程度地降低完全透明像素顏色的影響。它的速度很慢,但如果沒有其他解決方案,我會發布它。通過使用擴張操作來處理必要的像素可以使其更快。

EDIT3:預乘alpha是要走的路 - 看到馬克的回答

+0

是你的文件時加載到圖片什麼模式?也許alpha通道已經被刪除了? – Ber

+0

它說RGBA和縮放的圖像在邊界以外的GIMP中也看起來很好。 – Gonzo

+0

您是否嘗試將縮放後的圖像混合在其他物體上,如白色背景?縮放是否可以,那麼黑色的邊緣會消失。 – Ber

回答

4

看來,PIL不調整,這是必要的,以獲得正確的結果,之前做的α預乘。幸運的是,用蠻力來做這件事很容易。然後,您必須做相反的調整後的結果。

def premultiply(im): 
    pixels = im.load() 
    for y in range(im.size[1]): 
     for x in range(im.size[0]): 
      r, g, b, a = pixels[x, y] 
      if a != 255: 
       r = r * a // 255 
       g = g * a // 255 
       b = b * a // 255 
       pixels[x, y] = (r, g, b, a) 

def unmultiply(im): 
    pixels = im.load() 
    for y in range(im.size[1]): 
     for x in range(im.size[0]): 
      r, g, b, a = pixels[x, y] 
      if a != 255 and a != 0: 
       r = 255 if r >= a else 255 * r // a 
       g = 255 if g >= a else 255 * g // a 
       b = 255 if b >= a else 255 * b // a 
       pixels[x, y] = (r, g, b, a) 

結果:result of premultiply, resize, unmultiply

+0

比我的解決方案好得多。我不知道信息的損失有多糟糕。應該可以用numpy數組很好地加速這些。 – Gonzo

+0

預乘alpha損失:http://www.quasimondo.com/archives/000665.php結論:最好只在最終輸出時使用 – Gonzo

+1

@Phelix如果你想降低一些透明度,精度的損失只是一個問題原因。大多數人不這樣做。 –

2

您可以重新取樣每個單獨的頻段:通過避免像這樣高透明度值

im.load() 
bands = im.split() 
bands = [b.resize(size, Image.LINEAR) for b in bands] 
im = Image.merge('RGBA', bands) 

編輯

也許(需要numpy)

import numpy as np 

# ... 

im.load() 
bands = list(im.split()) 
a = np.asarray(bands[-1]) 
a.flags.writeable = True 
a[a != 0] = 1 
bands[-1] = Image.fromarray(a) 
bands = [b.resize(size, Image.LINEAR) for b in bands] 
a = np.asarray(bands[-1]) 
a.flags.writeable = True 
a[a != 0] = 255 
bands[-1] = Image.fromarray(a) 
im = Image.merge('RGBA', bands) 
+0

仍然是同樣的問題。 – Gonzo

+0

第二個建議刪除所有透明度。但我想保持光滑的邊界。 – Gonzo

+0

我在第二個版本中看到了一些透明效果,儘管它被製作成二進制蒙版。 –

0

也許你可以用你想要的顏色填充整個圖像,並且只在alpha通道中創建形狀?

+0

如果我理解正確,這會在邊界上放寬抗鋸齒。我想成爲粘貼到背景圖片上的圖片,以保持平滑過渡。 – Gonzo

+0

AA可以在alpha通道中完成(除非它不在那裏) – Ber

+0

這就是問題所在,我想盡可能地保留原始圖像的抗鋸齒效果 – Gonzo

0

抱歉回答自己,但這是我知道的唯一的工作解決方案。它將全透明像素的顏色值設置爲周圍非完全透明像素的平均值,以便在調整大小時最大程度地降低完全透明像素顏色的影響。有些特殊情況下不能達到正確的結果。

這是非常醜陋和緩慢。如果你能想出更好的東西,我會很樂意接受你的答案。

# might be possible to speed this up by only processing necessary pixels 
# using scipy dilate, numpy where 

import PIL.Image 

filename = "trans.png" # http://qrc-designer.com/stuff/trans.png 
size = (25,25) 

import numpy as np 

im = PIL.Image.open(filename) 

npImRgba = np.asarray(im, dtype=np.uint8) 
npImRgba2 = np.asarray(im, dtype=np.uint8) 
npImRgba2.flags.writeable = True 
lenY = npImRgba.shape[0] 
lenX = npImRgba.shape[1] 
for y in range(npImRgba.shape[0]): 
    for x in range(npImRgba.shape[1]): 
     if npImRgba[y, x, 3] != 0: # only change completely transparent pixels 
      continue   
     colSum = np.zeros((3), dtype=np.uint16) 
     i = 0 
     for oy in [-1, 0, 1]: 
      for ox in [-1, 0, 1]: 
       if not oy and not ox: 
        continue 
       iy = y + oy 
       if iy < 0: 
        continue 
       if iy >= lenY: 
        continue 
       ix = x + ox 
       if ix < 0: 
        continue 
       if ix >= lenX: 
        continue 
       col = npImRgba[iy, ix] 
       if not col[3]: 
        continue 
       colSum += col[:3] 
       i += 1 
     npImRgba2[y, x, :3] = colSum/i 

im = PIL.Image.fromarray(npImRgba2) 
im = im.transform(size, PIL.Image.EXTENT, (0,0) + im.size, PIL.Image.LINEAR) 
im.save("slime_"+filename) 

結果: enter image description here