แปลง RGBA PNG เป็น RGB ด้วย PIL


106

ฉันใช้ PIL เพื่อแปลงภาพ PNG โปร่งใสที่อัปโหลดด้วย Django เป็นไฟล์ 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ฟังก์ชั่นการไม่ประกอบอย่างง่ายเป็นวิธีแก้ปัญหาที่เร็วที่สุด แต่จะทิ้งเส้นขอบที่น่าเกลียดไว้เพราะไม่ได้จัดการกับพื้นที่กึ่งโปร่งใส

ทั้ง PIL บริสุทธิ์และโซลูชันการผสมที่เป็นตัวเลขให้ผลลัพธ์ที่ยอดเยี่ยม แต่alpha_composite_with_colorเร็วกว่า (8.93 มิลลิวินาที) มากกว่าpure_pil_alpha_to_color(79.6 มิลลิวินาที) มากหากมี numpy ในระบบของคุณนั่นคือวิธีที่จะไป (อัปเดต: PIL รุ่นใหม่ที่เร็วที่สุดในบรรดาโซลูชันที่กล่าวถึงทั้งหมด)

$ 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 อ่าแน่นอน :) ขอบคุณ
Danilo Bargen

คำตอบ:


138

นี่เป็นเวอร์ชันที่ง่ายกว่ามาก - ไม่แน่ใจว่าประสิทธิภาพเป็นอย่างไร อิงตามตัวอย่าง django บางส่วนที่ฉันพบในขณะที่สร้างRGBA -> JPG + BGการสนับสนุนสำหรับภาพขนาดย่อ sorl

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))ควรจะเป็น
Danilo Bargen

3
ขอแสดงความยินดีกับการหาวิธีpasteผสมผสานที่เหมาะสม
Mark Ransom

@DaniloBargen อ่า! แน่นอนมันไม่มีขนาด แต่loadต้องใช้splitเมธอดสำหรับเมธอด และมันยอดเยี่ยมมากที่ได้ยินว่ามันเร็ว / และ / ง่ายจริงๆ!
Yuji 'Tomita' Tomita

@YujiTomita: ขอบคุณสำหรับสิ่งนี้!
unutbu

12
tuple index out of rangeรหัสนี้ก็ก่อให้เกิดข้อผิดพลาดสำหรับฉัน: ฉันแก้ไขสิ่งนี้โดยทำตามคำถามอื่น ( stackoverflow.com/questions/1962795/… ) ฉันต้องแปลง PNG เป็น RGBA ก่อนแล้วจึงแบ่งเป็นส่วน ๆalpha = img.split()[-1]จากนั้นใช้สิ่งนั้นกับมาสก์พื้นหลัง
joehand

41

ด้วยการใช้Image.alpha_compositeวิธีแก้ปัญหาโดย Yuji 'Tomita' Tomita จะง่ายขึ้น รหัสนี้สามารถหลีกเลี่ยง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)

นี่เป็นทางออกที่ดีที่สุดสำหรับฉันเพราะภาพทั้งหมดของฉันไม่มีช่องอัลฟา
lenhhoxung

2
เมื่อฉันใช้รหัสนี้โหมดของวัตถุ png ยังคงเป็น 'RGBA'
logic1976

2
@ 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คำสั่งของ imagemagick :

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

ป้อนคำอธิบายภาพที่นี่


ในการผสมผสานคุณภาพที่ดีกว่าโดยใช้ numpy คุณสามารถใช้การผสมอัลฟา :

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 คุณสังเกตเห็นว่าคุณภาพของการแปลงไม่ดีหรือไม่? โซลูชันนี้ไม่คำนึงถึงความโปร่งใสบางส่วน
Mark Ransom

@MarkRansom: จริง. คุณรู้วิธีแก้ไขหรือไม่?
unutbu

ต้องมีการผสมผสานทั้งหมด (ด้วยสีขาว) ตามค่าอัลฟา ฉันได้ค้นหา PIL เพื่อหาวิธีที่เป็นธรรมชาติในการทำและฉันก็ว่างเปล่า
Mark Ransom

@MarkRansom ใช่ฉันสังเกตเห็นปัญหานั้นแล้ว แต่ในกรณีของฉันนั่นจะส่งผลต่อข้อมูลอินพุตเพียงเล็กน้อยเท่านั้นดังนั้นคุณภาพจึงดีพอสำหรับฉัน
Danilo Bargen

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

ขอบคุณมันทำงานได้ดี แต่ดูเหมือนว่าโซลูชัน numpy จะเร็วกว่ามาก: pastebin.com/rv4zcpAV (numpy: 8.92ms, pil: 79.7ms)
Danilo Bargen

ดูเหมือนว่าจะมีอีกรุ่นที่เร็วกว่าด้วย PIL บริสุทธิ์ ดูคำตอบใหม่
Danilo Bargen

2
@DaniloBargen ขอบคุณ - ฉันซาบซึ้งที่ได้เห็นคำตอบที่ดีกว่านี้และฉันจะไม่มีถ้าคุณไม่ได้ให้ความสนใจกับฉัน
Mark Ransom

1

มันไม่แตกหรอก กำลังทำในสิ่งที่คุณบอก พิกเซลเหล่านั้นเป็นสีดำและมีความโปร่งใสเต็มที่ คุณจะต้องวนซ้ำในพิกเซลทั้งหมดและแปลงพิกเซลที่มีความโปร่งใสทั้งหมดเป็นสีขาว


ขอบคุณ. แต่รอบ ๆ วงกลมสีน้ำเงินมีพื้นที่สีน้ำเงิน พื้นที่กึ่งโปร่งใสเหล่านั้นหรือไม่ มีวิธีแก้ไขด้วยหรือไม่?
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

นำเข้ารูปภาพ

def fig2img (fig): "" "@brief แปลงรูป Matplotlib เป็นภาพ PIL ในรูปแบบ RGBA และส่งคืน @param fig a matplotlib figure @return a Python Imaging Library (PIL) image" "" # ใส่รูป pixmap ลงใน อาร์เรย์ numpy buf = fig2data (fig) w, h, d = buf.shape ส่งคืน Image.frombytes ("RGBA", (w, h), buf.tostring ())

def fig2data (fig): "" "@brief แปลงรูป Matplotlib เป็นอาร์เรย์ตัวเลข 4D พร้อมช่อง RGBA และส่งคืน @param fig a matplotlib figure @ เปลี่ยนอาร์เรย์ 3 มิติที่เป็นตัวเลขของค่า RGBA" "# วาดรูปเรนเดอร์ 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), path = 'foo.jpg', is_already_saved = False, if_load = True): ถ้าไม่ใช่ is_already_saved: background = Image.new ("RGB", img.size, c) background.paste (img, mask = 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.')
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.