I'm trying to make a number of white pixels transparent, however I'm doing something wrong.
I can alter the colour of the pixels, however my code seems to ignore any alterations I make to the alpha value. I'm fairly new to PIL, and Python in general, so it's probably a relatively simple mistake.
Here's the code:
image_two = Image.open ("image_two.bmp")
image_two = image_two.convert ("RGBA")
pixels = image_two.load()
for y in xrange (image_two.size[1]):
for x in xrange (image_two.size[0]):
if pixels[x, y] == (0, 0, 0, 255):
pixels[x, y] = (0, 0, 0, 255)
else:
pixels[x, y] = (255, 255, 255, 0)
image_two.save("image_two")
My version of PIL does not support alpha channel in BMP files. I was able to use your code to load a PNG file with alpha. When I try to write that back out to a BMP file, I get a python exception that tells me "IOError: cannot write mode RGBA as BMP".
Your code says:
if pixels[x, y] == (0, 0, 0, 255): #black with alpha of 255
pixels[x, y] = (0, 0, 0, 255) #black with alpha of 255
else:
pixels[x, y] = (255, 255, 255, 0) #white with alpha of 255
A white pixel would have the r,g, and b set to "255". So probably what you want to do is something like this:
if pixels[x,y] == (255,255,255,255):
pixels[x,y] = (pixels[x,y][0], pixels[x,y][1], pixels[x,y][2], 0) #just set this pixel's alpha channel to 0
You may not need an else here, because if the pixels are not white with alpha of 255, you probably don't want to touch them.
I modified your code like this:
import Image
image_two = Image.open ("image_two.png")
image_two = image_two.convert ("RGBA")
pixels = image_two.load()
for y in xrange (image_two.size[1]):
for x in xrange (image_two.size[0]):
if pixels[x, y][3] == 255:
pixels[x, y] = (255, 0, 0, 255)
else:
pixels[x, y] = (255, 255, 255, 255)
image_two.save("image_two2.png")
This code takes my image and writes out a mask - a white pixel where the alpha is 0 and a red pixel where the alpha is 255.
Related
I am trying to save a 2D list as an image in python (greyscale image) so 0 values in the array would be black and 255 would be white. For example:
255 255 255
255 0 255
255 0 255
255 0 255
255 255 255
Would save an l like shape.
I have tried the following code utilising the PIL library as suggested by other questions on stack overflow:
WIDTH, HEIGHT = img.size
imgData = list(img.getdata())
imgData = [imgData[offset:offset + WIDTH] for offset in range(0, WIDTH * HEIGHT, WIDTH)]
#to print the image
for row in data:
print(' '.join('{:3}'.format(value) for value in row))
imgData = np.array(imgData)
**IMG VALUES AUGMENTED HERE**
newimg = Image.new('L', (WIDTH, HEIGHT), 'white')
newimg.putdata(imgData)
newimg.save('C:/File/Name.png')
However the image this creates does not reflect the list at all. If I was to have the 0s and 255s in different positions the same image is created. Anyone know a solution?
As your example is lacking any input data, I have just typed it in as you describe, made the image and then enlarged it. I also artificially added a red border so you can see the extent of it on StackOverflow's white background:
#!/usr/bin/env python3
from PIL import Image
import numpy as np
pixels = [[255,255,255],
[255,0,255],
[255,0,255],
[255,0,255],
[255,255,255]]
# Make list of pixels into Image
im = Image.fromarray(np.array(pixels,dtype=np.uint8))
im.save('result.png')
Instead of:
newimg.putdata(imgData)
you need the line:
newimg.putdata([j[0] for i in imgData for j in i])
The grayscale data is specified in a 1d list, not a 2d list.
This creates the list:
>>> [j[0] for i in imgData for j in i]
[255, 255, 255, 255, 0, 255, 255, 0, 255, 255, 0, 255, 255, 255, 255]
Which is:
[255, 255, 255,
255, 0 , 255,
255, 0 , 255,
255, 0 , 255,
255, 255, 255]
EDIT
The above solution works if you edit imgData with imgData[0][0] = [0, 0, 0, 255]. If you're editing imgData with imgData[0][0] = 0, then you'll need the line to be:
[j[0] if hasattr(j, '__iter__') else j for i in imgData for j in i]
or, you can make it nicer with:
imgData = np.array([[j[0] for j in i] for i in imgData])
imgData[0][0] = 0
newimg.putdata(imgData.flatten())
I tried doing this but it takes too long to run. The problem is that the black text in the image is made by a lot of different shades of gray and the color. I also want to remove shades of gray that range between 230 to 255.
How can I do this better?
OLD_PATH = r'C:\Users\avivb\Desktop\Untitled.png'
NEW_PATH = r'C:\Users\avivb\Desktop\test.png'
R_OLD, G_OLD, B_OLD = (range(230,255), range(230,255), range(230,255))
R_NEW, G_NEW, B_NEW = (255, 255, 255)
from PIL import Image
im = Image.open(OLD_PATH)
pixels = im.load()
width, height = im.size
for x in range(width):
for y in range(height):
r, g, b = pixels[x, y]
for i in R_OLD:
for j in G_OLD:
for k in B_OLD:
if (r, g, b) == (i, j, k):
pixels[x, y] = (R_NEW, G_NEW, B_NEW)
im.save(NEW_PATH)
If you are looking for performance, I would avoid as many for statements as I can, as they are slower in python than in other low-level languages (like C or C++).
This is my approach using openCV, should be very fast:
import cv2 as cv
# Set range of color values
lower = np.array([230, 230, 230])
upper = np.array([255, 255, 255])
# Threshold the image to get only selected colors
mask = cv.inRange(img, lower, upper)
# Set the new value to the masked image
img[mask.astype(bool)] = 255
Please note that there are no explicit for in this code!
Hope it helps!
There is no need to iterate over the old ranges like that - use an if on each channel and your speed will greatly improve.
threshold = 230
replacement_pixel = (255, 255, 255)
from PIL import Image
im = Image.open(OLD_PATH)
pixels = im.load()
width, height = im.size
for x in range(width):
for y in range(height):
r, g, b = pixels[x, y]
if r >= threshold and g >= threshold and b >= treshold:
pixels[x, y] = replacement_pixel
im.save(NEW_PATH)
I want to customize the image color to make a similar image with color variants.
Example :
For the above image, I want to replace the red color with other colors like blue, green, yellow, black, etc.
I tried :
from PIL import Image
filename ="./Logo.jpg"
picture = Image.open(filename, 'r')
_colors = [(255, 255, 255), (128, 128, 0), (128, 128, 128), (192, 128, 0), (128, 64, 0), (0, 192, 0), (128, 64, 128), (255, 255, 255)]
width, height = picture.size
for x in range(0, width):
for y in range(0, height):
current_color = picture.getpixel((x,y))
# print (current_color)
if current_color in _colors:
picture.putpixel((x,y), (255,5, 255))
# print ("Y")
picture.save("./test/change.png")
The above code is quite common code which is suggested for most of them.
But it is quite hard to as it replaces the pixel in the list " _colors "
The output image is :
Any solution to the above problem? Any smart way to deal with this using machine learning? Any solution using another programming language?
I'm not familiar with PIL, and I heard that it's slow. So here's a OpenCV version:
# for red color, it's easier to work with negative image
# since hue is in [170, 180] or [0,10]
hsv_inv = cv2.cvtColor(255-img, cv2.COLOR_BGR2HSV)
# these are cyan limit, but we're working on negative image, so...
lower_range = np.array([80,0,0])
upper_range = np.array([100,255,255])
# mask the red
mask = cv2.inRange(hsv_inv, lower_range, upper_range)
# replace red by green
green_hsv = hsv_inv.copy()
green_hsv[np.where(mask)] += np.array([60,0,0], dtype=np.uint8)
green_img = 255 - cv2.cvtColor(green_hsv, cv2.COLOR_HSV2BGR)
purple_hsv = hsv_inv.copy()
purple_hsv[np.where(mask)] -= np.array([30,0,0], dtype=np.uint8)
purple_img = 255 - cv2.cvtColor(purple_hsv, cv2.COLOR_HSV2BGR)
And result, pls ignore the ticks as I showed them by matplotlib.
I'm trying to work out an algorithm that does the following, input is left image, output is right image:
IMPORTANT: Images are dynamically generated and the horizontal lines I need to remove are always in the same position.
In the output I don't really care about the colors, it could be white, and black like this, although that might be a little more complicated:
So far I've came up with this algorithm for modifying the images:
def modify_image(filename):
img = Image.open(filename)
img = img.convert('RGBA')
pixdata = img.load()
for y in xrange(img.size[1]):
for x in xrange(img.size[0]):
if pixdata[x, y][0] in range(155, 190): #removes horizontal lines but doesn't change them to the vertical lines color
pixdata[x, y] = (255, 255, 255, 255)
if pixdata[x, y][0] < 135:
pixdata[x, y] = (0, 0, 0, 255)
for y in xrange(img.size[1]):
for x in xrange(img.size[0]):
if pixdata[x, y][1] < 195:
pixdata[x, y] = (0, 0, 0, 255)
for y in xrange(img.size[1]):
for x in xrange(img.size[0]):
if pixdata[x, y][2] > 0:
pixdata[x, y] = (255, 255, 255, 255)
img = img.resize((1000, 300), Image.NEAREST)
im2 = Image.new('P', img.size, 255)
im = img.convert('P')
img.save(base_path + "current.jpg")
modify_image (base_path + "image.gif")
This turns the images black and white and sometimes succeeds in isolating the middle bar from the background, the thing is, it doesn't remove the horizontal lines and it doesn't certainly change the color to the one of the vertical line either.
Bear in mind that the colors are merely illustrative and dynamic so think about any possible color.
Any suggestions/approaches are greatly appreciated :)
UPDATE: managed to remove horizontal lines but not replace them with the vertical lines color, so that only partially solves the problem (or not at all)
I don't know how accurate this needs to be but, since the positions of the horizontal bars are fixed, could you copy the pixel values from the row immediately preceding each horizontal bar to each row of the horizontal bar.
This will not look perfect because of the gradient background, but it might be enough for your purpose. Looking at the first example image, try this:
import os.path
from PIL import Image
HORIZONTAL_BAND_1_Y = range(37, 64)
HORIZONTAL_BAND_2_Y = range(125, 149)
img = Image.open(filename)
img = img.convert('RGBA')
pixdata = img.load()
for band in HORIZONTAL_BAND_1_Y, HORIZONTAL_BAND_2_Y:
for y in band:
for x in range(img.size[0]):
pixdata[x,y] = pixdata[x, band[0]-1]
new_filename = os.path.join(os.path.dirname(filename),
'new_{}'.format(os.path.basename(filename)))
img.save(new_filename)
Sample ouput
I have an image with a noisy background like this (blown-up, each square is a pixel). I'm trying to normalize the black background so that I can replace the color entirely.
This is what I'm thinking (psuedo code):
for pixel in image:
if is_similar(pixel, (0, 0, 0), threshold):
pixel = (0, 0, 0)
What sort of function would allow me to compare two color values to match within a certain threshold?
I ended up using the perceived luminance formula from this answer. It worked perfectly.
THRESHOLD = 18
def luminance(pixel):
return (0.299 * pixel[0] + 0.587 * pixel[1] + 0.114 * pixel[2])
def is_similar(pixel_a, pixel_b, threshold):
return abs(luminance(pixel_a) - luminance(pixel_b)) < threshold
width, height = img.size
pixels = img.load()
for x in range(width):
for y in range(height):
if is_similar(pixels[x, y], (0, 0, 0), THRESHOLD):
pixels[x, y] = (0, 0, 0)