Pixel image with pillow

I am working on a project in which 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.

Here is an example input:

Input

Below is a very small 8x8 image that will be the result:

Output

Here is a much larger version of the expected result ::

Big output

Here is my code: It only works with black and white images.

from PIL import Image import re black = [(110,110,110),(0,0,0)] #The highest value and the lowest RGB value for the color black img = Image.open("input.jpg") #The input image size = (8,8) #The dimensions of the output image out = img.resize(size,resample=Image.LANCZOS) #Resize the image for y in range(size[0]): #loop through every pixel for x in range(size[1]): if out.getpixel((x,y)) <= black[0] and out.getpixel((x,y)) >= black[1]: #check to see if the pixel is within the accepted black values out.putpixel((x,y), (0,0,0)) #Give the current pixel true color else: #otherwise make the pixel black out.putpixel((x,y), (255,255,255)) #Give the current pixel true color """Save the pixelated image""" out.save("output.jpg") 

And the result returned by my code:

Actual Output

My program works fine for black and white images, but I need help changing it to work with several colors (red, orange, yellow, light green, dark green, cyan, blue, violet, black and white).

Thanks in advance!

+7
python image pillow image-resizing
source share
2 answers

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), #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)) 

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) #reduce the palette 

After that, our image looks as follows:

quantized image

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:

small image

Increased by 1600%:

big image

+7
source share

Just for fun, I solved it with ImageMagick - which can also be called from Python ...

Firstly, I create a small custom palette according to your colors - your white color is not very white, and your green color is different from the idea of ​​the green ImageMagick image, so instead of color names I used six.

 convert xc:black xc:red xc:"rgb(200,200,200)" xc:"rgb(168,228,23)" xc:orange +append palette.png 

If I scale this palette, it looks like this:

enter image description here

Then I resize the image to 4x4 and map the result to a custom palette and scale it so you can see it like this:

 convert lego.jpg -resize 4x4! +dither -remap palette.png -scale 1600 result.png 

and here is the result

enter image description here

White is off to match the β€œwhite” in the original.

+3
source share

All Articles