You make some mistakes.
First of all, you should use PNG, not JPG for output. JPG introduces so many artifacts that small images, such as your output, completely degenerate.
Then you should reduce your palette. It is much easier to work with a noise-free input.
First of all, drilling 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 a palette - this should contain the colors of all possible LEGO bricks. I selected the following values ββfrom your image, but you can use black and white, as in the code, or any colors you want if they are similar to the colors in the original image:
palette = [ (45, 50, 50),
In the code below, a palette for PIL will be declared, since PIL needs a flat array, not an 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 will use it to reduce color 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 a size eight times larger than necessary, since we are going to display the average result 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)
After that, our image looks as follows:

We need to convert it back to RGB so that we can now select pixels:
img = img.convert('RGB')
Now we are going to build our final image. To do this, we will look at how many pixels of each color in the palette of each square in a large image. Then we will choose the color that is most common.
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)
Result:

Increased by 1600%:
