I am trying to get specific coordinates in an image. I have marked a red dot in the image at several locations to specify the coordinates I want to get. In GIMP I used the purist red I could find (HTML notation ff000). The idea was that I would iterate through the image until I found a pure shade of red and then print out the coordinates. I am using python and opencv to do so but I can't find any good tutorials (best I could find is this but it's not very clear...at least for me). Here is an example of the image I am dealing with.
I just want to know how to find the coordinates of the pixels with the red dots.
EDIT (added code):
import cv2
import numpy as np
img = cv2.imread('image.jpg')
width, height = img.shape[:2]
for i in range(0,width):
for j in range(0,height):
px = img[i,j]
I don't know what to do from here. I have tried code such as if px == [x,y,z] looking for color detection but that doesn't work.
You can do it with cv2 this way:
image = cv2.imread('image.jpg')
lower_red = np.array([0,0,220]) # BGR-code of your lowest red
upper_red = np.array([10,10,255]) # BGR-code of your highest red
mask = cv2.inRange(image, lower_red, upper_red)
#get all non zero values
coord=cv2.findNonZero(mask)
You can do this with PIL and numpy. I'm sure there is a similar implementation with cv2.
from PIL import Image
import numpy as np
img = Image.open('image.png')
width, height = img.size[:2]
px = np.array(img)
for i in range(height):
for j in range(width):
if(px[i,j,0] == 255 & px[i,j,1] == 0 & px[i,j,2] == 0):
print(i,j,px[i,j])
This doesn't work with the image you provided, since there aren't any pixels that are exactly (255,0,0). Something may have changed when it got compressed to a .jpg, or you didn't make them as red as you thought you did. Perhaps you could try turning off anti-aliasing in GIMP.
Related
The aim is to take a coloured image, and change any pixels within a certain luminosity range to black. For example, if luminosity is the average of a pixel's RGB values, any pixel with a value under 50 is changed to black.
I’ve attempted to begin using PIL and converting to grayscale, but having trouble trying to find a solution that can identify luminosity value and use that info to manipulate a pixel map.
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:
Into this:
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)
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)
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)
I created a function that returns a list with True if the pixel has a luminosity of less than a parameter, and False if it doesn't. It includes an RGB or RGBA option (True or False)
def get_avg_lum(pic,avg=50,RGBA=False):
num=3
numd=4
if RGBA==False:
num=2
numd=3
li=[[[0]for y in range(0,pic.size[1])] for x in range(0,pic.size[0])]
for x in range(0,pic.size[0]):
for y in range(0,pic.size[1]):
if sum(pic.getpixel((x,y))[:num])/numd<avg:
li[x][y]=True
else:
li[x][y]=False
return(li)
a=get_avg_lum(im)
The pixels match in the list, so (0,10) on the image is [0][10] in the list.
Hopefully this helps. My module is for standard PIL objects.
i'm totally new to this kind of things, i used SLIC to get superpixels from an image, now i have extracted the single superpixel detected but it's like the whole start img dimension except that there is the superpixel and the rest of the image is black, i'm sorry for my bad english, i'll try to explain below.
import cv2
import numpy as np
from skimage.segmentation import slic
myimg = cv2.imread('4.5.jpg')
segments = slic(myimg, n_segments=200, compactness=10, sigma=1)
for i, segVal in enumerate(np.unique(segments)):
mask = np.zeros(myimg.shape[:2], dtype = "uint8")
mask[segments == segVal] = 255
cv2.imwrite('output.png', cv2.bitwise_and(myimg, myimg, mask = mask))
#show the masked region
#cv2.imshow("Mask", mask)
cv2.imshow("Applied", cv2.bitwise_and(myimg, myimg, mask = mask))
cv2.waitKey(1)
that's actually my code to get superpixels, but when i store the single superpixel what i get is in that link (i'm not allowed yet to embed images):
superpixel
now as u can see there is a big black region with the H and W of the original image and the superpixel, i wish to crop only a "rectangle or square" with the superpixel region, how can i do that? thank you and sorry for my english
For this task you can use cv2.findContours. Refer to its documentation to know how to use it. After finding out the contours which will just be one in your case you can use
x,y,w,h = cv2.boundingRect(cnt)
where x, y are the coordinates of top left corner and w, h are the width and height of the rectangle. Now we can know all the points of the required recatngle and you can crop it using numpy indexing.
I was trying to change pixel of an image in python using this question. If mode is 0, it changes first pixel in top right corner of image to grey(#C8C8C8). But it doesn't change. There is not enough documentation about draw.point(). What is the problem with this code?
import random
from PIL import Image, ImageDraw
mode = 0
image = Image.open("dom.jpg")
draw = ImageDraw.Draw(image)
width = image.size[0]
height = image.size[1]
pix = image.load()
string = "kod"
n = 0
if (mode == 0):
draw.point((0, 0), (200, 200, 200))
if(mode == 1):
print(pix[0,0][0])
image.save("dom.jpg", "JPEG")
del draw
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.
My code currently consists of loading the image, which is successful and I don't believe has any connection to the problem.
Then I go on to transform the color image into a np.array named rgb
# convert image into array
rgb = np.array(img)
red = rgb[:,:,0]
green = rgb[:,:,1]
blue = rgb[:,:,2]
To double check my understanding of this array, in case that may be the root of the issue, it is an array such that rgb[x-coordinate, y-coordinate, color band] which holds the value between 0-255 of either red, green or blue.
Then, my idea was to make a nested for loop to traverse all pixels of my image (620px,400px) and sort them based on the ratio of green to blue and red in an attempt to single out the greener pixels and set all others to black or 0.
for i in range(xsize):
for j in range(ysize):
color = rgb[i,j] <-- Index error occurs here
if(color[0] > 128):
if(color[1] < 128):
if(color[2] > 128):
rgb[i,j] = [0,0,0]
The error I am receiving when trying to run this is as follows:
IndexError: index 400 is out of bounds for axis 0 with size 400
I thought it may have something to do with the bounds I was giving i and j so I tried only sorting through a small inner portion of the image but still got the same error. At this point I am lost as to what is even the root of the error let alone even the solution.
In direct answer to your question, the y axis is given first in numpy arrays, followed by the x axis, so interchange your indices.
Less directly, you will find that for loops are very slow in Python and you are generally better off using numpy vectorised operations instead. Also, you will often find it easier to find shades of green in HSV colourspace.
Let's start with an HSL colour wheel:
and assume you want to make all the greens into black. So, from that Wikipedia page, the Hue corresponding to Green is 120 degrees, which means you could do this:
#!/usr/local/bin/python3
import numpy as np
from PIL import Image
# Open image and make RGB and HSV versions
RGBim = Image.open("image.png").convert('RGB')
HSVim = RGBim.convert('HSV')
# Make numpy versions
RGBna = np.array(RGBim)
HSVna = np.array(HSVim)
# Extract Hue
H = HSVna[:,:,0]
# Find all green pixels, i.e. where 100 < Hue < 140
lo,hi = 100,140
# Rescale to 0-255, rather than 0-360 because we are using uint8
lo = int((lo * 255) / 360)
hi = int((hi * 255) / 360)
green = np.where((H>lo) & (H<hi))
# Make all green pixels black in original image
RGBna[green] = [0,0,0]
count = green[0].size
print("Pixels matched: {}".format(count))
Image.fromarray(RGBna).save('result.png')
Which gives:
Here is a slightly improved version that retains the alpha/transparency, and matches red pixels for extra fun:
#!/usr/local/bin/python3
import numpy as np
from PIL import Image
# Open image and make RGB and HSV versions
im = Image.open("image.png")
# Save Alpha if present, then remove
if 'A' in im.getbands():
savedAlpha = im.getchannel('A')
im = im.convert('RGB')
# Make HSV version
HSVim = im.convert('HSV')
# Make numpy versions
RGBna = np.array(im)
HSVna = np.array(HSVim)
# Extract Hue
H = HSVna[:,:,0]
# Find all red pixels, i.e. where 340 < Hue < 20
lo,hi = 340,20
# Rescale to 0-255, rather than 0-360 because we are using uint8
lo = int((lo * 255) / 360)
hi = int((hi * 255) / 360)
red = np.where((H>lo) | (H<hi))
# Make all red pixels black in original image
RGBna[red] = [0,0,0]
count = red[0].size
print("Pixels matched: {}".format(count))
result=Image.fromarray(RGBna)
# Replace Alpha if originally present
if savedAlpha is not None:
result.putalpha(savedAlpha)
result.save('result.png')
Keywords: Image processing, PIL, Pillow, Hue Saturation Value, HSV, HSL, color ranges, colour ranges, range, prime.
I'm new to opencv, I've managed to detect the object and place a ROI around it but I can't managed it so detect if the object is black or white. I've found something i think but i don't know if this is the right solution. The function should return True of False if it's black or white. Anyone experience with this?
def filter_color(img):
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
lower_black = np.array([0,0,0])
upper_black = np.array([350,55,100])
black = cv2.inRange(hsv, lower_black, upper_black)
If you are certain that the ROI is going to be basically black or white and not worried about misidentifying something, then you should be able to just average the pixels in the ROI and check if it is above or below some threshold.
In the code below, after you set an ROI using the newer numpy method, you can pass the roi/image into the method as if you were passing a full image.
Copy-Paste Sample
import cv2
import numpy as np
def is_b_or_w(image, black_max_bgr=(40, 40, 40)):
# use this if you want to check channels are all basically equal
# I split this up into small steps to find out where your error is coming from
mean_bgr_float = np.mean(image, axis=(0,1))
mean_bgr_rounded = np.round(mean_bgr_float)
mean_bgr = mean_bgr_rounded.astype(np.uint8)
# use this if you just want a simple threshold for simple grayscale
# or if you want to use an HSV (V) measurement as in your example
mean_intensity = int(round(np.mean(image)))
return 'black' if np.all(mean_bgr < black_max_bgr) else 'white'
# make a test image for ROIs
shape = (10, 10, 3) # 10x10 BGR image
im_blackleft_white_right = np.ndarray(shape, dtype=np.uint8)
im_blackleft_white_right[:, 0:4] = 10
im_blackleft_white_right[:, 5:9] = 255
roi_darkgray = im_blackleft_white_right[:,0:4]
roi_white = im_blackleft_white_right[:,5:9]
# test them with ROI
print 'dark gray image identified as: {}'.format(is_b_or_w(roi_darkgray))
print 'white image identified as: {}'.format(is_b_or_w(roi_white))
# output
# dark gray image identified as: black
# white image identified as: white
I don't know if this is the right approach but it worked for me.
black = [0,0,0]
Thres = 50
h,w = img.shape[:2]
black = 0
not_black = 0
for y in range(h):
for x in range(w):
pixel = img[y][x]
d = math.sqrt((pixel[0]-0)**2+(pixel[1]-0)**2+(pixel[2]-0)**2)
if d<Thres:
black = black + 1
else:
not_black = not_black +1
This one worked for me but like i said, don't know if this is the right approach. It's ask a lot of processing power therefore i defined a ROI which is much smaller. The Thres is currently hard-coded...