Trying to perform gamma correction on an image in a dash app. But original color images turn to grayscale after going through my function. I'm using Pillow Image.new("P", ...) for color instead of "L" which would be for gray scale. And if I use Image.new("RGB",...) the returned image is red. Please help.
def gamma_correct(gamma, url):
im = Image.open(requests.get(url, stream=True).raw)
gamma1 = gamma
row = im.size[0]
col = im.size[1]
result_img1 = Image.new("RGB", (row, col))
for x in range(1 , row):
for y in range(1, col):
r = pow(im.getpixel((x,y))[0]/255, (1/gamma1))*255
g = pow(im.getpixel((x,y))[1]/255, (1/gamma1))*255
b = pow(im.getpixel((x,y))[2]/255, (1/gamma1))*255
result_img1.putpixel((x,y)[0], int(r))
result_img1.putpixel((x,y)[1], int(g))
result_img1.putpixel((x,y)[2], int(b))
Bear in mind "P" and "RGB" are not the same. P is palette mode, and is limited to 256 colours.
Colour images have these dimensions: (width, tall, channels), with channels usually being = 3.
It seems like you are saving the image, with all colour values in a single channel, meaning you end up with an image like (width, tall, channels) with channel = 1.
This is also the reason why you get a red image when you use Image.new("RGB",...), since your image has data only in the first channel (R)
#ChristophRackwitz is correct in both his comments. Most often, libraries handling images actually handle the dimensions (height, width, channels), so if you load an image from disk which is, say, (1920,1080,3), the object in memory actually has dimensions (1080,1920,3). Additionally, some software (like opencv) even treat channels as BGR isntead of RGB by default
RGB images have 3 channels, which are the values of each colour. This means that for each pixel in the image, there are 3 colour values, one for red channel (R), one for green (G) and one for blue (B)
try debugging the image modification process, i.e:
for x in range(1 , row):
for y in range(1, col):
print(im.getpixel(x,y))
r = pow(im.getpixel((x,y))[0]/255, (1/gamma1))*255
g = pow(im.getpixel((x,y))[1]/255, (1/gamma1))*255
b = pow(im.getpixel((x,y))[2]/255, (1/gamma1))*255
print(im.getpixel(x,y))
The two print statements will print a 3-dimension array. Check that the values of each channel are as expected, both before and after
The error is that you are only passing one channel (that's R) parameter to the putpixel() method.
You need to pass a tuple of RGB values.
def gamma_correct(gamma, path):
im = Image.open(path)
gamma1 = gamma
row = im.size[0]
col = im.size[1]
result_img1 = Image.new(mode="RGB", size=(row, col), color=0)
for x in range(row):
for y in range(col):
r = pow(im.getpixel((x, y))[0] / 255, (1 / gamma1)) * 255
g = pow(im.getpixel((x, y))[1] / 255, (1 / gamma1)) * 255
b = pow(im.getpixel((x, y))[2] / 255, (1 / gamma1)) * 255
# add
color = (int(r), int(g), int(b))
result_img1.putpixel((x, y), color)
#show
result_img1.show()
Related
I'm trying to apply an overlay on a transparent logo using this function:
def overlay(path):
logo_img = cv2.imread(path, cv2.IMREAD_UNCHANGED)
'''Saving the alpha channel of the logo to the "alpha" variable.'''
alpha = logo_img[:, :, 3]
'''Creating the overlay of the logo by first creating an array of zeros in the shape of the logo.
The color on this will change later to become the overlay that will mask the logo.'''
mask = np.zeros(logo_img.shape, dtype=np.uint8)
'''Adding the alpha (transparency) of the original logo so that the overlay has the same transparecy
that the original logo has.'''
# mask[:, :, 2] = alpha
'''This code chooses random values for Red, Green and Blue channels of the image so that the final
overlay always has a different background'''
# r, g, b = (random.randint(0, 255),
# random.randint(0, 255),
# random.randint(0, 255))
r, g, b = (0, 255, 0)
'''There is a risk of losing the transparency when randomizing the overlay color so here I'm saving the
alpha value'''
a = 255
'''Creating the overlay'''
mask[:, :] = r, g, b, a
mask[:, :, 3] = alpha
'''Alp, short for alpha, is separate from above. This determines the opacity level of the logo. The
beta parameter determines the opacity level of the overlay.'''
alp = 1
beta = 1 - alp
'''addWeighted() is what masks the overlay on top of the logo'''
dst = cv2.addWeighted(logo_img, alp, mask, beta, 0, dtype=cv2.CV_32F).astype(np.uint8)
'''Converting the output dst to a PIL image with the RGBA channels.'''
pil_image = Image.fromarray(dst).convert('RGBA')
return pil_image
As you can see, I have these two tuples for setting the RGB. Whether I randomize it or I select a specific color, it makes no difference for the color of the overlay.
# r, g, b = (random.randint(0, 255),
# random.randint(0, 255),
# random.randint(0, 255))
r, g, b = (0, 255, 0)
Edit
You wish to "change the colour of the logo" with using an alpha matte. You cannot do this unless you actually manipulate the actual image pixels. Using an alpha matting approach is not the right answer for this. I would suggest you actually mask out the regions of the logo you want to change then replacing the colours with what is desired. Alpha matting is primarily used to blend objects together, not change the colour distribution of an object. I have left the answer below for posterity as the original method for alpha matting provided in the original question at its core was incorrect.
The core misunderstanding of this approach comes from how cv2.addWeighted is performed. Citing the documentation (emphasis mine):
In case of multi-channel arrays, each channel is processed independently. The function can be replaced with a matrix expression:
dst = src1*alpha + src2*beta + gamma;
cv2.addWeighted does not process the alpha channel in the way you are expecting correctly. Specifically, it will consider the alpha channel to be just another channel of information and does a weighted sum of this channel alone for the final output. It does not actually achieve alpha matting. Therefore if you want to do alpha matting you will need to actually compute the operation yourself.
Something like:
import numpy as np
import cv2
from PIL import Image
def overlay(path):
### Some code from your function - comments removed for brevity
logo_img = cv2.imread(path, cv2.IMREAD_UNCHANGED)
alpha = logo_img[:, :, 3]
mask = np.zeros(logo_img.shape, dtype=np.uint8)
# r, g, b = (random.randint(0, 255),
# random.randint(0, 255),
# random.randint(0, 255))
r, g, b = (0, 255, 0)
a = 255
mask[:, :] = r, g, b, a
mask[:, :, 3] = alpha
### Alpha matte code here
alp = alpha.astype(np.float32) / 255.0 # To make between [0, 1]
alp = alp[..., None] # For broadcasting
dst_tmp = logo_img[..., :3].astype(np.float32) * alp + mask[..., :3].astype(np.float32) * (1.0 - alp)
dst = np.zeros_like(logo_img)
dst[..., :3] = dst_tmp.astype(np.uint8)
dst[..., 3] = 255
pil_image = Image.fromarray(dst).convert('RGBA')
return pil_image
The first bit of this new function is from what you originally had. However, the section that has the alpha matting is seen above marked after the appropriate comment. The first line of the section will convert the alpha map into the [0, 1] range. This is required so that when you perform the alpha matting operation, which is a weighted sum of two images, you ensure that no output pixels span outside of the range of its native data type. Also, I introduce a singleton third dimension so that we can broadcast the alpha channel over each RGB channel separately to do the weighting correctly. After, we will compute the alpha matte as a weighted sum of the logo image and mask. Take note that I subset and pull out just the RGB channels of each. You don't need the alpha channel here specifically as I'm using it directly in the weighted sum instead. Once you finish this up, create a new output image with the first three channels being the resulting alpha matte but the alpha channel is all set to 255 since we've already achieved the blending at this point and you want all of the pixel values to show with no transparency now.
I am trying to calculate the percentage of black a pixel is. For example, let's say I have a pixel that is 75% black, so a gray. I have the RGBA values, so how do I get the level of black?
I have already completed getting each pixel and replacing it with a new RGBA value, and tried to use some RGBA logic to no avail.
#Gradient Testing here
from PIL import Image
picture = Image.open("img1.png")
img = Image.open('img1.png').convert('LA')
img.save('greyscale.png')
# Get the size of the image
width, height = picture.size
# Process every pixel
for x in range(width):
for y in range(height):
#Code I need here
r1, g1, b1, alpha = picture.getpixel( (x,y) )
r,g,b = 120, 140, 99
greylvl = 1 - (alpha(r1 + g1 + b1) / 765) #Code I tried
I would like to get new variable called that gives me a value, such as 0.75, which would represent a 0.75 percent black pixel.
I'm not quite sure what the "LA" format you're trying to convert to is for; I would try "L" instead.
Try this code: (Make sure you're using Python 3.)
from PIL import Image
picture = Image.open('img1.png').convert('L')
width, height = picture.size
for x in range(width):
for y in range(height):
value = picture.getpixel( (x, y) )
black_level = 1 - value / 255
print('Level of black at ({}, {}): {} %'.format(x, y, black_level * 100))
Is this what you're looking for?
I want to remove the dark(black strips) and also the white curves in the image, and then align the remained parts connected in a new small-sized image, making the colored parts looks continuously. I hope can get some suggestions for solutions.
I have tried to use PIL to read the image.
I don't know how to set the right threshold and resize the image
I'm not an expert at all in image processing, but let me know if this is enough for you.
Looking at the brightness (sum of the RGB values) distribution, maybe one option is to just filter pixel based on their value:
It looks like the dark parts have a brightness below 100 (or something like that). I filtered it this way:
from PIL import Image
import numpy as np
def filter_image(img,threshold=100):
data = np.array(img.convert('RGB'))
brightness = np.sum(data,axis=2)
filtered_img = data.copy()*0
for i in range(data.shape[0]):
k = 0 # k index the columns that are bright enough
for j in range(data.shape[1]):
if brightness[i,j] > threshold:
filtered_img[i,k,:] = data[i,j,:]
k += 1 # we increment only if it's bright enough
# End of column iterator. The right side of the image is black
return Image.fromarray(filtered_img)
img = Image.open("test.png")
filtered = filter_image(img)
filtered.show()
I get the following result. I'm sure experts can do much better, but it's a start:
The following is only looking for black pixels, as can be seen by the first image, many of the pixels you want out are not black. You will need to find a way to scale up what you will take out.
Also, research will need to be done on collapsing an image, as can be seen by my collapse. Although, this image collapse may work if you are able to get rid of everything but the reddish colors. Or you can reduce by width, which is what the third picture shows.
from PIL import Image, ImageDraw
def main():
picture = Image.open('/Volumes/Flashdrive/Stack_OverFlow/imageprocessing.png', 'r')
# pix_val = list(im.getdata())
# print(pix_val)
# https://code-maven.com/create-images-with-python-pil-pillowimg = Image.new('RGB', (100, 30), color = (73, 109, 137))
blackcount = 0
pix = picture.convert('RGB') # https://stackoverflow.com/questions/11064786/get-pixels-rgb-using-pil
width, height = picture.size
img = Image.new('RGB', (width, height), color=(73, 109, 137))
newpic = []
for i in range(width):
newpictemp = []
for j in range(height):
# https://stackoverflow.com/questions/13167269/changing-pixel-color-python
r, g, b = pix.getpixel((i, j))
if r == 0 and g == 0 and b == 0:
blackcount += 1
else:
img.putpixel((i, j), (r, g, b))
newpictemp.append((r, g, b))
newpic.append(newpictemp)
img.save('pil_text.png')
newheight = int(((width * height) - blackcount) / width)
print(newheight)
img2 = Image.new('RGB', (width, newheight), color=(73, 109, 137))
for i in range(width):
for j in range(newheight):
try:
z = newpic[i][j]
img2.putpixel((i, j), newpic[i][j])
except:
continue
img2.save('pil_text2.png')
if __name__ == "__main__":
main()
No black pixels on left, removed black pixels on right, remove and resize by width (height resize shown in code)
I want to make greyscale image by my own code.
from PIL import Image
path = "people.jpg"
img = Image.open(path)
img = img.convert("LA")
img.save("new_image.png")
So I change img.convert() for my own formula. But it does not work.
from PIL import Image
path = "people.jpg"
img = Image.open(path)
rgb = img.convert("RGB")
width,height = rgb.size
for x in range(width):
for y in range(height):
red,green,blue = rgb.getpixel((x,y))
value = red * 299/1000 + green * 587/1000 + blue * 114/1000
value = int(value)
rgb.putpixel((x,y),value)
rgb.save("new.png")
Any idea what is wrong? The new image is with red backgroud.
As martineau mentioned, rgb is initialized as an RGB (tri-band) image, so it is expecting RGB values. You want grayscale, which only requires a single band. You have two options here:
Follow martineau's advice and simply swap out rgb.putpixel((x,y),value) for rgb.putpixel((x, y), (value, value, value)). This will give it the proper three color values.
Create a new grayscale image using Image.new('L',...) before putting the pixels into it.
Here's how you might implement option 2 (note that I'm using Python 2.7):
from PIL import Image
path = "people.jpg"
img = Image.open(path)
width, height = rgb.size
gray = Image.new('L', (width, height))
for x in xrange(width):
for y in xrange(height):
r, g, b = img.getpixel((x, y))
value = r * 299.0/1000 + g * 587.0/1000 + b * 114.0/1000
value = int(value)
gray.putpixel((x, y), value)
gray.save("new.png")
Also, if speed is a concern, consider using putdata() instead of putpixel(). (That's beside the point, so I won't belabor it.)
Greyscale image will be having a tuple of two values for each pixel - (luminosity value, 255). Creating a new greyscale image and putting values to each pixel will work.
greyim=Image.new("LA",(width,height))
for x in range(width):
for y in range(height):
red,green,blue = rgb.getpixel((x,y))
value = red * 299/1000 + green * 587/1000 + blue * 114/1000
greyim.putpixel((x,y),(value,255))
I am trying to remove a certain color from my image however it's not working as well as I'd hoped. I tried to do the same thing as seen here Using PIL to make all white pixels transparent? however the image quality is a bit lossy so it leaves a little ghost of odd colored pixels around where what was removed. I tried doing something like change pixel if all three values are below 100 but because the image was poor quality the surrounding pixels weren't even black.
Does anyone know of a better way with PIL in Python to replace a color and anything surrounding it? This is probably the only sure fire way I can think of to remove the objects completely however I can't think of a way to do this.
The picture has a white background and text that is black. Let's just say I want to remove the text entirely from the image without leaving any artifacts behind.
Would really appreciate someone's help! Thanks
The best way to do it is to use the "color to alpha" algorithm used in Gimp to replace a color. It will work perfectly in your case. I reimplemented this algorithm using PIL for an open source python photo processor phatch. You can find the full implementation here. This a pure PIL implementation and it doesn't have other dependences. You can copy the function code and use it. Here is a sample using Gimp:
to
You can apply the color_to_alpha function on the image using black as the color. Then paste the image on a different background color to do the replacement.
By the way, this implementation uses the ImageMath module in PIL. It is much more efficient than accessing pixels using getdata.
EDIT: Here is the full code:
from PIL import Image, ImageMath
def difference1(source, color):
"""When source is bigger than color"""
return (source - color) / (255.0 - color)
def difference2(source, color):
"""When color is bigger than source"""
return (color - source) / color
def color_to_alpha(image, color=None):
image = image.convert('RGBA')
width, height = image.size
color = map(float, color)
img_bands = [band.convert("F") for band in image.split()]
# Find the maximum difference rate between source and color. I had to use two
# difference functions because ImageMath.eval only evaluates the expression
# once.
alpha = ImageMath.eval(
"""float(
max(
max(
max(
difference1(red_band, cred_band),
difference1(green_band, cgreen_band)
),
difference1(blue_band, cblue_band)
),
max(
max(
difference2(red_band, cred_band),
difference2(green_band, cgreen_band)
),
difference2(blue_band, cblue_band)
)
)
)""",
difference1=difference1,
difference2=difference2,
red_band = img_bands[0],
green_band = img_bands[1],
blue_band = img_bands[2],
cred_band = color[0],
cgreen_band = color[1],
cblue_band = color[2]
)
# Calculate the new image colors after the removal of the selected color
new_bands = [
ImageMath.eval(
"convert((image - color) / alpha + color, 'L')",
image = img_bands[i],
color = color[i],
alpha = alpha
)
for i in xrange(3)
]
# Add the new alpha band
new_bands.append(ImageMath.eval(
"convert(alpha_band * alpha, 'L')",
alpha = alpha,
alpha_band = img_bands[3]
))
return Image.merge('RGBA', new_bands)
image = color_to_alpha(image, (0, 0, 0, 255))
background = Image.new('RGB', image.size, (255, 255, 255))
background.paste(image.convert('RGB'), mask=image)
Using numpy and PIL:
This loads the image into a numpy array of shape (W,H,3), where W is the
width and H is the height. The third axis of the array represents the 3 color
channels, R,G,B.
import Image
import numpy as np
orig_color = (255,255,255)
replacement_color = (0,0,0)
img = Image.open(filename).convert('RGB')
data = np.array(img)
data[(data == orig_color).all(axis = -1)] = replacement_color
img2 = Image.fromarray(data, mode='RGB')
img2.show()
Since orig_color is a tuple of length 3, and data has
shape (W,H,3), NumPy
broadcasts
orig_color to an array of shape (W,H,3) to perform the comparison data ==
orig_color. The result in a boolean array of shape (W,H,3).
(data == orig_color).all(axis = -1) is a boolean array of shape (W,H) which
is True wherever the RGB color in data is original_color.
#!/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.
# change with your color
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'll need to represent the image as a 2-dimensional array. This means either making a list of lists of pixels, or viewing the 1-dimensional array as a 2d one with some clever math. Then, for each pixel that is targeted, you'll need to find all surrounding pixels. You could do this with a python generator thus:
def targets(x,y):
yield (x,y) # Center
yield (x+1,y) # Left
yield (x-1,y) # Right
yield (x,y+1) # Above
yield (x,y-1) # Below
yield (x+1,y+1) # Above and to the right
yield (x+1,y-1) # Below and to the right
yield (x-1,y+1) # Above and to the left
yield (x-1,y-1) # Below and to the left
So, you would use it like this:
for x in range(width):
for y in range(height):
px = pixels[x][y]
if px[0] == 255 and px[1] == 255 and px[2] == 255:
for i,j in targets(x,y):
newpixels[i][j] = replacementColor
If the pixels are not easily identifiable e.g you say (r < 100 and g < 100 and b < 100) also doesn't match correctly the black region, it means you have lots of noise.
Best way would be to identify a region and fill it with color you want, you can identify the region manually or may be by edge detection e.g. http://bitecode.co.uk/2008/07/edge-detection-in-python/
or more sophisticated approach would be to use library like opencv (http://opencv.willowgarage.com/wiki/) to identify objects.
This is part of my code, the result would like:
source
target
import os
import struct
from PIL import Image
def changePNGColor(sourceFile, fromRgb, toRgb, deltaRank = 10):
fromRgb = fromRgb.replace('#', '')
toRgb = toRgb.replace('#', '')
fromColor = struct.unpack('BBB', bytes.fromhex(fromRgb))
toColor = struct.unpack('BBB', bytes.fromhex(toRgb))
img = Image.open(sourceFile)
img = img.convert("RGBA")
pixdata = img.load()
for x in range(0, img.size[0]):
for y in range(0, img.size[1]):
rdelta = pixdata[x, y][0] - fromColor[0]
gdelta = pixdata[x, y][0] - fromColor[0]
bdelta = pixdata[x, y][0] - fromColor[0]
if abs(rdelta) <= deltaRank and abs(gdelta) <= deltaRank and abs(bdelta) <= deltaRank:
pixdata[x, y] = (toColor[0] + rdelta, toColor[1] + gdelta, toColor[2] + bdelta, pixdata[x, y][3])
img.save(os.path.dirname(sourceFile) + os.sep + "changeColor" + os.path.splitext(sourceFile)[1])
if __name__ == '__main__':
changePNGColor("./ok_1.png", "#000000", "#ff0000")