Change a Colour of a Pixel in Python

Changing pixel color Python

I assume you're trying to use the Image module. Here's an example:

from PIL import Image
picture = Image.open("/path/to/my/picture.jpg")
r,g,b = picture.getpixel( (0,0) )
print("Red: {0}, Green: {1}, Blue: {2}".format(r,g,b))

Running this on this image I get the output:

>>> from PIL import Image
>>> picture = Image.open("/home/gizmo/Downloads/image_launch_a5.jpg")
>>> r,g,b = picture.getpixel( (0,0) )
>>> print("Red: {0}, Green: {1}, Blue: {2}".format(r,g,b))
Red: 138, Green: 161, Blue: 175

EDIT:
To do what you want I would try something like this

from PIL import Image
picture = Image.open("/path/to/my/picture.jpg")

# Get the size of the image
width, height = picture.size()

# Process every pixel
for x in width:
for y in height:
current_color = picture.getpixel( (x,y) )
####################################################################
# Do your logic here and create a new (R,G,B) tuple called new_color
####################################################################
picture.putpixel( (x,y), new_color)

Change pixel colors between two different colors

We may start the solution by using cv2.floodFill method.

Main stages:

  • Fill the lower part with black using floodFill (assume only yellow and green pixels).
  • Fill the top part with black using floodFill (assume only red and green pixels).
  • Find pixels where both top and bottom are zeros (black).
  • Replace the pixels that are both black with red color.

Code sample:

import cv2
import numpy as np

img = cv2.imread('red_yellow_green.jpg')

cols, rows = img.shape[0], img.shape[1]

red = img[0, 0, :].tolist() # Get the red color from the top left corner

# Make the green a "true green"
img2 = img.copy()
green_ch = img2[:, :, 1]
green_ch[green_ch > 100] = 255

# Fill the lower part with black (assume only yellow and green pixels)
bot_black = img2
cv2.floodFill(bot_black, None, seedPoint=(rows-1, cols-1), newVal=(0, 0, 0), loDiff=(255, 20, 255), upDiff=(255, 20, 255))

# Fill the top part with black (assume only red and green pixels)
top_black = img.copy()
cv2.floodFill(top_black, None, seedPoint=(0, 0), newVal=(0, 0, 0), loDiff=(50, 255, 50), upDiff=(50, 255, 50))

# Find pixels where both top and bottom are zeros
both_black = np.logical_and(np.all(bot_black[:, :, 0:3] == (0, 0, 0), 2), np.all(top_black[:, :, 0:3] == (0, 0, 0), 2))

# Convert to uint8 and dilate (this part is just for aesthetics).
both_black = both_black.astype(np.uint8)*255
both_black = cv2.dilate(both_black, np.ones((5,5)))

# Replace the pixels that are both black with red color
img[both_black == 255] = red

# Show images for testing:
cv2.imshow('bot_black', bot_black)
cv2.imshow('top_black', top_black)
cv2.imshow('both_black', both_black)
cv2.imshow('img', img)
cv2.waitKey()
cv2.destroyAllWindows()

Results:

bot_black:

Sample Image

top_black:

Sample Image

both_black:

Sample Image

img:

Sample Image


The above solution is not the most general solution.

There is also an option to find all the green contours, create a mask of the contours perimeter, and analyze the colors of the perimeter of each contour (and fill contours with mixed perimeter colors with red color).

How to change the set of pixel colors in contatc with black color

Please, post lossless versions of your input images. Lossy images modify the value of the pixels, creating artifacts that affect processing. I recreated your image and saved it as a lossless PNF file.

I'm using OpenCV to get the result you want. I created a mask with the non-zero elements of your original input. Then, I used Flood-fill to fill the outer shapes with the color you want. The final image can be obtained if you AND both images.

Let's see the code:

# import opencv:
import cv2

# image path
path = "D://opencvImages//"
fileName = "rectsLossless.png"

# Reading an image in default mode:
inputImage = cv2.imread(path + fileName)

# Grayscale image:
grayscaleImage = cv2.cvtColor(inputImage, cv2.COLOR_BGR2GRAY)

# Get non-zero mask:
binaryThresh = 1
_, binaryMask = cv2.threshold(grayscaleImage, binaryThresh, 255, cv2.THRESH_BINARY)

This bit creates the non-zero pixels mask:

This will help to zero all the elements that are non-white. That image is the first part of the mask. Now, let's fill the outer shapes with red color. This is achieved in three steps:

# Get image dimensions:
(imageHeight, imageWidth) = inputImage.shape[:2]

# Get image center:
xCenter = int(0.5 * imageWidth)
yCenter = int(0.5 * imageHeight)

# Get flood-fill target color
floodColor = inputImage[yCenter, xCenter]
print("Flood Color: %s" % floodColor)
# numpy array to tuple
floodColor = (int(floodColor[0]), int(floodColor[1]), int(floodColor[2]))

The first step gets the actual filling color. I suppose that the red is located more or less at the center of the image. Then, the second step involves filling all the "foreground" pixels with white. Let's seed at the top left corner:

# Flood fill at top left corner:
leftCorner = (1, 1)
whiteColor = (255, 255, 255)
cv2.floodFill(inputImage, None, leftCorner, whiteColor)

This is the result:

Note how the shapes that are partially outside of the red rectangle are all now connected by the white color. Let's fill again, but this time using the red color I extracted previously:

# Second Flood-fill
cv2.floodFill(inputImage, None, leftCorner, floodColor)

This yields the following image:

Let's create the final image by ANDing this result with the original non-zero mask:

# Create final image:
outImage = cv2.bitwise_and(inputImage, inputImage, mask=binaryMask)

This is the final result:


How to change the color of a pixel using PIL?

Is using PIL a must in your case? If not then consider using OpenCV (cv2) for altering particular pixels of image.
Code which alter (0,0) pixel to (200,200,200) looks following way in opencv:

import cv2
img = cv2.imread('yourimage.jpg')
height = img.shape[0]
width = img.shape[1]
img[0][0] = [200,200,200]
cv2.imwrite('newimage.bmp',img)

Note that this code saves image in .bmp format - cv2 can also write .jpg images, but as jpg is generally lossy format, some small details might be lost. Keep in mind that in cv2 [0][0] is left upper corner and first value is y-coordinate of pixel, while second is x-coordinate, additionally color are three values from 0 to 255 (inclusive) in BGR order rather than RGB.

For OpenCV tutorials, including installation see this.

Customize (change) image (pixel) colours - python

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.

green

purple

How to change the colour of pixels in an image depending on their initial luminosity?

There are many ways to do this, but the simplest and probably fastest is with Numpy, which you should get accustomed to using with image processing in Python:

from PIL import Image
import numpy as np

# Load image and ensure RGB, not palette image
im = Image.open('start.png').convert('RGB')

# Make into Numpy array
na = np.array(im)

# Make all pixels of "na" where the mean of the R,G,B channels is less than 50 into black (0)
na[np.mean(na, axis=-1)<50] = 0

# Convert back to PIL Image to save or display
result = Image.fromarray(na)
result.show()

That turns this:

Sample Image

Into this:

Sample Image


Another slightly different way would be to convert the image to a more conventional greyscale, rather than averaging for the luminosity:

# Load image and ensure RGB
im = Image.open('start.png').convert('RGB')

# Calculate greyscale version
grey = im.convert('L')

# Point process over pixels to make mask of darker ones
mask = grey.point(lambda p: 255 if p<50 else 0)

# Paste black (i.e. 0) into image where mask indicates it is dark
im.paste(0, mask=mask)

Sample Image

Notice that the blue channel is given considerably less significance in the ITU-R 601-2 luma transform that PIL uses (see the lower 114 weighting for Blue versus 299 for Red and 587 for Green) in the formula:

L = R * 299/1000 + G * 587/1000 + B * 114/1000

so the blue shades are considered darker and become black.


Another way would be to make a greyscale and a mask as above. but then choose the darker pixel at each location when comparing the original and the mask:

from PIL import Image, ImageChops
im = Image.open('start.png').convert('RGB')
grey = im.convert('L')
mask = grey.point(lambda p: 0 if p<50 else 255)
res = ImageChops.darker(im, mask.convert('RGB'))

That gives the same result as above.


Another way, pure PIL and probably closest to what you actually asked, would be to derive a luminosity value by averaging the channels:

# Load image and ensure RGB
im = Image.open('start.png').convert('RGB')

# Calculate greyscale version by averaging R,G and B
grey = im.convert('L', matrix=(0.333, 0.333, 0.333, 0))

# Point process over pixels to make mask of darker ones
mask = grey.point(lambda p: 255 if p<50 else 0)

# Paste black (i.e. 0) into image where mask indicates it is dark
im.paste(0, mask=mask)

Sample Image


Another approach could be to split the image into its constituent RGB channels, evaluate a mathematical function over the channels and mask with the result:

from PIL import Image, ImageMath

# Load image and ensure RGB
im = Image.open('start.png').convert('RGB')

# Split into RGB channels
(R, G, B) = im.split()

# Evaluate mathematical function over channels
dark = ImageMath.eval('(((R+G+B)/3) <= 50) * 255', R=R, G=G, B=B)

# Paste black (i.e. 0) into image where mask indicates it is dark
im.paste(0, mask=dark)


Related Topics



Leave a reply



Submit