Change background and pixel color of image - colors

For an experiment I want to show the participants drawings from a database which includes black drawn lines on a white background. Eventually I only want to shown what is the 'drawn part' per image in a certain color. So I want the white parts of the image to be made gray, so it is indistinguishable from the gray background. And I want to show the black parts of the image (the actual drawing) in other colors, for example red.
I am quite new to programming and so far I couldn't find an answer. I have tried several things, including the 2 options below.
Could anyone maybe show me an example of how to change the colors of the image I have attached to this message?
It would be very much appreciated!
[enter image description here][1]
####### OPTION 1, not working
#picture = Image.open(fname)
fname = exp.get_file('PICTURE_1.png')
picture = Image.open(fname)
# Get the size of the image
width, height = picture.size
# Process every pixel
for x in range(width):
for y in range(height):
current_color = picture.getpixel( (x,y) )
if current_color == (255,255,255):
new_color = (255,0,0)
picture.putpixel( (x,y), new_color)
elif current_color == (0,0,0):
new_color2 = (115,115,115)
picture.putpixel( (x,y), new_color2)
picture.show()
#picture.show()
win.flip()
clock.sleep(1000)
Implemented changes as you suggested gives: TypeError: 'int' object has no attribute 'getitem'
for x in range(width):
for y in range(height):
current_color = picture.getpixel( (x,y) )
if (current_color[0]<200) and (current_color[1]<200) and (current_color[2]<200):
new_color = (255,0,0)
picture.putpixel( (x,y), new_color)
elif (current_color[0]>200) and (current_color[1]>200) and (current_color[2]>200):
new_color2 = (115,115,115)
picture.putpixel( (x,y), new_color2)
picture.show()

Your approach in option one is basically correct, but here are a few tips to help you get it working properly:
Instead of saying if current_color == (255,255,255):, you should instead put
if (current_color[0]>200) and (current_color[1]>200) and (current_color[2]>200):
as even though the white parts of the image look white the pixels may not be exactly (255,255,255).
I thought you wanted to turn the white parts grey and the black parts red? In your code for option one, the lines
if current_color == (255,255,255):
new_color = (255,0,0)
will turn white pixels red. To turn black pixels red, it should be if current_color == (0,0,0).
If your code is still not working when these changes are made, you could try creating a new image with the same dimensions as the original one, and adding pixels to the new image rather than editing the pixels in the original one.
Also, it would help if you could tell us what actually happens when you run your code. Is there an error message, or is an image shown but the image is not correct? Could you please attach an example output?
Update:
I fiddled around with your code, and got it to do what you want it to do. Here is the code I ended up with:
import PIL
from PIL import Image
picture = Image.open('image_one.png')
# Get the size of the image
width, height = picture.size
for x in range(width):
for y in range(height):
current_color = picture.getpixel( (x,y) )
if (current_color[0]<200) and (current_color[1]<200) and (current_color[2]<200):
new_color = (255,0,0)
picture.putpixel( (x,y), new_color)
elif (current_color[0]>200) and (current_color[1]>200) and (current_color[2]>200):
new_color2 = (115,115,115)
picture.putpixel( (x,y), new_color2)
picture.show()
If you copy and paste this code into a script and run it in the same folder as your image, it should work.

There are much more efficient ways to do this than looping through each pixel and changing its value.
Since it looks like you're using PsychoPy, you can save your images as greyscale with a transparent background. By using the greyscale image format you allow PsychoPy to change the color of the lines to anything you want simply by altering the stimulus color setting. By using a transparent background, whatever you see behind your lines will show through, so you can choose to have a white square, a different square or no square at all. By this method, all the calculations for the colors are being done on the graphics card and can be changed every frame with no problems.
If for some reason you need to alter the image in ways that PsychoPy doesn't inherently allow (and if speed of processing matters) then you should try to change all the pixels in a single operation (using the numpy arrays) rather than one pixel at a time in a for-loop.

Related

Change width of image in Opencv using Numpy

I'm making a Python file that will make a filter to have color on the Canny filter in OpenCV. I do this change from grayscale to color using the code provided below. My problem is when I apply the concatenate method (to add the color back as Canny filter is converted to grayscale), it cuts the width of the screen in 3 as I show in the 2 screenshots of before the color is added and after. The code snippet shown is only the transformation from grayscale to colored images.
What I've tried:
Tried using NumPy.tile: this wasn't the wisest attempt as it just repeated the same 1/3 of the screen twice more and didn't expand it to take up the whole screen as I had hoped.
Tried changing the image to only be from the index of 1/3 of the screen to cover the entire screen.
Tried setting the column index that is blank to equal None.
Image without the color added
Image with the color added
My code:
def convert_pixels(image, color):
rows, cols = image.shape
concat = np.zeros(image.shape)
image = np.concatenate((image, concat), axis=1)
image = np.concatenate((image, concat), axis=1)
image = image.reshape(rows, cols, 3)
index = image.nonzero()
#TODO: turn color into constantly changing color wheel or shifting colors
for i in zip(index[0], index[1], index[2]):
color.next_color()
image[i[0]][i[1]] = color.color
#TODO: fix this issue below:
#image[:, int(cols/3):cols] = None # turns right side (gliched) into None type
return image, color
In short, you're using concatenate on the wrong axis. axis=1 is the "columns" axis, so you're just putting two copies of zeros next to each other in the x direction. Since you want a three-channel image I would just initialize color_image with three channels and leave the original grayscale image alone:
def convert_pixels(image,color):
rows, cols = image.shape
color_image = np.zeros((rows,cols,3),dtype=np.uint8)
idx = image.nonzero()
for i in zip(*idx):
color_image[i] = color.color
return color_image,color
I've changed the indexing to match. I can't check this exactly since I don't know what your color object is, but I can confirm this works in terms of correctly shaping and indexing the new image.

Change image color in PIL module

I am trying to vary the intensity of colors to obtain a different colored image...
import PIL
from PIL import Image
from PIL import ImageEnhance
from PIL import ImageDraw
# read image and convert to RGB
image=Image.open("readonly/msi_recruitment.gif")
image=image.convert('RGB')
# build a list of 9 images which have different brightnesses
enhancer=ImageEnhance.Brightness(image)
images=[]
for i in range(1, 10):
images.append(enhancer.enhance(i/10))
# create a contact sheet from different brightnesses
first_image=images[0]
contact_sheet=PIL.Image.new(first_image.mode, (first_image.width*3,first_image.height*3))
x=0
y=0
for img in images:
# Lets paste the current image into the contact sheet
contact_sheet.paste(img, (x, y) )
# Now we update our X position. If it is going to be the width of the image, then we set it to 0
# and update Y as well to point to the next "line" of the contact sheet.
if x+first_image.width == contact_sheet.width:
x=0
y=y+first_image.height
else:
x=x+first_image.width
# resize and display the contact sheet
contact_sheet = contact_sheet.resize((int(contact_sheet.width/2),int(contact_sheet.height/2) ))
display(contact_sheet)
But the above code just varies brightness....
Please tell me what changes should i make to vary color intensity in this code.....
Im sorry but i am unable to upload the picture now, consider any image you find suitable and help me out... Appreciated!!!!
Please go to this link and answer this question instead of this one, I apologise for inconvenience....
Pixel colour intensity
Many colour operations are best done in a colourspace such as HSV which you can get in PIL with:
HSV = rgb.convert('HSV')
You can then use split() to get 3 separate channels:
H, S, V = hsv.split()
Now you can change your colours. You seem a little woolly on what you want. If you want to change the intensity of the colours, i.e. make them less saturated and less vivid decrease the S (Saturation). If you want to change the reds to purples, i.e. change the Hues, then add something to the Hue channel. If you want to make the image brighter or darker, change the Value (V) channel.
When you have finished, merge merge((H,S,V)) the edited channels back together and convert back to RGB with convert('RGB').
See Splitting and Merging and Processing Individual Bands on this page.
Here is an example, using this image:
Here is the basic framework to load the image, convert to HSV colourspace, split the channels, do some processing, recombine the channels and revert to RGB colourspace and save the result.
#!/usr/bin/env python3
from PIL import Image
# Load image and create HSV version
im = Image.open('colorwheel.jpg')
HSV= im.convert('HSV')
# Split into separate channels
H, S, V = HSV.split()
######################################
########## PROCESSING HERE ###########
######################################
# Recombine processed H, S and V back into a recombined image
HSVr = Image.merge('HSV', (H,S,V))
# Convert recombined HSV back to reconstituted RGB
RGBr = HSVr.convert('RGB')
# Save processed result
RGBr.save('result.png')
So, if you find the chunk labelled "PROCESSING HERE" and put code in there to divide the saturation by 2, it will make the colours less vivid:
# Desaturate the colours by halving the saturation
S = S.point(lambda p: p//2)
If, instead, we halve the brightness (V), like this:
# Halve the brightness
V=V.point(lambda p: p//2)
the result will be darker:
If, instead, we add 80 to the Hue, all the colours will rotate around the circle - this is called a "Hue rotation":
# Rotate Hues around the Hue circle by 80 on a range of 0..255, so around 1/3 or a circle, i.e. 120 degrees:
H = H.point(lambda p: p+80)
which gives this:

With Image Process change each shape's color to a unique color

I have a png with multiple shapes on it that represent fictional geographic locations. I would like for each of theses locations to render with a different color so that they are easier to process.
I know how to change anything that is x color to y color, but I don't know how to set up the code so that it changes the color of the current shape and then moves on.
if pixelMap[i,j] == white:
node.append pixelMap[i,j]
#look at all white pixels connected to current node 1
expected result is that I can input an image like this:
https://cdn.discordapp.com/attachments/404701706820124676/608881289080209408/Asset_2.png
and come out with each shape a unique color.
You are essentially looking for "flood-filling" of an area constrained by a boundary, and you can do that with PIL/Pillow's ImageDraw.floodfill() method like this:
#!/usr/bin/env python3
from PIL import Image, ImageDraw
import numpy as np
# Open the image
im = Image.open('map.png').convert('RGB')
# Make all pixels in top-left country into magenta (255,0,255)
ImageDraw.floodfill(im,xy=(40,40),value=(255,0,255),thresh=50)
# Make all pixels in bottom-right country into yellow (255,255,0)
ImageDraw.floodfill(im,xy=(100,100),value=(255,255,0),thresh=50)
That gets you this:
I am not certain what you mean by "moving on to the next area", but I presume you could put the flood fill commands in a loop and keep taking the position of the first white pixel as the seed for the flood filling until there are no more uncoloured (white) pixels left. To do that, I would convert the PIL Image into a Numpy Array and find the coordinates of the white pixels like this:
# Convert PIL Image to Numpy Array
n = np.array(im)
# Get X,Y coordinates of all remaining white pixels
y, x = np.nonzero(np.all(n==[255,255,255],axis=2))

How to convert the background of the entire image to white when both white and black backgrounds are present?

The form image contains text in different background. The image needs to be converted to one background (here white) and hence the heading needs to be converted into black.
input image :
output image:
My approach was to detect the grid(horizontal lines and vertical lines and sum them up) and then crop each section of the grid into new sub-images and then check the majority pixel color and transform accordingly. But after implementing that, the blue background image is not getting detected and getting cropped like :
So I am trying to convert the entire form image into one background so that I can avoid such outcomes.
Here's a different way of doing it that will cope with the "reverse video" being black, rather than relying on some colour saturation to find it.
#!/usr/bin/env python3
import cv2
import numpy as np
# Load image, greyscale and threshold
im = cv2.imread('form.jpg',cv2.IMREAD_GRAYSCALE)
# Threshold and invert
_,thr = cv2.threshold(im,127,255,cv2.THRESH_BINARY)
inv = 255 - thr
# Perform morphological closing with square 7x7 structuring element to remove details and thin lines
SE = np.ones((7,7),np.uint8)
closed = cv2.morphologyEx(thr, cv2.MORPH_CLOSE, SE)
# DEBUG save closed image
cv2.imwrite('closed.png', closed)
# Find row numbers of dark rows
meanByRow=np.mean(closed,axis=1)
rows = np.where(meanByRow<50)
# Replace selected rows with those from the inverted image
im[rows]=inv[rows]
# Save result
cv2.imwrite('result.png',im)
The result looks like this:
And the intermediate closed image looks like this - I artificially added a red border so you can see its extent on Stack Overflow's white background:
You can read about morphology here and an excellent description by Anthony Thyssen, here.
Here's a possible approach. Shades of blue will show up with a higher saturation than black and white if you convert to HSV colourspace, so...
convert to HSV
find mean saturation for each row and select rows where mean saturation exceeds a threshold
greyscale those rows, invert and threshold them
This approach should work if the reverse (standout) backgrounds are any colour other than black or white. It assumes you have de-skewed your images to be truly vertical/horizontal per your example.
That could look something like this in Python:
#!/usr/bin/env python3
import cv2
import numpy as np
# Load image
im = cv2.imread('form.jpg')
# Make HSV and extract S, i.e. Saturation
hsv = cv2.cvtColor(im, cv2.COLOR_BGR2HSV)
s=hsv[:,:,1]
# Save saturation just for debug
cv2.imwrite('saturation.png',s)
# Make greyscale version and inverted, thresholded greyscale version
gr = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
_,grinv = cv2.threshold(gr,127,255,cv2.THRESH_BINARY_INV)
# Find row numbers of rows with colour in them
meanSatByRow=np.mean(s,axis=1)
rows = np.where(meanSatByRow>50)
# Replace selected rows with those from the inverted, thresholded image
gr[rows]=grinv[rows]
# Save result
cv2.imwrite('result.png',gr)
The result looks like this:
The saturation image looks as follows - note that saturated colours (i.e. the blues) show up as light, everything else as black:
The greyscale, inverted image looks like this:

Detect Color of particular area of Image Nodejs OpenCV

I'm trying to write code to detect the color of a particular area of an image.
So far I have come across is using OpenCV, we can do this, But still haven't found any particular tutorial to help with this.
I want to do this with javascript, but I can also use python OpenCV to get the results.
can anyone please help me with sharing any useful link or can explain how can I achieve detecting the color of the particular area in the image.
For eg.
The box in red will show a different color. I need to figure out which color it is showing.
What I have tried:
I have tried OpenCV canny images, though I am successful to get area separated with canny images, how to detect the color of that particular canny area is still a challenge.
Also, I tried it with inRange method from OpenCV which works perfect
# find the colors within the specified boundaries and apply
# the mask
mask = cv2.inRange(image, lower, upper)
output = cv2.bitwise_and(image, image, mask = mask)
# show the images
cv2.imshow("images", np.hstack([image, output]))
It works well and extracts the color area from the image But is there any callback which responds if the image has particular color so that it can be all done automatically?
So I am assuming here that, you already know the location of the rect which is going to be dynamically changed and need to find out the single most dominant color in the desired ROI. There are a lot of ways to do the same, one is by getting the average, of all the pixels in the ROI, other is to count all the distinct pixel values in the given ROI, with some tolerance difference.
Method 1:
import cv2
import numpy as np
img = cv2.imread("path/to/img.jpg")
region_of_interest = (356, 88, 495, 227) # left, top, bottom, right
cropped_img = img[region_of_interest[1]:region_of_interest[3], region_of_interest[0]:region_of_interest[2]]
print cv2.mean(cropped_img)
>>> (53.430516018839604, 41.05708814243569, 244.54991977640907, 0.0)
Method 2:
To find out the various dominant clusters in the given image you can use cv2.kmeans() as:
import cv2
import numpy as np
img = cv2.imread("path/to/img.jpg")
region_of_interest = (356, 88, 495, 227)
cropped_img = img[region_of_interest[1]:region_of_interest[3], region_of_interest[0]:region_of_interest[2]]
Z = cropped_img.reshape((-1, 3))
Z = np.float32(Z)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
K = 4
ret, label, center = cv2.kmeans(Z, K, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
# Sort all the colors, as per their frequencies, as:
print center[sorted(range(K), key=lambda x: np.count_nonzero(label == [x]), reverse=True)[0]]
>>> [ 52.96525192 40.93861389 245.02325439]
#Prateek... nice to have the question narrowed down to the core. The code you provided does not address this issue at hand and remains just a question. I'll hint you towards a direction but you have to code it yourself.
steps that guide you towards a scripting result:
1) In your script add two (past & current) pixellists to store values (pixeltype + occurance).
2) Introduce a while-loop with an action true/stop statement (link to "3") for looping purpose because then it becomes a dynamic process.
3) Write a GUI with a flashy warning banner.
4) compare the pixellist with current_pixellist for serious state change (threshhold).
5) If the delta state change at "4" meets threshold throw the alert ("3").
When you've got written the code and enjoyed the trouble of tracking the tracebacks... then edit your question, update it with the code and reshape your question (i can help wiht that if you want). Then we can pick it up from there. Does that sound like a plan?
I am not sure why you need callback in this situation, but maybe this is what you mean?
def test_color(image, lower, upper):
mask = cv2.inRange(image, lower, upper)
return np.any(mask == 255)
Explanations:
cv2.inRange() will return 255 when pixel is in range (lower, upper), 0 otherwise (see docs)
Use np.any() to check if any element in the mask is actually 255

Resources