How to convert this HSV into RGB - python

How to convert this HSV into RGB I would like it using cv2.color cv2.COLOR_BGR2RGB but its not same result.
This is the image i've used
enter image description here
Heres' the result that I want but using the cv2.COLOR_BGR2RGB but the code below is using the cv2.COLOR_BGR2HSV
enter image description here
import cv2
import numpy as np
## Read
img = cv2.imread("ni.jpg")
## convert to hsv
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
## mask of green (36,25,25) ~ (86, 255,255)
# mask = cv2.inRange(hsv, (36, 25, 25), (86, 255,255))
mask = cv2.inRange(hsv, (7, 25, 25), (70, 255,255))
## slice the green
imask = mask>0
green = np.zeros_like(img, np.uint8)
green[imask] = img[imask]
## save
cv2.imwrite("green.png", green)

To convert an image from HSV to RGB you can do:
rgb = cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB)
and to do HSV to BGR it is
bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
You have to know that OpenCV is using BGR when reading/saving images.
You can convert between RGB and BGR with cvtColor and cv2.COLOR_RGB2BGR, or cv2.COLOR_BGR2RGB.
EDIT:
However, if what you want is having a mask of green bananas (or yellow bananas), The issue is the way you defined green color : it is including a lot of other colors right now, including yellow.
What you can do with the HSV, is to only look at the first channel, the hue :
Here you can see that green and yellow can be differentiated : green bananas have pixel value roughly between 30 and 50, and yellow between 20 and 30.
You can do a mask with that. I used another library to do the cleanup of pixel we don't want. It is Scikit-image. This can be done in OpenCV as well, but it takes a bit more time...
SO here is my code :
import cv2
import numpy as np
import matplotlib.pyplot as plt
from skimage.morphology import remove_small_objects, remove_small_holes
## Read
img = cv2.imread("ni.jpg")
## convert to hsv
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
hue = hsv[:,:,0]
# plt.imshow(hue) # this show the figure in my post
# plt.show()
# mask = np.bitwise_and(hue > 20, hue < 35) # for yellow
mask = np.bitwise_and(hue > 30, hue < 50) # for green
mask = remove_small_objects(mask, 1000)
mask = remove_small_holes(mask, 1000)
green = np.zeros_like(img, np.uint8)
green[mask] = img[mask]
## save
cv2.imwrite("green.png", green)

Related

Change range of colors in an image using python

I'm working on automating changing image colors using python. The image I'm using is below, i'd love to move it from red to another range of colors, say green, keeping the detail and shading if possible. I've been able to convert some of the image to a solid color, losing all detail.
The code I'm currently using is below, I can't quite figure out the correct range of red to make it work correctly, and also it only converts to a single color, again losing all detail and shade.
Any help is appreciated, thank you.
import cv2
import numpy as np
import skimage.exposure
# load image and get dimensions
img = cv2.imread("test5.jpg")
# convert to hsv
hsv = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
## mask of upper red (170,50,50) ~ (180,255,255)
## mask of lower red (0,50,50) ~ (10,255,255)
# threshold using inRange
range1 = (0,50,50)
range2 = (1,255,255)
mask = cv2.inRange(hsv,range1,range2)
mask = 255 - mask
# apply morphology opening to mask
kernel = np.ones((3,3), np.uint8)
mask = cv2.morphologyEx(mask, cv2.MORPH_ERODE, kernel)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
# antialias mask
mask = cv2.GaussianBlur(mask, (0,0), sigmaX=3, sigmaY=3, borderType = cv2.BORDER_DEFAULT)
mask = skimage.exposure.rescale_intensity(mask, in_range=(127.5,255), out_range=(0,255))
result = img.copy()
result[mask==0] = (255,255,255)
# write result to disk
cv2.imwrite("test6.jpg", result)
This is one way to approach the problem in Python/OpenCV. But for red, it is very hard to do because red spans 0 hue, which also is the hue for gray and white and black, which you have in your image. The other issue you have is that skin tones has red shades, so you cannot pick too large of ranges for your colors. Also when dealing with red ranges, you need two sets, one for hues up to 180 and another for hues above 0.
Input:
import cv2
import numpy as np
# load image
img = cv2.imread('red_clothes.jpg')
# convert to HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h,s,v = cv2.split(hsv)
blue_hue = 120
red_hue = 0
# diff hue (blue_hue - red_hue)
diff_hue = blue_hue - red_hue
# create mask for red color in hsv
lower1 = (150,150,150)
upper1 = (180,255,255)
mask1 = cv2.inRange(hsv, lower1, upper1)
lower2 = (0,150,150)
upper2 = (30,255,255)
mask2 = cv2.inRange(hsv, lower2, upper2)
mask = cv2.add(mask1,mask2)
mask = cv2.merge([mask,mask,mask])
# apply morphology to clean mask
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9,9))
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
# modify hue channel by adding difference and modulo 180
hnew = np.mod(h + diff_hue, 180).astype(np.uint8)
# recombine channels
hsv_new = cv2.merge([hnew,s,v])
# convert back to bgr
bgr_new = cv2.cvtColor(hsv_new, cv2.COLOR_HSV2BGR)
# blend with original using mask
result = np.where(mask==(255, 255, 255), bgr_new, img)
# save output
cv2.imwrite('red_clothes_mask.png', mask)
cv2.imwrite('red_clothes_hue_shift.png', bgr_new)
cv2.imwrite('red_clothes_red2blue.png', result)
# Display various images to see the steps
cv2.imshow('mask1',mask1)
cv2.imshow('mask2',mask2)
cv2.imshow('mask',mask)
cv2.imshow('bgr_new',bgr_new)
cv2.imshow('result',result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Mask:
Hue Shifted Image:
Blend between Input and Hue Shifted Image using Mask to blend:
So the result is speckled because of the black mixed with the red and from limited ranges due to skin color.
You can start with red, but the trick is to invert the image so red is now at hue 90 in OpenCV range and for example blue is at hue 30. So in Python/OpenCV, you can do the following:
Input:
import cv2
import numpy as np
# load image
img = cv2.imread('red_clothes.jpg')
# invert image
imginv = 255 - img
# convert to HSV
hsv = cv2.cvtColor(imginv, cv2.COLOR_BGR2HSV)
h,s,v = cv2.split(hsv)
blueinv_hue = 30 #(=120+180/2=210-180=30)
redinv_hue = 90 #(=0+180/2=90)
# diff hue (blue_hue - red_hue)
diff_hue = blueinv_hue - redinv_hue
# create mask for redinv color in hsv
lower = (80,150,150)
upper = (100,255,255)
mask = cv2.inRange(hsv, lower, upper)
mask = cv2.merge([mask,mask,mask])
# apply morphology to clean mask
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9,9))
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
# modify hue channel by adding difference and modulo 180
hnew = np.mod(h + diff_hue, 180).astype(np.uint8)
# recombine channels
hsv_new = cv2.merge([hnew,s,v])
# convert back to bgr
bgrinv_new = cv2.cvtColor(hsv_new, cv2.COLOR_HSV2BGR)
# invert
bgr_new = 255 -bgrinv_new
# blend with original using mask
result = np.where(mask==(255, 255, 255), bgr_new, img)
# save output
cv2.imwrite('red_clothes_mask.png', mask)
cv2.imwrite('red_clothes_hue_shift.png', bgr_new)
cv2.imwrite('red_clothes_red2blue.png', result)
# Display various images to see the steps
cv2.imshow('mask',mask)
cv2.imshow('bgr_new',bgr_new)
cv2.imshow('result',result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Mask:
Red to Blue before masking:
Red to Blue after masking:
However, one is still limited by the fact that red is close to skin tones, so the range for red is limited.
Starting with a blue image rather than red allows one to use an expanded range for inRange() and do a better job in Python/OpenCV. Here is a change from blue to red.
Input:
import cv2
import numpy as np
# load image
img = cv2.imread('blue_clothes.jpg')
# convert to HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h,s,v = cv2.split(hsv)
red_hue = 0
blue_hue = 120
# diff hue (red_hue - blue_hue)
diff_hue = red_hue - blue_hue
# create mask for blue color in hsv
lower = (100,90,90)
upper = (140,255,255)
mask = cv2.inRange(hsv, lower, upper)
mask = cv2.merge([mask,mask,mask])
# apply morphology to clean mask
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9,9))
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
# modify hue channel by adding difference and modulo 180
hnew = np.mod(h + diff_hue, 180).astype(np.uint8)
# recombine channels
hsv_new = cv2.merge([hnew,s,v])
# convert back to bgr
bgr_new = cv2.cvtColor(hsv_new, cv2.COLOR_HSV2BGR)
# blend with original using mask
result = np.where(mask==(255, 255, 255), bgr_new, img)
# save output
cv2.imwrite('blue_clothes_mask.png', mask)
cv2.imwrite('blue_clothes_hue_shift.png', bgr_new)
cv2.imwrite('blue_clothes_blue2red.png', result)
# Display various images to see the steps
cv2.imshow('mask',mask)
cv2.imshow('bgr_new',bgr_new)
cv2.imshow('result',result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Mask:
Blue to Red before masking:
Blue to Red after masking:

Taking a 'White' pixel count of each masked frame in a video file

I am trying to develop a way to output the number of pixels that fall between the HSV mask limits of each frame of a given video on a Raspberry Pi camera.
The aim of this is to determine the 'Red' intensity of a red dimmer light and therefore split its intensity into different levels, and hence determine which intensity the light is switched to in each frame. How would I go about calculating said pixel count?
My progress so far is that I have a method and limits for masking a frame using OpenCV commands. I just need a way to count the remaining pixels of each frame.
Here is my current code, which I have slightly adapted from this great tutorial I found: Automatic Vision Object Tracking
import cv2
import numpy as np
img = cv2.imread('hsvmeasure.jpg', 1)
img = cv2.resize(img, (0,0), fx=0.2, fy=0.2)
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
lower_range = np.array([160,100,100], dtype=np.uint8)
upper_range = np.array([180,255,255], dtype=np.uint8)
mask = cv2.inRange(hsv, lower_range, upper_range)
cv2.imshow('mask', mask)
cv2.imshow('img', img)
while(1):
k = cv2.waitKey(0)
if (k == 27):
break
cv2.destroyAllWindows()
You have already done most of the work, now you can just make a range of lower and upper intensity (non-overlapping) and count how many pixels are 255 in the mask.
import cv2
import numpy as np
img = cv2.imread('test.jpg', 1)
img = cv2.resize(img, (0,0), fx=0.2, fy=0.2)
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
lower_intensity_levels = [ [150,100,100], [161, 100,100], [171, 100, 100] ] # non overlapping
upper_intensity_levels = [ [160,255,255] , [170, 255, 255], [180, 255, 255] ] # make these list based on your intensity requirements
mask_on_counts = []
for i in range(len(lower_intensity_levels)):
lower_range = np.array(lower_intensity_levels[i], dtype=np.uint8)
upper_range = np.array(upper_intensity_levels[i], dtype=np.uint8)
mask = cv2.inRange(hsv, lower_range, upper_range)
mask_on_counts.append(np.sum(mask==255))
import matplotlib.pyplot as plt
plt.imshow(mask)
plt.show()
for i in range(len(mask_on_counts)):
print(f'level {i+1} number of pixels: {mask_on_counts[i]}')

changing hsv values of an image in opencv python isnt working

Im trying to set the Minimum and Maximum value of HSV of an Image in opencv python but after running the code all I can see is a blank rectangle box.
import cv2
import sys
import numpy as np
# Load in image
image = cv2.imread('power.jpg')
# Set minimum and max HSV values to display
lower = np.array([0, 209, 0])
upper = np.array([179, 255, 236])
# Create HSV Image and threshold into a range.
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv, lower, upper)
output = cv2.bitwise_and(image,image, mask= mask)
# Display output image
cv2.imshow('image',output)
I was able to solve it.
import numpy as np
import cv2
img = cv2.imread( "power.jpg" )
## convert to hsv
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
## mask of red (36,0,0) ~ (70, 255,255)
mask = cv2.inRange(hsv, (0, 209, 0), (179, 255,236))
bak = img.copy()
# Show only red
#bak[mask > 0] = (0, 0, 255)
imask = mask>0
green = np.zeros_like(img, np.uint8)
green[imask] = img[imask]
## save
cv2.imwrite("image.png", green)

Numpy where() creates spots of different color than the ones defined

I'm writing a script that creates a mask for an image. My input image looks like this:
The original image is only 40x40px, here it is for reference:
I want to create a mask of the purple area in the center of the image. This is what I do:
# read the 40x40 image and convert it to RGB
input_image = cv2.cvtColor(cv2.imread('image.png'), cv2.COLOR_BGR2RGB)
# get the value of the color in the center of the image
center_color = input_image[20, 20]
# create the mask: pixels with same color = 255 (white), other pixels = 0 (black)
mask_bw = np.where(input_image == center_color, 255, 0)
# show the image
plt.imshow(mask_bw)
Most of the time this works perfectly fine, but for some images (like the one I attached to this question) I consistently get some blue areas in my mask like on the image below. This is reproducible and the areas are always the same for the same input images.
This is already weird enough, but if I try to remove the blue areas, this doesn't work either.
mask_bw[mask_bw != (255, 255, 255)] = 0 # this doesn't change anything..
Why is this happening and how do I fix this?
Additional info
tried with numpy version 1.17.3 and 1.17.4
Reproduced in my local environment and in a google colab notebook
The main problem is that you're trying to compare three channels but only setting the value for one channel. This is most likely causing the blue areas on the mask. When you use np.where() to set the other pixels to black, you are only setting this on the 1st channel instead of all three channels. You can visualize this by splitting each channel and printing the before/after arrays which will show you that the resulting array values are RGB(0,0,255). So to fix this problem, we need to compare each individual channel then set the desired area in white while setting any black areas on the mask to black for all three channels. Here is one way to do it:
import numpy as np
import cv2
image = cv2.imread('1.png')
center_color = image[20, 20]
b, g, r = cv2.split(image)
mask = (b == center_color[0]) & (g == center_color[1]) & (r == center_color[2])
image[mask] = 255
image[mask==0] = 0
cv2.imshow('image', image)
cv2.waitKey()
A hotfix to remove the blue areas using your current code would be to convert the image to grayscale (1-channel) then change all non-white pixels to black.
import numpy as np
import cv2
# Load image, find color, create mask
image = cv2.imread('1.png')
center_color = image[20, 20]
mask = np.where(image == center_color, 255, 0)
mask = np.array(mask, dtype=np.uint8)
# Convert image to grayscale, convert all non-white pixels to black
mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
mask[mask != 255] = 0
cv2.imshow('mask', mask)
cv2.waitKey()
Here are two alternative methods to obtain a mask of the purple area
Method #1: Work in grayscale space
import numpy as np
import cv2
image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
center_color = gray[20, 20]
mask = np.array(np.where(gray == center_color, 255, 0), dtype=np.uint8)
cv2.imshow('mask', mask)
cv2.waitKey()
Method #2: Color thresholding
The idea is to convert the image to HSV color space then use a lower and upper color range to segment the image to create a binary mask
import numpy as np
import cv2
image = cv2.imread('1.png')
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
lower = np.array([0, 124, 0])
upper = np.array([179, 255, 255])
mask = cv2.inRange(hsv, lower, upper)
cv2.imshow('mask', mask)
cv2.waitKey()
Both methods should yield the same result
If you have a 3-channel image (i.e. RGB or BGR or somesuch) and you want to generate a single channel mask (i.e. you want 0/1 or True/False) for each pixel, then you effectively need to group the 3 values into a single using np.all() like this:
import cv2
import numpy as np
# Load image and get centre colour
image = cv2.imread('40x40.png')
cc = im[20, 20]
print(image.shape)
(40, 40, 3)
# Generate list of unique colours present in image so we know what we are dealing with
print(np.unique(im.reshape(-1,3), axis=0))
array([[140, 109, 142],
[151, 106, 140],
[160, 101, 137],
[165, 134, 157],
[175, 149, 171],
[206, 87, 109],
[206, 185, 193]], dtype=uint8)
# Generate mask of pixels matching centre colour
mask_bw = np.where(np.all(im==cc,axis=2), 255, 0)
# Check shape of mask - no 3rd dimension !!!
print(mask_bw.shape)
(40, 40)
# Check unique colours in mask
print(np.unique(mask_bw.reshape(-1,1), axis=0))
array([[ 0],
[255]])

OpenCV inRange() is working for RGB but not HSV color space

Ive generated a scatter plot of my image in RGB and HSV format and am using inRange() to threshold a single color from eyeballing the plot.
To get exact RGB and HSV values I'm using paint.net's color picker to get the RGB value and then an RGB to HSV converter to get the HSV values.
The pixel colors and the scatter plot are generated by:
img = cv2.imread('C:\\b_.jpg')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
pixel_colors = img.reshape((np.shape(img)[0]*np.shape(img)[1], 3))
norm = colors.Normalize(vmin=-1., vmax=1.)
norm.autoscale(pixel_colors)
pixel_colors = norm(pixel_colors).tolist()
h, s, v = cv2.split(img_hsv)
fig = plt.figure()
axis = fig.add_subplot(1, 1, 1, projection="3d")
axis.scatter(h.flatten(), s.flatten(), v.flatten(), facecolors=pixel_colors, marker=".")
axis.set_xlabel("Hue")
axis.set_ylabel("Saturation")
axis.set_zlabel("Value")
plt.show()
I need to extract the yellow from my image. As mentioned I am using paint.net color picker to get the RGB values of light yellow and dark yellow. And then use the converter to get the HSV values for the inRange() function.
light_yellow = (60, 89, 97) # HSV VALUES
dark_yellow = (61, 36.6, 43.9)
mask = cv2.inRange(img_hsv, light_yellow, dark_yellow)
result = cv2.bitwise_and(img, img, mask=mask)
but the result generated is a black image, however if I use the RGB values directly of light and dark yellow and use the RGB image, not the HSV converted image, the segmentation works.
light_yellow = (249, 249, 125) # RGB VALUES
dark_yellow = (111, 112, 71)
mask = cv2.inRange(img, light_yellow, dark_yellow)
result = cv2.bitwise_and(img, img, mask=mask)
Although the above is RGB segmentation, I feel it may be improved in HSV. Why is my HSV range not giving an output?
As it is expressed in the documentation of cvtColor. When it is CV_8U the H value which normally goes from 0 to 360, it is divided by 2 and goes from 0-180. The S and V values are usually percentage (0-100%) and they go from 0-255.
So your value:
light_yellow = (60, 89, 97) # HSV VALUES
dark_yellow = (61, 36.6, 43.9)
Should be more like:
# (H/2, (S/100) * 255, (V/100) * 255)
light_yellow = (30, 227, 247) # HSV VALUES
dark_yellow = (31, 93, 112)
Now you have another problem, inRanges looks for valus inside the low and high range, not by light and dark yellow. So your limits should be:
low = (30,93,112)
high = (31, 227,247)
mask = cv2.inRange(img_hsv, low, high)
One more thing, I would use a bigger range for the H color... 30-31 is quite small, maybe 20-32 is better?

Categories