Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

python+opencv - How to plot hsv range?

Tags:

python

opencv

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]])
like image 969
pwan Avatar asked Nov 29 '22 06:11

pwan


1 Answers

What are HSV colors

HSV, like HSL (or in OpenCV, HLS), is one of the cylindrical colorspaces.

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()

Gif of range values

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:

Gif of cspaceThresh program

like image 194
alkasm Avatar answered Dec 05 '22 02:12

alkasm