How do you lightness thresh hold with HSL on OpenCV? - python

There is a project that im working on which required the color white detection, after some research i decided to use covert RGB image to HSL image and thresh hold the lightness to get the color white, im working with openCV so wonder if there is a way to do it.
enter image description here

You can do it with 4 easy steps:
Convert HLS
img = cv2.imread("HLS.png")
imgHLS = cv2.cvtColor(img, cv2.COLOR_BGR2HLS)
Get the L channel
Lchannel = imgHLS[:,:,1]
Create the mask
#change 250 to lower numbers to include more values as "white"
mask = cv2.inRange(Lchannel, 250, 255)
Apply Mask to original image
res = cv2.bitwise_and(img,img, mask= mask)
This also depends on what is white for you, and you may change the values :) I used inRange in the L channel but you can save one step and do
mask = cv2.inRange(imgHLS, np.array([0,250,0]), np.array([255,255,255]))
instead of the lines:
Lchannel = imgHLS[:,:,1]
mask = cv2.inRange(Lchannel, 250, 255)
It is shorter, but I did it the other way first to make it more explicit and to show what I was doing.
Image:
Result:
The result looks almost as the mask (almost binary), but depending on your lowerbound (I chose 250) you may get even some almost white colors.

Related

Python - replicating the GIMP's "Erase color" blend mode

I'm looking for a way to recreate the GIMP's Erase color blending mode in Python 3 & OpenCV2.
I know it's possible to erase color using the that library, but the code I run works on exactly one of them. Furthermore, I don't believe such small amount of code could do that advanced thing.
Looking for a solution, I found the blend-modes by flrs, but it also doesn't include the option I want.
Sadly, I have no experience in OpenCV2 at the moment, but I think developing such thing could be very helpful.
Can someone guide me how to make this more reliable, or is it even possible to do with things that I've got already?
OpenCV2 color removal
Code
import cv2
from PIL import Image
#-=-=-=-#
File_Name = r"Spectrogram.png"
SRC = cv2.imread(File_Name, 1)
TMP = cv2.cvtColor(SRC, cv2.COLOR_BGR2GRAY)
_, A = cv2.threshold(TMP, 0, 255, cv2.THRESH_BINARY)
B, G, R = cv2.split(SRC)
Colors = [B, G, R, A]
Picture = cv2.merge(Colors, 4)
#-=-=-=-#
# My CV2 image display doesn't include transparency
im = cv2.cvtColor(Picture, cv2.COLOR_BGR2RGB)
im = Image.fromarray(im)
im.show()
Result
Original
Result
GIMP Erase color blending-mode
Type
Background
Foreground
Result
Image
Blending
Normal
Erase color
Normal
Here is one simple way in Python/OpenCV.
Read the input
Choose a color range
Apply range to threshold the image
Invert the range as a mask to be used later for the alpha channel
Convert the image from BGR to BGRA
Put mask into the alpha channel of the BGRA image
Save the result
Input:
import cv2
import numpy as np
# load image and set the bounds
img = cv2.imread("red_black.png")
# choose color range
lower =(0,0,0) # lower bound for each BGR channel
upper = (140,0,190) # upper bound for each BRG channel
# create the mask
mask = cv2.inRange(img, lower, upper)
# invert mask
mask = 255 - mask
# convert image to BGRA
result = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
# put mask into alpha channel
result[:,:,3] = mask
# write result to disk
cv2.imwrite("red_black_color_removed.png", result)
# display it (though does not display transparency properly)
cv2.imshow("mask", mask)
cv2.imshow("results", result)
cv2.waitKey(0)
Result:

Convert image to binary doesn't shows white lines of a soccer field

I am inspired by the following blogpost, however I am struggling with step 2/3.
I want to creates a binary image from a gray image based on the threshold values and ultimately displaying all white lines on the image. My desired output looks as follows:
First, I want to isolate the soccer field by using colour-thresholding and morphology.
def isolate_field(img):
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# find green pitch
light_green = np.array([40, 40, 40])
dark_green = np.array([70, 255, 255])
mask = cv2.inRange(hsv, light_green, dark_green)
# removing small noises
kernel = np.ones((5, 5), np.uint8)
opening = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
# apply mask over original frame
return cv2.bitwise_and(frame, frame, mask=opening)
This gives the following output:
I am happy with the results so far, but because of the large shadow I am struggling with the image-processing when I grayscale the picture. As a result, the binary thresholding is based on the sunny part in the upper-left corner instead of the white lines around the soccer field.
Following the methodology on the tutorials I get the following output for the simple thresholding:
and adaptive thresholding:
and finally, Otsu's thresholding:
How can I make sure that the white lines become more visible? I was thinking about cropping the frame so I only see the field and then use a mask based on the color white. That didn't work out unfortunately.
Help is much appreciated,
You can modify inRange to also exclude saturated colors (meaning the greens). I don't have your original image, so I used your intermediate result:
The result of inRange is the binary image you want. I expect you can achieve better results with the original image. I used this script in the image - which makes it easy to search for good HSV values.

How to add a mask on an image blurring their intersection?

I have a mask and a background image. I want to add this mask on the background image and blurring (or perhaps something like cv2.inpaint()) the intersection to make it more natural, but am blocked for the bluring effect, any help would be highly appreciated.
More details:
I have two RGB images. The first image (foreground) is associated with a binary mask, which I would like to add on the second image (background).
The issue is that when we look at the final image we clearly see which part was added on the background image. Hence, I would like to add a blurring effect at the intersection of the mask and the background image. For now my code looks like:
#foreground image: we'll use only the mask part
#background image: where we will add the mask
foreground = cv2.imread(path1)
background = cv2.imread(path2)
#Convert to float
foreground = foreground.astype(float)
background = background.astype(float)
mask = mask.astype(float)
#Multiply the foreground with the mask
foreground = cv2.multiply(mask, foreground)
#Multiply the background with everywhere except with mask
background = cv2.multiply(1.0 - mask, background)
#Add the masked foreground to background image
outImage = cv2.add(foreground, background)
I could not find a straightforward way to do it, but I guess their should have one. A lot of related answer on internet works by thresholding to some pixel value, but this can not be used here. For now the easiest way I found is:
create a mask of the part I want to blurr
blurr the final images (background +foreground mask)
take from the blurr images only the part of the mask of 1) and add it the initial final image (background +foreground mask)
Before doing this, I was wondering if someone would have some advices.
The way I am doing it so far works well, but I am staying open to any other better solution!
Here's mine:
def foreground_background_into1(background, foreground, with_smooth=True, thickness=3, mask=[]):
'''will add the foreground image to the background image to create a new image, with smooth intersection
-foreground image: this image must be either black where there is no mask, or the mask parameter must be specified in
the mask parameter
-background image: image on which we will add the mask
-mask: binary mask if foreground image does not already contain this information
Note: not tested with mask'''
#make a copy for the smooth part
img = foreground.copy()
#create binary mask (as needed to multiply with the backgound) from foreground image if not existing (replacing all value
#bigger than 0 to 1)
if len(mask)==0:
_,mask = cv2.threshold(foreground,1,1,cv2.THRESH_BINARY)
#verification
if foreground.shape!=background.shape:
raise Warning("the foreground is not of same shape as background, we will convert it")
foreground = imresize(foreground, size=background.shape)
#if mask has one channel add two others
if len(mask.shape)==2:
mask = skimage.color.gray2rgb(mask)
#add foreground to background
foreground = foreground.astype(float)
background = background.astype(float)
mask = mask.astype(float)
foreground = cv2.multiply(mask, foreground)
background = cv2.multiply(1 - mask, background) #is the initial background with black where the mask of forground
outImage = cv2.add(foreground, background)
result = outImage.astype(np.uint8)
if with_smooth:
#find contour
foreground_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(foreground_gray,1,255,0)
_, contours, __ = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
#create intersection_mask
intersection_mask = cv2.drawContours(np.zeros(foreground.shape, np.uint8),
contours,-1,(0,255,0),thickness)
#inpaint the contour in the first final image
intersection_mask = cv2.cvtColor(intersection_mask, cv2.COLOR_BGR2GRAY)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (1,1))
intersection_mask = cv2.dilate(intersection_mask, kernel, iterations=1)
result = cv2.inpaint(result,intersection_mask,1,cv2.INPAINT_TELEA)
return(result)
I am basically adding the mask on the background image, and then smooth the intersection part by inpaint with openCV the contour of the mask.

How to overlay object with transparent color in OpenCV

Example
I will try to explain my question according the image. Firstly i use Python3 and OpenCV3. I just want to colorize the white pixels of mask(for example with shinny blue). Then using addWeighted, i want to blend that mask onto original image. But the problem i can't colorize the mask. Mask is the result of inRange fuction and i can't transform it to RGB.
https://www.youtube.com/watch?v=hQ-bpfdWQh8
Just like in the video but single frame.
For a quick mask visualization, try this:
debug_img = img/2 + mask/2
If img isn't grayscale already, replace img with img.mean(axis=2) or use cvtColor().
Another way is to use indexing:
debug_img = img.copy()
debug_img[mask>0] = (0, 255, 0) # replace masked pixels with green
To make the green transparent, simply add
debug_img = debug_img/2 + img/2

How to filter unwanted points in a mask

I would like to detect a color then I work in HSV and use 3 treshold.
r,BGR = cap.read()
HSV = cv2.cvtColor(BGR, cv2.COLOR_BGR2HSV)
lowerb = np.array([Hrange[0],Srange[0],Vrange[0]])
higherb = np.array([Hrange[1],Srange[1],Vrange[1]])
#Values comes from a trackbar
mask= cv2.inRange(HSV, lowerb, higherb)
result = cv2.bitwise_and(BGR,BGR, mask=mask)
H,S,V Ranges are like :
[20,40], [50-170], [50,170]
As you see, the result is pixelised and I would like to unify every single area detected. (What is more white go white & what is more black go black)
Is there a Numpy or OpenCV2 function to do it?
Thanks
inRange gives you a binary image already, so there's no "more white" or "more black".
What you're looking for is morphological operations Erode and Dilate.
Alternatively, although arguably slower and not exactly the same thing, median filter is also something worth looking into.

Categories