To extract the color, we have this function
# define range of blue color in HSV
lower_blue = np.array([110,50,50])
upper_blue = np.array([130,255,255])
# Threshold the HSV image to get only blue colors
mask = cv2.inRange(hsv, lower_blue, upper_blue)
How do we actually visualize the range(lower_blue,upper_blue) I define on hsv space?
Also How do I actually plot a hsv color,but it is not working...?
I have this code:
upper = np.array([60, 255, 255])
upper = cv2.cvtColor(upper, cv2.COLOR_HSV2BGR)
upper = totuple(upper/-255)
print(upper)
plt.imshow([[upper]])
What are HSV colors
HSV, like HSL (or in OpenCV, HLS), is one of the cylindrical colorspaces.
The name is somewhat descriptive of how their values are referenced.
The hue is represented as degrees from 0 to 360 (in OpenCV, to fit into the an 8-bit unsigned integer format, they degrees are divided by two to get a number from 0 to 179; so 110 in OpenCV is 220 degrees). If you were to take a "range" of hue values, it's like cutting a slice from a cake. You're just taking some angle chunk of the cake.
The saturation channel is how far from the center you are---the radius you're at. The center has absolutely no saturation---only gray colors from black to white. If you took a range of these values, it is akin to shaving off the outside of the cylinder, or cutting out a circle from the center. For example, if the range is 0 to 255, then the range 0 to 127 would be a cylinder only extending to half the radius; the range 127 to 255 would be cutting an inner cylinder with half the radius out.
The value channel is a slightly confusing name; it's not exactly darkness-to-brightness because the highest value represents the direct color, while the lowest value is black. This is the height of the cylinder. Not too hard to imagine cutting a slice of the cylinder vertically.
Ranges of HSV values
The function cv2.inRange(image, lower_bound, upper_bound) finds all values of the image between lower_bound and upper_bound. For instance, if your image was a 3x3 image (just for simple demonstration purposes) with 3-channels, it might look something like this:
# h channel # s channel # v channel
100 150 250 150 150 100 50 75 225
50 100 125 75 25 50 255 100 50
0 255 125 100 200 250 50 75 100
If we wanted to select hues between 100 and 200, then our lower_b should be [100, 0, 0] and upper_b should be [200, 255, 255]. That way our mask would only take into account values in the hue channel, and not be affected by the saturation and value. That's why HSV is so popular---you can select colors by hue regardless of their brightness or darkness, so a dark red and bright red can be selected just by specifying the min and max of the hue channel.
But say we only wanted to select bright white colors. Take a look back at the cylinder model---we see that white is given at the top-center of the cylinder, so where s values are low, and v values are high, and the color angle doesn't matter. So the lower_b would look something like [0, 0, 200] and upper_b would look something like [255, 50, 255]. That means all H values will be included and won't affect our mask. But then only S values between 0 and 50 would be included (towards the center of the cylinder) and only V values from 200 to 255 will be included (towards the top of the cylinder).
Visualizing a range of colors from HSV
One way to visualize all the colors in a range is to create gradients going the length of both directions for each of two channels, and then animate over the changing third channel.
For instance, you could create a gradient of values from left to right for the range of S values, from top to bottom for the range of V values, and then loop over each H value. This whole program could look something like this:
import numpy as np
import cv2
lower_b = np.array([110,50,50])
upper_b = np.array([130,255,255])
s_gradient = np.ones((500,1), dtype=np.uint8)*np.linspace(lower_b[1], upper_b[1], 500, dtype=np.uint8)
v_gradient = np.rot90(np.ones((500,1), dtype=np.uint8)*np.linspace(lower_b[1], upper_b[1], 500, dtype=np.uint8))
h_array = np.arange(lower_b[0], upper_b[0]+1)
for hue in h_array:
h = hue*np.ones((500,500), dtype=np.uint8)
hsv_color = cv2.merge((h, s_gradient, v_gradient))
rgb_color = cv2.cvtColor(hsv_color, cv2.COLOR_HSV2BGR)
cv2.imshow('', rgb_color)
cv2.waitKey(250)
cv2.destroyAllWindows()
Now this gif shows a new H value every frame. And from left to right we have the min to max S values, and from top to bottom we have the min to max V values. Every single one of the colors showing up in this animation will be selected from your image to be part of your mask.
Make your own inRange() function
To fully understand the OpenCV function, the easiest way is just to make your own function to complete the task. It's not difficult at all, and not very much code.
The idea behind the function is simple: find where the values of each channel fall between min and max, and then & all the channels together.
def inRange(img, lower_b, upper_b):
ch1, ch2, ch3 = cv2.split(img)
ch1m = (lower_b[0] <= ch1) & (ch1 <= upper_b[0])
ch2m = (lower_b[1] <= ch2) & (ch2 <= upper_b[1])
ch3m = (lower_b[2] <= ch3) & (ch3 <= upper_b[2])
mask = ch1m & ch2m & ch3m
return mask.astype(np.uint8)*255
You can read the OpenCV docs to see that this is indeed the formula used. And we can verify it too.
lower_b = np.array([200,200,200])
upper_b = np.array([255,255,255])
mask = cv2.inRange(img, lower_b, upper_b) # OpenCV function
mask2 = inRange(img, lower_b, upper_b) # above defined function
print((mask==mask2).all()) # checks that the masks agree on all values
# True
How to find the right colors
It can be a little tricky to find the correct values to use for a particular image. There is an easy way to experiment, though. You can create trackbars in OpenCV and use them to control the min and max for each channel and have the Python program update your mask every time you change the values. I made a program for this which you can grab on GitHub here. Here's an animated .gif of it being used, to demonstrate:
Related
What is the best way to measure the width (perpendicular distance) of this glowing object along its height?
Adding some context:
Is the bright orange surrounding stable?
Yes, it is. These objects are very hot (about 1000 degrees) and, therefore, incandescent. As the middle one is closer to the camera, it looks darker.
Does it cover the whole background or is it left and right of the object, but not behind it?
The background is a wall at room temperature (about 15 degrees) and will therefore always be 'black'.
Can you assume that the center of the image is always inside of the object?
I am using a model to detect and localize the object and always extract the Region of Interest in this way. So yes, we can assume that the frames will always look like this: three bright objects with one in the middle slightly darker than the others in a 475 x 1079 frame with a black background.
Why is there a rapid (but noisy) switch from very bright (bottom) to very dark (top) in the object? Is that switch always in about at that position?
This is due to the difference in temperature. The bottom of the object is warmer than the top, so there is a change in brightness.
This change is similar in all objects. Sometimes there is a little dust (black spots) that happens 20% of the time and mostly on the upper part of the object but we can ignore it. I thought about dividing the image (the object actually) into 10 pieces, for example, and ignoring the ones with these black spots.
Here is an example of what I would like to achieve:
Here are the steps I have performed so far:
Original picture:
Dark spots:
Equalization of the histogram of the Y channel:
img_yuv[:,:,0] = cv2.equalizeHist(img_yuv[:,:,0])
Denoising by applying fastNlMeansDenoisingColored:
temporary_image = cv2.fastNlMeansDenoisingColored(temporary_image, None, 10, 10, 7, 21)
Then I tried 3 things:
To filter by color:
hMin = 15
sMin = 92
vMin = 120
hMax = 25
sMax = 147
vMax = 255
# Set minimum and max HSV values to display
lower = np.array([hMin, sMin, vMin])
upper = np.array([hMax, sMax, vMax])
# Create HSV Image and threshold into a range.
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv, lower, upper)
output = cv2.bitwise_and(img, img, mask= mask)
To use gradient:
To use Canny Edge Detection:
The problem I'm facing is finding a single line or structure that I can trust to measure it from.
I am finding a lot of noise even after the filtering process.
Any ideas or recommendations?
Thanks for your help.
I use different OpenCV haar cascade classifiers to detect vehicles, traffic signs, and smiles. To obtain better results, I try to filter out the blue which is the background color pixels. To do so, I apply the HSV filter, but I'm confused about the color range.
To select only white, red, and yellow pixels, how should I define lower and upper filter ranges?
When I apply the HSV filter on the image in these ranges, I received this result:
Well, you've set a range of 0..45 for Hue, so that means 0..90 degrees around the Hue wheel. Blue is at 240 degrees, but there is a scale factor of 2 for Hue in OpenCV, to allow 360 degrees of Hue to become 180 because that will fit in uint8 range of 0..255. So you probably want 120, so set range 105..135 and test.
You've set a range of 0..255 for Saturation. That means you don't care how saturated/vivid or unsaturated your blues are, even grey will be ok. So you need to increase the 0 to 50+.
You've set the Value in range 100..255 which means you only want light blues not dark ones. Decrease the 100 if darker blues are ok.
You can also load your image into Nathancy's GUI here which dynamically shows you the selection. If you run that, you get these values:
(hMin = 69 , sMin = 37, vMin = 0), (hMax = 134 , sMax = 255, vMax = 255)
I've set the lower's range to ([0,20,0]) and upper's range to ([50,255,255]). Then, I received a much better result:
All I did was to increase the lower saturation value from 0 to 20.
I am currently working on simple lane detection and I have some trouble finding the range/input values for yellow colored lane lines.
def color_filter(image):
#convert to HLS to mask based on HLS
hls = cv2.cvtColor(image, cv2.COLOR_BGR2HLS)
lower = np.array([0,190,0])
upper = np.array([255,255,255])
yellower = np.array([40,70,60]) #NOT SURE WHAT TO PUT
yelupper = np.array([50,90,65]) #NOT SURE WHAT TO PUT
yellowmask = cv2.inRange(hls, yellower, yelupper)
whitemask = cv2.inRange(hls, lower, upper)
mask = cv2.bitwise_or(yellowmask, whitemask)
masked = cv2.bitwise_and(image, image, mask = mask)
return masked
Here is the image that I've filtered (only white lanes are showing):
http://prntscr.com/ng2cgp
Here's the original image:
http://prntscr.com/ng2cx6
I suggest, you have a further reading on how the HSL/HSV color space works, maybe starting at the Wikipedia article? Furthermore, to easily get some initial values to work on, you can use a HSL calculator, e.g. this one.
To detect white-ish parts in the image, the hue (H) value might by arbitrary, as long as the lightness (L) value is high enough (we want bright colors), and the saturation (S) value is low enough (we want low saturated colors).
In general, H values are within [0 ... 360], whereas S and L values are within [0.0 ... 1.0]. The OpenCV documentation on color conversions tells you, that these values are mapped to H within [0 ... 180], and S and L within [0 ... 255] (for 8-bit images).
Now, to detect yellow-ish parts in the image, appropriate H, S, and L values can be taken from the afore-mentioned HSL calculator by "playing around", what might fit to the colors to be found in the image.
I prepared the following example code, please have a look:
import cv2
import numpy as np
# Load input image
input = cv2.imread('images/input.png', cv2.IMREAD_COLOR)
# Convert to HLS color space
hls = cv2.cvtColor(input, cv2.COLOR_BGR2HLS)
# White-ish areas in image
# H value can be arbitrary, thus within [0 ... 360] (OpenCV: [0 ... 180])
# L value must be relatively high (we want high brightness), e.g. within [0.7 ... 1.0] (OpenCV: [0 ... 255])
# S value must be relatively low (we want low saturation), e.g. within [0.0 ... 0.3] (OpenCV: [0 ... 255])
white_lower = np.array([np.round( 0 / 2), np.round(0.75 * 255), np.round(0.00 * 255)])
white_upper = np.array([np.round(360 / 2), np.round(1.00 * 255), np.round(0.30 * 255)])
white_mask = cv2.inRange(hls, white_lower, white_upper)
# Yellow-ish areas in image
# H value must be appropriate (see HSL color space), e.g. within [40 ... 60]
# L value can be arbitrary (we want everything between bright and dark yellow), e.g. within [0.0 ... 1.0]
# S value must be above some threshold (we want at least some saturation), e.g. within [0.35 ... 1.0]
yellow_lower = np.array([np.round( 40 / 2), np.round(0.00 * 255), np.round(0.35 * 255)])
yellow_upper = np.array([np.round( 60 / 2), np.round(1.00 * 255), np.round(1.00 * 255)])
yellow_mask = cv2.inRange(hls, yellow_lower, yellow_upper)
# Calculate combined mask, and masked image
mask = cv2.bitwise_or(yellow_mask, white_mask)
masked = cv2.bitwise_and(input, input, mask = mask)
# Write output images
cv2.imwrite('images/white_mask.png', white_mask)
cv2.imwrite('images/yellow_mask.png', yellow_mask)
cv2.imwrite('images/masked.png', masked)
The white-ish mask looks like this:
The yellow-ish mask looks like this:
The masked image from your code looks like this:
As you can see, fine-tuning the parameters must be done. But I hope, you now get the general idea, and can continue on your own.
I have a numpy array and I am minusing a constant value from the array. I want the values to go negative if necessary (and not wrap around or floor to zero). I then need to extract all array values that are around zero and produce a new binary array/image. So the resulting image will show white where in the areas that were close to zero.
I have tried to implement this but its hacky and I'm not sure if its correct. Can you assist what I am trying to do above?
# roi is a numpy array/image in Cielab colour space
swatch_colour = (255, 10, 30) # Cielab colour space
swatch_roi = np.full((roi.shape[0], roi.shape[1], 3), swatch_colour, dtype='int8')
int_roi = roi.astype('int8')
diff = np.subtract(int_roi, swatch_roi)
thresh = diff.copy()
# Get all pixels whose Cielab colour is close to zero
thresh[np.abs(thresh) < (12,6,12)] = 0
# the remaining pixels are greater than/less than the above threshold
thresh[np.abs(thresh) > (0,0,0)] = 255
thresh = thresh.astype('uint8')
# convert from 3 channels to 1 channel
thresh = cv2.cvtColor(thresh, cv2.COLOR_BGR2GRAY)
# Invert the image so that the pixels that were close to zero are white
thresh = cv2.bitwise_not(thresh)
Numpy can index slice logical operators
why can you do something like?
image[image.logical_and( image > -05 , image < 05 )]
https://docs.scipy.org/doc/numpy/reference/generated/numpy.logical_and.html
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.