PIL के साथ RGBA PNG को RGB में कनवर्ट करें


96

मैं PIL का उपयोग Django फ़ाइल के साथ अपलोड की गई पारदर्शी PNG छवि को JPG फ़ाइल में बदलने के लिए कर रहा हूँ। आउटपुट टूटा हुआ दिखता है।

मूल फाइल

पारदर्शी स्रोत फ़ाइल

कोड

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

या

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

परिणाम

दोनों तरीके, परिणामी छवि इस तरह दिखती है:

परिणामी फ़ाइल

क्या इसे ठीक करने का कोई तरीका है? मुझे सफ़ेद पृष्ठभूमि पसंद है जहाँ पारदर्शी पृष्ठभूमि हुआ करती थी।


उपाय

महान जवाब के लिए धन्यवाद, मैं निम्नलिखित समारोह संग्रह के साथ आया हूं:

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

प्रदर्शन

सरल गैर-कंपोज़िटिंग alpha_to_colorफ़ंक्शन सबसे तेज़ समाधान है, लेकिन बदसूरत सीमाओं को पीछे छोड़ देता है क्योंकि यह अर्ध पारदर्शी क्षेत्रों को नहीं संभालता है।

शुद्ध पीआईएल और सुन्न कंपोजिट सॉल्यूशन दोनों शानदार परिणाम देते हैं, लेकिन (79.6 एमएसएक्स alpha_composite_with_color) की तुलना में बहुत तेज (8.93 मिसे) है pure_pil_alpha_to_colorयदि आपके सिस्टम पर सुन्नता उपलब्ध है, तो जाने का रास्ता है। (अपडेट: नया शुद्ध पीआईएल संस्करण सभी उल्लिखित समाधानों में सबसे तेज है।)

$ 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

थोड़ी और गति के लिए, मेरा मानना ​​है कि परिणाम को बदले बिना im = image.copy()हटाया जा सकता है pure_pil_alpha_to_color_v2। (बाद के बाद के उदाहरणों को बदलने imके लिए image, निश्चित रूप से।)
unutbu

@unutbu आह, निश्चित रूप से :) धन्यवाद।
डैनिलो बर्गन

जवाबों:


127

यहाँ एक संस्करण है जो बहुत सरल है - यह सुनिश्चित नहीं है कि यह कितना अच्छा है। भारी कुछ RGBA -> JPG + BGसोंजियो स्निपेट के आधार पर मैंने सोरल थंबनेल के लिए समर्थन का निर्माण करते हुए पाया ।

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)

परिणाम @ 80%

यहां छवि विवरण दर्ज करें

परिणाम @ 50%
यहां छवि विवरण दर्ज करें


1
ऐसा लगता है कि आपका संस्करण सबसे तेज़ है: pastebin.com/mC4Wgqzv धन्यवाद! हालांकि आपके पोस्ट के बारे में दो बातें: png.load () कमांड अनावश्यक लगती है, और लाइन 4 होनी चाहिए background = Image.new("RGB", png.size, (255, 255, 255))
डैनिलो बर्गन

3
कैसे pasteएक उचित मिश्रण करने के लिए समझ से बाहर बधाई ।
मार्क रैनसम

@DaniloBargen, आह! वास्तव में यह आकार गायब था, लेकिन loadविधि के लिए विधि आवश्यक है split। और यह सुनने के लिए बहुत बढ़िया है कि यह वास्तव में तेज़ / और सरल है!
Yuji 'Tomita' Tomita

@YujiTomita: इसके लिए धन्यवाद!
unutbu

12
यह कोड मेरे लिए त्रुटि पैदा कर रहा था tuple index out of range:। मैंने एक और सवाल ( stackoverflow.com/questions/1962795/… ) का अनुसरण करके इसे ठीक किया । मुझे पहले PNG को RGBA में बदलना था और फिर इसे स्लाइस करना: alpha = img.split()[-1]फिर बैकग्राउंड मास्क पर उपयोग करना था।
जोहंद

37

उपयोग करके Image.alpha_composite, युजी 'टमिता' टोमिता द्वारा समाधान सरल हो जाता है। tuple index out of rangeयदि png के पास कोई अल्फा चैनल नहीं है, तो यह कोड एक त्रुटि से बच सकता है ।

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)

यह मेरे लिए सबसे अच्छा समाधान है क्योंकि मेरी सभी छवियों में अल्फा चैनल नहीं है।
लेन्हॉक्सुंग

2
जब मैं इस कोड का उपयोग करता हूं, तो पीएनजी ऑब्जेक्ट का मोड अभी भी 'RGBA' है
319 पर तर्क 3076

1
@ logic1976 सिर्फ एक में फेंक .convert("RGB")सहेजने से पहले
josch

13

पारदर्शी भागों में ज्यादातर RGBA मान (0,0,0,0) होता है। चूंकि JPG में कोई पारदर्शिता नहीं है, इसलिए jpeg मान (0,0,0) पर सेट है, जो काला है।

सर्कुलर आइकन के आसपास, नॉनजेरो RGB मूल्यों के साथ पिक्सेल होते हैं जहाँ A = 0. तो वे PNG में पारदर्शी दिखते हैं, लेकिन JPG में मजाकिया रंग के होते हैं।

आप सभी पिक्सेल सेट कर सकते हैं जहाँ A == 0 के लिए R = G = B = 255 का उपयोग करके इस तरह से numpy कर सकते हैं:

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

यहां छवि विवरण दर्ज करें


ध्यान दें कि लोगो में शब्दों और आइकन के चारों ओर किनारों को चिकना करने के लिए कुछ अर्ध-पारदर्शी पिक्सेल भी हैं। Jpeg को सहेजना अर्ध-पारदर्शिता को अनदेखा करता है, जिससे परिणामी jpeg काफी दांतेदार दिखता है।

एक बेहतर गुणवत्ता वाला परिणाम इमेजमैजिक convertकमांड का उपयोग करके बनाया जा सकता है :

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

यहां छवि विवरण दर्ज करें


सुपी का उपयोग करके एक अच्छा गुणवत्ता मिश्रण बनाने के लिए, आप अल्फा कंपोज़िंग का उपयोग कर सकते हैं :

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

यहां छवि विवरण दर्ज करें


धन्यवाद, वह व्याख्या पूरी तरह से समझ में आती है :)
Danilo Bargen

@DaniloBargen, क्या आपने देखा कि रूपांतरण की गुणवत्ता खराब है? यह समाधान आंशिक पारदर्शिता के लिए जिम्मेदार नहीं है।
मार्क रैनसम

@MarkRansom: सच है। क्या आप जानते हैं कि इसे कैसे ठीक किया जाए?
unutbu

इसे अल्फा मान के आधार पर एक पूर्ण मिश्रण (सफेद के साथ) की आवश्यकता होती है। मैं इसे करने के लिए एक प्राकृतिक तरीके के लिए पीआईएल खोज रहा हूं और मैं खाली आया हूं।
मार्क रैनसम

@MarkRansom हाँ, मैंने उस समस्या पर ध्यान दिया है। लेकिन मेरे मामले में जो केवल इनपुट डेटा के बहुत कम प्रतिशत को प्रभावित करेगा, इसलिए गुणवत्ता मेरे लिए काफी अच्छी है।
दानिलो बर्गन

4

यहाँ शुद्ध PIL में एक समाधान है।

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

धन्यवाद, यह अच्छी तरह से काम करता है। लेकिन खतना समाधान बहुत तेजी से प्रकट होता है: pastebin.com/rv4zcpAV (numpy: 8.92ms, pil: 79.7ms)
डैनिलो बार्गेन

ऐसा लगता है कि शुद्ध पीआईएल के साथ एक और तेज संस्करण है। नया जवाब देखें।
डैनिलो बार्गेन

2
@DaniloBargen, धन्यवाद - मैं बेहतर जवाब देखकर सराहना करता हूं और अगर आपने इसे मेरे ध्यान में नहीं लाया होता तो मैं ऐसा नहीं करता।
मार्क रैनसम

1

यह टूटा नहीं है। यह वही कर रहा है जो आपने इसे बताया था; वे पिक्सेल पूरी पारदर्शिता के साथ काले रंग के हैं। आपको सभी पिक्सल में पुनरावृति करने और पूरी पारदर्शिता के साथ सफेद में बदलने की आवश्यकता होगी।


धन्यवाद। लेकिन नीले घेरे के आसपास नीले क्षेत्र हैं। क्या वे अर्ध-पारदर्शी क्षेत्र हैं? क्या कोई रास्ता है जो मैं भी ठीक कर सकता हूँ?
डैनिलो बार्गेन

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

आयात छवि

def fig2img (अंजीर): "" @ दु: ख एक Matplotlib आकृति को RGB छवि में PIL छवि में कनवर्ट करता है और इसे वापस करता है @param एक matplotlib चित्र @ pearon इमेजिंग लाइब्रेरी (PIL) छवि @return को मूर्तित करता है "" #। a numpy array buf = fig2data (अंजीर) w, h, d = buf.shape वापसी Image.frombytes ("RGBA", (w, h), buf.tostring ())

def fig2data (अंजीर): "" @ दु: ख को Matplotlib आकृति में बदलकर RGBA चैनल के साथ 4D के बराबर आरे में रखें और इसे वापस करें @param एक matplotlib अंजीर को @ आरजीए के मानों को एक 3 डी सरणी में बदलें "" # रेंडरर अंजीर खींचें। कैनवास। वापस ()

# 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), path = 'foo.jpg', is_already_saved = गलत, if_load = True): यदि नहीं है तो_समर्थित: बैकग्राउंड = Image.new ("RGB", img.size, c) बैकग्राउंड.पेस्ट (img, मास्क = img.split () [3]) # 3 अल्फा चैनल है

    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.')
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.