programing

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

sourcetip 2023. 9. 9. 23:22
반응형

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

저는 PIL을 사용하여 Django와 함께 업로드 된 투명 PNG 이미지를 JPG 파일로 변환하고 있습니다.출력이 고장 난 것 같습니다.

소스파일

transparent source file

코드

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

아니면

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

결과

두 가지 방법 모두 결과 이미지는 다음과 같습니다.

resulting file

이것을 고칠 방법이 있습니까?저는 투명한 배경이 있던 흰색 배경으로 하고 싶습니다.


해결책

훌륭한 답변 덕분에 다음과 같은 기능 모음을 생각해 냈습니다.

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보다 훨씬 빠릅니다(8.93msec).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

여기에 훨씬 더 간단한 버전이 있습니다. 성능이 어느 정도인지는 확실하지 않습니다.내가 빌드하는 동안 발견한 일부 장고 토막글을 크게 기반으로 합니다.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%

enter image description here

결과 @ 50%
enter image description here

를 사용함으로써, 유지 '토미타' 토미타의 해결책은 간단해집니다.이 코드는 다음 코드를 피할 수 있습니다.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)

투명 부품은 대부분 RGBA 값(0,0,0,0)을 갖습니다.JPG에는 투명도가 없으므로 jpeg 값은 (0,0,0)으로 설정되며, 이 값은 검은색입니다.

원형 아이콘 주변에는 0이 아닌 RGB 값이 A = 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')

enter image description here


로고에는 단어와 아이콘 주변의 가장자리를 매끄럽게 하는 데 사용되는 반투명 픽셀도 있습니다.jpeg에 저장하면 반투명성이 무시되어 결과 jpeg가 상당히 들쭉날쭉해 보입니다.

이미지 매직을 사용하면 더 좋은 품질의 결과를 얻을 수 있습니다.convert명령:

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

enter image description here


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

enter image description here

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

그건 부러지지 않았어.그것은 당신이 지시한 대로 하고 있습니다. 그 픽셀들은 완전한 투명도를 가진 검은색입니다.모든 픽셀을 반복하고 투명도가 완전한 픽셀을 흰색으로 변환해야 합니다.

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

위의 예를 바탕으로:

이것은 RGBA 이미지를 받아들여 알파 채널을 화이트 컬러로 변환한 RGB 이미지를 반환합니다.

from PIL import Image

def imageAlphaToWhite(image):
    background = Image.new("RGBA", image.size, "WHITE")
    alphaComposite = Image.alpha_composite(background, image)
    alphaComposite.convert("RGB")
    return alphaComposite
from PIL import Image
 
def fig2img ( fig ):
    """
    @brief Convert a Matplotlib figure to a PIL Image in RGBA format and return it
    @param fig a matplotlib figure
    @return a Python Imaging Library ( PIL ) image
    """
    # put the figure pixmap into a numpy array
    buf = fig2data ( fig )
    w, h, d = buf.shape
    return Image.frombytes( "RGBA", ( w ,h ), buf.tostring( ) )

def fig2data ( fig ):
    """
    @brief Convert a Matplotlib figure to a 4D numpy array with RGBA channels and return it
    @param fig a matplotlib figure
    @return a numpy 3D array of RGBA values
    """
    # draw the 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), 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 is the alpha channel

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

언급URL : https://stackoverflow.com/questions/9166400/convert-rgba-png-to-rgb-with-pil

반응형