Channel mix with Pillow

前端 未结 2 940
被撕碎了的回忆
被撕碎了的回忆 2021-01-19 17:18

I would like to do some color transformations, for example given RGB channels

R =  G + B / 2

or some other transformation where a channel v

2条回答
  •  悲哀的现实
    2021-01-19 17:31

    An alternative to using PIL.ImageChops is to convert the image data to a Numpy array. Numpy uses native machine data types and its compiled routines can processes array data very quickly compared to doing Python loops on Python numeric objects. So the speed of Numpy code is comparable to the speed of using ImageChops. And you can do all sorts of mathematical operations in Numpy, or using related libraries, like SciPy.

    Numpy provides a function np.asarray which can create a Numpy array from PIL data. And PIL.Image has a .fromarray method to load image data from a Numpy array.

    Here's a script that shows two different Numpy approaches, as well as an approach based on kennytm's ImageChops code.

    #!/usr/bin/env python3
    
    ''' PIL Image channel manipulation demo
    
        Replace each RGB channel by the mean of the other 2 channels, i.e.,
    
        R_new = (G_old + B_old) / 2
        G_new = (R_old + B_old) / 2
        B_new = (R_old + G_old) / 2
    
        This can be done using PIL's own ImageChops functions
        or by converting the pixel data to a Numpy array and
        using standard Numpy aray arithmetic
    
        Written by kennytm & PM 2Ring 2017.03.18
    '''
    
    from PIL import Image, ImageChops
    import numpy as np
    
    def comp_mean_pil(iname, oname):
        print('Loading', iname)
        img = Image.open(iname)
        #img.show()
    
        rgb = img.split()
        half = ImageChops.constant(rgb[0], 128)
        rh, gh, bh = [ImageChops.multiply(x, half) for x in rgb] 
        rgb = [
            ImageChops.add(gh, bh), 
            ImageChops.add(rh, bh), 
            ImageChops.add(rh, gh),
        ]
        out_img = Image.merge(img.mode, rgb)
        out_img.show()
        out_img.save(oname)
        print('Saved to', oname)
    
    # Do the arithmetic using 'uint8' arrays, so we must be 
    # careful that the data doesn't overflow
    def comp_mean_npA(iname, oname):
        print('Loading', iname)
        img = Image.open(iname)
        in_data = np.asarray(img)
    
        # Halve all RGB values
        in_data = in_data // 2
    
        # Split image data into R, G, B channels
        r, g, b = np.split(in_data, 3, axis=2)
    
        # Create new channel data
        rgb = (g + b), (r + b), (r + g)
    
        # Merge channels
        out_data = np.concatenate(rgb, axis=2)
    
        out_img = Image.fromarray(out_data)
        out_img.show()
        out_img.save(oname)
        print('Saved to', oname)
    
    # Do the arithmetic using 'uint16' arrays, so we don't need
    # to worry about data overflow. We can use dtype='float'
    # if we want to do more sophisticated operations
    def comp_mean_npB(iname, oname):
        print('Loading', iname)
        img = Image.open(iname)
        in_data = np.asarray(img, dtype='uint16')
    
        # Split image data into R, G, B channels
        r, g, b = in_data.T
    
        # Transform channel data
        r, g, b = (g + b) // 2, (r + b) // 2, (r + g) // 2
    
        # Merge channels
        out_data = np.stack((r.T, g.T, b.T), axis=2).astype('uint8')
    
        out_img = Image.fromarray(out_data)
        out_img.show()
        out_img.save(oname)
        print('Saved to', oname)
    
    # Test
    
    iname = 'Glasses0.png'
    oname = 'Glasses0_out.png'
    
    comp_mean = comp_mean_npB
    
    comp_mean(iname, oname)
    

    input image

    output image

    FWIW, that output image was created using comp_mean_npB.

    The calculated channel values produced by the 3 functions can differ from one another by 1, due to the differences in the way they perform the calculations, but of course such differences aren't readily visible. :)

提交回复
热议问题