Chuyển đổi RGBA PNG sang RGB bằng PIL


97

Tôi đang sử dụng PIL để chuyển đổi hình ảnh PNG trong suốt được tải lên bằng Django thành tệp JPG. Đầu ra có vẻ bị hỏng.

Tệp nguồn

tệp nguồn minh bạch

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

hoặc là

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

Kết quả

Cả hai cách, hình ảnh kết quả trông như thế này:

tập tin kết quả

Có cách nào để sửa lỗi này? Tôi muốn có nền trắng như nền trong suốt từng có.


Giải pháp

Nhờ những câu trả lời tuyệt vời, tôi đã nghĩ ra bộ sưu tập hàm sau:

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

Hiệu suất

alpha_to_colorChức năng không kết hợp đơn giản là giải pháp nhanh nhất, nhưng để lại những đường viền xấu xí vì nó không xử lý các vùng bán trong suốt.

Cả PIL thuần túy và các giải pháp tổng hợp numpy đều cho kết quả tuyệt vời, nhưng alpha_composite_with_colornhanh hơn nhiều (8,93 msec) so với pure_pil_alpha_to_color(79,6 msec).Nếu numpy có sẵn trên hệ thống của bạn, đó là cách để thực hiện. (Cập nhật: Phiên bản PIL thuần túy mới là giải pháp nhanh nhất trong số tất cả các giải pháp được đề cập.)

$ 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

Đối với tốc độ nhanh hơn một chút, tôi tin rằng im = image.copy()có thể được xóa khỏi pure_pil_alpha_to_color_v2mà không thay đổi kết quả. (Tất nhiên sau khi thay đổi các trường hợp tiếp theo của imthành image.)
unutbu

@unutbu ah, tất nhiên :) cảm ơn.
Danilo Bargen 27/02/12

Câu trả lời:


129

Đây là một phiên bản đơn giản hơn nhiều - không chắc nó hoạt động như thế nào. Chủ yếu dựa trên một số đoạn mã django mà tôi đã tìm thấy khi xây dựng RGBA -> JPG + BGhỗ trợ cho hình thu nhỏ 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)

Kết quả @ 80%

nhập mô tả hình ảnh ở đây

Kết quả @ 50%
nhập mô tả hình ảnh ở đây


1
Có vẻ như phiên bản của bạn là nhanh nhất: pastebin.com/mC4Wgqzv Cảm ơn! Tuy nhiên, có hai điều về bài đăng của bạn: Lệnh png.load () dường như là không cần thiết và dòng 4 là như vậy background = Image.new("RGB", png.size, (255, 255, 255)).
Danilo Bargen

3
Chúc mừng bạn đã tìm ra cách pastepha trộn thích hợp.
Mark Ransom

@DaniloBargen, ah! Thật vậy, nó bị thiếu kích thước, nhưng loadphương thức là bắt buộc đối với splitphương thức. Và thật tuyệt khi nghe nói rằng nó thực sự nhanh / và / đơn giản!
Yuji 'Tomita' Tomita

@YujiTomita: Cảm ơn bạn vì điều này!
unutbu

12
Mã này đã gây ra một lỗi cho tôi: tuple index out of range. Tôi đã sửa lỗi này bằng cách làm theo một câu hỏi khác ( stackoverflow.com/questions/1962795/… ). Trước tiên, tôi phải chuyển đổi PNG thành RGBA và sau đó cắt nó: alpha = img.split()[-1]sau đó sử dụng nó trên mặt nạ nền.
joehand

38

Bằng cách sử dụng Image.alpha_composite, giải pháp của Yuji 'Tomita' Tomita trở nên đơn giản hơn. Mã này có thể tránh được tuple index out of rangelỗi nếu png không có kênh alpha.

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)

Đây là giải pháp tốt nhất cho tôi vì tất cả hình ảnh của tôi không có kênh alpha.
lenhhoxung

2
Khi tôi sử dụng mã này chế độ của đối tượng png vẫn là 'RGBA'
logic1976

1
@ logic1976 chỉ cần ném .convert("RGB")trước khi lưu nó
josch

13

Các phần trong suốt chủ yếu có giá trị RGBA (0,0,0,0). Vì JPG không có độ trong suốt, giá trị jpeg được đặt thành (0,0,0), có màu đen.

Xung quanh biểu tượng hình tròn, có các điểm ảnh có giá trị RGB khác với A = 0. Vì vậy, chúng trông trong suốt trong PNG nhưng có màu hài hước trong JPG.

Bạn có thể đặt tất cả các pixel trong đó A == 0 có R = G = B = 255 bằng cách sử dụng numpy như sau:

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

nhập mô tả hình ảnh ở đây


Lưu ý rằng biểu trưng cũng có một số pixel bán trong suốt được sử dụng để làm mịn các cạnh xung quanh các từ và biểu tượng. Lưu vào jpeg bỏ qua tính bán trong suốt, làm cho jpeg kết quả trông khá lởm chởm.

Kết quả chất lượng tốt hơn có thể được tạo ra bằng cách sử dụng convertlệnh của imagemagick :

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

nhập mô tả hình ảnh ở đây


Để tạo ra một hỗn hợp chất lượng đẹp hơn bằng cách sử dụng numpy, bạn có thể sử dụng kết hợp alpha :

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

nhập mô tả hình ảnh ở đây


Cảm ơn bạn, mà giải thích làm cho một toàn bộ rất nhiều ý nghĩa :)
Danilo Bargen

@DaniloBargen, bạn có nhận thấy rằng chất lượng chuyển đổi kém không? Giải pháp này không giải thích cho sự minh bạch một phần.
Mark Ransom

@MarkRansom: Đúng. Bạn có biết làm thế nào để khắc phục điều đó?
unutbu

Nó yêu cầu sự pha trộn đầy đủ (với màu trắng) dựa trên giá trị alpha. Tôi đã tìm kiếm PIL cho một cách tự nhiên để làm điều đó và tôi đã nhận được sản phẩm nào.
Mark Ransom

@MarkRansom vâng, tôi đã nhận thấy vấn đề đó. nhưng trong trường hợp của tôi, điều đó sẽ chỉ ảnh hưởng đến một tỷ lệ phần trăm rất nhỏ của dữ liệu đầu vào, vì vậy chất lượng là đủ tốt cho tôi.
Danilo Bargen

4

Đây là một giải pháp trong PIL tinh khiết.

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

Cảm ơn, điều này hoạt động tốt. Nhưng giải pháp NumPy dường như là nhanh hơn nhiều: pastebin.com/rv4zcpAV (NumPy: 8.92ms, pil: 79.7ms)
Danilo Bargen

Có vẻ như có một phiên bản khác, nhanh hơn với PIL thuần túy. Xem câu trả lời mới.
Danilo Bargen

2
@DaniloBargen, cảm ơn - Tôi đánh giá cao câu trả lời hay hơn và tôi sẽ không có nếu bạn không làm tôi chú ý.
Mark Ransom

1

Nó không bị hỏng. Nó đang làm chính xác những gì bạn đã nói với nó; những pixel đó có màu đen với độ trong suốt hoàn toàn. Bạn sẽ cần phải lặp lại tất cả các pixel và chuyển đổi các pixel có độ trong suốt hoàn toàn sang màu trắng.


Cảm ơn. Nhưng xung quanh vòng tròn màu xanh có những vùng màu xanh lam. Đó có phải là những khu vực bán trong suốt không? Có cách nào tôi có thể sửa những lỗi đó không?
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

nhập hình ảnh

def fig2img (fig): "" "@brief Chuyển đổi hình Matplotlib thành Hình ảnh PIL ở định dạng RGBA và trả lại nó @param fig hình matplotlib @return a Python Imaging Library (PIL) image" "" # đặt hình ảnh pixmap vào một mảng numpy buf = fig2data (fig) w, h, d = buf.shape return Image.frombytes ("RGBA", (w, h), buf.tostring ())

def fig2data (fig): "" "@brief Chuyển đổi một hình Matplotlib thành một mảng số 4D với các kênh RGBA và trả về nó @param fig một hình matplotlib @ quay lại một mảng 3D phức tạp của các giá trị RGBA" "" # vẽ hình trình kết xuất. 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): if not is_already_saved: background = Image.new ("RGB", img.size, c) background.paste (img, mask = img.split () [3]) # 3 là kênh alpha

    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.')
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.