change white background to a specifc color - python

Hello I am trying to give a black and white photo a colored background, first I convert pixels to either white or black then I would like to replace the white with a specific color (148,105,39) then save it.
here is the image I am working on
here is my function so far it doesn't change the color (also doesn't give error)
def binary_image(img):
gray = img.convert('L')
bw = gray.point(lambda x: 0 if x<128 else 255, '1')
img = bw.convert('RGBA')
pixdata = img.load()
for y in range(img.size[1]):
for x in range(img.size[0]):
if pixdata[x, y] == (255, 255, 255, 255): #white color
pixdata[x, y] = (148,105,39,255)
return full_4
img=Image.open('logo.jpg')
bg_fps = binary_image(img)
bg_fps.show()

You really, really want to avoid for loops with PIL images, try to use Numpy and it will be easier to write and faster to run.
I would do it like this:
import numpy as np
from PIL import image
# Load image and ensure greyscale
im = Image.open('U6IhW.jpg').convert('L')
# Make Numpy version for fast, efficient access
npim = np.array(im)
# Make solid black RGB output image, same width and height but 3 channels (RGB)
res = np.zeros((npim.shape[0],npim.shape[1],3), dtype=np.uint8)
# Anywhere the grey image is >127, make result image new colour
res[npim>127] = [148,105,39]
# Convert back to PIL Image and save to disk
Image.fromarray(res).save('result.png')
You could equally use PIL's ImageOps.colorize() like this:
from PIL import Image, ImageOps
im = Image.open('U6IhW.jpg').convert('L')
# Threshold to pure black and white
bw = im.point(lambda x: 0 if x<128 else 255)
# Colorize
result = ImageOps.colorize(bw, (0,0,0), (148,105,39))

Related

How to generate an alpha image with a color range with PIL?

I have a grayscale image and I want to create an alpha layer based on a range of pixel values. I want to know how can I create a fall-off function to generate such image.
The original image is the following:
I can use the color range in photoshop to select the shadows with fuzziness of 20%
And the resultant alpha channel is the following:
With fuzziness of 100%:
How can I generate such alpha channels in python with PIL?
I thought that maybe a subtract, but it does not generates a
The code to generate the image with Numpy and PIL:
from PIL import Image
import numpy as np
img = np.arange(0,256, 0.1).astype(np.uint8)
img = np.reshape(img, (img.shape[0], 1))
img = np.repeat((img), 500, axis=1)
img = Image.fromarray(img.T)
I tried to create a fall-off function from the distance of the pixel values but it does not have the same gradient. Maybe there is a different way?
def gauss_falloff(distance, c=0.2, alpha=255):
new_value = alpha * np.exp(-1 * ((distance) ** 2) / (c**2))
new_value = new_value.clip(0,255)
return new_value.astype(np.uint8)
test = img.T / 255
test = np.abs(test - pixel)
test = gauss_falloff(test, c=0.2, alpha=255)
test = Image.fromarray(test)
With my code:
Here's how you could do that
from PIL import Image, ImageDraw
# Create a new image with a transparent background
width, height = 300, 300
image = Image.new('RGBA', (width, height), (255, 255, 255, 0))
# Create a drawing context for the image
draw = ImageDraw.Draw(image)
# Set the starting and ending colors for the gradient
start_color = (255, 0, 0)
end_color = (0, 0, 255)
# Draw a gradient line with the specified color range
for x in range(width):
color = tuple(int(start_color[i] + (end_color[i] - start_color[i]) * x / width)
for i in range(3))
draw.line((x, 0, x, height), fill=color)
# Save the image
image.save('gradient.png')
This code creates a new image with a transparent background and a drawing context for that image. Then it draws a gradient line on the image with the specified color range. Finally, it saves the image as a PNG file.
Note: The Python Imaging Library (PIL) has been replaced by the Pillow library, which is a fork of PIL. If you are using Pillow, you can use the same code as above, but you need to import the Image and ImageDraw modules from the Pillow package instead of the PIL package.

How to remove blue from an image using opencv?

I have this map with a blue square I would like to remove:
I've tried using open cv to do so but I've been wildly unsuccessful. I used the solution here to no avail. The output looks exactly like the original. The blue square is the one intersecting with the red one at the bottom right. That's what I want to remove.
Here is my latest attempt:
from PIL import Image
import PIL.ImageOps
import numpy as np
from skimage.io import imsave
import cv2
in_path = 'map.jpeg'
out_path = 'new_output.jpeg'
Image = cv2.imread(in_path)
Image2 = np.array(Image, copy=True)
white_px = np.asarray([0, 0, 255])
black_px = np.asarray([255 , 255 , 255 ])
(row, col, _) = Image.shape
for r in range(row):
for c in range(col):
px = Image[r][c]
if all(px == white_px):
Image2[r][c] = black_px
imsave(out_path, Image2)
I made a test image like this:
magick -size 400x200 gradient:magenta-lime -stroke blue -fill none +antialias -draw "rectangle 10,10 390,190" a.png
Now there are a couple of ways you can approach this. You could replace all blue pixels with white:
import numpy as np
import cv2
# Load image
filename = 'a.png'
im = cv2.imread(filename)
# Method 1: Simplistic overpaint blue with white
im[np.all(im == (255, 0, 0), axis=-1)] = np.uint8([255,255,255])
cv2.imwrite('result-white.png', im)
Or, maybe slightly better suited to the upper half of this image, replace blue with magenta:
Or you could "inpaint" - what Photoshop calls "Content Aware Fill" - which means that replacement pixels are guesstimated using the surrounding area:
# Method 2: Inpaint - or Photoshop "Content Aware Fill"
im = cv2.imread(filename)
# Make mask of blue pixels - True where blue, False elsewhere
mask = np.all(im == (255, 0, 0), axis=-1)
# Inpaint white areas and save
# Options are: cv2.INPAINT_TELEA or cv2.INPAINT_NS
result = cv2.inpaint(im,np.uint8(mask)*255,3,cv2.INPAINT_TELEA)
cv2.imwrite('result-TELEA.png',result)
result = cv2.inpaint(im,np.uint8(mask)*255,3,cv2.INPAINT_NS)
cv2.imwrite('result-NAVIER-STOKES.png',result)
That gives this result:
just assign blue channel to zeros
src = cv2.imread('D:/original.png', cv2.IMREAD_UNCHANGED)
src[:,:,0] = np.zeros([src.shape[0], src.shape[1]])
cv2.imwrite('D:/no-blue-channel.png',src)

How to generate a bespoke black and white bitmap

image example:
I want to create a black and white bitmap from scratch (not converting or manipulating an existing image) and have the ability to change individual pixels to either black or white using pixel coordinates, somehow, maybe via a dictionary?. Something like a chessboard but with one pixel per chessboard-square (if that makes sense?).I found something to generate a colour spectrum image but don't know how to adapt this.
from PIL import Image
img = Image.new( 'RGB', (300,50), "black") # Create a new black image
pixels = img.load() # Create the pixel map
for i in range(img.size[0]): # For every pixel:
for j in range(img.size[1]):
pixels[i,j] = (i, j, 100) # Set the colour accordingly
img.show()
Zoomed in on leftmost edge of bitmap
You can do individual odd pixels here and there like this:
from PIL import PIL
# Create new black image - L mode for b/w
img = Image.new( 'L', (10,6))
# Make pixels at locations (0,5) and (2,1) white (255)
img.putpixel((0,5), 255)
img.putpixel((2,1), 255)
# Save result
img.save('result.png')
However, if you want to do whole rows, or columns, or longer lines, I would recommend round-tripping to Numpy like this:
import numpy as np
# Create new black image - L mode for b/w
img = Image.new( 'L', (10,6))
# Convert to Numpy array for easy processing
na = np.array(img)
# Make row 1 white
na[1,:] = 255
# Make column 8 white
na[:,8] = 255
# Revert to PIL Image from Numpy array and save
Image.fromarray(na).save('result.png')
Or if you want to do a block:
... as above ...
na[1:3,5:9] = 255
... as above ...

How to efficiently change colors on a lot of images?

I have a huge dataset of images like this:
I would like to change the colors on these. All white should stay white, all purple should turn white and everything else should turn black. The desired output would look like this:
I've made the code underneath and it is doing what I want, but it takes way to long to go through the amount of pictures I have. Is there another and faster way of doing this?
path = r"C:path"
for f in os.listdir(path):
f_name = (os.path.join(path,f))
if f_name.endswith(".png"):
im = Image.open(f_name)
fn, fext = os.path.splitext(f_name)
print (fn)
im =im.convert("RGBA")
for x in range(im.size[0]):
for y in range(im.size[1]):
if im.getpixel((x, y)) == (255, 255, 255, 255):
im.putpixel((x, y),(255, 255, 255,255))
elif im.getpixel((x, y)) == (128, 64, 128, 255):
im.putpixel((x, y),(255, 255, 255,255))
else:
im.putpixel((x, y),(0, 0, 0,255))
im.show()
Your images seem to be palettised as they represent segmentations, or labelled classes and there are typically fewer than 256 classes. As such, each pixel is just a label (or class number) and the actual colours are looked up in a 256-element table, i.e. the palette.
Have a look here if you are unfamiliar with palletised images.
So, you don't need to iterate over all 12 million pixels, you can instead just iterate over the palette which is only 256 elements long...
#!/usr/bin/env python3
import sys
import numpy as np
from PIL import Image
# Load image
im = Image.open('image.png')
# Check it is palettised as expected
if im.mode != 'P':
sys.exit("ERROR: Was expecting a palettised image")
# Get palette and make into Numpy array of 256 entries of 3 RGB colours
palette = np.array(im.getpalette(),dtype=np.uint8).reshape((256,3))
# Name our colours for readability
purple = [128,64,128]
white = [255,255,255]
black = [0,0,0]
# Go through palette, setting purple to white
palette[np.all(palette==purple, axis=-1)] = white
# Go through palette, setting anything not white to black
palette[~np.all(palette==white, axis=-1)] = black
# Apply our modified palette and save
im.putpalette(palette.ravel().tolist())
im.save('result.png')
That takes 290ms including loading and saving the image.
If you have many thousands of images to do, and you are on a decent OS, you can use GNU Parallel. Change the above code to accept a command-line parameter which is the name of the image, and save it as recolour.py then use:
parallel ./recolour.py {} ::: *.png
It will keep all CPU cores on your CPU busy till they are all processed.
Keywords: Image processing, Python, Numpy, PIL, Pillow, palette, getpalette, putpalette, classes, classification, label, labels, labelled image.
If you're open to use NumPy, you can heavily speed-up pixel manipulations:
from PIL import Image
import numpy as np
# Open PIL image
im = Image.open('path/to/your/image.png').convert('RGBA')
# Convert to NumPy array
pixels = np.array(im)
# Get logical indices of all white and purple pixels
idx_white = (pixels == (255, 255, 255, 255)).all(axis=2)
idx_purple = (pixels == (128, 64, 128, 255)).all(axis=2)
# Generate black image; set alpha channel to 255
out = np.zeros(pixels.shape, np.uint8)
out[:, :, 3] = 255
# Set white and purple pixels to white
out[idx_white | idx_purple] = (255, 255, 255, 255)
# Convert back to PIL image
im = Image.fromarray(out)
That code generates the desired output, and takes around 1 second on my machine, whereas your loop code needs 33 seconds.
Hope that helps!

Python: PIL replace a single RGBA color

I have already taken a look at this question: SO question and seem to have implemented a very similar technique for replacing a single color including the alpha values:
c = Image.open(f)
c = c.convert("RGBA")
w, h = c.size
cnt = 0
for px in c.getdata():
c.putpixel((int(cnt % w), int(cnt / w)), (255, 0, 0, px[3]))
cnt += 1
However, this is very slow. I found this recipe out on the interwebs, but have not had success using it thus far.
What I am trying to do is take various PNG images that consist of a single color, white. Each pixel is 100% white with various alpha values, including alpha = 0. What I want to do is basically colorize the image with a new set color, for instance #ff0000<00-ff>. SO my starting and resulting images would look like this where the left side is my starting image and the right is my ending image (NOTE: background has been changed to a light gray so you can see it since it is actually transparent and you wouldn't be able to see the dots on the left.)
Any better way to do this?
If you have numpy, it provides a much, much faster way to operate on PIL images.
E.g.:
import Image
import numpy as np
im = Image.open('test.png')
im = im.convert('RGBA')
data = np.array(im) # "data" is a height x width x 4 numpy array
red, green, blue, alpha = data.T # Temporarily unpack the bands for readability
# Replace white with red... (leaves alpha values alone...)
white_areas = (red == 255) & (blue == 255) & (green == 255)
data[..., :-1][white_areas.T] = (255, 0, 0) # Transpose back needed
im2 = Image.fromarray(data)
im2.show()
Edit: It's a slow Monday, so I figured I'd add a couple of examples:
Just to show that it's leaving the alpha values alone, here's the results for a version of your example image with a radial gradient applied to the alpha channel:
Original:
Result:
Try this , in this sample we set the color to black if color is not white .
#!/usr/bin/python
from PIL import Image
import sys
img = Image.open(sys.argv[1])
img = img.convert("RGBA")
pixdata = img.load()
# Clean the background noise, if color != white, then set to black.
for y in xrange(img.size[1]):
for x in xrange(img.size[0]):
if pixdata[x, y] == (255, 255, 255, 255):
pixdata[x, y] = (0, 0, 0, 255)
you can use color picker in gimp to absorb the color and see that's rgba color
The Pythonware PIL online book chapter for the Image module stipulates that putpixel() is slow and suggests that it can be sped up by inlining. Or depending on PIL version, using load() instead.

Categories