Pixelate Image With Pillow

后端 未结 3 2069
独厮守ぢ
独厮守ぢ 2021-02-09 03:06

I am working on a project where I want to take a picture of a colored grid as an input (made with Lego bricks in this example) and return a much smaller modified picture.

<
3条回答
  •  走了就别回头了
    2021-02-09 04:09

    You're doing a few things wrong.

    First of all, you should use PNG, not JPG for your output. JPG introduces so many artifacts, that small images like your output get completely degenerated.

    Then, you should reduce your palette. It's much easier to work with input containing no noise.

    First of all, boring initialization:

    from PIL import Image
    import operator
    from collections import defaultdict
    import re
    
    input_path = 'input.jpg'
    output_path = 'output.png'
    size = (4,4)
    

    Then we declare the palette - this should contain colors of all possible LEGO bricks. I sampled the values below from your image, but you can use black and white like you do in your code, or any colors you want as long as they're similar to colors in the source image:

    palette = [
        (45,  50,  50),  #black
        (240, 68,  64),  #red
        (211, 223, 223), #white
        (160, 161, 67),  #green
        (233, 129, 76),  #orange
    ]
    while len(palette) < 256:
        palette.append((0, 0, 0))
    

    The code below will declare palette for PIL, since PIL needs flat array rather than array of tuples:

    flat_palette = reduce(lambda a, b: a+b, palette)
    assert len(flat_palette) == 768
    

    Now we can declare an image that will hold the palette. We'll use it to reduce the colors from the original image later.

    palette_img = Image.new('P', (1, 1), 0)
    palette_img.putpalette(flat_palette)
    

    Here we open the image and quantize it. We scale it to size eight times bigger than needed, since we're going to sample the average output later.

    multiplier = 8
    img = Image.open(input_path)
    img = img.resize((size[0] * multiplier, size[1] * multiplier), Image.BICUBIC)
    img = img.quantize(palette=palette_img) #reduce the palette
    

    After this, our image looks like this:

    quantized image

    We need to convert it back to RGB so that we can sample pixels now:

    img = img.convert('RGB')
    

    Now we're going to construct our final image. To do this, we'll sample how many pixels of each palette color each square in the bigger image contains. Then we'll choose the color that occurs most often.

    out = Image.new('RGB', size)
    for x in range(size[0]):
        for y in range(size[1]):
            #sample at get average color in the corresponding square
            histogram = defaultdict(int)
            for x2 in range(x * multiplier, (x + 1) * multiplier):
                for y2 in range(y * multiplier, (y + 1) * multiplier):
                    histogram[img.getpixel((x2,y2))] += 1
            color = max(histogram.iteritems(), key=operator.itemgetter(1))[0]
            out.putpixel((x, y), color)
    

    Finally, we save the output:

    out.save(output_path)
    

    The result:

    small image

    Upscaled by 1600%:

    big image

提交回复
热议问题