PIL을 사용하여 RGBA PNG를 RGB로 변환


97

Django로 업로드 한 투명한 PNG 이미지를 JPG 파일로 변환하기 위해 PIL을 사용하고 있습니다. 출력이 깨져 보입니다.

소스 파일

투명한 소스 파일

암호

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과 numpy 합성 솔루션 모두 훌륭한 결과를 제공하지만 alpha_composite_with_color(79.6msec)보다 훨씬 빠릅니다 (8.93msec pure_pil_alpha_to_color).시스템에서 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

답변:


129

여기에 훨씬 더 간단한 버전이 있습니다. 얼마나 성능이 좋은지 확실하지 않습니다. RGBA -> JPG + BGsorl 썸네일에 대한 지원을 빌드 하는 동안 찾은 일부 django 스 니펫을 기반으로 합니다.

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 2011

38

를 사용 Image.alpha_composite하면 Yuji 'Tomita'Tomita의 솔루션이 더 간단 해집니다. 이 코드는 tuple index out of rangepng에 알파 채널이없는 경우 오류 를 피할 수 있습니다 .

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

1
@ logic1976은 .convert("RGB")저장하기 전에 그냥 던져
josch

13

투명한 부분은 대부분 RGBA 값 (0,0,0,0)을 갖습니다. JPG에는 투명도가 없기 때문에 jpeg 값은 검은 색인 (0,0,0)으로 설정됩니다.

원형 아이콘 주위에는 A = 0 인 RGB 값이 0이 아닌 픽셀이 있습니다. 따라서 PNG에서는 투명하게 보이지만 JPG에서는 재미있는 색상으로 보입니다.

다음과 같이 numpy를 사용하여 A == 0 인 모든 픽셀을 R = G = B = 255로 설정할 수 있습니다.

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가 상당히 들쭉날쭉하게 보입니다.

imagemagick의 convert명령을 사용하면 더 나은 품질의 결과를 얻을 수 있습니다 .

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

여기에 이미지 설명 입력


설명이 :) 감각의 전체를 많이한다, 당신을 감사
다닐 Bargen

@DaniloBargen, 변환 품질이 좋지 않다는 것을 알았습니까? 이 솔루션은 부분 투명성을 고려하지 않습니다.
Mark Ransom

@MarkRansom : 맞습니다. 그것을 고치는 방법을 알고 있습니까?
unutbu

알파 값을 기준으로 전체 블렌드 (흰색 포함)가 필요합니다. 나는 그것을하는 자연스러운 방법을 PIL을 찾고 있었고 나는 비어 있습니다.
Mark Ransom 2012

@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, 파일 : 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 Figure를 RGBA 형식의 PIL 이미지로 변환하고 반환 @param fig a matplotlib figure @return a Python Imaging Library (PIL) 이미지" ""# 그림 pixmap을 numpy 배열 buf = fig2data (fig) w, h, d = buf.shape return Image.frombytes ( "RGBA", (w, h), buf.tostring ())

def fig2data (fig) : "" "@brief Matplotlib Figure를 RGBA 채널이있는 4D numpy 배열로 변환하고 반환합니다. @param fig a matplotlib figure @return a numpy 3D array of RGBA values" ""# 렌더러를 그립니다. 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.