RGBA PNG'yi PIL ile RGB'ye dönüştürün


106

Django ile yüklenen şeffaf bir PNG görüntüsünü JPG dosyasına dönüştürmek için PIL kullanıyorum. Çıktı bozuk görünüyor.

Kaynak dosyası

şeffaf kaynak dosyası

Kod

Image.open(object.logo.path).save('/tmp/output.jpg', 'JPEG')

veya

Image.open(object.logo.path).convert('RGB').save('/tmp/output.png')

Sonuç

Her iki durumda da ortaya çıkan görüntü şuna benzer:

sonuç dosyası

Bunu düzeltmenin bir yolu var mı? Eskiden şeffaf arka planın olduğu yerde beyaz bir arka plana sahip olmak istiyorum.


Çözüm

Harika cevaplar sayesinde aşağıdaki fonksiyon koleksiyonunu buldum:

import Image
import numpy as np


def alpha_to_color(image, color=(255, 255, 255)):
    """Set all fully transparent pixels of an RGBA image to the specified color.
    This is a very simple solution that might leave over some ugly edges, due
    to semi-transparent areas. You should use alpha_composite_with color instead.

    Source: http://stackoverflow.com/a/9166671/284318

    Keyword Arguments:
    image -- PIL RGBA Image object
    color -- Tuple r, g, b (default 255, 255, 255)

    """ 
    x = np.array(image)
    r, g, b, a = np.rollaxis(x, axis=-1)
    r[a == 0] = color[0]
    g[a == 0] = color[1]
    b[a == 0] = color[2] 
    x = np.dstack([r, g, b, a])
    return Image.fromarray(x, 'RGBA')


def alpha_composite(front, back):
    """Alpha composite two RGBA images.

    Source: http://stackoverflow.com/a/9166671/284318

    Keyword Arguments:
    front -- PIL RGBA Image object
    back -- PIL RGBA Image object

    """
    front = np.asarray(front)
    back = np.asarray(back)
    result = np.empty(front.shape, dtype='float')
    alpha = np.index_exp[:, :, 3:]
    rgb = np.index_exp[:, :, :3]
    falpha = front[alpha] / 255.0
    balpha = back[alpha] / 255.0
    result[alpha] = falpha + balpha * (1 - falpha)
    old_setting = np.seterr(invalid='ignore')
    result[rgb] = (front[rgb] * falpha + back[rgb] * balpha * (1 - falpha)) / result[alpha]
    np.seterr(**old_setting)
    result[alpha] *= 255
    np.clip(result, 0, 255)
    # astype('uint8') maps np.nan and np.inf to 0
    result = result.astype('uint8')
    result = Image.fromarray(result, 'RGBA')
    return result


def alpha_composite_with_color(image, color=(255, 255, 255)):
    """Alpha composite an RGBA image with a single color image of the
    specified color and the same size as the original image.

    Keyword Arguments:
    image -- PIL RGBA Image object
    color -- Tuple r, g, b (default 255, 255, 255)

    """
    back = Image.new('RGBA', size=image.size, color=color + (255,))
    return alpha_composite(image, back)


def pure_pil_alpha_to_color_v1(image, color=(255, 255, 255)):
    """Alpha composite an RGBA Image with a specified color.

    NOTE: This version is much slower than the
    alpha_composite_with_color solution. Use it only if
    numpy is not available.

    Source: http://stackoverflow.com/a/9168169/284318

    Keyword Arguments:
    image -- PIL RGBA Image object
    color -- Tuple r, g, b (default 255, 255, 255)

    """ 
    def blend_value(back, front, a):
        return (front * a + back * (255 - a)) / 255

    def blend_rgba(back, front):
        result = [blend_value(back[i], front[i], front[3]) for i in (0, 1, 2)]
        return tuple(result + [255])

    im = image.copy()  # don't edit the reference directly
    p = im.load()  # load pixel array
    for y in range(im.size[1]):
        for x in range(im.size[0]):
            p[x, y] = blend_rgba(color + (255,), p[x, y])

    return im

def pure_pil_alpha_to_color_v2(image, color=(255, 255, 255)):
    """Alpha composite an RGBA Image with a specified color.

    Simpler, faster version than the solutions above.

    Source: http://stackoverflow.com/a/9459208/284318

    Keyword Arguments:
    image -- PIL RGBA Image object
    color -- Tuple r, g, b (default 255, 255, 255)

    """
    image.load()  # needed for split()
    background = Image.new('RGB', image.size, color)
    background.paste(image, mask=image.split()[3])  # 3 is the alpha channel
    return background

Verim

Basit birleştirme dışı alpha_to_colorişlev en hızlı çözümdür, ancak yarı saydam alanları işlemediği için çirkin sınırların arkasında kalır.

Hem saf PIL hem de uyuşmuş birleştirme çözümleri harika sonuçlar verir, ancak (79,6 milisaniye) alpha_composite_with_colordeğerinden çok daha hızlıdır (8,93 pure_pil_alpha_to_colormilisaniye).Sisteminizde numpy mevcutsa, gidilecek yol budur. (Güncelleme: Yeni saf PIL sürümü, bahsedilen tüm çözümlerin en hızlısıdır.)

$ python -m timeit "import Image; from apps.front import utils; i = Image.open(u'logo.png'); i2 = utils.alpha_to_color(i)"
10 loops, best of 3: 4.67 msec per loop
$ python -m timeit "import Image; from apps.front import utils; i = Image.open(u'logo.png'); i2 = utils.alpha_composite_with_color(i)"
10 loops, best of 3: 8.93 msec per loop
$ python -m timeit "import Image; from apps.front import utils; i = Image.open(u'logo.png'); i2 = utils.pure_pil_alpha_to_color(i)"
10 loops, best of 3: 79.6 msec per loop
$ python -m timeit "import Image; from apps.front import utils; i = Image.open(u'logo.png'); i2 = utils.pure_pil_alpha_to_color_v2(i)"
10 loops, best of 3: 1.1 msec per loop

Biraz daha hız için, sonucu değiştirmeden im = image.copy()çıkarılabileceğine inanıyorum pure_pil_alpha_to_color_v2. Sonra (daha sonra örneklerini değiştirmek imiçin imagetabii ki,.)
unutbu

@unutbu ah, elbette :) teşekkürler.
Danilo Bargen

Yanıtlar:


138

İşte çok daha basit bir sürüm - ne kadar performanslı olduğundan emin değilim. RGBA -> JPG + BGSorl küçük resimleri için destek oluştururken bulduğum bazı django pasajlarına dayanıyor.

from PIL import Image

png = Image.open(object.logo.path)
png.load() # required for png.split()

background = Image.new("RGB", png.size, (255, 255, 255))
background.paste(png, mask=png.split()[3]) # 3 is the alpha channel

background.save('foo.jpg', 'JPEG', quality=80)

Sonuç @% 80

görüntü açıklamasını buraya girin

Sonuç @% 50
görüntü açıklamasını buraya girin


1
Görünüşe göre sürümünüz en hızlısı: pastebin.com/mC4Wgqzv Teşekkürler! Yine de gönderinizle ilgili iki şey var: png.load () komutu gereksiz görünüyor ve 4. satır olmalı background = Image.new("RGB", png.size, (255, 255, 255)).
Danilo Bargen

3
pasteDoğru bir karışımın nasıl yapılacağını bulduğunuz için tebrikler .
Mark Ransom

@DaniloBargen, ah! Aslında boyut eksikti, ancak loadyöntem için yöntem gerekli split. Ve aslında hızlı / ve / basit olduğunu duymak harika!
Yuji 'Tomita' Tomita

@YujiTomita: Bunun için teşekkürler!
unutbu

12
Bu kod benim için hataya neden oldu: tuple index out of range. Bunu başka bir soruyu takip ederek düzelttim ( stackoverflow.com/questions/1962795/… ). Önce PNG'yi RGBA'ya dönüştürmem ve sonra dilimlemem gerekiyordu: alpha = img.split()[-1]sonra bunu arka plan maskesinde kullanmalıydım.
joehand

41

Image.alpha_compositeYuji 'Tomita' Tomita'nın çözümü kullanılarak daha basit hale geldi. tuple index out of rangePng'nin alfa kanalı yoksa bu kod bir hatayı önleyebilir .

from PIL import Image

png = Image.open(img_path).convert('RGBA')
background = Image.new('RGBA', png.size, (255,255,255))

alpha_composite = Image.alpha_composite(background, png)
alpha_composite.save('foo.jpg', 'JPEG', quality=80)

Bu benim için en iyi çözüm çünkü tüm resimlerimin alfa kanalı yok.
lenhhoxung

2
Bu kodu kullandığımda, png nesnesinin modu hala 'RGBA' oluyor
logic1976

2
@ logic1976 .convert("RGB")kaydetmeden önce bir
ekleyin

13

Şeffaf kısımlar çoğunlukla RGBA değerine (0,0,0,0) sahiptir. JPG'de şeffaflık olmadığından, jpeg değeri siyah olan (0,0,0) olarak ayarlanır.

Dairesel simgenin etrafında, A = 0 olan, sıfır olmayan RGB değerlerine sahip pikseller vardır. Dolayısıyla, bunlar PNG'de şeffaf, ancak JPG'de komik renkte görünürler.

Aşağıdaki gibi numpy kullanarak A == 0 olan tüm pikselleri R = G = B = 255 olacak şekilde ayarlayabilirsiniz:

import Image
import numpy as np

FNAME = 'logo.png'
img = Image.open(FNAME).convert('RGBA')
x = np.array(img)
r, g, b, a = np.rollaxis(x, axis = -1)
r[a == 0] = 255
g[a == 0] = 255
b[a == 0] = 255
x = np.dstack([r, g, b, a])
img = Image.fromarray(x, 'RGBA')
img.save('/tmp/out.jpg')

görüntü açıklamasını buraya girin


Logoda ayrıca sözcüklerin ve simgenin kenarlarını yumuşatmak için kullanılan bazı yarı saydam pikseller bulunduğunu unutmayın. Jpeg'e kaydetme, yarı saydamlığı göz ardı ederek sonuçta ortaya çıkan jpeg'in oldukça pürüzlü görünmesine neden olur.

İmagemagick'in convertkomutu kullanılarak daha kaliteli bir sonuç elde edilebilir :

convert logo.png -background white -flatten /tmp/out.jpg

görüntü açıklamasını buraya girin


Numpy kullanarak daha kaliteli bir karışım yapmak için alfa birleştirme kullanabilirsiniz :

import Image
import numpy as np

def alpha_composite(src, dst):
    '''
    Return the alpha composite of src and dst.

    Parameters:
    src -- PIL RGBA Image object
    dst -- PIL RGBA Image object

    The algorithm comes from http://en.wikipedia.org/wiki/Alpha_compositing
    '''
    # http://stackoverflow.com/a/3375291/190597
    # http://stackoverflow.com/a/9166671/190597
    src = np.asarray(src)
    dst = np.asarray(dst)
    out = np.empty(src.shape, dtype = 'float')
    alpha = np.index_exp[:, :, 3:]
    rgb = np.index_exp[:, :, :3]
    src_a = src[alpha]/255.0
    dst_a = dst[alpha]/255.0
    out[alpha] = src_a+dst_a*(1-src_a)
    old_setting = np.seterr(invalid = 'ignore')
    out[rgb] = (src[rgb]*src_a + dst[rgb]*dst_a*(1-src_a))/out[alpha]
    np.seterr(**old_setting)    
    out[alpha] *= 255
    np.clip(out,0,255)
    # astype('uint8') maps np.nan (and np.inf) to 0
    out = out.astype('uint8')
    out = Image.fromarray(out, 'RGBA')
    return out            

FNAME = 'logo.png'
img = Image.open(FNAME).convert('RGBA')
white = Image.new('RGBA', size = img.size, color = (255, 255, 255, 255))
img = alpha_composite(img, white)
img.save('/tmp/out.jpg')

görüntü açıklamasını buraya girin


Teşekkürler, bu açıklama çok mantıklı :)
Danilo Bargen

@DaniloBargen, dönüşüm kalitesinin düşük olduğunu fark ettiniz mi? Bu çözüm, kısmi şeffaflığı hesaba katmaz.
Mark Ransom

@MarkRansom: Doğru. Bunu nasıl düzelteceğini biliyor musun?
unutbu

Alfa değerine göre tam bir karışım (beyazla) gerektirir. PIL'i bunu yapmanın doğal bir yolunu arıyordum ve boş geldim.
Mark Ransom

@MarkRansom evet, bu sorunu fark ettim. ancak benim durumumda bu, giriş verilerinin yalnızca çok küçük bir yüzdesini etkileyecek, bu nedenle kalite benim için yeterince iyi.
Danilo Bargen

4

İşte saf PIL'de bir çözüm.

def blend_value(under, over, a):
    return (over*a + under*(255-a)) / 255

def blend_rgba(under, over):
    return tuple([blend_value(under[i], over[i], over[3]) for i in (0,1,2)] + [255])

white = (255, 255, 255, 255)

im = Image.open(object.logo.path)
p = im.load()
for y in range(im.size[1]):
    for x in range(im.size[0]):
        p[x,y] = blend_rgba(white, p[x,y])
im.save('/tmp/output.png')

Teşekkürler, bu iyi çalışıyor. Ancak uyuşmuş çözüm çok daha hızlı görünüyor: pastebin.com/rv4zcpAV (uyuşuk: 8.92ms, pil: 79.7ms)
Danilo Bargen

Görünüşe göre saf PIL ile başka, daha hızlı bir versiyon var. Yeni cevabı görün.
Danilo Bargen

2
@DaniloBargen, teşekkürler - Daha iyi cevabı gördüğüm için minnettarım ve dikkatimi çekmeseydin bunu yapmazdım.
Mark Ransom

1

Kırılmamış. Tam olarak ne söylediğinizi yapıyor; bu pikseller tam şeffaflıkla siyahtır. Tüm piksellerde yinelemeniz ve tam şeffaf olanları beyaza dönüştürmeniz gerekecektir.


Teşekkürler. Ancak mavi dairenin etrafında mavi alanlar var. Bunlar yarı şeffaf alanlar mı? Bunları da düzeltebilmemin bir yolu var mı?
Danilo Bargen

0
import numpy as np
import PIL

def convert_image(image_file):
    image = Image.open(image_file) # this could be a 4D array PNG (RGBA)
    original_width, original_height = image.size

    np_image = np.array(image)
    new_image = np.zeros((np_image.shape[0], np_image.shape[1], 3)) 
    # create 3D array

    for each_channel in range(3):
        new_image[:,:,each_channel] = np_image[:,:,each_channel]  
        # only copy first 3 channels.

    # flushing
    np_image = []
    return new_image

-1

Görüntüyü içe aktar

def fig2img (fig): "" "@brief Bir Matplotlib şeklini RGBA formatında bir PIL Görüntüsüne dönüştürün ve onu @param fig bir matplotlib şekli @ Python Görüntüleme Kütüphanesi (PIL) görüntüsünü geri döndürün" "" # şekil pixmap'i içine koyun bir uyuşuk dizi buf = fig2data (fig) w, h, d = buf.shape return Image.frombytes ("RGBA", (w, h), buf.tostring ())

def fig2data (fig): "" "@brief Bir Matplotlib figürünü RGBA kanalları ile 4D numpy dizisine dönüştürün ve onu @param fig a matplotlib figürü @ RGBA değerlerinden oluşan bir 3 boyutlu diziyi geri döndür" "" # renderer fig. canvas.draw ()

# Get the RGBA buffer from the figure
w,h = fig.canvas.get_width_height()
buf = np.fromstring ( fig.canvas.tostring_argb(), dtype=np.uint8 )
buf.shape = ( w, h, 4 )

# canvas.tostring_argb give pixmap in ARGB mode. Roll the ALPHA channel to have it in RGBA mode
buf = np.roll ( buf, 3, axis = 2 )
return buf

def rgba2rgb (img, c = (0, 0, 0), yol = 'foo.jpg', is_already_saved = False, if_load = True): değilse is_already_saved: background = Image.new ("RGB", img.size, c) background.paste (img, mask = img.split () [3]) # 3 alfa kanalı

    background.save(path, 'JPEG', quality=100)   
    is_already_saved = True
if if_load:
    if is_already_saved:
        im = Image.open(path)
        return np.array(im)
    else:
        raise ValueError('No image to load.')
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.